mirror of
https://github.com/roc-lang/roc.git
synced 2024-08-16 06:10:44 +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
|
||||
|
||||
- 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):
|
||||
- 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
|
||||
```
|
||||
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).
|
||||
|
||||
#### Commit signing on NixOS
|
||||
|
||||
|
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -2117,9 +2117,14 @@ dependencies = [
|
||||
"roc_build",
|
||||
"roc_cli",
|
||||
"roc_repl_cli",
|
||||
"roc_repl_ui",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_wasm_interp",
|
||||
"rustyline",
|
||||
"strip-ansi-escapes",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2814,11 +2819,11 @@ dependencies = [
|
||||
"roc_error_macros",
|
||||
"roc_gen_llvm",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_repl_eval",
|
||||
"roc_repl_ui",
|
||||
"roc_reporting",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
@ -2885,6 +2890,22 @@ dependencies = [
|
||||
"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]]
|
||||
name = "roc_repl_wasm"
|
||||
version = "0.0.1"
|
||||
@ -2901,6 +2922,7 @@ dependencies = [
|
||||
"roc_load",
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
"roc_repl_ui",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
@ -4318,9 +4340,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
|
@ -12,6 +12,7 @@ members = [
|
||||
"crates/repl_cli",
|
||||
"crates/repl_eval",
|
||||
"crates/repl_test",
|
||||
"crates/repl_ui",
|
||||
"crates/repl_wasm",
|
||||
"crates/repl_expect",
|
||||
"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.
|
||||
|
||||
## `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`
|
||||
|
||||
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.
|
||||
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||
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_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.
|
||||
# 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.
|
||||
# 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: Switch out Wymum on systems with slow 128bit multiplication.
|
||||
LowLevelHasher := { originalSeed : U64, state : U64 } implements [
|
||||
LowLevelHasher := { initializedSeed : U64, state : U64 } implements [
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
@ -1036,14 +1036,30 @@ createLowLevelHasher = \seedOpt ->
|
||||
when seedOpt is
|
||||
PseudoRandSeed -> pseudoSeed {}
|
||||
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 { originalSeed, state }, { a, b, seed, length } ->
|
||||
tmp = wymix (Num.bitwiseXor wyp1 a) (Num.bitwiseXor seed b)
|
||||
hash = wymix (Num.bitwiseXor wyp1 length) tmp
|
||||
combineState = \@LowLevelHasher { initializedSeed, state }, { a, b, seed, length } ->
|
||||
mum =
|
||||
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
|
||||
|
||||
@ -1052,8 +1068,7 @@ complete = \@LowLevelHasher { state } -> state
|
||||
# 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.
|
||||
# Either way, the performance will be similar and we can change this later.
|
||||
addU8 = \@LowLevelHasher { originalSeed, state }, u8 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
addU8 = \@LowLevelHasher { initializedSeed, state }, u8 ->
|
||||
p0 = Num.toU64 u8
|
||||
a =
|
||||
Num.shiftLeftBy p0 16
|
||||
@ -1061,10 +1076,9 @@ addU8 = \@LowLevelHasher { originalSeed, state }, u8 ->
|
||||
|> Num.bitwiseOr p0
|
||||
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 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
addU16 = \@LowLevelHasher { initializedSeed, state }, u16 ->
|
||||
p0 = Num.bitwiseAnd u16 0xFF |> Num.toU64
|
||||
p1 = Num.shiftRightZfBy u16 8 |> Num.toU64
|
||||
a =
|
||||
@ -1073,26 +1087,23 @@ addU16 = \@LowLevelHasher { originalSeed, state }, u16 ->
|
||||
|> Num.bitwiseOr p1
|
||||
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 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
addU32 = \@LowLevelHasher { initializedSeed, state }, u32 ->
|
||||
p0 = Num.toU64 u32
|
||||
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 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
addU64 = \@LowLevelHasher { initializedSeed, state }, u64 ->
|
||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF u64
|
||||
p1 = Num.shiftRightZfBy u64 32
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p1
|
||||
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 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
addU128 = \@LowLevelHasher { initializedSeed, state }, u128 ->
|
||||
lower = u128 |> Num.toU64
|
||||
upper = Num.shiftRightZfBy u128 64 |> Num.toU64
|
||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF lower
|
||||
@ -1102,12 +1113,11 @@ addU128 = \@LowLevelHasher { originalSeed, state }, u128 ->
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p2
|
||||
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 { originalSeed, state }, list ->
|
||||
addBytes = \@LowLevelHasher { initializedSeed, state }, list ->
|
||||
length = List.len list
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
abs =
|
||||
if length <= 16 then
|
||||
if length >= 4 then
|
||||
@ -1117,17 +1127,17 @@ addBytes = \@LowLevelHasher { originalSeed, state }, list ->
|
||||
(wyr4 list (Num.subWrap length 4) |> Num.shiftLeftBy 32)
|
||||
|> Num.bitwiseOr (wyr4 list (Num.subWrap length 4 |> Num.subWrap x))
|
||||
|
||||
{ a, b, seed }
|
||||
{ a, b, seed: initializedSeed }
|
||||
else if length > 0 then
|
||||
{ a: wyr3 list 0 length, b: 0, seed }
|
||||
{ a: wyr3 list 0 length, b: 0, seed: initializedSeed }
|
||||
else
|
||||
{ a: 0, b: 0, seed }
|
||||
{ a: 0, b: 0, seed: initializedSeed }
|
||||
else if length <= 48 then
|
||||
hashBytesHelper16 seed list 0 length
|
||||
hashBytesHelper16 initializedSeed list 0 length
|
||||
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 = \seed, see1, see2, list, index, remaining ->
|
||||
@ -1240,7 +1250,7 @@ expect
|
||||
|> addBytes []
|
||||
|> complete
|
||||
|
||||
hash == 0x1C3F_F8BF_07F9_B0B3
|
||||
hash == 0xD59C59757DBBE6B3
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1248,7 +1258,7 @@ expect
|
||||
|> addBytes [0x42]
|
||||
|> complete
|
||||
|
||||
hash == 0x8F9F_0A1E_E06F_0D52
|
||||
hash == 0x38CE03D0E61AF963
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1256,7 +1266,7 @@ expect
|
||||
|> addU8 0x42
|
||||
|> complete
|
||||
|
||||
hash == 0x8F9F_0A1E_E06F_0D52
|
||||
hash == 0x38CE03D0E61AF963
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1264,7 +1274,7 @@ expect
|
||||
|> addBytes [0xFF, 0xFF]
|
||||
|> complete
|
||||
|
||||
hash == 0x86CC_8B71_563F_F084
|
||||
hash == 0xE1CB2FA0D6A64113
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1272,7 +1282,7 @@ expect
|
||||
|> addU16 0xFFFF
|
||||
|> complete
|
||||
|
||||
hash == 0x86CC_8B71_563F_F084
|
||||
hash == 0xE1CB2FA0D6A64113
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1280,7 +1290,7 @@ expect
|
||||
|> addBytes [0x36, 0xA7]
|
||||
|> complete
|
||||
|
||||
hash == 0xD1A5_0F24_2536_84F8
|
||||
hash == 0x26B8319EDAF81B15
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1288,7 +1298,7 @@ expect
|
||||
|> addU16 0xA736
|
||||
|> complete
|
||||
|
||||
hash == 0xD1A5_0F24_2536_84F8
|
||||
hash == 0x26B8319EDAF81B15
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1296,7 +1306,7 @@ expect
|
||||
|> addBytes [0x00, 0x00, 0x00, 0x00]
|
||||
|> complete
|
||||
|
||||
hash == 0x3762_ACB1_7604_B541
|
||||
hash == 0xA187D7CA074F9EE7
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1304,7 +1314,7 @@ expect
|
||||
|> addU32 0x0000_0000
|
||||
|> complete
|
||||
|
||||
hash == 0x3762_ACB1_7604_B541
|
||||
hash == 0xA187D7CA074F9EE7
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1312,7 +1322,7 @@ expect
|
||||
|> addBytes [0xA9, 0x2F, 0xEE, 0x21]
|
||||
|> complete
|
||||
|
||||
hash == 0x20F3_3FD7_D32E_C7A9
|
||||
hash == 0xA499EFE4C1454D09
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1320,7 +1330,7 @@ expect
|
||||
|> addU32 0x21EE_2FA9
|
||||
|> complete
|
||||
|
||||
hash == 0x20F3_3FD7_D32E_C7A9
|
||||
hash == 0xA499EFE4C1454D09
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1328,7 +1338,7 @@ expect
|
||||
|> addBytes [0x5D, 0x66, 0xB1, 0x8F, 0x68, 0x44, 0xC7, 0x03, 0xE1, 0xDD, 0x23, 0x34, 0xBB, 0x9A, 0x42, 0xA7]
|
||||
|> complete
|
||||
|
||||
hash == 0xA16F_DDAA_C167_74C7
|
||||
hash == 0xDD39A206AED64C73
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1336,7 +1346,7 @@ expect
|
||||
|> addU128 0xA742_9ABB_3423_DDE1_03C7_4468_8FB1_665D
|
||||
|> complete
|
||||
|
||||
hash == 0xA16F_DDAA_C167_74C7
|
||||
hash == 0xDD39A206AED64C73
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1344,7 +1354,7 @@ expect
|
||||
|> Hash.hashStrBytes "abcdefghijklmnopqrstuvwxyz"
|
||||
|> complete
|
||||
|
||||
hash == 0xBEE0_A8FD_E990_D285
|
||||
hash == 0x51C59DF5B1D15F40
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1352,7 +1362,7 @@ expect
|
||||
|> Hash.hashStrBytes "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|> complete
|
||||
|
||||
hash == 0xB3C5_8528_9D82_A6EF
|
||||
hash == 0xD8D0A129D97A4E95
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1360,7 +1370,7 @@ expect
|
||||
|> Hash.hashStrBytes "1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||
|> complete
|
||||
|
||||
hash == 0xDB6B_7997_7A55_BA03
|
||||
hash == 0x8188065B44FB4AAA
|
||||
|
||||
expect
|
||||
hash =
|
||||
@ -1368,7 +1378,7 @@ expect
|
||||
|> addBytes (List.repeat 0x77 100)
|
||||
|> complete
|
||||
|
||||
hash == 0x171F_EEE2_B764_8E5E
|
||||
hash == 0x47A2A606EADF3378
|
||||
|
||||
# Note, had to specify u8 in the lists below to avoid ability type resolution error.
|
||||
# Apparently it won't pick the default integer.
|
||||
@ -1378,7 +1388,7 @@ expect
|
||||
|> Hash.hashUnordered [8u8, 82u8, 3u8, 8u8, 24u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash == 0x999F_B530_3529_F17D
|
||||
hash == 0xB2E8254C08F16B20
|
||||
|
||||
expect
|
||||
hash1 =
|
||||
|
@ -1,28 +1,28 @@
|
||||
procedure Dict.1 (Dict.556):
|
||||
let Dict.565 : List {[], []} = Array [];
|
||||
let Dict.572 : U64 = 0i64;
|
||||
let Dict.573 : U64 = 8i64;
|
||||
let Dict.566 : List U64 = CallByName List.11 Dict.572 Dict.573;
|
||||
let Dict.569 : I8 = CallByName Dict.39;
|
||||
let Dict.570 : U64 = 8i64;
|
||||
let Dict.567 : List I8 = CallByName List.11 Dict.569 Dict.570;
|
||||
let Dict.568 : U64 = 0i64;
|
||||
let Dict.564 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.565, Dict.566, Dict.567, Dict.568};
|
||||
ret Dict.564;
|
||||
procedure Dict.1 (Dict.554):
|
||||
let Dict.563 : List {[], []} = Array [];
|
||||
let Dict.570 : U64 = 0i64;
|
||||
let Dict.571 : U64 = 8i64;
|
||||
let Dict.564 : List U64 = CallByName List.11 Dict.570 Dict.571;
|
||||
let Dict.567 : I8 = CallByName Dict.39;
|
||||
let Dict.568 : U64 = 8i64;
|
||||
let Dict.565 : List I8 = CallByName List.11 Dict.567 Dict.568;
|
||||
let Dict.566 : U64 = 0i64;
|
||||
let Dict.562 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.563, Dict.564, Dict.565, Dict.566};
|
||||
ret Dict.562;
|
||||
|
||||
procedure Dict.39 ():
|
||||
let Dict.571 : I8 = -128i64;
|
||||
ret Dict.571;
|
||||
let Dict.569 : I8 = -128i64;
|
||||
ret Dict.569;
|
||||
|
||||
procedure Dict.4 (Dict.562):
|
||||
let Dict.100 : U64 = StructAtIndex 3 Dict.562;
|
||||
let #Derived_gen.8 : List {[], []} = StructAtIndex 0 Dict.562;
|
||||
procedure Dict.4 (Dict.560):
|
||||
let Dict.101 : U64 = StructAtIndex 3 Dict.560;
|
||||
let #Derived_gen.8 : List {[], []} = StructAtIndex 0 Dict.560;
|
||||
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;
|
||||
let #Derived_gen.6 : List I8 = StructAtIndex 2 Dict.562;
|
||||
let #Derived_gen.6 : List I8 = StructAtIndex 2 Dict.560;
|
||||
dec #Derived_gen.6;
|
||||
ret Dict.100;
|
||||
ret Dict.101;
|
||||
|
||||
procedure List.11 (List.124, List.125):
|
||||
let List.536 : List I8 = CallByName List.68 List.125;
|
||||
|
@ -11,7 +11,6 @@ version.workspace = true
|
||||
# pipe target to roc_build
|
||||
target-aarch64 = ["roc_build/target-aarch64"]
|
||||
target-arm = ["roc_build/target-arm"]
|
||||
target-wasm32 = ["roc_build/target-wasm32"]
|
||||
target-x86 = ["roc_build/target-x86"]
|
||||
target-x86_64 = ["roc_build/target-x86_64"]
|
||||
|
||||
@ -21,7 +20,6 @@ roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_gen_llvm = { path = "../compiler/gen_llvm" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
@ -31,6 +29,7 @@ roc_std = { path = "../roc_std" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_repl_ui = { path = "../repl_ui" }
|
||||
|
||||
bumpalo.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::externs::add_default_roc_externs;
|
||||
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::layout::STLayoutInterner;
|
||||
use roc_parse::ast::Expr;
|
||||
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_reporting::report::DEFAULT_PALETTE;
|
||||
use roc_std::RocStr;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
||||
use roc_types::subs::Subs;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
||||
defs: I,
|
||||
src: &str,
|
||||
target: Triple,
|
||||
pub fn eval_llvm(
|
||||
mut loaded: MonomorphizedModule<'_>,
|
||||
target: &Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> (Option<ReplOutput>, Problems) {
|
||||
) -> Option<ReplOutput> {
|
||||
let arena = Bump::new();
|
||||
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);
|
||||
}
|
||||
};
|
||||
let target_info = TargetInfo::from(target);
|
||||
|
||||
debug_assert_eq!(loaded.exposed_to_host.top_level_values.len(), 1);
|
||||
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,
|
||||
);
|
||||
|
||||
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
None => {
|
||||
let empty_vec: Vec<String> = Vec::new(); // rustc can't infer the type of this Vec.
|
||||
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 (_, main_fn_layout) = *loaded
|
||||
.procedures
|
||||
.keys()
|
||||
.find(|(s, _)| *s == main_fn_symbol)?;
|
||||
|
||||
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();
|
||||
|
||||
(
|
||||
Some(ReplOutput {
|
||||
expr: expr_str,
|
||||
expr_type: expr_type_str,
|
||||
}),
|
||||
problems,
|
||||
)
|
||||
Some(ReplOutput {
|
||||
expr: expr_str,
|
||||
expr_type: expr_type_str,
|
||||
})
|
||||
}
|
||||
|
||||
struct CliApp {
|
||||
@ -191,11 +160,11 @@ impl ReplAppMemory for CliMemory {
|
||||
|
||||
fn mono_module_to_dylib<'a>(
|
||||
arena: &'a Bump,
|
||||
target: Triple,
|
||||
target: &Triple,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<(libloading::Library, &'a str, Subs, STLayoutInterner<'a>), libloading::Error> {
|
||||
let target_info = TargetInfo::from(&target);
|
||||
let target_info = TargetInfo::from(target);
|
||||
|
||||
let MonomorphizedModule {
|
||||
procedures,
|
||||
@ -210,7 +179,7 @@ fn mono_module_to_dylib<'a>(
|
||||
let context = Context::create();
|
||||
let builder = context.create_builder();
|
||||
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
||||
&target, &context, "",
|
||||
target, &context, "",
|
||||
));
|
||||
|
||||
let module = arena.alloc(module);
|
||||
@ -287,6 +256,6 @@ fn mono_module_to_dylib<'a>(
|
||||
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))
|
||||
}
|
||||
|
@ -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).
|
||||
mod cli_gen;
|
||||
mod colors;
|
||||
pub mod repl_state;
|
||||
|
||||
use colors::{BLUE, END_COL, PINK};
|
||||
use const_format::concatcp;
|
||||
use repl_state::ReplState;
|
||||
use std::borrow::Cow;
|
||||
|
||||
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!(
|
||||
"\n The rockin’ ",
|
||||
BLUE,
|
||||
"roc repl",
|
||||
END_COL,
|
||||
"\n",
|
||||
PINK,
|
||||
"────────────────────────",
|
||||
END_COL,
|
||||
"\n\n"
|
||||
);
|
||||
use crate::cli_gen::eval_llvm;
|
||||
|
||||
// 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";
|
||||
#[derive(Completer, Helper, Hinter, Default)]
|
||||
pub struct ReplHelper {
|
||||
validator: InputValidator,
|
||||
state: ReplState,
|
||||
}
|
||||
|
||||
pub fn main() -> i32 {
|
||||
use rustyline::error::ReadlineError;
|
||||
@ -34,9 +35,12 @@ pub fn main() -> i32 {
|
||||
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
|
||||
print!("{WELCOME_MESSAGE}{SHORT_INSTRUCTIONS}");
|
||||
|
||||
let mut editor = Editor::<ReplState>::new();
|
||||
let repl_helper = ReplState::new();
|
||||
let mut editor = Editor::<ReplHelper>::new();
|
||||
let repl_helper = ReplHelper::default();
|
||||
editor.set_helper(Some(repl_helper));
|
||||
let target = Triple::host();
|
||||
let target_info = TargetInfo::from(&target);
|
||||
let mut arena = Bump::new();
|
||||
|
||||
loop {
|
||||
match editor.readline(PROMPT) {
|
||||
@ -44,18 +48,34 @@ pub fn main() -> i32 {
|
||||
editor.add_history_entry(line.trim());
|
||||
|
||||
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) {
|
||||
Ok(output) => {
|
||||
arena.reset();
|
||||
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!
|
||||
// (This happens for something like a type annotation.)
|
||||
if !output.is_empty() {
|
||||
println!("{output}");
|
||||
}
|
||||
}
|
||||
Err(exit_code) => return exit_code,
|
||||
};
|
||||
ReplAction::Exit => {
|
||||
return 0;
|
||||
}
|
||||
ReplAction::Help => {
|
||||
println!("{TIPS}");
|
||||
}
|
||||
ReplAction::Nothing => {}
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
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,
|
||||
expr: &str,
|
||||
target_info: TargetInfo,
|
||||
function_kind: FunctionKind,
|
||||
palette: Palette,
|
||||
) -> (Option<MonomorphizedModule<'a>>, Problems) {
|
||||
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()),
|
||||
LoadConfig {
|
||||
target_info,
|
||||
function_kind,
|
||||
function_kind: FunctionKind::LambdaSet,
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
palette,
|
||||
threading: Threading::Single,
|
||||
|
@ -13,18 +13,23 @@ roc_cli = { path = "../cli" }
|
||||
[dev-dependencies]
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_repl_cli = { path = "../repl_cli" }
|
||||
roc_repl_ui = { path = "../repl_ui" }
|
||||
roc_test_utils = { path = "../test_utils" }
|
||||
roc_wasm_interp = { path = "../wasm_interp" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
indoc.workspace = true
|
||||
strip-ansi-escapes.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
rustyline.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["target-aarch64", "target-x86_64", "target-wasm32"]
|
||||
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||
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_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
||||
wasm = ["target-wasm32"]
|
||||
|
@ -3,7 +3,7 @@ use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
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;
|
||||
|
||||
const ERROR_MESSAGE_START: char = '─';
|
||||
|
@ -1,5 +1,12 @@
|
||||
use bumpalo::Bump;
|
||||
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
|
||||
// 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]
|
||||
fn one_plus_one() {
|
||||
complete("1 + 1", &mut ReplState::new(), Ok(("2 : Num *", "val1")));
|
||||
complete("1 + 1", &mut ReplState::new(), "2 : Num *", "val1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_expr_names() {
|
||||
let mut state = ReplState::new();
|
||||
|
||||
complete("2 * 3", &mut state, Ok(("6 : Num *", "val1")));
|
||||
complete("4 - 1", &mut state, Ok(("3 : Num *", "val2")));
|
||||
complete("val1 + val2", &mut state, Ok(("9 : Num *", "val3")));
|
||||
complete("1 + (val2 * val3)", &mut state, Ok(("28 : Num *", "val4")));
|
||||
complete("2 * 3", &mut state, "6 : Num *", "val1");
|
||||
complete("4 - 1", &mut state, "3 : Num *", "val2");
|
||||
complete("val1 + val2", &mut state, "9 : Num *", "val3");
|
||||
complete("1 + (val2 * val3)", &mut state, "28 : Num *", "val4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_defs() {
|
||||
let mut state = ReplState::new();
|
||||
|
||||
complete("x = 5", &mut state, Ok(("5 : Num *", "x")));
|
||||
complete("7 - 3", &mut state, Ok(("4 : Num *", "val1")));
|
||||
complete("y = 6", &mut state, Ok(("6 : Num *", "y")));
|
||||
complete("val1 + x + y", &mut state, Ok(("15 : Num *", "val2")));
|
||||
complete("x = 5", &mut state, "5 : Num *", "x");
|
||||
complete("7 - 3", &mut state, "4 : Num *", "val1");
|
||||
complete("y = 6", &mut state, "6 : Num *", "y");
|
||||
complete("val1 + x + y", &mut state, "15 : Num *", "val2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -38,7 +45,7 @@ fn annotated_body() {
|
||||
|
||||
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]
|
||||
@ -53,7 +60,7 @@ fn exhaustiveness_problem() {
|
||||
|
||||
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
|
||||
@ -88,7 +95,11 @@ fn exhaustiveness_problem() {
|
||||
#[test]
|
||||
fn tips() {
|
||||
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]
|
||||
@ -98,35 +109,49 @@ fn standalone_annotation() {
|
||||
|
||||
incomplete(&mut 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
|
||||
/// 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));
|
||||
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) {
|
||||
Ok(string) => {
|
||||
match action {
|
||||
ReplAction::Eval {
|
||||
opt_mono,
|
||||
problems,
|
||||
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();
|
||||
|
||||
let comment_index = escaped.rfind('#').unwrap_or(escaped.len());
|
||||
|
||||
assert_eq!(
|
||||
expected_step_result.map(|(starts_with, _)| starts_with),
|
||||
Ok(escaped[0..comment_index].trim())
|
||||
);
|
||||
assert_eq!(expected_start, (escaped[0..comment_index].trim()));
|
||||
|
||||
assert_eq!(
|
||||
expected_step_result.map(|(_, ends_with)| ends_with),
|
||||
expected_end,
|
||||
// +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.
|
||||
fn error(input: &str, state: &mut ReplState, expected_step_result: String) {
|
||||
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| {
|
||||
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap()).unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(Ok(expected_step_result), escaped);
|
||||
match action {
|
||||
ReplAction::Eval {
|
||||
opt_mono,
|
||||
problems,
|
||||
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 mut instance = {
|
||||
@ -140,27 +140,33 @@ fn run(src: &'static str) -> Result<String, String> {
|
||||
};
|
||||
|
||||
let len = Value::I32(src.len() as i32);
|
||||
let wasm_ok: i32 = instance
|
||||
.call_export("entrypoint_from_test", [len])
|
||||
.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)
|
||||
}
|
||||
instance.call_export("entrypoint_from_test", [len]).unwrap();
|
||||
instance.import_dispatcher.answer.to_owned()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_success(input: &'static str, expected: &str) {
|
||||
assert_eq!(run(input), Ok(expected.into()));
|
||||
expect(input, expected);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
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
|
||||
|
||||
# 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 const_format::concatcp;
|
||||
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::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
||||
use roc_parse::parser::Parser;
|
||||
@ -12,65 +9,20 @@ use roc_parse::parser::{EWhen, Either};
|
||||
use roc_parse::state::State;
|
||||
use roc_parse::{join_alias_to_body, join_ann_to_body};
|
||||
use roc_region::all::Loc;
|
||||
use roc_repl_eval::gen::{Problems, ReplOutput};
|
||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||
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, " ");
|
||||
use roc_repl_eval::gen::{compile_to_mono, Problems};
|
||||
use roc_reporting::report::Palette;
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
/// 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"
|
||||
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)]
|
||||
struct PastDef {
|
||||
ident: String,
|
||||
src: String,
|
||||
}
|
||||
|
||||
#[derive(Completer, Helper, Hinter)]
|
||||
pub struct ReplState {
|
||||
validator: InputValidator,
|
||||
past_defs: Vec<PastDef>,
|
||||
past_def_idents: MutSet<String>,
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
validator: InputValidator::new(),
|
||||
past_defs: Default::default(),
|
||||
past_def_idents: Default::default(),
|
||||
last_auto_ident: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, line: &str, dimensions: Option<(usize, usize)>) -> Result<String, i32> {
|
||||
let arena = Bump::new();
|
||||
|
||||
match parse_src(&arena, line) {
|
||||
ParseOutcome::Empty => Ok(TIPS.to_string()),
|
||||
ParseOutcome::Expr(_)
|
||||
| ParseOutcome::ValueDef(_)
|
||||
| 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();
|
||||
pub fn step<'a>(
|
||||
&mut self,
|
||||
arena: &'a Bump,
|
||||
line: &str,
|
||||
target_info: TargetInfo,
|
||||
palette: Palette,
|
||||
) -> ReplAction<'a> {
|
||||
let pending_past_def;
|
||||
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 => {
|
||||
pending_past_def = None;
|
||||
// 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.
|
||||
opt_var_name = None;
|
||||
|
||||
src
|
||||
line
|
||||
}
|
||||
ParseOutcome::ValueDef(value_def) => {
|
||||
match value_def {
|
||||
@ -134,11 +87,11 @@ impl ReplState {
|
||||
_,
|
||||
) => {
|
||||
// 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
|
||||
// cannnot be evaluated as expressions.
|
||||
return String::new();
|
||||
// cannot be evaluated as expressions.
|
||||
return ReplAction::Nothing;
|
||||
}
|
||||
ValueDef::Body(
|
||||
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());
|
||||
|
||||
// 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
|
||||
// reported because we filter out errors whose regions are in past defs.
|
||||
let mut buf = bumpalo::collections::string::String::with_capacity_in(
|
||||
ident.len() + src.len() + 1,
|
||||
&arena,
|
||||
ident.len() + line.len() + 1,
|
||||
arena,
|
||||
);
|
||||
|
||||
buf.push_str(src);
|
||||
buf.push_str(line);
|
||||
buf.push('\n');
|
||||
buf.push_str(ident);
|
||||
|
||||
@ -214,36 +167,37 @@ impl ReplState {
|
||||
..
|
||||
}) => {
|
||||
// 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
|
||||
// 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
|
||||
// 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!)
|
||||
let (output, problems) =
|
||||
let (opt_mono, problems) =
|
||||
match opt_var_name.or_else(|| self.past_def_idents.get(src.trim()).cloned()) {
|
||||
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()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
target_info,
|
||||
palette,
|
||||
)
|
||||
}
|
||||
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()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
target_info,
|
||||
palette,
|
||||
);
|
||||
|
||||
// Don't persist defs that have compile errors
|
||||
@ -266,7 +220,11 @@ impl ReplState {
|
||||
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 {
|
||||
@ -284,7 +242,7 @@ impl ReplState {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ParseOutcome<'a> {
|
||||
pub enum ParseOutcome<'a> {
|
||||
ValueDef(ValueDef<'a>),
|
||||
TypeDef(TypeDef<'a>),
|
||||
Expr(Expr<'a>),
|
||||
@ -295,7 +253,7 @@ enum ParseOutcome<'a> {
|
||||
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() {
|
||||
"" => ParseOutcome::Empty,
|
||||
":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_parse = { path = "../compiler/parse" }
|
||||
roc_repl_eval = { path = "../repl_eval" }
|
||||
roc_repl_ui = { path = "../repl_ui" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_solve = { path = "../compiler/solve" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
|
@ -24,20 +24,29 @@ You should run it from the project root directory.
|
||||
|
||||
```bash
|
||||
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.
|
||||
Any server will do, but this example should work on any system that has Python 3 installed:
|
||||
|
||||
```bash
|
||||
cd www/build
|
||||
cd www/public
|
||||
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.)
|
||||
|
||||
|
@ -6,12 +6,6 @@ use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
|
||||
|
||||
const PLATFORM_FILENAME: &str = "repl_platform";
|
||||
|
||||
#[cfg(not(windows))]
|
||||
const OBJECT_EXTENSION: &str = "o";
|
||||
|
||||
#[cfg(windows)]
|
||||
const OBJECT_EXTENSION: &str = "obj";
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
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());
|
||||
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()
|
||||
.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 {
|
||||
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())
|
||||
.args([
|
||||
|
@ -30,6 +30,6 @@ macro_rules! console_log {
|
||||
/// 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]
|
||||
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
|
||||
}
|
||||
|
@ -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.
|
||||
/// - Uses an extra callback to allocate & copy the input string (in the browser version, wasm_bindgen does this)
|
||||
#[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 src = unsafe {
|
||||
test_copy_input_string(src_buffer.as_mut_ptr());
|
||||
String::from_utf8_unchecked(src_buffer)
|
||||
};
|
||||
let result_async = crate::repl::entrypoint_from_js(src);
|
||||
let result = executor::block_on(result_async);
|
||||
let ok = result.is_ok();
|
||||
|
||||
let output = result.unwrap_or_else(|s| s);
|
||||
let output = executor::block_on(result_async);
|
||||
|
||||
unsafe { test_copy_output_string(output.as_ptr(), output.len()) }
|
||||
|
||||
ok
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
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_gen_wasm::wasm32_result;
|
||||
@ -7,10 +8,14 @@ use roc_load::MonomorphizedModule;
|
||||
use roc_parse::ast::Expr;
|
||||
use roc_repl_eval::{
|
||||
eval::jit_to_ast,
|
||||
gen::{compile_to_mono, format_answer},
|
||||
gen::{format_answer, ReplOutput},
|
||||
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_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";
|
||||
|
||||
// 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> {
|
||||
arena: &'a Bump,
|
||||
}
|
||||
@ -164,51 +175,60 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
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)]
|
||||
const PRE_LINKED_BINARY: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/pre_linked_binary.obj")) as &[_];
|
||||
|
||||
pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||
pub async fn entrypoint_from_js(src: String) -> String {
|
||||
// If our Rust code panics, redirect the error message to JS console.error
|
||||
// Also, our JS code overrides console.error to display the error message text (including stack trace) in the REPL output.
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// TODO: make this a global and reset it?
|
||||
let arena = &Bump::new();
|
||||
|
||||
// Compile the app
|
||||
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.
|
||||
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
||||
if !buf.is_empty() {
|
||||
buf.push_str("\n\n");
|
||||
}
|
||||
// Advance the REPL state machine
|
||||
let action = REPL_STATE.with(|repl_state_cell| {
|
||||
let mut repl_state = repl_state_cell.borrow_mut();
|
||||
repl_state.step(arena, &src, target_info, DEFAULT_PALETTE_HTML)
|
||||
});
|
||||
|
||||
buf.push_str(message);
|
||||
}
|
||||
|
||||
return Err(buf);
|
||||
// Perform the action the state machine asked for, and return the appropriate output string
|
||||
match action {
|
||||
ReplAction::Help => TIPS.to_string(),
|
||||
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 {
|
||||
module_id,
|
||||
procedures,
|
||||
@ -225,7 +245,7 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||
let main_fn_var = *main_fn_var;
|
||||
|
||||
// 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,
|
||||
&mut subs,
|
||||
module_id,
|
||||
@ -233,10 +253,7 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||
DebugPrint::NOTHING,
|
||||
);
|
||||
|
||||
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
None => return Ok(format!("<function> : {expr_type_str}")),
|
||||
};
|
||||
let (_, main_fn_layout) = *procedures.keys().find(|(s, _)| *s == main_fn_symbol)?;
|
||||
|
||||
let app_module_bytes = {
|
||||
let env = roc_gen_wasm::Env {
|
||||
@ -279,10 +296,16 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||
buffer
|
||||
};
|
||||
|
||||
// Send the compiled binary out to JS and create an executable instance from it
|
||||
js_create_app(&app_module_bytes)
|
||||
.await
|
||||
.map_err(|js| format!("{js:?}"))?;
|
||||
// Send the compiled binary out to JS, which will asynchronously create an executable WebAssembly instance
|
||||
match js_create_app(&app_module_bytes).await {
|
||||
Ok(()) => {}
|
||||
Err(js_exception) => {
|
||||
return Some(ReplOutput {
|
||||
expr: format!("<span class='color-red'>{js_exception:?}</span>"),
|
||||
expr_type: String::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let mut app = WasmReplApp { arena };
|
||||
|
||||
@ -300,11 +323,8 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||
target_info,
|
||||
);
|
||||
|
||||
let var_name = String::new(); // TODO turn this into something like " # val1"
|
||||
|
||||
// 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);
|
||||
let expr = format_answer(arena, res_answer).to_string();
|
||||
|
||||
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);
|
||||
|
||||
/// A machine-readable format for text styles (colors and other styles)
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct StyleCodes {
|
||||
pub red: &'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>
|
||||
</section>
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -6,7 +6,7 @@ body {
|
||||
background-color: #222;
|
||||
color: #ccc;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.body-wrapper {
|
||||
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.elemSourceInput.disabled = false;
|
||||
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;
|
||||
|
||||
// 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) {
|
||||
case UP:
|
||||
if (repl.inputHistory.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
|
||||
repl.inputStash = el.value;
|
||||
}
|
||||
@ -86,6 +98,9 @@ function onInputKeyup(event) {
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
if (repl.inputHistory.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
|
||||
setInput(repl.inputStash);
|
||||
} else {
|
||||
@ -95,7 +110,7 @@ function onInputKeyup(event) {
|
||||
break;
|
||||
|
||||
case ENTER:
|
||||
if (!event.shiftKey) {
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
|
||||
onInputChange({ target: repl.elemSourceInput });
|
||||
}
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user