mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-11 10:55:31 +03:00
Merge branch 'main' of github.com:roc-lang/roc into remove-editor
This commit is contained in:
commit
38bd84d603
@ -53,17 +53,7 @@ This command will generate the documentation in the [`generated-docs`](generated
|
|||||||
|
|
||||||
### Commit signing
|
### Commit signing
|
||||||
|
|
||||||
- All your commits need to be signed [to prevent impersonation](https://dev.to/martiliones/how-i-got-linus-torvalds-in-my-contributors-on-github-3k4g):
|
All your commits need to be signed [to prevent impersonation](https://dev.to/martiliones/how-i-got-linus-torvalds-in-my-contributors-on-github-3k4g). Check out [our guide for commit signing](devtools/signing.md).
|
||||||
- If you don't have signing set up on your device and you only want to change a single file, it will be easier to use [github's edit button](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files). This will sign your commit automatically.
|
|
||||||
- For multi-file or complex changes you will want to set up signing on your device:
|
|
||||||
1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
|
|
||||||
2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
|
|
||||||
3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
|
|
||||||
4. Make git sign your commits automatically:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git config --global commit.gpgsign true
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Commit signing on NixOS
|
#### Commit signing on NixOS
|
||||||
|
|
||||||
|
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -2117,9 +2117,14 @@ dependencies = [
|
|||||||
"roc_build",
|
"roc_build",
|
||||||
"roc_cli",
|
"roc_cli",
|
||||||
"roc_repl_cli",
|
"roc_repl_cli",
|
||||||
|
"roc_repl_ui",
|
||||||
|
"roc_reporting",
|
||||||
|
"roc_target",
|
||||||
"roc_test_utils",
|
"roc_test_utils",
|
||||||
"roc_wasm_interp",
|
"roc_wasm_interp",
|
||||||
|
"rustyline",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
|
"target-lexicon",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2814,11 +2819,11 @@ dependencies = [
|
|||||||
"roc_error_macros",
|
"roc_error_macros",
|
||||||
"roc_gen_llvm",
|
"roc_gen_llvm",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
"roc_module",
|
|
||||||
"roc_mono",
|
"roc_mono",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
"roc_repl_eval",
|
"roc_repl_eval",
|
||||||
|
"roc_repl_ui",
|
||||||
"roc_reporting",
|
"roc_reporting",
|
||||||
"roc_std",
|
"roc_std",
|
||||||
"roc_target",
|
"roc_target",
|
||||||
@ -2885,6 +2890,22 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_repl_ui"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"const_format",
|
||||||
|
"roc_collections",
|
||||||
|
"roc_load",
|
||||||
|
"roc_parse",
|
||||||
|
"roc_region",
|
||||||
|
"roc_repl_eval",
|
||||||
|
"roc_reporting",
|
||||||
|
"roc_target",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roc_repl_wasm"
|
name = "roc_repl_wasm"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
@ -2901,6 +2922,7 @@ dependencies = [
|
|||||||
"roc_load",
|
"roc_load",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_repl_eval",
|
"roc_repl_eval",
|
||||||
|
"roc_repl_ui",
|
||||||
"roc_reporting",
|
"roc_reporting",
|
||||||
"roc_solve",
|
"roc_solve",
|
||||||
"roc_target",
|
"roc_target",
|
||||||
@ -4318,9 +4340,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki"
|
name = "webpki"
|
||||||
version = "0.22.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
@ -12,6 +12,7 @@ members = [
|
|||||||
"crates/repl_cli",
|
"crates/repl_cli",
|
||||||
"crates/repl_eval",
|
"crates/repl_eval",
|
||||||
"crates/repl_test",
|
"crates/repl_test",
|
||||||
|
"crates/repl_ui",
|
||||||
"crates/repl_wasm",
|
"crates/repl_wasm",
|
||||||
"crates/repl_expect",
|
"crates/repl_expect",
|
||||||
"crates/roc_std",
|
"crates/roc_std",
|
||||||
|
@ -92,6 +92,12 @@ Command Line Interface(CLI) functionality for the Read-Evaluate-Print-Loop (REPL
|
|||||||
|
|
||||||
Provides the functionality for the REPL to evaluate Roc expressions.
|
Provides the functionality for the REPL to evaluate Roc expressions.
|
||||||
|
|
||||||
|
## `repl_state/` - `roc_repl_state`
|
||||||
|
|
||||||
|
Implements the state machine the to handle user input for the REPL (CLI and web)
|
||||||
|
If the user enters an expression, like `x * 2`, check it evaluate it.
|
||||||
|
If the user enters a declaration, like `x = 123`, check it and remember it, but don't evaluate.
|
||||||
|
|
||||||
## `repl_expect/` - `roc_repl_expect`
|
## `repl_expect/` - `roc_repl_expect`
|
||||||
|
|
||||||
Supports evaluating `expect` and printing contextual information when they fail.
|
Supports evaluating `expect` and printing contextual information when they fail.
|
||||||
|
@ -26,7 +26,7 @@ run-wasm32 = ["roc_wasm_interp"]
|
|||||||
# Compiling for a different target than the current machine can cause linker errors.
|
# Compiling for a different target than the current machine can cause linker errors.
|
||||||
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||||
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
||||||
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
|
target-wasm32 = ["roc_build/target-wasm32"]
|
||||||
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
||||||
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
||||||
|
|
||||||
|
@ -1005,12 +1005,12 @@ expect
|
|||||||
|
|
||||||
# We have decided not to expose the standard roc hashing algorithm.
|
# We have decided not to expose the standard roc hashing algorithm.
|
||||||
# This is to avoid external dependence and the need for versioning.
|
# This is to avoid external dependence and the need for versioning.
|
||||||
# The current implementation is a form of [Wyhash final3](https://github.com/wangyi-fudan/wyhash/blob/a5995b98ebfa7bd38bfadc0919326d2e7aabb805/wyhash.h).
|
# The current implementation is a form of [Wyhash final4](https://github.com/wangyi-fudan/wyhash/blob/77e50f267fbc7b8e2d09f2d455219adb70ad4749/wyhash.h).
|
||||||
# It is 64bit and little endian specific currently.
|
# It is 64bit and little endian specific currently.
|
||||||
# TODO: wyhash is slow for large keys, use something like cityhash if the keys are too long.
|
# TODO: wyhash is slow for large keys, use something like cityhash if the keys are too long.
|
||||||
# TODO: Add a builtin to distinguish big endian systems and change loading orders.
|
# TODO: Add a builtin to distinguish big endian systems and change loading orders.
|
||||||
# TODO: Switch out Wymum on systems with slow 128bit multiplication.
|
# TODO: Switch out Wymum on systems with slow 128bit multiplication.
|
||||||
LowLevelHasher := { originalSeed : U64, state : U64 } implements [
|
LowLevelHasher := { initializedSeed : U64, state : U64 } implements [
|
||||||
Hasher {
|
Hasher {
|
||||||
addBytes,
|
addBytes,
|
||||||
addU8,
|
addU8,
|
||||||
@ -1036,14 +1036,30 @@ createLowLevelHasher = \seedOpt ->
|
|||||||
when seedOpt is
|
when seedOpt is
|
||||||
PseudoRandSeed -> pseudoSeed {}
|
PseudoRandSeed -> pseudoSeed {}
|
||||||
WithSeed s -> s
|
WithSeed s -> s
|
||||||
@LowLevelHasher { originalSeed: seed, state: seed }
|
@LowLevelHasher { initializedSeed: initSeed seed, state: seed }
|
||||||
|
|
||||||
combineState : LowLevelHasher, { a : U64, b : U64, seed : U64, length : U64 } -> LowLevelHasher
|
combineState : LowLevelHasher, { a : U64, b : U64, seed : U64, length : U64 } -> LowLevelHasher
|
||||||
combineState = \@LowLevelHasher { originalSeed, state }, { a, b, seed, length } ->
|
combineState = \@LowLevelHasher { initializedSeed, state }, { a, b, seed, length } ->
|
||||||
tmp = wymix (Num.bitwiseXor wyp1 a) (Num.bitwiseXor seed b)
|
mum =
|
||||||
hash = wymix (Num.bitwiseXor wyp1 length) tmp
|
a
|
||||||
|
|> Num.bitwiseXor wyp1
|
||||||
|
|> wymum (Num.bitwiseXor b seed)
|
||||||
|
nexta =
|
||||||
|
mum.lower
|
||||||
|
|> Num.bitwiseXor wyp0
|
||||||
|
|> Num.bitwiseXor length
|
||||||
|
nextb =
|
||||||
|
mum.upper
|
||||||
|
|> Num.bitwiseXor wyp1
|
||||||
|
hash = wymix nexta nextb
|
||||||
|
|
||||||
@LowLevelHasher { originalSeed, state: wymix state hash }
|
@LowLevelHasher { initializedSeed, state: wymix state hash }
|
||||||
|
|
||||||
|
initSeed = \seed ->
|
||||||
|
seed
|
||||||
|
|> Num.bitwiseXor wyp0
|
||||||
|
|> wymix wyp1
|
||||||
|
|> Num.bitwiseXor seed
|
||||||
|
|
||||||
complete = \@LowLevelHasher { state } -> state
|
complete = \@LowLevelHasher { state } -> state
|
||||||
|
|
||||||
@ -1052,8 +1068,7 @@ complete = \@LowLevelHasher { state } -> state
|
|||||||
# like using the output of the last hash as the seed to the current hash.
|
# like using the output of the last hash as the seed to the current hash.
|
||||||
# I am simply not sure the tradeoffs here. Theoretically this method is more sound.
|
# I am simply not sure the tradeoffs here. Theoretically this method is more sound.
|
||||||
# Either way, the performance will be similar and we can change this later.
|
# Either way, the performance will be similar and we can change this later.
|
||||||
addU8 = \@LowLevelHasher { originalSeed, state }, u8 ->
|
addU8 = \@LowLevelHasher { initializedSeed, state }, u8 ->
|
||||||
seed = Num.bitwiseXor originalSeed wyp0
|
|
||||||
p0 = Num.toU64 u8
|
p0 = Num.toU64 u8
|
||||||
a =
|
a =
|
||||||
Num.shiftLeftBy p0 16
|
Num.shiftLeftBy p0 16
|
||||||
@ -1061,10 +1076,9 @@ addU8 = \@LowLevelHasher { originalSeed, state }, u8 ->
|
|||||||
|> Num.bitwiseOr p0
|
|> Num.bitwiseOr p0
|
||||||
b = 0
|
b = 0
|
||||||
|
|
||||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 1 }
|
combineState (@LowLevelHasher { initializedSeed, state }) { a, b, seed: initializedSeed, length: 1 }
|
||||||
|
|
||||||
addU16 = \@LowLevelHasher { originalSeed, state }, u16 ->
|
addU16 = \@LowLevelHasher { initializedSeed, state }, u16 ->
|
||||||
seed = Num.bitwiseXor originalSeed wyp0
|
|
||||||
p0 = Num.bitwiseAnd u16 0xFF |> Num.toU64
|
p0 = Num.bitwiseAnd u16 0xFF |> Num.toU64
|
||||||
p1 = Num.shiftRightZfBy u16 8 |> Num.toU64
|
p1 = Num.shiftRightZfBy u16 8 |> Num.toU64
|
||||||
a =
|
a =
|
||||||
@ -1073,26 +1087,23 @@ addU16 = \@LowLevelHasher { originalSeed, state }, u16 ->
|
|||||||
|> Num.bitwiseOr p1
|
|> Num.bitwiseOr p1
|
||||||
b = 0
|
b = 0
|
||||||
|
|
||||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 2 }
|
combineState (@LowLevelHasher { initializedSeed, state }) { a, b, seed: initializedSeed, length: 2 }
|
||||||
|
|
||||||
addU32 = \@LowLevelHasher { originalSeed, state }, u32 ->
|
addU32 = \@LowLevelHasher { initializedSeed, state }, u32 ->
|
||||||
seed = Num.bitwiseXor originalSeed wyp0
|
|
||||||
p0 = Num.toU64 u32
|
p0 = Num.toU64 u32
|
||||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p0
|
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p0
|
||||||
|
|
||||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b: a, seed, length: 4 }
|
combineState (@LowLevelHasher { initializedSeed, state }) { a, b: a, seed: initializedSeed, length: 4 }
|
||||||
|
|
||||||
addU64 = \@LowLevelHasher { originalSeed, state }, u64 ->
|
addU64 = \@LowLevelHasher { initializedSeed, state }, u64 ->
|
||||||
seed = Num.bitwiseXor originalSeed wyp0
|
|
||||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF u64
|
p0 = Num.bitwiseAnd 0xFFFF_FFFF u64
|
||||||
p1 = Num.shiftRightZfBy u64 32
|
p1 = Num.shiftRightZfBy u64 32
|
||||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p1
|
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p1
|
||||||
b = Num.shiftLeftBy p1 32 |> Num.bitwiseOr p0
|
b = Num.shiftLeftBy p1 32 |> Num.bitwiseOr p0
|
||||||
|
|
||||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 8 }
|
combineState (@LowLevelHasher { initializedSeed, state }) { a, b, seed: initializedSeed, length: 8 }
|
||||||
|
|
||||||
addU128 = \@LowLevelHasher { originalSeed, state }, u128 ->
|
addU128 = \@LowLevelHasher { initializedSeed, state }, u128 ->
|
||||||
seed = Num.bitwiseXor originalSeed wyp0
|
|
||||||
lower = u128 |> Num.toU64
|
lower = u128 |> Num.toU64
|
||||||
upper = Num.shiftRightZfBy u128 64 |> Num.toU64
|
upper = Num.shiftRightZfBy u128 64 |> Num.toU64
|
||||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF lower
|
p0 = Num.bitwiseAnd 0xFFFF_FFFF lower
|
||||||
@ -1102,12 +1113,11 @@ addU128 = \@LowLevelHasher { originalSeed, state }, u128 ->
|
|||||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p2
|
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p2
|
||||||
b = Num.shiftLeftBy p3 32 |> Num.bitwiseOr p1
|
b = Num.shiftLeftBy p3 32 |> Num.bitwiseOr p1
|
||||||
|
|
||||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 16 }
|
combineState (@LowLevelHasher { initializedSeed, state }) { a, b, seed: initializedSeed, length: 16 }
|
||||||
|
|
||||||
addBytes : LowLevelHasher, List U8 -> LowLevelHasher
|
addBytes : LowLevelHasher, List U8 -> LowLevelHasher
|
||||||
addBytes = \@LowLevelHasher { originalSeed, state }, list ->
|
addBytes = \@LowLevelHasher { initializedSeed, state }, list ->
|
||||||
length = List.len list
|
length = List.len list
|
||||||
seed = Num.bitwiseXor originalSeed wyp0
|
|
||||||
abs =
|
abs =
|
||||||
if length <= 16 then
|
if length <= 16 then
|
||||||
if length >= 4 then
|
if length >= 4 then
|
||||||
@ -1117,17 +1127,17 @@ addBytes = \@LowLevelHasher { originalSeed, state }, list ->
|
|||||||
(wyr4 list (Num.subWrap length 4) |> Num.shiftLeftBy 32)
|
(wyr4 list (Num.subWrap length 4) |> Num.shiftLeftBy 32)
|
||||||
|> Num.bitwiseOr (wyr4 list (Num.subWrap length 4 |> Num.subWrap x))
|
|> Num.bitwiseOr (wyr4 list (Num.subWrap length 4 |> Num.subWrap x))
|
||||||
|
|
||||||
{ a, b, seed }
|
{ a, b, seed: initializedSeed }
|
||||||
else if length > 0 then
|
else if length > 0 then
|
||||||
{ a: wyr3 list 0 length, b: 0, seed }
|
{ a: wyr3 list 0 length, b: 0, seed: initializedSeed }
|
||||||
else
|
else
|
||||||
{ a: 0, b: 0, seed }
|
{ a: 0, b: 0, seed: initializedSeed }
|
||||||
else if length <= 48 then
|
else if length <= 48 then
|
||||||
hashBytesHelper16 seed list 0 length
|
hashBytesHelper16 initializedSeed list 0 length
|
||||||
else
|
else
|
||||||
hashBytesHelper48 seed seed seed list 0 length
|
hashBytesHelper48 initializedSeed initializedSeed initializedSeed list 0 length
|
||||||
|
|
||||||
combineState (@LowLevelHasher { originalSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length: Num.toU64 length }
|
combineState (@LowLevelHasher { initializedSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length: Num.toU64 length }
|
||||||
|
|
||||||
hashBytesHelper48 : U64, U64, U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
|
hashBytesHelper48 : U64, U64, U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
|
||||||
hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
|
hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
|
||||||
@ -1240,7 +1250,7 @@ expect
|
|||||||
|> addBytes []
|
|> addBytes []
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x1C3F_F8BF_07F9_B0B3
|
hash == 0xD59C59757DBBE6B3
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1248,7 +1258,7 @@ expect
|
|||||||
|> addBytes [0x42]
|
|> addBytes [0x42]
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x8F9F_0A1E_E06F_0D52
|
hash == 0x38CE03D0E61AF963
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1256,7 +1266,7 @@ expect
|
|||||||
|> addU8 0x42
|
|> addU8 0x42
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x8F9F_0A1E_E06F_0D52
|
hash == 0x38CE03D0E61AF963
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1264,7 +1274,7 @@ expect
|
|||||||
|> addBytes [0xFF, 0xFF]
|
|> addBytes [0xFF, 0xFF]
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x86CC_8B71_563F_F084
|
hash == 0xE1CB2FA0D6A64113
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1272,7 +1282,7 @@ expect
|
|||||||
|> addU16 0xFFFF
|
|> addU16 0xFFFF
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x86CC_8B71_563F_F084
|
hash == 0xE1CB2FA0D6A64113
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1280,7 +1290,7 @@ expect
|
|||||||
|> addBytes [0x36, 0xA7]
|
|> addBytes [0x36, 0xA7]
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xD1A5_0F24_2536_84F8
|
hash == 0x26B8319EDAF81B15
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1288,7 +1298,7 @@ expect
|
|||||||
|> addU16 0xA736
|
|> addU16 0xA736
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xD1A5_0F24_2536_84F8
|
hash == 0x26B8319EDAF81B15
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1296,7 +1306,7 @@ expect
|
|||||||
|> addBytes [0x00, 0x00, 0x00, 0x00]
|
|> addBytes [0x00, 0x00, 0x00, 0x00]
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x3762_ACB1_7604_B541
|
hash == 0xA187D7CA074F9EE7
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1304,7 +1314,7 @@ expect
|
|||||||
|> addU32 0x0000_0000
|
|> addU32 0x0000_0000
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x3762_ACB1_7604_B541
|
hash == 0xA187D7CA074F9EE7
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1312,7 +1322,7 @@ expect
|
|||||||
|> addBytes [0xA9, 0x2F, 0xEE, 0x21]
|
|> addBytes [0xA9, 0x2F, 0xEE, 0x21]
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x20F3_3FD7_D32E_C7A9
|
hash == 0xA499EFE4C1454D09
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1320,7 +1330,7 @@ expect
|
|||||||
|> addU32 0x21EE_2FA9
|
|> addU32 0x21EE_2FA9
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x20F3_3FD7_D32E_C7A9
|
hash == 0xA499EFE4C1454D09
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1328,7 +1338,7 @@ expect
|
|||||||
|> addBytes [0x5D, 0x66, 0xB1, 0x8F, 0x68, 0x44, 0xC7, 0x03, 0xE1, 0xDD, 0x23, 0x34, 0xBB, 0x9A, 0x42, 0xA7]
|
|> addBytes [0x5D, 0x66, 0xB1, 0x8F, 0x68, 0x44, 0xC7, 0x03, 0xE1, 0xDD, 0x23, 0x34, 0xBB, 0x9A, 0x42, 0xA7]
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xA16F_DDAA_C167_74C7
|
hash == 0xDD39A206AED64C73
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1336,7 +1346,7 @@ expect
|
|||||||
|> addU128 0xA742_9ABB_3423_DDE1_03C7_4468_8FB1_665D
|
|> addU128 0xA742_9ABB_3423_DDE1_03C7_4468_8FB1_665D
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xA16F_DDAA_C167_74C7
|
hash == 0xDD39A206AED64C73
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1344,7 +1354,7 @@ expect
|
|||||||
|> Hash.hashStrBytes "abcdefghijklmnopqrstuvwxyz"
|
|> Hash.hashStrBytes "abcdefghijklmnopqrstuvwxyz"
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xBEE0_A8FD_E990_D285
|
hash == 0x51C59DF5B1D15F40
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1352,7 +1362,7 @@ expect
|
|||||||
|> Hash.hashStrBytes "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|> Hash.hashStrBytes "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xB3C5_8528_9D82_A6EF
|
hash == 0xD8D0A129D97A4E95
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1360,7 +1370,7 @@ expect
|
|||||||
|> Hash.hashStrBytes "1234567890123456789012345678901234567890123456789012345678901234567890"
|
|> Hash.hashStrBytes "1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0xDB6B_7997_7A55_BA03
|
hash == 0x8188065B44FB4AAA
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash =
|
hash =
|
||||||
@ -1368,7 +1378,7 @@ expect
|
|||||||
|> addBytes (List.repeat 0x77 100)
|
|> addBytes (List.repeat 0x77 100)
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x171F_EEE2_B764_8E5E
|
hash == 0x47A2A606EADF3378
|
||||||
|
|
||||||
# Note, had to specify u8 in the lists below to avoid ability type resolution error.
|
# Note, had to specify u8 in the lists below to avoid ability type resolution error.
|
||||||
# Apparently it won't pick the default integer.
|
# Apparently it won't pick the default integer.
|
||||||
@ -1378,7 +1388,7 @@ expect
|
|||||||
|> Hash.hashUnordered [8u8, 82u8, 3u8, 8u8, 24u8] List.walk
|
|> Hash.hashUnordered [8u8, 82u8, 3u8, 8u8, 24u8] List.walk
|
||||||
|> complete
|
|> complete
|
||||||
|
|
||||||
hash == 0x999F_B530_3529_F17D
|
hash == 0xB2E8254C08F16B20
|
||||||
|
|
||||||
expect
|
expect
|
||||||
hash1 =
|
hash1 =
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
procedure Dict.1 (Dict.556):
|
procedure Dict.1 (Dict.554):
|
||||||
let Dict.565 : List {[], []} = Array [];
|
let Dict.563 : List {[], []} = Array [];
|
||||||
let Dict.572 : U64 = 0i64;
|
let Dict.570 : U64 = 0i64;
|
||||||
let Dict.573 : U64 = 8i64;
|
let Dict.571 : U64 = 8i64;
|
||||||
let Dict.566 : List U64 = CallByName List.11 Dict.572 Dict.573;
|
let Dict.564 : List U64 = CallByName List.11 Dict.570 Dict.571;
|
||||||
let Dict.569 : I8 = CallByName Dict.39;
|
let Dict.567 : I8 = CallByName Dict.39;
|
||||||
let Dict.570 : U64 = 8i64;
|
let Dict.568 : U64 = 8i64;
|
||||||
let Dict.567 : List I8 = CallByName List.11 Dict.569 Dict.570;
|
let Dict.565 : List I8 = CallByName List.11 Dict.567 Dict.568;
|
||||||
let Dict.568 : U64 = 0i64;
|
let Dict.566 : U64 = 0i64;
|
||||||
let Dict.564 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.565, Dict.566, Dict.567, Dict.568};
|
let Dict.562 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.563, Dict.564, Dict.565, Dict.566};
|
||||||
ret Dict.564;
|
ret Dict.562;
|
||||||
|
|
||||||
procedure Dict.39 ():
|
procedure Dict.39 ():
|
||||||
let Dict.571 : I8 = -128i64;
|
let Dict.569 : I8 = -128i64;
|
||||||
ret Dict.571;
|
ret Dict.569;
|
||||||
|
|
||||||
procedure Dict.4 (Dict.562):
|
procedure Dict.4 (Dict.560):
|
||||||
let Dict.100 : U64 = StructAtIndex 3 Dict.562;
|
let Dict.101 : U64 = StructAtIndex 3 Dict.560;
|
||||||
let #Derived_gen.8 : List {[], []} = StructAtIndex 0 Dict.562;
|
let #Derived_gen.8 : List {[], []} = StructAtIndex 0 Dict.560;
|
||||||
dec #Derived_gen.8;
|
dec #Derived_gen.8;
|
||||||
let #Derived_gen.7 : List U64 = StructAtIndex 1 Dict.562;
|
let #Derived_gen.7 : List U64 = StructAtIndex 1 Dict.560;
|
||||||
dec #Derived_gen.7;
|
dec #Derived_gen.7;
|
||||||
let #Derived_gen.6 : List I8 = StructAtIndex 2 Dict.562;
|
let #Derived_gen.6 : List I8 = StructAtIndex 2 Dict.560;
|
||||||
dec #Derived_gen.6;
|
dec #Derived_gen.6;
|
||||||
ret Dict.100;
|
ret Dict.101;
|
||||||
|
|
||||||
procedure List.11 (List.124, List.125):
|
procedure List.11 (List.124, List.125):
|
||||||
let List.536 : List I8 = CallByName List.68 List.125;
|
let List.536 : List I8 = CallByName List.68 List.125;
|
||||||
|
@ -11,7 +11,6 @@ version.workspace = true
|
|||||||
# pipe target to roc_build
|
# pipe target to roc_build
|
||||||
target-aarch64 = ["roc_build/target-aarch64"]
|
target-aarch64 = ["roc_build/target-aarch64"]
|
||||||
target-arm = ["roc_build/target-arm"]
|
target-arm = ["roc_build/target-arm"]
|
||||||
target-wasm32 = ["roc_build/target-wasm32"]
|
|
||||||
target-x86 = ["roc_build/target-x86"]
|
target-x86 = ["roc_build/target-x86"]
|
||||||
target-x86_64 = ["roc_build/target-x86_64"]
|
target-x86_64 = ["roc_build/target-x86_64"]
|
||||||
|
|
||||||
@ -21,7 +20,6 @@ roc_builtins = { path = "../compiler/builtins" }
|
|||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
roc_gen_llvm = { path = "../compiler/gen_llvm" }
|
roc_gen_llvm = { path = "../compiler/gen_llvm" }
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_module = { path = "../compiler/module" }
|
|
||||||
roc_mono = { path = "../compiler/mono" }
|
roc_mono = { path = "../compiler/mono" }
|
||||||
roc_parse = { path = "../compiler/parse" }
|
roc_parse = { path = "../compiler/parse" }
|
||||||
roc_region = { path = "../compiler/region" }
|
roc_region = { path = "../compiler/region" }
|
||||||
@ -31,6 +29,7 @@ roc_std = { path = "../roc_std" }
|
|||||||
roc_target = { path = "../compiler/roc_target" }
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
roc_types = { path = "../compiler/types" }
|
roc_types = { path = "../compiler/types" }
|
||||||
roc_error_macros = { path = "../error_macros" }
|
roc_error_macros = { path = "../error_macros" }
|
||||||
|
roc_repl_ui = { path = "../repl_ui" }
|
||||||
|
|
||||||
bumpalo.workspace = true
|
bumpalo.workspace = true
|
||||||
const_format.workspace = true
|
const_format.workspace = true
|
||||||
|
@ -7,49 +7,26 @@ use roc_error_macros::internal_error;
|
|||||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||||
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
||||||
use roc_load::{EntryPoint, FunctionKind, MonomorphizedModule};
|
use roc_load::{EntryPoint, MonomorphizedModule};
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
use roc_mono::layout::STLayoutInterner;
|
use roc_mono::layout::STLayoutInterner;
|
||||||
use roc_parse::ast::Expr;
|
use roc_parse::ast::Expr;
|
||||||
use roc_repl_eval::eval::jit_to_ast;
|
use roc_repl_eval::eval::jit_to_ast;
|
||||||
use roc_repl_eval::gen::{compile_to_mono, format_answer, Problems, ReplOutput};
|
use roc_repl_eval::gen::{format_answer, ReplOutput};
|
||||||
use roc_repl_eval::{ReplApp, ReplAppMemory};
|
use roc_repl_eval::{ReplApp, ReplAppMemory};
|
||||||
use roc_reporting::report::DEFAULT_PALETTE;
|
|
||||||
use roc_std::RocStr;
|
use roc_std::RocStr;
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
pub fn eval_llvm(
|
||||||
defs: I,
|
mut loaded: MonomorphizedModule<'_>,
|
||||||
src: &str,
|
target: &Triple,
|
||||||
target: Triple,
|
|
||||||
opt_level: OptLevel,
|
opt_level: OptLevel,
|
||||||
) -> (Option<ReplOutput>, Problems) {
|
) -> Option<ReplOutput> {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let target_info = TargetInfo::from(&target);
|
let target_info = TargetInfo::from(target);
|
||||||
let function_kind = FunctionKind::LambdaSet;
|
|
||||||
|
|
||||||
let mut loaded;
|
|
||||||
let problems;
|
|
||||||
|
|
||||||
match compile_to_mono(
|
|
||||||
&arena,
|
|
||||||
defs,
|
|
||||||
src,
|
|
||||||
target_info,
|
|
||||||
function_kind,
|
|
||||||
DEFAULT_PALETTE,
|
|
||||||
) {
|
|
||||||
(Some(mono), probs) => {
|
|
||||||
loaded = mono;
|
|
||||||
problems = probs;
|
|
||||||
}
|
|
||||||
(None, probs) => {
|
|
||||||
return (None, probs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug_assert_eq!(loaded.exposed_to_host.top_level_values.len(), 1);
|
debug_assert_eq!(loaded.exposed_to_host.top_level_values.len(), 1);
|
||||||
let (main_fn_symbol, main_fn_var) = loaded
|
let (main_fn_symbol, main_fn_var) = loaded
|
||||||
@ -70,15 +47,10 @@ pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
|||||||
DebugPrint::NOTHING,
|
DebugPrint::NOTHING,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
let (_, main_fn_layout) = *loaded
|
||||||
Some(layout) => *layout,
|
.procedures
|
||||||
None => {
|
.keys()
|
||||||
let empty_vec: Vec<String> = Vec::new(); // rustc can't infer the type of this Vec.
|
.find(|(s, _)| *s == main_fn_symbol)?;
|
||||||
debug_assert_ne!(problems.errors, empty_vec, "Got no errors but also no valid layout for the generated main function in the repl!");
|
|
||||||
|
|
||||||
return (None, problems);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let interns = loaded.interns.clone();
|
let interns = loaded.interns.clone();
|
||||||
|
|
||||||
@ -100,13 +72,10 @@ pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
|||||||
);
|
);
|
||||||
let expr_str = format_answer(&arena, expr).to_string();
|
let expr_str = format_answer(&arena, expr).to_string();
|
||||||
|
|
||||||
(
|
|
||||||
Some(ReplOutput {
|
Some(ReplOutput {
|
||||||
expr: expr_str,
|
expr: expr_str,
|
||||||
expr_type: expr_type_str,
|
expr_type: expr_type_str,
|
||||||
}),
|
})
|
||||||
problems,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CliApp {
|
struct CliApp {
|
||||||
@ -191,11 +160,11 @@ impl ReplAppMemory for CliMemory {
|
|||||||
|
|
||||||
fn mono_module_to_dylib<'a>(
|
fn mono_module_to_dylib<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
target: Triple,
|
target: &Triple,
|
||||||
loaded: MonomorphizedModule<'a>,
|
loaded: MonomorphizedModule<'a>,
|
||||||
opt_level: OptLevel,
|
opt_level: OptLevel,
|
||||||
) -> Result<(libloading::Library, &'a str, Subs, STLayoutInterner<'a>), libloading::Error> {
|
) -> Result<(libloading::Library, &'a str, Subs, STLayoutInterner<'a>), libloading::Error> {
|
||||||
let target_info = TargetInfo::from(&target);
|
let target_info = TargetInfo::from(target);
|
||||||
|
|
||||||
let MonomorphizedModule {
|
let MonomorphizedModule {
|
||||||
procedures,
|
procedures,
|
||||||
@ -210,7 +179,7 @@ fn mono_module_to_dylib<'a>(
|
|||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
let builder = context.create_builder();
|
let builder = context.create_builder();
|
||||||
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
||||||
&target, &context, "",
|
target, &context, "",
|
||||||
));
|
));
|
||||||
|
|
||||||
let module = arena.alloc(module);
|
let module = arena.alloc(module);
|
||||||
@ -287,6 +256,6 @@ fn mono_module_to_dylib<'a>(
|
|||||||
internal_error!("Errors defining module:\n{}", errors.to_string());
|
internal_error!("Errors defining module:\n{}", errors.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm_module_to_dylib(env.module, &target, opt_level)
|
llvm_module_to_dylib(env.module, target, opt_level)
|
||||||
.map(|lib| (lib, main_fn_name, subs, layout_interner))
|
.map(|lib| (lib, main_fn_name, subs, layout_interner))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
pub const BLUE: &str = "\u{001b}[36m";
|
|
||||||
pub const PINK: &str = "\u{001b}[35m";
|
|
||||||
pub const GREEN: &str = "\u{001b}[32m";
|
|
||||||
pub const END_COL: &str = "\u{001b}[0m";
|
|
@ -1,29 +1,30 @@
|
|||||||
//! Command Line Interface (CLI) functionality for the Read-Evaluate-Print-Loop (REPL).
|
//! Command Line Interface (CLI) functionality for the Read-Evaluate-Print-Loop (REPL).
|
||||||
mod cli_gen;
|
mod cli_gen;
|
||||||
mod colors;
|
|
||||||
pub mod repl_state;
|
|
||||||
|
|
||||||
use colors::{BLUE, END_COL, PINK};
|
use std::borrow::Cow;
|
||||||
use const_format::concatcp;
|
|
||||||
use repl_state::ReplState;
|
|
||||||
|
|
||||||
use crate::repl_state::PROMPT;
|
use bumpalo::Bump;
|
||||||
|
use roc_load::MonomorphizedModule;
|
||||||
|
use roc_mono::ir::OptLevel;
|
||||||
|
use roc_repl_eval::gen::Problems;
|
||||||
|
use roc_repl_ui::repl_state::{ReplAction, ReplState};
|
||||||
|
use roc_repl_ui::{
|
||||||
|
format_output, is_incomplete, CONT_PROMPT, PROMPT, SHORT_INSTRUCTIONS, TIPS, WELCOME_MESSAGE,
|
||||||
|
};
|
||||||
|
use roc_reporting::report::{ANSI_STYLE_CODES, DEFAULT_PALETTE};
|
||||||
|
use roc_target::TargetInfo;
|
||||||
|
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||||
|
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||||
|
use rustyline_derive::{Completer, Helper, Hinter};
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
pub const WELCOME_MESSAGE: &str = concatcp!(
|
use crate::cli_gen::eval_llvm;
|
||||||
"\n The rockin’ ",
|
|
||||||
BLUE,
|
|
||||||
"roc repl",
|
|
||||||
END_COL,
|
|
||||||
"\n",
|
|
||||||
PINK,
|
|
||||||
"────────────────────────",
|
|
||||||
END_COL,
|
|
||||||
"\n\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// For when nothing is entered in the repl
|
#[derive(Completer, Helper, Hinter, Default)]
|
||||||
// TODO add link to repl tutorial(does not yet exist).
|
pub struct ReplHelper {
|
||||||
pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n\n";
|
validator: InputValidator,
|
||||||
|
state: ReplState,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() -> i32 {
|
pub fn main() -> i32 {
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
@ -34,9 +35,12 @@ pub fn main() -> i32 {
|
|||||||
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
|
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
|
||||||
print!("{WELCOME_MESSAGE}{SHORT_INSTRUCTIONS}");
|
print!("{WELCOME_MESSAGE}{SHORT_INSTRUCTIONS}");
|
||||||
|
|
||||||
let mut editor = Editor::<ReplState>::new();
|
let mut editor = Editor::<ReplHelper>::new();
|
||||||
let repl_helper = ReplState::new();
|
let repl_helper = ReplHelper::default();
|
||||||
editor.set_helper(Some(repl_helper));
|
editor.set_helper(Some(repl_helper));
|
||||||
|
let target = Triple::host();
|
||||||
|
let target_info = TargetInfo::from(&target);
|
||||||
|
let mut arena = Bump::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match editor.readline(PROMPT) {
|
match editor.readline(PROMPT) {
|
||||||
@ -44,18 +48,34 @@ pub fn main() -> i32 {
|
|||||||
editor.add_history_entry(line.trim());
|
editor.add_history_entry(line.trim());
|
||||||
|
|
||||||
let dimensions = editor.dimensions();
|
let dimensions = editor.dimensions();
|
||||||
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
let repl_state = &mut editor
|
||||||
|
.helper_mut()
|
||||||
|
.expect("Editor helper was not set")
|
||||||
|
.state;
|
||||||
|
|
||||||
match repl_helper.step(&line, dimensions) {
|
arena.reset();
|
||||||
Ok(output) => {
|
match repl_state.step(&arena, &line, target_info, DEFAULT_PALETTE) {
|
||||||
|
ReplAction::Eval {
|
||||||
|
opt_mono,
|
||||||
|
problems,
|
||||||
|
opt_var_name,
|
||||||
|
} => {
|
||||||
|
let output =
|
||||||
|
evaluate(opt_mono, problems, opt_var_name, &target, dimensions);
|
||||||
// If there was no output, don't print a blank line!
|
// If there was no output, don't print a blank line!
|
||||||
// (This happens for something like a type annotation.)
|
// (This happens for something like a type annotation.)
|
||||||
if !output.is_empty() {
|
if !output.is_empty() {
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(exit_code) => return exit_code,
|
ReplAction::Exit => {
|
||||||
};
|
return 0;
|
||||||
|
}
|
||||||
|
ReplAction::Help => {
|
||||||
|
println!("{TIPS}");
|
||||||
|
}
|
||||||
|
ReplAction::Nothing => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
Err(ReadlineError::WindowResize) => {
|
Err(ReadlineError::WindowResize) => {
|
||||||
@ -76,3 +96,64 @@ pub fn main() -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(
|
||||||
|
opt_mono: Option<MonomorphizedModule<'_>>,
|
||||||
|
problems: Problems,
|
||||||
|
opt_var_name: Option<String>,
|
||||||
|
target: &Triple,
|
||||||
|
dimensions: Option<(usize, usize)>,
|
||||||
|
) -> String {
|
||||||
|
let opt_output = opt_mono.and_then(|mono| eval_llvm(mono, target, OptLevel::Normal));
|
||||||
|
format_output(
|
||||||
|
ANSI_STYLE_CODES,
|
||||||
|
opt_output,
|
||||||
|
problems,
|
||||||
|
opt_var_name,
|
||||||
|
dimensions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InputValidator {}
|
||||||
|
|
||||||
|
impl Validator for InputValidator {
|
||||||
|
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
||||||
|
if is_incomplete(ctx.input()) {
|
||||||
|
Ok(ValidationResult::Incomplete)
|
||||||
|
} else {
|
||||||
|
Ok(ValidationResult::Valid(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Highlighter for ReplHelper {
|
||||||
|
fn has_continuation_prompt(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||||
|
&'s self,
|
||||||
|
prompt: &'p str,
|
||||||
|
info: PromptInfo<'_>,
|
||||||
|
) -> Cow<'b, str> {
|
||||||
|
if info.line_no() > 0 {
|
||||||
|
CONT_PROMPT.into()
|
||||||
|
} else {
|
||||||
|
prompt.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Validator for ReplHelper {
|
||||||
|
fn validate(
|
||||||
|
&self,
|
||||||
|
ctx: &mut validate::ValidationContext,
|
||||||
|
) -> rustyline::Result<validate::ValidationResult> {
|
||||||
|
self.validator.validate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_while_typing(&self) -> bool {
|
||||||
|
self.validator.validate_while_typing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,7 +50,6 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
|
|||||||
defs: I,
|
defs: I,
|
||||||
expr: &str,
|
expr: &str,
|
||||||
target_info: TargetInfo,
|
target_info: TargetInfo,
|
||||||
function_kind: FunctionKind,
|
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
) -> (Option<MonomorphizedModule<'a>>, Problems) {
|
) -> (Option<MonomorphizedModule<'a>>, Problems) {
|
||||||
let filename = PathBuf::from("");
|
let filename = PathBuf::from("");
|
||||||
@ -64,7 +63,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
|
|||||||
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
||||||
LoadConfig {
|
LoadConfig {
|
||||||
target_info,
|
target_info,
|
||||||
function_kind,
|
function_kind: FunctionKind::LambdaSet,
|
||||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||||
palette,
|
palette,
|
||||||
threading: Threading::Single,
|
threading: Threading::Single,
|
||||||
|
@ -13,18 +13,23 @@ roc_cli = { path = "../cli" }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_build = { path = "../compiler/build" }
|
roc_build = { path = "../compiler/build" }
|
||||||
roc_repl_cli = { path = "../repl_cli" }
|
roc_repl_cli = { path = "../repl_cli" }
|
||||||
|
roc_repl_ui = { path = "../repl_ui" }
|
||||||
roc_test_utils = { path = "../test_utils" }
|
roc_test_utils = { path = "../test_utils" }
|
||||||
roc_wasm_interp = { path = "../wasm_interp" }
|
roc_wasm_interp = { path = "../wasm_interp" }
|
||||||
|
roc_reporting = { path = "../reporting" }
|
||||||
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
|
|
||||||
bumpalo.workspace = true
|
bumpalo.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
strip-ansi-escapes.workspace = true
|
strip-ansi-escapes.workspace = true
|
||||||
|
target-lexicon.workspace = true
|
||||||
|
rustyline.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["target-aarch64", "target-x86_64", "target-wasm32"]
|
default = ["target-aarch64", "target-x86_64", "target-wasm32"]
|
||||||
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||||
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
||||||
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
|
target-wasm32 = ["roc_build/target-wasm32"]
|
||||||
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
||||||
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
||||||
wasm = ["target-wasm32"]
|
wasm = ["target-wasm32"]
|
||||||
|
@ -3,7 +3,7 @@ use std::io::Write;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, ExitStatus, Stdio};
|
use std::process::{Command, ExitStatus, Stdio};
|
||||||
|
|
||||||
use roc_repl_cli::{SHORT_INSTRUCTIONS, WELCOME_MESSAGE};
|
use roc_repl_ui::{SHORT_INSTRUCTIONS, WELCOME_MESSAGE};
|
||||||
use roc_test_utils::assert_multiline_str_eq;
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
|
|
||||||
const ERROR_MESSAGE_START: char = '─';
|
const ERROR_MESSAGE_START: char = '─';
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
|
use bumpalo::Bump;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use roc_repl_cli::repl_state::{is_incomplete, ReplState, TIPS};
|
use roc_repl_cli::{evaluate, ReplHelper};
|
||||||
|
use roc_repl_ui::is_incomplete;
|
||||||
|
use roc_repl_ui::repl_state::{ReplAction, ReplState};
|
||||||
|
use roc_reporting::report::DEFAULT_PALETTE;
|
||||||
|
use roc_target::TargetInfo;
|
||||||
|
use rustyline::Editor;
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
// These are tests of the REPL state machine. They work without actually
|
// These are tests of the REPL state machine. They work without actually
|
||||||
// running the CLI, and without using rustyline, and instead verify
|
// running the CLI, and without using rustyline, and instead verify
|
||||||
@ -7,27 +14,27 @@ use roc_repl_cli::repl_state::{is_incomplete, ReplState, TIPS};
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_plus_one() {
|
fn one_plus_one() {
|
||||||
complete("1 + 1", &mut ReplState::new(), Ok(("2 : Num *", "val1")));
|
complete("1 + 1", &mut ReplState::new(), "2 : Num *", "val1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generated_expr_names() {
|
fn generated_expr_names() {
|
||||||
let mut state = ReplState::new();
|
let mut state = ReplState::new();
|
||||||
|
|
||||||
complete("2 * 3", &mut state, Ok(("6 : Num *", "val1")));
|
complete("2 * 3", &mut state, "6 : Num *", "val1");
|
||||||
complete("4 - 1", &mut state, Ok(("3 : Num *", "val2")));
|
complete("4 - 1", &mut state, "3 : Num *", "val2");
|
||||||
complete("val1 + val2", &mut state, Ok(("9 : Num *", "val3")));
|
complete("val1 + val2", &mut state, "9 : Num *", "val3");
|
||||||
complete("1 + (val2 * val3)", &mut state, Ok(("28 : Num *", "val4")));
|
complete("1 + (val2 * val3)", &mut state, "28 : Num *", "val4");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persisted_defs() {
|
fn persisted_defs() {
|
||||||
let mut state = ReplState::new();
|
let mut state = ReplState::new();
|
||||||
|
|
||||||
complete("x = 5", &mut state, Ok(("5 : Num *", "x")));
|
complete("x = 5", &mut state, "5 : Num *", "x");
|
||||||
complete("7 - 3", &mut state, Ok(("4 : Num *", "val1")));
|
complete("7 - 3", &mut state, "4 : Num *", "val1");
|
||||||
complete("y = 6", &mut state, Ok(("6 : Num *", "y")));
|
complete("y = 6", &mut state, "6 : Num *", "y");
|
||||||
complete("val1 + x + y", &mut state, Ok(("15 : Num *", "val2")));
|
complete("val1 + x + y", &mut state, "15 : Num *", "val2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -38,7 +45,7 @@ fn annotated_body() {
|
|||||||
|
|
||||||
input.push_str("t = A");
|
input.push_str("t = A");
|
||||||
|
|
||||||
complete(&input, &mut ReplState::new(), Ok(("A : [A, B, C]", "t")));
|
complete(&input, &mut ReplState::new(), "A : [A, B, C]", "t");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -53,7 +60,7 @@ fn exhaustiveness_problem() {
|
|||||||
|
|
||||||
input.push_str("t = A");
|
input.push_str("t = A");
|
||||||
|
|
||||||
complete(&input, &mut state, Ok(("A : [A, B, C]", "t")));
|
complete(&input, &mut state, "A : [A, B, C]", "t");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a `when` on it that isn't exhaustive
|
// Run a `when` on it that isn't exhaustive
|
||||||
@ -88,7 +95,11 @@ fn exhaustiveness_problem() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tips() {
|
fn tips() {
|
||||||
assert!(!is_incomplete(""));
|
assert!(!is_incomplete(""));
|
||||||
assert_eq!(ReplState::new().step("", None), Ok(TIPS.to_string()));
|
let arena = Bump::new();
|
||||||
|
let target = Triple::host();
|
||||||
|
let target_info = TargetInfo::from(&target);
|
||||||
|
let action = ReplState::default().step(&arena, "", target_info, DEFAULT_PALETTE);
|
||||||
|
assert!(matches!(action, ReplAction::Help));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -98,35 +109,49 @@ fn standalone_annotation() {
|
|||||||
|
|
||||||
incomplete(&mut input);
|
incomplete(&mut input);
|
||||||
assert!(!is_incomplete(&input));
|
assert!(!is_incomplete(&input));
|
||||||
assert_eq!(state.step(&input, None), Ok(String::new()));
|
let arena = Bump::new();
|
||||||
|
let target = Triple::host();
|
||||||
|
let target_info = TargetInfo::from(&target);
|
||||||
|
let action = state.step(&arena, &input, target_info, DEFAULT_PALETTE);
|
||||||
|
assert!(matches!(action, ReplAction::Nothing));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// validate and step the given input, then check the Result vs the output
|
/// validate and step the given input, then check the Result vs the output
|
||||||
/// with ANSI escape codes stripped.
|
/// with ANSI escape codes stripped.
|
||||||
fn complete(input: &str, state: &mut ReplState, expected_step_result: Result<(&str, &str), i32>) {
|
fn complete(input: &str, state: &mut ReplState, expected_start: &str, expected_end: &str) {
|
||||||
assert!(!is_incomplete(input));
|
assert!(!is_incomplete(input));
|
||||||
|
let arena = Bump::new();
|
||||||
|
let target = Triple::host();
|
||||||
|
let target_info = TargetInfo::from(&target);
|
||||||
|
let action = state.step(&arena, input, target_info, DEFAULT_PALETTE);
|
||||||
|
let repl_helper = ReplHelper::default();
|
||||||
|
let mut editor = Editor::<ReplHelper>::new();
|
||||||
|
editor.set_helper(Some(repl_helper));
|
||||||
|
let dimensions = editor.dimensions();
|
||||||
|
|
||||||
match state.step(input, None) {
|
match action {
|
||||||
Ok(string) => {
|
ReplAction::Eval {
|
||||||
|
opt_mono,
|
||||||
|
problems,
|
||||||
|
opt_var_name,
|
||||||
|
} => {
|
||||||
|
let string = evaluate(opt_mono, problems, opt_var_name, &target, dimensions);
|
||||||
let escaped =
|
let escaped =
|
||||||
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap())
|
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let comment_index = escaped.rfind('#').unwrap_or(escaped.len());
|
let comment_index = escaped.rfind('#').unwrap_or(escaped.len());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(expected_start, (escaped[0..comment_index].trim()));
|
||||||
expected_step_result.map(|(starts_with, _)| starts_with),
|
|
||||||
Ok(escaped[0..comment_index].trim())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_step_result.map(|(_, ends_with)| ends_with),
|
expected_end,
|
||||||
// +1 because we want to skip over the '#' itself
|
// +1 because we want to skip over the '#' itself
|
||||||
Ok(escaped[comment_index + 1..].trim())
|
(escaped[comment_index + 1..].trim())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
_ => {
|
||||||
assert_eq!(expected_step_result, Err(err));
|
panic!("Unexpected action: {:?}", action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,10 +168,29 @@ fn incomplete(input: &mut String) {
|
|||||||
/// with ANSI escape codes stripped.
|
/// with ANSI escape codes stripped.
|
||||||
fn error(input: &str, state: &mut ReplState, expected_step_result: String) {
|
fn error(input: &str, state: &mut ReplState, expected_step_result: String) {
|
||||||
assert!(!is_incomplete(input));
|
assert!(!is_incomplete(input));
|
||||||
|
let arena = Bump::new();
|
||||||
|
let target = Triple::host();
|
||||||
|
let target_info = TargetInfo::from(&target);
|
||||||
|
let action = state.step(&arena, input, target_info, DEFAULT_PALETTE);
|
||||||
|
let repl_helper = ReplHelper::default();
|
||||||
|
let mut editor = Editor::<ReplHelper>::new();
|
||||||
|
editor.set_helper(Some(repl_helper));
|
||||||
|
let dimensions = editor.dimensions();
|
||||||
|
|
||||||
let escaped = state.step(input, None).map(|string| {
|
match action {
|
||||||
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap()).unwrap()
|
ReplAction::Eval {
|
||||||
});
|
opt_mono,
|
||||||
|
problems,
|
||||||
assert_eq!(Ok(expected_step_result), escaped);
|
opt_var_name,
|
||||||
|
} => {
|
||||||
|
let string = evaluate(opt_mono, problems, opt_var_name, &target, dimensions);
|
||||||
|
let escaped =
|
||||||
|
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(expected_step_result, escaped);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Unexpected action: {:?}", action);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ impl<'a> ImportDispatcher for CompilerDispatcher<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(src: &'static str) -> Result<String, String> {
|
fn run(src: &'static str) -> String {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
|
||||||
let mut instance = {
|
let mut instance = {
|
||||||
@ -140,27 +140,33 @@ fn run(src: &'static str) -> Result<String, String> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let len = Value::I32(src.len() as i32);
|
let len = Value::I32(src.len() as i32);
|
||||||
let wasm_ok: i32 = instance
|
instance.call_export("entrypoint_from_test", [len]).unwrap();
|
||||||
.call_export("entrypoint_from_test", [len])
|
instance.import_dispatcher.answer.to_owned()
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.expect_i32()
|
|
||||||
.unwrap();
|
|
||||||
let answer_str = instance.import_dispatcher.answer.to_owned();
|
|
||||||
|
|
||||||
if wasm_ok == 0 {
|
|
||||||
Err(answer_str)
|
|
||||||
} else {
|
|
||||||
Ok(answer_str)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn expect_success(input: &'static str, expected: &str) {
|
pub fn expect_success(input: &'static str, expected: &str) {
|
||||||
assert_eq!(run(input), Ok(expected.into()));
|
expect(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn expect_failure(input: &'static str, expected: &str) {
|
pub fn expect_failure(input: &'static str, expected: &str) {
|
||||||
assert_eq!(run(input), Err(expected.into()));
|
expect(input, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn expect(input: &'static str, expected: &str) {
|
||||||
|
let raw_output = run(input);
|
||||||
|
|
||||||
|
// We need to get rid of HTML tags, and we can be quite specific about it!
|
||||||
|
// If we ever write more complex test cases, we might need regex here.
|
||||||
|
let without_html = raw_output
|
||||||
|
.replace("<span class='color-magenta'> : </span>", " : ")
|
||||||
|
.replace("<span class='color-green'> # val1</span>", "");
|
||||||
|
|
||||||
|
// Whitespace that was originally in front of the `# val1` is now at the end,
|
||||||
|
// and there's other whitespace at both ends too. Trim it all.
|
||||||
|
let clean_output = without_html.trim();
|
||||||
|
|
||||||
|
assert_eq!(clean_output, expected);
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,4 @@ set -euxo pipefail
|
|||||||
RUSTFLAGS="" cargo build --locked --profile release-with-lto --target wasm32-wasi -p roc_repl_wasm --no-default-features --features wasi_test
|
RUSTFLAGS="" cargo build --locked --profile release-with-lto --target wasm32-wasi -p roc_repl_wasm --no-default-features --features wasi_test
|
||||||
|
|
||||||
# Build & run the test code on *native* target, not WebAssembly
|
# Build & run the test code on *native* target, not WebAssembly
|
||||||
cargo test --locked --release -p repl_test --features wasm
|
cargo test --locked --release -p repl_test --features wasm $@
|
||||||
|
25
crates/repl_ui/Cargo.toml
Normal file
25
crates/repl_ui/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "roc_repl_ui"
|
||||||
|
description = "UI for the Roc REPL, shared between CLI and WebAssembly versions."
|
||||||
|
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_collections = { path = "../compiler/collections" }
|
||||||
|
roc_load = { path = "../compiler/load" }
|
||||||
|
roc_parse = { path = "../compiler/parse" }
|
||||||
|
roc_region = { path = "../compiler/region" }
|
||||||
|
roc_repl_eval = { path = "../repl_eval" }
|
||||||
|
roc_reporting = { path = "../reporting" }
|
||||||
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
|
|
||||||
|
bumpalo.workspace = true
|
||||||
|
const_format.workspace = true
|
||||||
|
unicode-segmentation.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "roc_repl_ui"
|
||||||
|
path = "src/lib.rs"
|
12
crates/repl_ui/src/colors.rs
Normal file
12
crates/repl_ui/src/colors.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use roc_reporting::report::{StyleCodes, ANSI_STYLE_CODES, HTML_STYLE_CODES};
|
||||||
|
|
||||||
|
const STYLE_CODES: StyleCodes = if cfg!(target_family = "wasm") {
|
||||||
|
HTML_STYLE_CODES
|
||||||
|
} else {
|
||||||
|
ANSI_STYLE_CODES
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BLUE: &str = STYLE_CODES.blue;
|
||||||
|
pub const PINK: &str = STYLE_CODES.magenta;
|
||||||
|
pub const GREEN: &str = STYLE_CODES.green;
|
||||||
|
pub const END_COL: &str = STYLE_CODES.reset;
|
202
crates/repl_ui/src/lib.rs
Normal file
202
crates/repl_ui/src/lib.rs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
//! UI functionality, shared between CLI and web, for the Read-Evaluate-Print-Loop (REPL).
|
||||||
|
// We don't do anything here related to the terminal (doesn't exist on the web) or LLVM (too big for the web).
|
||||||
|
pub mod colors;
|
||||||
|
pub mod repl_state;
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use colors::{BLUE, END_COL, GREEN, PINK};
|
||||||
|
use const_format::concatcp;
|
||||||
|
use repl_state::{parse_src, ParseOutcome};
|
||||||
|
use roc_parse::ast::{Expr, ValueDef};
|
||||||
|
use roc_repl_eval::gen::{Problems, ReplOutput};
|
||||||
|
use roc_reporting::report::StyleCodes;
|
||||||
|
|
||||||
|
pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||||
|
"\n The rockin’ ",
|
||||||
|
BLUE,
|
||||||
|
"roc repl",
|
||||||
|
END_COL,
|
||||||
|
"\n",
|
||||||
|
PINK,
|
||||||
|
"────────────────────────",
|
||||||
|
END_COL,
|
||||||
|
"\n\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO add link to repl tutorial(does not yet exist).
|
||||||
|
pub const TIPS: &str = concatcp!(
|
||||||
|
"\nEnter an expression to evaluate, or a definition (like ",
|
||||||
|
BLUE,
|
||||||
|
"x = 1",
|
||||||
|
END_COL,
|
||||||
|
") to use in future expressions.\n\nUnless there was a compile-time error, expressions get automatically named so you can refer to them later.\nFor example, if you see ",
|
||||||
|
GREEN,
|
||||||
|
"# val1",
|
||||||
|
END_COL,
|
||||||
|
" after an output, you can now refer to that expression as ",
|
||||||
|
BLUE,
|
||||||
|
"val1",
|
||||||
|
END_COL,
|
||||||
|
" in future expressions.\n\n",
|
||||||
|
"Tips:\n\n",
|
||||||
|
if cfg!(target_family = "wasm") {
|
||||||
|
// In the web REPL, the :quit command doesn't make sense. Just close the browser tab!
|
||||||
|
// We use Shift-Enter for newlines because it's nicer than our workaround for Unix terminals (see below)
|
||||||
|
concatcp!(
|
||||||
|
BLUE,
|
||||||
|
" - ",
|
||||||
|
END_COL,
|
||||||
|
PINK,
|
||||||
|
"Shift-Enter",
|
||||||
|
END_COL,
|
||||||
|
" or ",
|
||||||
|
PINK,
|
||||||
|
"Ctrl-Enter",
|
||||||
|
END_COL,
|
||||||
|
" makes a newline\n\n",
|
||||||
|
BLUE,
|
||||||
|
" - ",
|
||||||
|
END_COL,
|
||||||
|
":help"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// We use ctrl-v + ctrl-j for newlines because on Unix, terminals cannot distinguish between Shift-Enter and Enter
|
||||||
|
concatcp!(
|
||||||
|
BLUE,
|
||||||
|
" - ",
|
||||||
|
END_COL,
|
||||||
|
PINK,
|
||||||
|
"ctrl-v",
|
||||||
|
END_COL,
|
||||||
|
" + ",
|
||||||
|
PINK,
|
||||||
|
"ctrl-j",
|
||||||
|
END_COL,
|
||||||
|
" makes a newline\n\n",
|
||||||
|
BLUE,
|
||||||
|
" - ",
|
||||||
|
END_COL,
|
||||||
|
":q to quit\n\n",
|
||||||
|
BLUE,
|
||||||
|
" - ",
|
||||||
|
END_COL,
|
||||||
|
":help"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// For when nothing is entered in the repl
|
||||||
|
// TODO add link to repl tutorial(does not yet exist).
|
||||||
|
pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n\n";
|
||||||
|
pub const PROMPT: &str = concatcp!(BLUE, "»", END_COL, " ");
|
||||||
|
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||||
|
|
||||||
|
pub fn is_incomplete(input: &str) -> bool {
|
||||||
|
let arena = Bump::new();
|
||||||
|
|
||||||
|
match parse_src(&arena, input) {
|
||||||
|
ParseOutcome::Incomplete => !input.ends_with('\n'),
|
||||||
|
// Standalone annotations are default incomplete, because we can't know
|
||||||
|
// whether they're about to annotate a body on the next line
|
||||||
|
// (or if not, meaning they stay standalone) until you press Enter again!
|
||||||
|
//
|
||||||
|
// So it's Incomplete until you've pressed Enter again (causing the input to end in "\n")
|
||||||
|
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) if !input.ends_with('\n') => true,
|
||||||
|
ParseOutcome::Expr(Expr::When(_, _)) => {
|
||||||
|
// There might be lots of `when` branches, so don't assume the user is done entering
|
||||||
|
// them until they enter a blank line!
|
||||||
|
!input.ends_with('\n')
|
||||||
|
}
|
||||||
|
ParseOutcome::Empty
|
||||||
|
| ParseOutcome::Help
|
||||||
|
| ParseOutcome::Exit
|
||||||
|
| ParseOutcome::ValueDef(_)
|
||||||
|
| ParseOutcome::TypeDef(_)
|
||||||
|
| ParseOutcome::SyntaxErr
|
||||||
|
| ParseOutcome::Expr(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_output(
|
||||||
|
style_codes: StyleCodes,
|
||||||
|
opt_output: Option<ReplOutput>,
|
||||||
|
problems: Problems,
|
||||||
|
opt_var_name: Option<String>,
|
||||||
|
dimensions: Option<(usize, usize)>,
|
||||||
|
) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
||||||
|
if !buf.is_empty() {
|
||||||
|
buf.push_str("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push('\n');
|
||||||
|
buf.push_str(message);
|
||||||
|
buf.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ReplOutput { expr, expr_type }) = opt_output {
|
||||||
|
// If expr was empty, it was a type annotation or ability declaration;
|
||||||
|
// don't print anything!
|
||||||
|
//
|
||||||
|
// Also, for now we also don't print anything if there was a compile-time error.
|
||||||
|
// In the future, it would be great to run anyway and print useful output here!
|
||||||
|
if !expr.is_empty() && problems.errors.is_empty() {
|
||||||
|
const EXPR_TYPE_SEPARATOR: &str = " : "; // e.g. in "5 : Num *"
|
||||||
|
|
||||||
|
// Print the expr and its type
|
||||||
|
{
|
||||||
|
buf.push('\n');
|
||||||
|
buf.push_str(&expr);
|
||||||
|
buf.push_str(style_codes.magenta); // Color for the type separator
|
||||||
|
buf.push_str(EXPR_TYPE_SEPARATOR);
|
||||||
|
buf.push_str(style_codes.reset);
|
||||||
|
buf.push_str(&expr_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print var_name right-aligned on the last line of output.
|
||||||
|
if let Some(var_name) = opt_var_name {
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
const VAR_NAME_PREFIX: &str = " # "; // e.g. in " # val1"
|
||||||
|
const VAR_NAME_COLUMN_MAX: usize = 32; // Right-align the var_name at this column
|
||||||
|
|
||||||
|
let term_width = match dimensions {
|
||||||
|
Some((width, _)) => width.min(VAR_NAME_COLUMN_MAX),
|
||||||
|
None => VAR_NAME_COLUMN_MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr_with_type = format!("{expr}{EXPR_TYPE_SEPARATOR}{expr_type}");
|
||||||
|
|
||||||
|
// Count graphemes because we care about what's *rendered* in the terminal
|
||||||
|
let last_line_len = expr_with_type
|
||||||
|
.split('\n')
|
||||||
|
.last()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.graphemes(true)
|
||||||
|
.count();
|
||||||
|
let var_name_len =
|
||||||
|
var_name.graphemes(true).count() + VAR_NAME_PREFIX.graphemes(true).count();
|
||||||
|
let spaces_needed = if last_line_len + var_name_len > term_width {
|
||||||
|
buf.push('\n');
|
||||||
|
term_width - var_name_len
|
||||||
|
} else {
|
||||||
|
term_width - last_line_len - var_name_len
|
||||||
|
};
|
||||||
|
|
||||||
|
for _ in 0..spaces_needed {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(style_codes.green);
|
||||||
|
buf.push_str(VAR_NAME_PREFIX);
|
||||||
|
buf.push_str(&var_name);
|
||||||
|
buf.push_str(style_codes.reset);
|
||||||
|
buf.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
@ -1,9 +1,6 @@
|
|||||||
use crate::cli_gen::gen_and_eval_llvm;
|
|
||||||
use crate::colors::{BLUE, END_COL, GREEN, PINK};
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use const_format::concatcp;
|
|
||||||
use roc_collections::MutSet;
|
use roc_collections::MutSet;
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_load::MonomorphizedModule;
|
||||||
use roc_parse::ast::{Expr, Pattern, TypeDef, TypeHeader, ValueDef};
|
use roc_parse::ast::{Expr, Pattern, TypeDef, TypeHeader, ValueDef};
|
||||||
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
||||||
use roc_parse::parser::Parser;
|
use roc_parse::parser::Parser;
|
||||||
@ -12,65 +9,20 @@ use roc_parse::parser::{EWhen, Either};
|
|||||||
use roc_parse::state::State;
|
use roc_parse::state::State;
|
||||||
use roc_parse::{join_alias_to_body, join_ann_to_body};
|
use roc_parse::{join_alias_to_body, join_ann_to_body};
|
||||||
use roc_region::all::Loc;
|
use roc_region::all::Loc;
|
||||||
use roc_repl_eval::gen::{Problems, ReplOutput};
|
use roc_repl_eval::gen::{compile_to_mono, Problems};
|
||||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
use roc_reporting::report::Palette;
|
||||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
use roc_target::TargetInfo;
|
||||||
use rustyline_derive::{Completer, Helper, Hinter};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use target_lexicon::Triple;
|
|
||||||
|
|
||||||
pub const PROMPT: &str = concatcp!(BLUE, "»", END_COL, " ");
|
|
||||||
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
|
||||||
|
|
||||||
/// The prefix we use for the automatic variable names we assign to each expr,
|
/// The prefix we use for the automatic variable names we assign to each expr,
|
||||||
/// e.g. if the prefix is "val" then the first expr you enter will be named "val1"
|
/// e.g. if the prefix is "val" then the first expr you enter will be named "val1"
|
||||||
pub const AUTO_VAR_PREFIX: &str = "val";
|
pub const AUTO_VAR_PREFIX: &str = "val";
|
||||||
|
|
||||||
// TODO add link to repl tutorial(does not yet exist).
|
|
||||||
pub const TIPS: &str = concatcp!(
|
|
||||||
"\nEnter an expression to evaluate, or a definition (like ",
|
|
||||||
BLUE,
|
|
||||||
"x = 1",
|
|
||||||
END_COL,
|
|
||||||
") to use in future expressions.\n\nUnless there was a compile-time error, expressions get automatically named so you can refer to them later.\nFor example, if you see ",
|
|
||||||
GREEN,
|
|
||||||
"# val1",
|
|
||||||
END_COL,
|
|
||||||
" after an output, you can now refer to that expression as ",
|
|
||||||
BLUE,
|
|
||||||
"val1",
|
|
||||||
END_COL,
|
|
||||||
" in future expressions.\n\nTips:\n\n",
|
|
||||||
BLUE,
|
|
||||||
" - ",
|
|
||||||
END_COL,
|
|
||||||
PINK,
|
|
||||||
"ctrl-v",
|
|
||||||
END_COL,
|
|
||||||
" + ",
|
|
||||||
PINK,
|
|
||||||
"ctrl-j",
|
|
||||||
END_COL,
|
|
||||||
" makes a newline\n\n",
|
|
||||||
BLUE,
|
|
||||||
" - ",
|
|
||||||
END_COL,
|
|
||||||
":q to quit\n\n",
|
|
||||||
BLUE,
|
|
||||||
" - ",
|
|
||||||
END_COL,
|
|
||||||
":help"
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
struct PastDef {
|
struct PastDef {
|
||||||
ident: String,
|
ident: String,
|
||||||
src: String,
|
src: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Completer, Helper, Hinter)]
|
|
||||||
pub struct ReplState {
|
pub struct ReplState {
|
||||||
validator: InputValidator,
|
|
||||||
past_defs: Vec<PastDef>,
|
past_defs: Vec<PastDef>,
|
||||||
past_def_idents: MutSet<String>,
|
past_def_idents: MutSet<String>,
|
||||||
last_auto_ident: u64,
|
last_auto_ident: u64,
|
||||||
@ -82,39 +34,40 @@ impl Default for ReplState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum ReplAction<'a> {
|
||||||
|
Eval {
|
||||||
|
opt_mono: Option<MonomorphizedModule<'a>>,
|
||||||
|
problems: Problems,
|
||||||
|
opt_var_name: Option<String>,
|
||||||
|
},
|
||||||
|
Exit,
|
||||||
|
Help,
|
||||||
|
Nothing,
|
||||||
|
}
|
||||||
|
|
||||||
impl ReplState {
|
impl ReplState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
validator: InputValidator::new(),
|
|
||||||
past_defs: Default::default(),
|
past_defs: Default::default(),
|
||||||
past_def_idents: Default::default(),
|
past_def_idents: Default::default(),
|
||||||
last_auto_ident: 0,
|
last_auto_ident: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, line: &str, dimensions: Option<(usize, usize)>) -> Result<String, i32> {
|
pub fn step<'a>(
|
||||||
let arena = Bump::new();
|
&mut self,
|
||||||
|
arena: &'a Bump,
|
||||||
match parse_src(&arena, line) {
|
line: &str,
|
||||||
ParseOutcome::Empty => Ok(TIPS.to_string()),
|
target_info: TargetInfo,
|
||||||
ParseOutcome::Expr(_)
|
palette: Palette,
|
||||||
| ParseOutcome::ValueDef(_)
|
) -> ReplAction<'a> {
|
||||||
| ParseOutcome::TypeDef(_)
|
|
||||||
| ParseOutcome::SyntaxErr
|
|
||||||
| ParseOutcome::Incomplete => Ok(self.eval_and_format(line, dimensions)),
|
|
||||||
ParseOutcome::Help => {
|
|
||||||
// TODO add link to repl tutorial(does not yet exist).
|
|
||||||
Ok(TIPS.to_string())
|
|
||||||
}
|
|
||||||
ParseOutcome::Exit => Err(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_and_format(&mut self, src: &str, dimensions: Option<(usize, usize)>) -> String {
|
|
||||||
let arena = Bump::new();
|
|
||||||
let pending_past_def;
|
let pending_past_def;
|
||||||
let mut opt_var_name;
|
let mut opt_var_name;
|
||||||
let src = match parse_src(&arena, src) {
|
let src: &str = match parse_src(arena, line) {
|
||||||
|
ParseOutcome::Empty | ParseOutcome::Help => return ReplAction::Help,
|
||||||
|
ParseOutcome::Exit => return ReplAction::Exit,
|
||||||
ParseOutcome::Expr(_) | ParseOutcome::Incomplete | ParseOutcome::SyntaxErr => {
|
ParseOutcome::Expr(_) | ParseOutcome::Incomplete | ParseOutcome::SyntaxErr => {
|
||||||
pending_past_def = None;
|
pending_past_def = None;
|
||||||
// If it's a SyntaxErr (or Incomplete at this point, meaning it will
|
// If it's a SyntaxErr (or Incomplete at this point, meaning it will
|
||||||
@ -122,7 +75,7 @@ impl ReplState {
|
|||||||
// proceed as normal and let the error reporting happen during eval.
|
// proceed as normal and let the error reporting happen during eval.
|
||||||
opt_var_name = None;
|
opt_var_name = None;
|
||||||
|
|
||||||
src
|
line
|
||||||
}
|
}
|
||||||
ParseOutcome::ValueDef(value_def) => {
|
ParseOutcome::ValueDef(value_def) => {
|
||||||
match value_def {
|
match value_def {
|
||||||
@ -134,11 +87,11 @@ impl ReplState {
|
|||||||
_,
|
_,
|
||||||
) => {
|
) => {
|
||||||
// Record the standalone type annotation for future use.
|
// Record the standalone type annotation for future use.
|
||||||
self.add_past_def(ident.trim_end().to_string(), src.to_string());
|
self.add_past_def(ident.trim_end().to_string(), line.to_string());
|
||||||
|
|
||||||
// Return early without running eval, since standalone annotations
|
// Return early without running eval, since standalone annotations
|
||||||
// cannnot be evaluated as expressions.
|
// cannot be evaluated as expressions.
|
||||||
return String::new();
|
return ReplAction::Nothing;
|
||||||
}
|
}
|
||||||
ValueDef::Body(
|
ValueDef::Body(
|
||||||
Loc {
|
Loc {
|
||||||
@ -155,7 +108,7 @@ impl ReplState {
|
|||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
pending_past_def = Some((ident.to_string(), src.to_string()));
|
pending_past_def = Some((ident.to_string(), line.to_string()));
|
||||||
opt_var_name = Some(ident.to_string());
|
opt_var_name = Some(ident.to_string());
|
||||||
|
|
||||||
// Recreate the body of the def and then evaluate it as a lookup.
|
// Recreate the body of the def and then evaluate it as a lookup.
|
||||||
@ -163,11 +116,11 @@ impl ReplState {
|
|||||||
// if we just did a lookup on the past def, then errors wouldn't get
|
// if we just did a lookup on the past def, then errors wouldn't get
|
||||||
// reported because we filter out errors whose regions are in past defs.
|
// reported because we filter out errors whose regions are in past defs.
|
||||||
let mut buf = bumpalo::collections::string::String::with_capacity_in(
|
let mut buf = bumpalo::collections::string::String::with_capacity_in(
|
||||||
ident.len() + src.len() + 1,
|
ident.len() + line.len() + 1,
|
||||||
&arena,
|
arena,
|
||||||
);
|
);
|
||||||
|
|
||||||
buf.push_str(src);
|
buf.push_str(line);
|
||||||
buf.push('\n');
|
buf.push('\n');
|
||||||
buf.push_str(ident);
|
buf.push_str(ident);
|
||||||
|
|
||||||
@ -214,36 +167,37 @@ impl ReplState {
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
// Record the type for future use.
|
// Record the type for future use.
|
||||||
self.add_past_def(ident.trim_end().to_string(), src.to_string());
|
self.add_past_def(ident.trim_end().to_string(), line.to_string());
|
||||||
|
|
||||||
// Return early without running eval, since none of these
|
// Return early without running eval, since none of these
|
||||||
// can be evaluated as expressions.
|
// can be evaluated as expressions.
|
||||||
return String::new();
|
return ReplAction::Nothing;
|
||||||
}
|
}
|
||||||
ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Record e.g. "val1" as a past def, unless our input was exactly the name of
|
// Record e.g. "val1" as a past def, unless our input was exactly the name of
|
||||||
// an existing identifer (e.g. I just typed "val1" into the prompt - there's no
|
// an existing identifer (e.g. I just typed "val1" into the prompt - there's no
|
||||||
// need to reassign "val1" to "val2" just because I wanted to see what its value was!)
|
// need to reassign "val1" to "val2" just because I wanted to see what its value was!)
|
||||||
let (output, problems) =
|
let (opt_mono, problems) =
|
||||||
match opt_var_name.or_else(|| self.past_def_idents.get(src.trim()).cloned()) {
|
match opt_var_name.or_else(|| self.past_def_idents.get(src.trim()).cloned()) {
|
||||||
Some(existing_ident) => {
|
Some(existing_ident) => {
|
||||||
opt_var_name = Some(existing_ident);
|
opt_var_name = Some(existing_ident);
|
||||||
|
|
||||||
gen_and_eval_llvm(
|
compile_to_mono(
|
||||||
|
arena,
|
||||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||||
src,
|
src,
|
||||||
Triple::host(),
|
target_info,
|
||||||
OptLevel::Normal,
|
palette,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let (output, problems) = gen_and_eval_llvm(
|
let (output, problems) = compile_to_mono(
|
||||||
|
arena,
|
||||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||||
src,
|
src,
|
||||||
Triple::host(),
|
target_info,
|
||||||
OptLevel::Normal,
|
palette,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Don't persist defs that have compile errors
|
// Don't persist defs that have compile errors
|
||||||
@ -266,7 +220,11 @@ impl ReplState {
|
|||||||
self.add_past_def(ident, src);
|
self.add_past_def(ident, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
format_output(output, problems, opt_var_name, dimensions)
|
ReplAction::Eval {
|
||||||
|
opt_mono,
|
||||||
|
problems,
|
||||||
|
opt_var_name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_auto_ident(&mut self) -> u64 {
|
fn next_auto_ident(&mut self) -> u64 {
|
||||||
@ -284,7 +242,7 @@ impl ReplState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum ParseOutcome<'a> {
|
pub enum ParseOutcome<'a> {
|
||||||
ValueDef(ValueDef<'a>),
|
ValueDef(ValueDef<'a>),
|
||||||
TypeDef(TypeDef<'a>),
|
TypeDef(TypeDef<'a>),
|
||||||
Expr(Expr<'a>),
|
Expr(Expr<'a>),
|
||||||
@ -295,7 +253,7 @@ enum ParseOutcome<'a> {
|
|||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
pub fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
||||||
match line.trim().to_lowercase().as_str() {
|
match line.trim().to_lowercase().as_str() {
|
||||||
"" => ParseOutcome::Empty,
|
"" => ParseOutcome::Empty,
|
||||||
":help" => ParseOutcome::Help,
|
":help" => ParseOutcome::Help,
|
||||||
@ -456,161 +414,3 @@ fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InputValidator {}
|
|
||||||
|
|
||||||
impl InputValidator {
|
|
||||||
pub fn new() -> InputValidator {
|
|
||||||
InputValidator {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Validator for InputValidator {
|
|
||||||
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
|
||||||
if is_incomplete(ctx.input()) {
|
|
||||||
Ok(ValidationResult::Incomplete)
|
|
||||||
} else {
|
|
||||||
Ok(ValidationResult::Valid(None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_incomplete(input: &str) -> bool {
|
|
||||||
let arena = Bump::new();
|
|
||||||
|
|
||||||
match parse_src(&arena, input) {
|
|
||||||
ParseOutcome::Incomplete => !input.ends_with('\n'),
|
|
||||||
// Standalone annotations are default incomplete, because we can't know
|
|
||||||
// whether they're about to annotate a body on the next line
|
|
||||||
// (or if not, meaning they stay standalone) until you press Enter again!
|
|
||||||
//
|
|
||||||
// So it's Incomplete until you've pressed Enter again (causing the input to end in "\n")
|
|
||||||
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) if !input.ends_with('\n') => true,
|
|
||||||
ParseOutcome::Expr(Expr::When(_, _)) => {
|
|
||||||
// There might be lots of `when` branches, so don't assume the user is done entering
|
|
||||||
// them until they enter a blank line!
|
|
||||||
!input.ends_with('\n')
|
|
||||||
}
|
|
||||||
ParseOutcome::Empty
|
|
||||||
| ParseOutcome::Help
|
|
||||||
| ParseOutcome::Exit
|
|
||||||
| ParseOutcome::ValueDef(_)
|
|
||||||
| ParseOutcome::TypeDef(_)
|
|
||||||
| ParseOutcome::SyntaxErr
|
|
||||||
| ParseOutcome::Expr(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Highlighter for ReplState {
|
|
||||||
fn has_continuation_prompt(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
|
||||||
&'s self,
|
|
||||||
prompt: &'p str,
|
|
||||||
info: PromptInfo<'_>,
|
|
||||||
) -> Cow<'b, str> {
|
|
||||||
if info.line_no() > 0 {
|
|
||||||
CONT_PROMPT.into()
|
|
||||||
} else {
|
|
||||||
prompt.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Validator for ReplState {
|
|
||||||
fn validate(
|
|
||||||
&self,
|
|
||||||
ctx: &mut validate::ValidationContext,
|
|
||||||
) -> rustyline::Result<validate::ValidationResult> {
|
|
||||||
self.validator.validate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_while_typing(&self) -> bool {
|
|
||||||
self.validator.validate_while_typing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_output(
|
|
||||||
opt_output: Option<ReplOutput>,
|
|
||||||
problems: Problems,
|
|
||||||
opt_var_name: Option<String>,
|
|
||||||
dimensions: Option<(usize, usize)>,
|
|
||||||
) -> String {
|
|
||||||
let mut buf = String::new();
|
|
||||||
|
|
||||||
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
|
||||||
if !buf.is_empty() {
|
|
||||||
buf.push_str("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push('\n');
|
|
||||||
buf.push_str(message);
|
|
||||||
buf.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ReplOutput { expr, expr_type }) = opt_output {
|
|
||||||
// If expr was empty, it was a type annotation or ability declaration;
|
|
||||||
// don't print anything!
|
|
||||||
//
|
|
||||||
// Also, for now we also don't print anything if there was a compile-time error.
|
|
||||||
// In the future, it would be great to run anyway and print useful output here!
|
|
||||||
if !expr.is_empty() && problems.errors.is_empty() {
|
|
||||||
const EXPR_TYPE_SEPARATOR: &str = " : "; // e.g. in "5 : Num *"
|
|
||||||
|
|
||||||
// Print the expr and its type
|
|
||||||
{
|
|
||||||
buf.push('\n');
|
|
||||||
buf.push_str(&expr);
|
|
||||||
buf.push_str(PINK); // Color for the type separator
|
|
||||||
buf.push_str(EXPR_TYPE_SEPARATOR);
|
|
||||||
buf.push_str(END_COL);
|
|
||||||
buf.push_str(&expr_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print var_name right-aligned on the last line of output.
|
|
||||||
if let Some(var_name) = opt_var_name {
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
|
||||||
|
|
||||||
const VAR_NAME_PREFIX: &str = " # "; // e.g. in " # val1"
|
|
||||||
const VAR_NAME_COLUMN_MAX: usize = 32; // Right-align the var_name at this column
|
|
||||||
|
|
||||||
let term_width = match dimensions {
|
|
||||||
Some((width, _)) => width.min(VAR_NAME_COLUMN_MAX),
|
|
||||||
None => VAR_NAME_COLUMN_MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
let expr_with_type = format!("{expr}{EXPR_TYPE_SEPARATOR}{expr_type}");
|
|
||||||
|
|
||||||
// Count graphemes because we care about what's *rendered* in the terminal
|
|
||||||
let last_line_len = expr_with_type
|
|
||||||
.split('\n')
|
|
||||||
.last()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.graphemes(true)
|
|
||||||
.count();
|
|
||||||
let var_name_len =
|
|
||||||
var_name.graphemes(true).count() + VAR_NAME_PREFIX.graphemes(true).count();
|
|
||||||
let spaces_needed = if last_line_len + var_name_len > term_width {
|
|
||||||
buf.push('\n');
|
|
||||||
term_width - var_name_len
|
|
||||||
} else {
|
|
||||||
term_width - last_line_len - var_name_len
|
|
||||||
};
|
|
||||||
|
|
||||||
for _ in 0..spaces_needed {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(GREEN);
|
|
||||||
buf.push_str(VAR_NAME_PREFIX);
|
|
||||||
buf.push_str(&var_name);
|
|
||||||
buf.push_str(END_COL);
|
|
||||||
buf.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
@ -31,6 +31,7 @@ roc_gen_wasm = { path = "../compiler/gen_wasm" }
|
|||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_parse = { path = "../compiler/parse" }
|
roc_parse = { path = "../compiler/parse" }
|
||||||
roc_repl_eval = { path = "../repl_eval" }
|
roc_repl_eval = { path = "../repl_eval" }
|
||||||
|
roc_repl_ui = { path = "../repl_ui" }
|
||||||
roc_reporting = { path = "../reporting" }
|
roc_reporting = { path = "../reporting" }
|
||||||
roc_solve = { path = "../compiler/solve" }
|
roc_solve = { path = "../compiler/solve" }
|
||||||
roc_target = { path = "../compiler/roc_target" }
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
|
@ -24,20 +24,29 @@ You should run it from the project root directory.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
crates/repl_wasm/build-www.sh
|
crates/repl_wasm/build-www.sh
|
||||||
cp crates/repl_wasm/build/* www/build/repl/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Run a local HTTP server
|
### 3. Make symlinks to the generated Wasm and JS
|
||||||
|
```bash
|
||||||
|
cd www/public
|
||||||
|
ln -s ../../../crates/repl_wasm/build/roc_repl_wasm_bg.wasm
|
||||||
|
ln -s ../../../crates/repl_wasm/build/roc_repl_wasm.js
|
||||||
|
```
|
||||||
|
These symlinks are ignored by Git.
|
||||||
|
This is slightly different from how we do the production build but it makes development easy.
|
||||||
|
You can directly edit files like repl.js and just reload your browser to see changes.
|
||||||
|
|
||||||
|
### 4. Run a local HTTP server
|
||||||
|
|
||||||
Browsers won't load .wasm files over the `file://` protocol, so you need to serve the files in `./www/build/` from a local web server.
|
Browsers won't load .wasm files over the `file://` protocol, so you need to serve the files in `./www/build/` from a local web server.
|
||||||
Any server will do, but this example should work on any system that has Python 3 installed:
|
Any server will do, but this example should work on any system that has Python 3 installed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd www/build
|
cd www/public
|
||||||
python3 -m http.server
|
python3 -m http.server
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Open your browser
|
### 5. Open your browser
|
||||||
|
|
||||||
You should be able to find the Roc REPL at <http://127.0.0.1:8000/repl> (or whatever port your web server mentioned when it started up.)
|
You should be able to find the Roc REPL at <http://127.0.0.1:8000/repl> (or whatever port your web server mentioned when it started up.)
|
||||||
|
|
||||||
|
@ -6,12 +6,6 @@ use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
|
|||||||
|
|
||||||
const PLATFORM_FILENAME: &str = "repl_platform";
|
const PLATFORM_FILENAME: &str = "repl_platform";
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
const OBJECT_EXTENSION: &str = "o";
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
const OBJECT_EXTENSION: &str = "obj";
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
let source_path = format!("src/{PLATFORM_FILENAME}.c");
|
let source_path = format!("src/{PLATFORM_FILENAME}.c");
|
||||||
@ -26,7 +20,7 @@ fn main() {
|
|||||||
|
|
||||||
let mut pre_linked_binary_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
let mut pre_linked_binary_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||||
pre_linked_binary_path.extend(["pre_linked_binary"]);
|
pre_linked_binary_path.extend(["pre_linked_binary"]);
|
||||||
pre_linked_binary_path.set_extension(OBJECT_EXTENSION);
|
pre_linked_binary_path.set_extension("wasm");
|
||||||
|
|
||||||
let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile()
|
let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile()
|
||||||
.expect("failed to write host builtins object to tempfile");
|
.expect("failed to write host builtins object to tempfile");
|
||||||
@ -65,7 +59,7 @@ fn zig_executable() -> String {
|
|||||||
|
|
||||||
fn build_wasm_platform(out_dir: &str, source_path: &str) -> PathBuf {
|
fn build_wasm_platform(out_dir: &str, source_path: &str) -> PathBuf {
|
||||||
let mut platform_obj = PathBuf::from(out_dir).join(PLATFORM_FILENAME);
|
let mut platform_obj = PathBuf::from(out_dir).join(PLATFORM_FILENAME);
|
||||||
platform_obj.set_extension(OBJECT_EXTENSION);
|
platform_obj.set_extension("wasm");
|
||||||
|
|
||||||
Command::new(zig_executable())
|
Command::new(zig_executable())
|
||||||
.args([
|
.args([
|
||||||
|
@ -30,6 +30,6 @@ macro_rules! console_log {
|
|||||||
/// The browser only has an async API to generate a Wasm module from bytes
|
/// The browser only has an async API to generate a Wasm module from bytes
|
||||||
/// wasm_bindgen manages the interaction between Rust Futures and JS Promises
|
/// wasm_bindgen manages the interaction between Rust Futures and JS Promises
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
pub async fn entrypoint_from_js(src: String) -> String {
|
||||||
crate::repl::entrypoint_from_js(src).await
|
crate::repl::entrypoint_from_js(src).await
|
||||||
}
|
}
|
||||||
|
@ -30,19 +30,14 @@ pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize {
|
|||||||
/// - Synchronous API, to avoid the need to run an async executor across the Wasm/native boundary.
|
/// - Synchronous API, to avoid the need to run an async executor across the Wasm/native boundary.
|
||||||
/// - Uses an extra callback to allocate & copy the input string (in the browser version, wasm_bindgen does this)
|
/// - Uses an extra callback to allocate & copy the input string (in the browser version, wasm_bindgen does this)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn entrypoint_from_test(src_len: usize) -> bool {
|
pub extern "C" fn entrypoint_from_test(src_len: usize) {
|
||||||
let mut src_buffer = std::vec![0; src_len];
|
let mut src_buffer = std::vec![0; src_len];
|
||||||
let src = unsafe {
|
let src = unsafe {
|
||||||
test_copy_input_string(src_buffer.as_mut_ptr());
|
test_copy_input_string(src_buffer.as_mut_ptr());
|
||||||
String::from_utf8_unchecked(src_buffer)
|
String::from_utf8_unchecked(src_buffer)
|
||||||
};
|
};
|
||||||
let result_async = crate::repl::entrypoint_from_js(src);
|
let result_async = crate::repl::entrypoint_from_js(src);
|
||||||
let result = executor::block_on(result_async);
|
let output = executor::block_on(result_async);
|
||||||
let ok = result.is_ok();
|
|
||||||
|
|
||||||
let output = result.unwrap_or_else(|s| s);
|
|
||||||
|
|
||||||
unsafe { test_copy_output_string(output.as_ptr(), output.len()) }
|
unsafe { test_copy_output_string(output.as_ptr(), output.len()) }
|
||||||
|
|
||||||
ok
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use bumpalo::{collections::vec::Vec, Bump};
|
use bumpalo::{collections::vec::Vec, Bump};
|
||||||
use std::mem::size_of;
|
use roc_reporting::report::{DEFAULT_PALETTE_HTML, HTML_STYLE_CODES};
|
||||||
|
use std::{cell::RefCell, mem::size_of};
|
||||||
|
|
||||||
use roc_collections::all::MutSet;
|
use roc_collections::all::MutSet;
|
||||||
use roc_gen_wasm::wasm32_result;
|
use roc_gen_wasm::wasm32_result;
|
||||||
@ -7,10 +8,14 @@ use roc_load::MonomorphizedModule;
|
|||||||
use roc_parse::ast::Expr;
|
use roc_parse::ast::Expr;
|
||||||
use roc_repl_eval::{
|
use roc_repl_eval::{
|
||||||
eval::jit_to_ast,
|
eval::jit_to_ast,
|
||||||
gen::{compile_to_mono, format_answer},
|
gen::{format_answer, ReplOutput},
|
||||||
ReplApp, ReplAppMemory,
|
ReplApp, ReplAppMemory,
|
||||||
};
|
};
|
||||||
use roc_reporting::report::DEFAULT_PALETTE_HTML;
|
use roc_repl_ui::{
|
||||||
|
format_output,
|
||||||
|
repl_state::{ReplAction, ReplState},
|
||||||
|
TIPS,
|
||||||
|
};
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
||||||
|
|
||||||
@ -18,6 +23,12 @@ use crate::{js_create_app, js_get_result_and_memory, js_run_app};
|
|||||||
|
|
||||||
const WRAPPER_NAME: &str = "wrapper";
|
const WRAPPER_NAME: &str = "wrapper";
|
||||||
|
|
||||||
|
// On the web, we keep the REPL state in a global variable, because `main` is not in our Rust code!
|
||||||
|
// We return back to JS after every line of input. `main` is in the browser engine, running the JS event loop.
|
||||||
|
std::thread_local! {
|
||||||
|
static REPL_STATE: RefCell<ReplState> = RefCell::new(ReplState::new());
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WasmReplApp<'a> {
|
pub struct WasmReplApp<'a> {
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
}
|
}
|
||||||
@ -164,51 +175,60 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
const PRE_LINKED_BINARY: &[u8] =
|
const PRE_LINKED_BINARY: &[u8] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/pre_linked_binary.o")) as &[_];
|
include_bytes!(concat!(env!("OUT_DIR"), "/pre_linked_binary.wasm")) as &[_];
|
||||||
|
|
||||||
#[cfg(windows)]
|
pub async fn entrypoint_from_js(src: String) -> String {
|
||||||
const PRE_LINKED_BINARY: &[u8] =
|
// If our Rust code panics, redirect the error message to JS console.error
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/pre_linked_binary.obj")) as &[_];
|
// Also, our JS code overrides console.error to display the error message text (including stack trace) in the REPL output.
|
||||||
|
|
||||||
pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|
||||||
#[cfg(feature = "console_error_panic_hook")]
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
// TODO: make this a global and reset it?
|
||||||
let arena = &Bump::new();
|
let arena = &Bump::new();
|
||||||
|
|
||||||
// Compile the app
|
// Compile the app
|
||||||
let target_info = TargetInfo::default_wasm32();
|
let target_info = TargetInfo::default_wasm32();
|
||||||
let function_kind = roc_solve::FunctionKind::LambdaSet;
|
|
||||||
// TODO use this to filter out problems and warnings in wrapped defs.
|
|
||||||
// See the variable by the same name in the CLI REPL for how to do this!
|
|
||||||
let mono = match compile_to_mono(
|
|
||||||
arena,
|
|
||||||
std::iter::empty(),
|
|
||||||
&src,
|
|
||||||
target_info,
|
|
||||||
function_kind,
|
|
||||||
DEFAULT_PALETTE_HTML,
|
|
||||||
) {
|
|
||||||
(Some(m), problems) if problems.is_empty() => m, // TODO render problems and continue if possible
|
|
||||||
(_, problems) => {
|
|
||||||
// TODO always report these, but continue if possible with the MonomorphizedModule if we have one.
|
|
||||||
let mut buf = String::new();
|
|
||||||
|
|
||||||
// Join all the errors and warnings together with blank lines.
|
// Advance the REPL state machine
|
||||||
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
let action = REPL_STATE.with(|repl_state_cell| {
|
||||||
if !buf.is_empty() {
|
let mut repl_state = repl_state_cell.borrow_mut();
|
||||||
buf.push_str("\n\n");
|
repl_state.step(arena, &src, target_info, DEFAULT_PALETTE_HTML)
|
||||||
}
|
});
|
||||||
|
|
||||||
buf.push_str(message);
|
// Perform the action the state machine asked for, and return the appropriate output string
|
||||||
}
|
match action {
|
||||||
|
ReplAction::Help => TIPS.to_string(),
|
||||||
return Err(buf);
|
ReplAction::Exit => {
|
||||||
|
"To exit the web version of the REPL, just close the browser tab!".to_string()
|
||||||
}
|
}
|
||||||
|
ReplAction::Nothing => String::new(),
|
||||||
|
ReplAction::Eval {
|
||||||
|
opt_mono,
|
||||||
|
problems,
|
||||||
|
opt_var_name,
|
||||||
|
} => {
|
||||||
|
let opt_output = match opt_mono {
|
||||||
|
Some(mono) => eval_wasm(arena, target_info, mono).await,
|
||||||
|
None => None,
|
||||||
};
|
};
|
||||||
|
let dimensions = None; // TODO: we could get the window dimensions from JS...
|
||||||
|
format_output(
|
||||||
|
HTML_STYLE_CODES,
|
||||||
|
opt_output,
|
||||||
|
problems,
|
||||||
|
opt_var_name,
|
||||||
|
dimensions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn eval_wasm<'a>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
target_info: TargetInfo,
|
||||||
|
mono: MonomorphizedModule<'a>,
|
||||||
|
) -> Option<ReplOutput> {
|
||||||
let MonomorphizedModule {
|
let MonomorphizedModule {
|
||||||
module_id,
|
module_id,
|
||||||
procedures,
|
procedures,
|
||||||
@ -225,7 +245,7 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||||||
let main_fn_var = *main_fn_var;
|
let main_fn_var = *main_fn_var;
|
||||||
|
|
||||||
// pretty-print the expr type string for later.
|
// pretty-print the expr type string for later.
|
||||||
let expr_type_str = name_and_print_var(
|
let expr_type = name_and_print_var(
|
||||||
main_fn_var,
|
main_fn_var,
|
||||||
&mut subs,
|
&mut subs,
|
||||||
module_id,
|
module_id,
|
||||||
@ -233,10 +253,7 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||||||
DebugPrint::NOTHING,
|
DebugPrint::NOTHING,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
let (_, main_fn_layout) = *procedures.keys().find(|(s, _)| *s == main_fn_symbol)?;
|
||||||
Some(layout) => *layout,
|
|
||||||
None => return Ok(format!("<function> : {expr_type_str}")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let app_module_bytes = {
|
let app_module_bytes = {
|
||||||
let env = roc_gen_wasm::Env {
|
let env = roc_gen_wasm::Env {
|
||||||
@ -279,10 +296,16 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||||||
buffer
|
buffer
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send the compiled binary out to JS and create an executable instance from it
|
// Send the compiled binary out to JS, which will asynchronously create an executable WebAssembly instance
|
||||||
js_create_app(&app_module_bytes)
|
match js_create_app(&app_module_bytes).await {
|
||||||
.await
|
Ok(()) => {}
|
||||||
.map_err(|js| format!("{js:?}"))?;
|
Err(js_exception) => {
|
||||||
|
return Some(ReplOutput {
|
||||||
|
expr: format!("<span class='color-red'>{js_exception:?}</span>"),
|
||||||
|
expr_type: String::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut app = WasmReplApp { arena };
|
let mut app = WasmReplApp { arena };
|
||||||
|
|
||||||
@ -300,11 +323,8 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||||||
target_info,
|
target_info,
|
||||||
);
|
);
|
||||||
|
|
||||||
let var_name = String::new(); // TODO turn this into something like " # val1"
|
|
||||||
|
|
||||||
// Transform the Expr to a string
|
// Transform the Expr to a string
|
||||||
// `Result::Err` becomes a JS exception that will be caught and displayed
|
let expr = format_answer(arena, res_answer).to_string();
|
||||||
let expr = format_answer(arena, res_answer);
|
|
||||||
|
|
||||||
Ok(format!("{expr} : {expr_type_str}{var_name}"))
|
Some(ReplOutput { expr, expr_type })
|
||||||
}
|
}
|
||||||
|
@ -241,6 +241,7 @@ pub const DEFAULT_PALETTE: Palette = default_palette_from_style_codes(ANSI_STYLE
|
|||||||
pub const DEFAULT_PALETTE_HTML: Palette = default_palette_from_style_codes(HTML_STYLE_CODES);
|
pub const DEFAULT_PALETTE_HTML: Palette = default_palette_from_style_codes(HTML_STYLE_CODES);
|
||||||
|
|
||||||
/// A machine-readable format for text styles (colors and other styles)
|
/// A machine-readable format for text styles (colors and other styles)
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct StyleCodes {
|
pub struct StyleCodes {
|
||||||
pub red: &'static str,
|
pub red: &'static str,
|
||||||
pub green: &'static str,
|
pub green: &'static str,
|
||||||
|
71
devtools/signing.md
Normal file
71
devtools/signing.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Commit Signing Guide
|
||||||
|
|
||||||
|
If you don't have signing set up on your device and you only want to make simple changes, it will be easier to use [github's edit button](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files) for single file changes or [github's online VSCode editor](https://docs.github.com/en/codespaces/the-githubdev-web-based-editor#opening-the-githubdev-editor) for multi-file changes. These tools will sign your commit automatically.
|
||||||
|
|
||||||
|
For complex changes you will want to set up signing on your device.
|
||||||
|
Follow along with the subsection below that applies to you.
|
||||||
|
|
||||||
|
If your situation is not listed below, consider adding your steps to help out others.
|
||||||
|
|
||||||
|
## Setting up commit signing for the first time
|
||||||
|
|
||||||
|
If you have a Yubikey, and use macOS or Linux, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k) and [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73).
|
||||||
|
For windows with a Yubikey, follow [this guide](https://scatteredcode.net/signing-git-commits-using-yubikey-on-windows/).
|
||||||
|
|
||||||
|
Without a Yubikey:
|
||||||
|
1. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
|
||||||
|
2. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
|
||||||
|
3. Make git sign your commits automatically:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git config --global commit.gpgsign true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transferring existing key from Linux to Windows
|
||||||
|
|
||||||
|
### With Yubikey
|
||||||
|
|
||||||
|
This explanation was based on the steps outlined [here](https://scatteredcode.net/signing-git-commits-using-yubikey-on-windows/).
|
||||||
|
|
||||||
|
On linux, run:
|
||||||
|
```
|
||||||
|
gpg --list-keys --keyid-format SHORT | grep ^pub
|
||||||
|
gpg --export --armor [Your_Key_ID] > public.asc
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the public.asc file to windows.
|
||||||
|
|
||||||
|
Download and install [Gpg4win](https://www.gpg4win.org/get-gpg4win.html).
|
||||||
|
|
||||||
|
Open the program Kleopatra (installed with gpg4win) and go to Smartcards.
|
||||||
|
You should see your Yubikey there, it should also say something like `failed to find public key locally`. Click the import button and open the `public.asc` file you created earlier.
|
||||||
|
Close Kleopatra.
|
||||||
|
|
||||||
|
Install the [YubiKey Minidriver for 64-bit systems – Windows Installer](https://www.yubico.com/support/download/smart-card-drivers-tools/).
|
||||||
|
|
||||||
|
Insert your Yubikey and check if it is mentioned in the output of `gpg --card-status` (powershell).
|
||||||
|
|
||||||
|
Open powershell and execute:
|
||||||
|
```
|
||||||
|
git config --global gpg.program "c:\Program Files (x86)\GnuPG\bin\gpg.exe"
|
||||||
|
git config --global commit.gpgsign true
|
||||||
|
gpg --list-secret-keys --keyid-format LONG
|
||||||
|
```
|
||||||
|
The last command will show your keyid. On the line that says `[SC]`, copy the id.
|
||||||
|
In the example below the id is 683AB68D867FEB5C
|
||||||
|
```
|
||||||
|
sec> rsa4096/683AB68D867FEB5C 2020-02-02 [SC] [expires: 2022-02-02]
|
||||||
|
```
|
||||||
|
|
||||||
|
Tell git your keyid:
|
||||||
|
```
|
||||||
|
>git config --global user.signingkey YOUR_KEY_ID_HERE
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
|
### Without Yubikey
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
@ -33,7 +33,6 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin-top: 20px;">⚠️ This web REPL misses some features that are available in the CLI (roc repl) like defining variables without a final expression, which will result in the "Missing Final Expression" error.</p>
|
|
||||||
<script type="module" src="/repl/repl.js"></script>
|
<script type="module" src="/repl/repl.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -6,7 +6,7 @@ body {
|
|||||||
background-color: #222;
|
background-color: #222;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 18px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.body-wrapper {
|
.body-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -45,8 +45,17 @@ roc_repl_wasm.default("/repl/roc_repl_wasm_bg.wasm").then((instance) => {
|
|||||||
repl.elemHistory.querySelector("#loading-message").remove();
|
repl.elemHistory.querySelector("#loading-message").remove();
|
||||||
repl.elemSourceInput.disabled = false;
|
repl.elemSourceInput.disabled = false;
|
||||||
repl.elemSourceInput.placeholder =
|
repl.elemSourceInput.placeholder =
|
||||||
"Type some Roc code and press Enter. (Use Shift+Enter for multi-line input)";
|
"Type some Roc code and press Enter. (Use Shift-Enter or Ctrl-Enter for multi-line input)";
|
||||||
repl.compiler = instance;
|
repl.compiler = instance;
|
||||||
|
|
||||||
|
// Show the help text by providing fake input
|
||||||
|
repl.inputQueue.push(":help");
|
||||||
|
processInputQueue();
|
||||||
|
|
||||||
|
// Remove the fake input
|
||||||
|
repl.inputHistory.shift();
|
||||||
|
repl.inputHistoryIndex = 0;
|
||||||
|
document.querySelector(".input").remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -75,6 +84,9 @@ function onInputKeyup(event) {
|
|||||||
|
|
||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
case UP:
|
case UP:
|
||||||
|
if (repl.inputHistory.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
|
if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
|
||||||
repl.inputStash = el.value;
|
repl.inputStash = el.value;
|
||||||
}
|
}
|
||||||
@ -86,6 +98,9 @@ function onInputKeyup(event) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case DOWN:
|
case DOWN:
|
||||||
|
if (repl.inputHistory.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
|
if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
|
||||||
setInput(repl.inputStash);
|
setInput(repl.inputStash);
|
||||||
} else {
|
} else {
|
||||||
@ -95,7 +110,7 @@ function onInputKeyup(event) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ENTER:
|
case ENTER:
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
|
||||||
onInputChange({ target: repl.elemSourceInput });
|
onInputChange({ target: repl.elemSourceInput });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user