diff --git a/Cargo.lock b/Cargo.lock index 84a6839e12..8a1d024080 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1871,9 +1871,9 @@ checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" [[package]] name = "insta" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dc501f12ec0c339b385787fa89ffda3d5d2caa62e558da731134c24d6e0c4" +checksum = "58a931b01c76064c5be919faa2ef0dc570e9a889dcd1e5fef08a8ca6eb4d6c0b" dependencies = [ "console", "linked-hash-map", @@ -2776,9 +2776,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "peg" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c" +checksum = "a07f2cafdc3babeebc087e499118343442b742cc7c31b4d054682cc598508554" dependencies = [ "peg-macros", "peg-runtime", @@ -2786,9 +2786,9 @@ dependencies = [ [[package]] name = "peg-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16" +checksum = "4a90084dc05cf0428428e3d12399f39faad19b0909f64fb9170c9fdd6d9cd49b" dependencies = [ "peg-runtime", "proc-macro2", @@ -2797,9 +2797,9 @@ dependencies = [ [[package]] name = "peg-runtime" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f" +checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739" [[package]] name = "percent-encoding" diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 1720f94876..75b379c2a6 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -87,6 +87,7 @@ mod cli_run { executable_filename: &'a str, stdin: &'a [&'a str], arguments: &'a [Arg<'a>], + env: &'a [(&'a str, &'a str)], expected_ending: &'a str, use_valgrind: bool, } @@ -136,12 +137,14 @@ mod cli_run { compile_out } + #[allow(clippy::too_many_arguments)] fn check_output_with_stdin( file: &Path, stdin: &[&str], executable_filename: &str, flags: &[&str], app_args: &[String], + extra_env: &[(&str, &str)], expected_ending: &str, use_valgrind: bool, ) { @@ -214,16 +217,31 @@ mod cli_run { file.with_file_name(executable_filename).to_str().unwrap(), stdin.iter().copied(), app_args, + extra_env.iter().copied(), ) } } - CliMode::Roc => run_roc_on(file, flags.clone(), stdin, app_args), - CliMode::RocRun => run_roc_on( - file, - iter::once(CMD_RUN).chain(flags.clone()), - stdin, - app_args, - ), + CliMode::Roc => { + if !extra_env.is_empty() { + // TODO: environment is not currently forwarded by Roc to the target + // binary, so this would fail! + continue; + } + run_roc_on(file, flags.clone(), stdin, app_args) + } + CliMode::RocRun => { + if !extra_env.is_empty() { + // TODO: environment is not currently forwarded by Roc to the target + // binary, so this would fail! + continue; + } + run_roc_on( + file, + iter::once(CMD_RUN).chain(flags.clone()), + stdin, + app_args, + ) + } }; if !&out.stdout.ends_with(expected_ending) { @@ -367,6 +385,7 @@ mod cli_run { example.executable_filename, &custom_flags, &app_args, + example.env, example.expected_ending, example.use_valgrind, ); @@ -381,6 +400,7 @@ mod cli_run { example.executable_filename, &custom_flags, &app_args, + example.env, example.expected_ending, example.use_valgrind, ); @@ -394,6 +414,7 @@ mod cli_run { example.executable_filename, &[LINKER_FLAG, "legacy"], &app_args, + example.env, example.expected_ending, example.use_valgrind, ); @@ -431,6 +452,7 @@ mod cli_run { executable_filename: "helloWorld", stdin: &[], arguments: &[], + env: &[], expected_ending:"Hello, World!\n", use_valgrind: true, }, @@ -439,6 +461,7 @@ mod cli_run { executable_filename: "rocLovesPlatforms", stdin: &[], arguments: &[], + env: &[], expected_ending:"Which platform am I running on now?\n", use_valgrind: true, }, @@ -458,6 +481,7 @@ mod cli_run { executable_filename: "rocLovesRust", stdin: &[], arguments: &[], + env: &[], expected_ending:"Roc <3 Rust!\n", use_valgrind: true, }, @@ -466,6 +490,7 @@ mod cli_run { executable_filename: "rocLovesSwift", stdin: &[], arguments: &[], + env: &[], expected_ending:"Roc <3 Swift!\n", use_valgrind: true, }, @@ -474,6 +499,7 @@ mod cli_run { executable_filename: "rocLovesWebAssembly", stdin: &[], arguments: &[], + env: &[], expected_ending:"Roc <3 Web Assembly!\n", use_valgrind: true, }, @@ -482,6 +508,7 @@ mod cli_run { executable_filename: "rocLovesZig", stdin: &[], arguments: &[], + env: &[], expected_ending:"Roc <3 Zig!\n", use_valgrind: true, }, @@ -490,6 +517,7 @@ mod cli_run { executable_filename: "libhello", stdin: &[], arguments: &[], + env: &[], expected_ending:"", use_valgrind: true, }, @@ -498,6 +526,7 @@ mod cli_run { executable_filename: "fibonacci", stdin: &[], arguments: &[], + env: &[], expected_ending:"55\n", use_valgrind: true, }, @@ -506,6 +535,7 @@ mod cli_run { executable_filename: "hello-gui", stdin: &[], arguments: &[], + env: &[], expected_ending: "", use_valgrind: false, }, @@ -514,6 +544,7 @@ mod cli_run { executable_filename: "breakout", stdin: &[], arguments: &[], + env: &[], expected_ending: "", use_valgrind: false, }, @@ -522,6 +553,7 @@ mod cli_run { executable_filename: "quicksort", stdin: &[], arguments: &[], + env: &[], expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", use_valgrind: true, }, @@ -538,14 +570,28 @@ mod cli_run { executable_filename: "args", stdin: &[], arguments: &[Arg::PlainText("log"), Arg::PlainText("-b"), Arg::PlainText("3"), Arg::PlainText("--num"), Arg::PlainText("81")], + env: &[], expected_ending: "4\n", use_valgrind: false, }, + env:"interactive" => Example { + filename: "env.roc", + executable_filename: "env", + stdin: &[], + arguments: &[], + env: &[("EDITOR", "roc-editor"), ("SHLVL", "3"), ("LETTERS", "a,c,e,j")], + expected_ending: ( + "Your favorite editor is roc-editor!\n\ + Your current shell level is 3!\n\ + Your favorite letters are: a c e j\n"), + use_valgrind: false, + }, effects:"interactive" => Example { filename: "effects.roc", executable_filename: "effects", stdin: &["hi there!"], arguments: &[], + env: &[], expected_ending: "hi there!\nIt is known\n", use_valgrind: true, }, @@ -562,6 +608,7 @@ mod cli_run { executable_filename: "form", stdin: &["Giovanni\n", "Giorgio\n"], arguments: &[], + env: &[], expected_ending: "Hi, Giovanni Giorgio! 👋\n", use_valgrind: false, }, @@ -570,6 +617,7 @@ mod cli_run { executable_filename: "tui", stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks arguments: &[], + env: &[], expected_ending: "Hello Worldfoo!\n", use_valgrind: true, }, @@ -595,6 +643,7 @@ mod cli_run { executable_filename: "false", stdin: &[], arguments: &[Arg::ExamplePath("examples/hello.false")], + env: &[], expected_ending:"Hello, World!\n", use_valgrind: false, } @@ -604,6 +653,7 @@ mod cli_run { executable_filename: "swiftui", stdin: &[], arguments: &[], + env: &[], expected_ending: "", use_valgrind: false, }, @@ -613,6 +663,7 @@ mod cli_run { executable_filename: "static-site", stdin: &[], arguments: &[Arg::ExamplePath("input"), Arg::ExamplePath("output")], + env: &[], expected_ending: "Processed 3 files with 3 successes and 0 errors\n", use_valgrind: false, } @@ -623,6 +674,7 @@ mod cli_run { executable_filename: "parse-movies-csv", stdin: &[], arguments: &[], + env: &[], expected_ending: "Parse success!\n", use_valgrind: false, } @@ -671,6 +723,7 @@ mod cli_run { benchmark.executable_filename, &[], &app_args, + benchmark.env, benchmark.expected_ending, benchmark.use_valgrind, ); @@ -689,6 +742,7 @@ mod cli_run { benchmark.executable_filename, &[PREBUILT_PLATFORM], &app_args, + benchmark.env, benchmark.expected_ending, benchmark.use_valgrind, ); @@ -700,6 +754,7 @@ mod cli_run { benchmark.executable_filename, &[PREBUILT_PLATFORM, OPTIMIZE_FLAG], &app_args, + benchmark.env, benchmark.expected_ending, benchmark.use_valgrind, ); @@ -813,6 +868,7 @@ mod cli_run { executable_filename: "nqueens", stdin: &["6"], arguments: &[], + env: &[], expected_ending: "4\n", use_valgrind: true, }, @@ -821,6 +877,7 @@ mod cli_run { executable_filename: "cfold", stdin: &["3"], arguments: &[], + env: &[], expected_ending: "11 & 11\n", use_valgrind: true, }, @@ -829,6 +886,7 @@ mod cli_run { executable_filename: "deriv", stdin: &["2"], arguments: &[], + env: &[], expected_ending: "1 count: 6\n2 count: 22\n", use_valgrind: true, }, @@ -837,6 +895,7 @@ mod cli_run { executable_filename: "rbtree-ck", stdin: &["100"], arguments: &[], + env: &[], expected_ending: "10\n", use_valgrind: true, }, @@ -845,6 +904,7 @@ mod cli_run { executable_filename: "rbtree-insert", stdin: &[], arguments: &[], + env: &[], expected_ending: "Node Black 0 {} Empty Empty\n", use_valgrind: true, }, @@ -853,6 +913,7 @@ mod cli_run { // executable_filename: "rbtree-del", // stdin: &["420"], // arguments: &[], + // env: &[], // expected_ending: "30\n", // use_valgrind: true, // }, @@ -861,6 +922,7 @@ mod cli_run { executable_filename: "test-astar", stdin: &[], arguments: &[], + env: &[], expected_ending: "True\n", use_valgrind: false, }, @@ -869,6 +931,7 @@ mod cli_run { executable_filename: "test-base64", stdin: &[], arguments: &[], + env: &[], expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", use_valgrind: true, }, @@ -877,6 +940,7 @@ mod cli_run { executable_filename: "closure", stdin: &[], arguments: &[], + env: &[], expected_ending: "", use_valgrind: false, }, @@ -885,6 +949,7 @@ mod cli_run { executable_filename: "issue2279", stdin: &[], arguments: &[], + env: &[], expected_ending: "Hello, world!\n", use_valgrind: true, }, @@ -893,6 +958,7 @@ mod cli_run { executable_filename: "quicksortapp", stdin: &[], arguments: &[], + env: &[], expected_ending: "todo put the correct quicksort answer here", use_valgrind: true, }, @@ -973,6 +1039,7 @@ mod cli_run { "multi-dep-str", &[], &[], + &[], "I am Dep2.str2\n", true, ); @@ -987,6 +1054,7 @@ mod cli_run { "multi-dep-str", &[OPTIMIZE_FLAG], &[], + &[], "I am Dep2.str2\n", true, ); @@ -1001,6 +1069,7 @@ mod cli_run { "multi-dep-thunk", &[], &[], + &[], "I am Dep2.value2\n", true, ); @@ -1015,6 +1084,7 @@ mod cli_run { "multi-dep-thunk", &[OPTIMIZE_FLAG], &[], + &[], "I am Dep2.value2\n", true, ); diff --git a/crates/cli_utils/src/bench_utils.rs b/crates/cli_utils/src/bench_utils.rs index c9f971266d..8afb84be99 100644 --- a/crates/cli_utils/src/bench_utils.rs +++ b/crates/cli_utils/src/bench_utils.rs @@ -48,12 +48,12 @@ fn check_cmd_output( let out = if cmd_str.contains("cfold") { let child = thread::Builder::new() .stack_size(CFOLD_STACK_SIZE) - .spawn(move || run_cmd(&cmd_str, [stdin_str], &[])) + .spawn(move || run_cmd(&cmd_str, [stdin_str], &[], [])) .unwrap(); child.join().unwrap() } else { - run_cmd(&cmd_str, [stdin_str], &[]) + run_cmd(&cmd_str, [stdin_str], &[], []) }; if !&out.stdout.ends_with(expected_ending) { @@ -96,13 +96,14 @@ fn bench_cmd( if let Some(bench_group) = bench_group_opt { bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| { - b.iter(|| run_cmd(black_box(&cmd_str), black_box([stdin_str]), &[])) + b.iter(|| run_cmd(black_box(&cmd_str), black_box([stdin_str]), &[], [])) }); } else { run_cmd( black_box(file.with_file_name(executable_filename).to_str().unwrap()), black_box([stdin_str]), &[], + [], ); } } diff --git a/crates/cli_utils/src/helpers.rs b/crates/cli_utils/src/helpers.rs index f2f37bd3cd..020e890c37 100644 --- a/crates/cli_utils/src/helpers.rs +++ b/crates/cli_utils/src/helpers.rs @@ -161,10 +161,11 @@ where } } -pub fn run_cmd<'a, I: IntoIterator>( +pub fn run_cmd<'a, I: IntoIterator, E: IntoIterator>( cmd_name: &str, stdin_vals: I, args: &[String], + env: E, ) -> Out { let mut cmd = Command::new(cmd_name); @@ -172,6 +173,10 @@ pub fn run_cmd<'a, I: IntoIterator>( cmd.arg(arg); } + for (env, val) in env.into_iter() { + cmd.env(env, val); + } + let mut child = cmd .stdin(Stdio::piped()) .stdout(Stdio::piped()) diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 352ac88b5c..b85db78390 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -3732,14 +3732,21 @@ fn send_header<'a>( // Also build a list of imported_values_to_expose (like `bar` above.) for (qualified_module_name, exposed_idents, region) in imported.into_iter() { let cloned_module_name = qualified_module_name.module.clone(); - let pq_module_name = match qualified_module_name.opt_package { - None => match opt_shorthand { - Some(shorthand) => { - PQModuleName::Qualified(shorthand, qualified_module_name.module) - } - None => PQModuleName::Unqualified(qualified_module_name.module), - }, - Some(package) => PQModuleName::Qualified(package, cloned_module_name), + let pq_module_name = if qualified_module_name.is_builtin() { + // If this is a builtin, it must be unqualified, and we should *never* prefix it + // with the package shorthand! The user intended to import the module as-is here. + debug_assert!(qualified_module_name.opt_package.is_none()); + PQModuleName::Unqualified(qualified_module_name.module) + } else { + match qualified_module_name.opt_package { + None => match opt_shorthand { + Some(shorthand) => { + PQModuleName::Qualified(shorthand, qualified_module_name.module) + } + None => PQModuleName::Unqualified(qualified_module_name.module), + }, + Some(package) => PQModuleName::Qualified(package, cloned_module_name), + } }; let module_id = module_ids.get_or_insert(&pq_module_name); diff --git a/crates/compiler/solve/Cargo.toml b/crates/compiler/solve/Cargo.toml index 2bd339c042..4e085f2da1 100644 --- a/crates/compiler/solve/Cargo.toml +++ b/crates/compiler/solve/Cargo.toml @@ -37,4 +37,4 @@ tempfile = "3.2.0" bumpalo = { version = "3.11.0", features = ["collections"] } regex = "1.5.5" lazy_static = "1.4.0" -insta = "1.19.0" +insta = "1.20.0" diff --git a/crates/compiler/test_derive/Cargo.toml b/crates/compiler/test_derive/Cargo.toml index 11a3622eee..2b03ef57d4 100644 --- a/crates/compiler/test_derive/Cargo.toml +++ b/crates/compiler/test_derive/Cargo.toml @@ -29,4 +29,4 @@ lazy_static = "1.4.0" indoc = "1.0.7" ven_pretty = { path = "../../vendor/pretty" } pretty_assertions = "1.3.0" -insta = "1.19.0" +insta = "1.20.0" diff --git a/crates/docs/Cargo.toml b/crates/docs/Cargo.toml index e613e30065..a8723f3c6b 100644 --- a/crates/docs/Cargo.toml +++ b/crates/docs/Cargo.toml @@ -22,7 +22,7 @@ roc_highlight = { path = "../highlight"} roc_reporting = { path = "../reporting"} bumpalo = { version = "3.11.0", features = ["collections"] } snafu = { version = "0.7.1", features = ["backtraces"] } -peg = "0.8.0" +peg = "0.8.1" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/crates/highlight/Cargo.toml b/crates/highlight/Cargo.toml index 5c4f498091..8277ed2cb9 100644 --- a/crates/highlight/Cargo.toml +++ b/crates/highlight/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" description = "For syntax highlighting, starts with a string and returns our markup nodes." [dependencies] -peg = "0.8.0" +peg = "0.8.1" roc_code_markup = { path = "../code_markup"} diff --git a/crates/reporting/Cargo.toml b/crates/reporting/Cargo.toml index 75df48621b..0e548f7fcd 100644 --- a/crates/reporting/Cargo.toml +++ b/crates/reporting/Cargo.toml @@ -34,4 +34,4 @@ roc_test_utils = { path = "../test_utils" } roc_solve = { path = "../compiler/solve" } pretty_assertions = "1.3.0" indoc = "1.0.7" -insta = "1.19.0" +insta = "1.20.0" diff --git a/examples/interactive/.gitignore b/examples/interactive/.gitignore index 084ea28578..8e260e400a 100644 --- a/examples/interactive/.gitignore +++ b/examples/interactive/.gitignore @@ -6,3 +6,4 @@ form tui http-get file-io +env diff --git a/examples/interactive/cli-platform/Env.roc b/examples/interactive/cli-platform/Env.roc index 12a520b974..dcd08cda6b 100644 --- a/examples/interactive/cli-platform/Env.roc +++ b/examples/interactive/cli-platform/Env.roc @@ -1,6 +1,5 @@ interface Env - exposes [cwd, dict, var] - imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask] + exposes [cwd, dict, var, decode] imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask, EnvDecoding] ## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory) ## from the environment. File operations on relative [Path]s are relative to this directory. @@ -33,31 +32,40 @@ var = \name -> |> Effect.map (\result -> Result.mapErr result \{} -> VarNotFound) |> InternalTask.fromEffect -# ## Reads the given environment variable and attempts to decode it. -# ## -# ## The type being decoded into will be determined by type inference. For example, -# ## if this ends up being used like a `Task U16 …` then the environment variable -# ## will be decoded as a string representation of a `U16`. -# ## -# ## getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError]* [Env]* -# ## getU16Var = \var -> Env.decode var -# ## # If the environment contains a variable NUM_THINGS=123, then calling -# ## # (getU16Var "NUM_THINGS") would return a task which succeeds with the U16 number 123. -# ## # -# ## # However, if the NUM_THINGS environment variable was set to 1234567, then -# ## # (getU16Var "NUM_THINGS") would fail because that number is too big to fit in a U16. -# ## -# ## Supported types: -# ## - strings -# ## - numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers -# ## - comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas -# ## -# ## Trying to decode into any other types will always fail with a `DecodeErr`. -# decode : Str -> Task val [VarNotFound, DecodeErr DecodeError]* [Env]* -# | val has Decode -# decode = \var -> -# Effect.envVar var -# |> InternalTask.fromEffect +## Reads the given environment variable and attempts to decode it. +## +## The type being decoded into will be determined by type inference. For example, +## if this ends up being used like a `Task U16 …` then the environment variable +## will be decoded as a string representation of a `U16`. +## +## getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError]* [Env]* +## getU16Var = \var -> Env.decode var +## # If the environment contains a variable NUM_THINGS=123, then calling +## # (getU16Var "NUM_THINGS") would return a task which succeeds with the U16 number 123. +## # +## # However, if the NUM_THINGS environment variable was set to 1234567, then +## # (getU16Var "NUM_THINGS") would fail because that number is too big to fit in a U16. +## +## Supported types: +## - strings +## - numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers +## - comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas +## +## Trying to decode into any other types will always fail with a `DecodeErr`. +decode : Str -> Task val [VarNotFound, DecodeErr DecodeError]* [Env]* | val has Decoding +decode = \name -> + Effect.envVar name + |> Effect.map + ( + \result -> + result + |> Result.mapErr (\{} -> VarNotFound) + |> Result.try + (\varStr -> + Decode.fromBytes (Str.toUtf8 varStr) (EnvDecoding.format {}) + |> Result.mapErr (\_ -> DecodeErr TooShort))) + |> InternalTask.fromEffect + ## Reads all the process's environment variables into a [Dict]. ## ## If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) diff --git a/examples/interactive/cli-platform/EnvDecoding.roc b/examples/interactive/cli-platform/EnvDecoding.roc new file mode 100644 index 0000000000..62b25f3f9d --- /dev/null +++ b/examples/interactive/cli-platform/EnvDecoding.roc @@ -0,0 +1,95 @@ +interface EnvDecoding exposes [EnvFormat, format] imports [] + +EnvFormat := {} has [ + DecoderFormatting { + u8: envU8, + u16: envU16, + u32: envU32, + u64: envU64, + u128: envU128, + i8: envI8, + i16: envI16, + i32: envI32, + i64: envI64, + i128: envI128, + f32: envF32, + f64: envF64, + dec: envDec, + bool: envBool, + string: envString, + list: envList, + record: envRecord, + }, + ] + +format : {} -> EnvFormat +format = \{} -> @EnvFormat {} + +decodeBytesToNum = \bytes, transformer -> + when Str.fromUtf8 bytes is + Ok s -> + when transformer s is + Ok n -> { result: Ok n, rest: [] } + Err _ -> { result: Err TooShort, rest: bytes } + + Err _ -> { result: Err TooShort, rest: bytes } + +envU8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU8 +envU16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU16 +envU32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU32 +envU64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU64 +envU128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU128 +envI8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI8 +envI16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI16 +envI32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI32 +envI64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI64 +envI128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI128 +envF32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF32 +envF64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF64 +envDec = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toDec +envBool = Decode.custom \bytes, @EnvFormat {} -> + when Str.fromUtf8 bytes is + Ok "true" -> { result: Ok Bool.true, rest: [] } + Ok "false" -> { result: Ok Bool.false, rest: [] } + _ -> { result: Err TooShort, rest: bytes } +envString = Decode.custom \bytes, @EnvFormat {} -> + when Str.fromUtf8 bytes is + Ok s -> { result: Ok s, rest: [] } + Err _ -> { result: Err TooShort, rest: bytes } + +envList = \decodeElem -> Decode.custom \bytes, @EnvFormat {} -> + # Per our supported methods of decoding, this is either a list of strings or + # a list of numbers; in either case, the list of bytes must be Utf-8 + # decodable. So just parse it as a list of strings and pass each chunk to + # the element decoder. By construction, our element decoders expect to parse + # a whole list of bytes anyway. + decodeElems = \allBytes, accum -> + { toParse, remainder } = + when List.splitFirst allBytes (Num.toU8 ',') is + Ok { before, after } -> + { toParse: before, remainder: Some after } + + Err NotFound -> + { toParse: allBytes, remainder: None } + + when Decode.decodeWith toParse decodeElem (@EnvFormat {}) is + { result, rest } -> + when result is + Ok val -> + when remainder is + Some restBytes -> decodeElems restBytes (List.append accum val) + None -> Done (List.append accum val) + + Err e -> Errored e rest + + when decodeElems bytes [] is + Errored e rest -> { result: Err e, rest } + Done vals -> + { result: Ok vals, rest: [] } + +# TODO: we must currently annotate the arrows here so that the lambda sets are +# exercised, and the solver can find an ambient lambda set for the +# specialization. +envRecord : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_ -> _) -> Decoder _ _ +envRecord = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> + { result: Err TooShort, rest: bytes } diff --git a/examples/interactive/env.roc b/examples/interactive/env.roc new file mode 100644 index 0000000000..c53f7d7b26 --- /dev/null +++ b/examples/interactive/env.roc @@ -0,0 +1,25 @@ +app "env" + packages { pf: "cli-platform/main.roc" } + imports [pf.Stdout, pf.Env, pf.Task, pf.Program.{ Program }] + provides [main] to pf + +main : Program +main = + Env.decode "EDITOR" + |> Task.await (\editor -> Stdout.line "Your favorite editor is \(editor)!") + |> Task.await (\{} -> Env.decode "SHLVL") + |> Task.await + (\lvl -> + when lvl is + 1u8 -> Stdout.line "You're running this in a root shell!" + n -> + lvlStr = Num.toStr n + + Stdout.line "Your current shell level is \(lvlStr)!") + |> Task.await (\{} -> Env.decode "LETTERS") + |> Task.await + (\letters -> + joinedLetters = Str.joinWith letters " " + + Stdout.line "Your favorite letters are: \(joinedLetters)") + |> Program.quick