From 555ed2c7fc530fe7811b0635e46b433bda1a982d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 05:28:20 +0000 Subject: [PATCH 01/26] Bump serde from 1.0.139 to 1.0.144 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.139 to 1.0.144. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.139...v1.0.144) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- crates/code_markup/Cargo.toml | 2 +- crates/editor/Cargo.toml | 2 +- crates/linker/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35d4df06a9..bb7a461f78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4365,9 +4365,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.139" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] @@ -4405,9 +4405,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", diff --git a/crates/code_markup/Cargo.toml b/crates/code_markup/Cargo.toml index f3f2f170a2..1f6c9652fd 100644 --- a/crates/code_markup/Cargo.toml +++ b/crates/code_markup/Cargo.toml @@ -10,7 +10,7 @@ description = "Our own markup language for Roc code. Used by the editor and the roc_ast = { path = "../ast" } roc_module = { path = "../compiler/module" } roc_utils = { path = "../utils" } -serde = { version = "1.0.130", features = ["derive"] } +serde = { version = "1.0.144", features = ["derive"] } palette = "0.6.1" snafu = { version = "0.7.1", features = ["backtraces"] } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 5d287d286a..5fd3d195f0 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -54,7 +54,7 @@ palette = "0.6.1" confy = { git = 'https://github.com/rust-cli/confy', features = [ "yaml_conf" ], default-features = false } -serde = { version = "1.0.130", features = ["derive"] } +serde = { version = "1.0.144", features = ["derive"] } nonempty = "0.8.0" fs_extra = "1.2.0" rodio = { version = "0.15.0", optional = true } # to play sounds diff --git a/crates/linker/Cargo.toml b/crates/linker/Cargo.toml index 33271e3245..38b34606cf 100644 --- a/crates/linker/Cargo.toml +++ b/crates/linker/Cargo.toml @@ -22,7 +22,7 @@ iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "d memmap2 = "0.5.7" object = { version = "0.29.0", features = ["read", "write"] } mach_object = "0.1" -serde = { version = "1.0.130", features = ["derive"] } +serde = { version = "1.0.144", features = ["derive"] } bincode = "1.3.3" target-lexicon = "0.12.3" tempfile = "3.2.0" From 4fa52e6deeeb3fb136db0a0f8f5c9070870c757c Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Mon, 12 Sep 2022 01:12:12 -0600 Subject: [PATCH 02/26] Fix file example app executable name Signed-off-by: Jan Van Bruggen --- examples/interactive/file.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index 83b873ae8a..4f30de7065 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -1,4 +1,4 @@ -app "echo" +app "file" packages { pf: "cli-platform/main.roc" } imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path] provides [main] to pf @@ -14,4 +14,4 @@ main = Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied" Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported" Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)" - _ -> Stdout.line "Successfully wrote a string to out.txt" \ No newline at end of file + _ -> Stdout.line "Successfully wrote a string to out.txt" From b3744a3647cac51454cce9b087a6929fb0cb753a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 10:33:06 +0000 Subject: [PATCH 03/26] Bump pest from 2.1.3 to 2.3.1 Bumps [pest](https://github.com/pest-parser/pest) from 2.1.3 to 2.3.1. - [Release notes](https://github.com/pest-parser/pest/releases) - [Commits](https://github.com/pest-parser/pest/commits/v2.3.1) --- updated-dependencies: - dependency-name: pest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 17 ++++++++++++----- crates/editor/Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35d4df06a9..212fac4de4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2809,10 +2809,11 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" dependencies = [ + "thiserror", "ucd-trie", ] @@ -2945,14 +2946,14 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_assertions" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" dependencies = [ - "ansi_term", "ctor", "diff", "output_vt100", + "yansi", ] [[package]] @@ -6156,3 +6157,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 5d287d286a..acd5a7fbab 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -47,7 +47,7 @@ futures = "0.3.24" cgmath = "0.18.0" snafu = { version = "0.7.1", features = ["backtraces"] } colored = "2.0.0" -pest = "2.1.3" +pest = "2.3.1" pest_derive = "2.1.0" copypasta = "0.8.1" palette = "0.6.1" From 35b2e9c80fa5a15c65433931baa3449fc0b47bad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 10:33:19 +0000 Subject: [PATCH 04/26] Bump clap from 3.2.18 to 3.2.20 Bumps [clap](https://github.com/clap-rs/clap) from 3.2.18 to 3.2.20. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/v3.2.20/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/v3.2.18...v3.2.20) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 24 +++++++++++++++--------- crates/cli/Cargo.toml | 4 ++-- crates/docs_cli/Cargo.toml | 2 +- crates/glue/Cargo.toml | 4 ++-- crates/linker/Cargo.toml | 2 +- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35d4df06a9..483398f3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.18" +version = "3.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15f2ea93df33549dbe2e8eecd1ca55269d63ae0b3ba1f55db030817d1c2867f" +checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" dependencies = [ "atty", "bitflags", @@ -2945,14 +2945,14 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_assertions" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" dependencies = [ - "ansi_term", "ctor", "diff", "output_vt100", + "yansi", ] [[package]] @@ -3446,7 +3446,7 @@ name = "roc_cli" version = "0.1.0" dependencies = [ "bumpalo", - "clap 3.2.18", + "clap 3.2.20", "cli_utils", "const_format", "criterion", @@ -3591,7 +3591,7 @@ dependencies = [ name = "roc_docs_cli" version = "0.0.1" dependencies = [ - "clap 3.2.18", + "clap 3.2.20", "libc", "roc_docs", ] @@ -3740,7 +3740,7 @@ name = "roc_glue" version = "0.0.1" dependencies = [ "bumpalo", - "clap 3.2.18", + "clap 3.2.20", "cli_utils", "dircpy", "fnv", @@ -3807,7 +3807,7 @@ version = "0.0.1" dependencies = [ "bincode", "bumpalo", - "clap 3.2.18", + "clap 3.2.20", "iced-x86", "mach_object", "memmap2 0.5.7", @@ -6156,3 +6156,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 4afc049c62..4d031c52c7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -60,7 +60,7 @@ roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } roc_repl_cli = { path = "../repl_cli", optional = true } roc_tracing = { path = "../tracing" } -clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions"] } +clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] } const_format = { version = "0.2.23", features = ["const_generics"] } bumpalo = { version = "3.8.0", features = ["collections"] } mimalloc = { version = "0.1.26", default-features = false } @@ -93,7 +93,7 @@ wasmer = { version = "2.2.1", optional = true, default-features = false, feature [dev-dependencies] wasmer-wasi = "2.2.1" -pretty_assertions = "1.0.0" +pretty_assertions = "1.3.0" roc_test_utils = { path = "../test_utils" } indoc = "1.0.7" serial_test = "0.9.0" diff --git a/crates/docs_cli/Cargo.toml b/crates/docs_cli/Cargo.toml index c5903b0d77..fad2c96728 100644 --- a/crates/docs_cli/Cargo.toml +++ b/crates/docs_cli/Cargo.toml @@ -18,7 +18,7 @@ bench = false [dependencies] roc_docs = { path = "../docs" } -clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions", "derive"] } +clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] } [target.'cfg(windows)'.dependencies] libc = "0.2.132" diff --git a/crates/glue/Cargo.toml b/crates/glue/Cargo.toml index d8d91a422c..1531f08b2b 100644 --- a/crates/glue/Cargo.toml +++ b/crates/glue/Cargo.toml @@ -20,14 +20,14 @@ roc_target = { path = "../compiler/roc_target" } roc_error_macros = { path = "../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.3" -clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions", "derive"] } +clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] } strum = "0.24.0" strum_macros = "0.24" indexmap = "1.8.1" fnv = "1.0.7" [dev-dependencies] -pretty_assertions = "1.0.0" +pretty_assertions = "1.3.0" tempfile = "3.2.0" indoc = "1.0.7" cli_utils = { path = "../cli_utils" } diff --git a/crates/linker/Cargo.toml b/crates/linker/Cargo.toml index 33271e3245..6eee05067a 100644 --- a/crates/linker/Cargo.toml +++ b/crates/linker/Cargo.toml @@ -17,7 +17,7 @@ roc_build = { path = "../compiler/build" } roc_collections = { path = "../compiler/collections" } roc_error_macros = { path = "../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } -clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions"] } +clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] } iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } memmap2 = "0.5.7" object = { version = "0.29.0", features = ["read", "write"] } From 15f66e3eaaf9f23b3ff150f6b0b945f6789c9a03 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 7 Sep 2022 12:42:46 -0500 Subject: [PATCH 05/26] Add some tracing for debug purposes --- Cargo.lock | 1 + crates/glue/Cargo.toml | 1 + crates/glue/src/types.rs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4836879b0f..b73ad63824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3759,6 +3759,7 @@ dependencies = [ "roc_std", "roc_target", "roc_test_utils", + "roc_tracing", "roc_types", "strum", "strum_macros", diff --git a/crates/glue/Cargo.toml b/crates/glue/Cargo.toml index 4cc9eefa23..777041e60f 100644 --- a/crates/glue/Cargo.toml +++ b/crates/glue/Cargo.toml @@ -18,6 +18,7 @@ roc_module = { path = "../compiler/module" } roc_collections = { path = "../compiler/collections" } roc_target = { path = "../compiler/roc_target" } roc_error_macros = { path = "../error_macros" } +roc_tracing = { path = "../tracing" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.3" clap = { version = "3.2.18", default-features = false, features = ["std", "color", "suggestions", "derive"] } diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index 79b39bc0d5..45fc1ff953 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -653,6 +653,8 @@ impl<'a> Env<'a> { } fn add_type(&mut self, var: Variable, types: &mut Types) -> TypeId { + roc_tracing::debug!(content=?roc_types::subs::SubsFmtContent(self.subs.get_content_without_compacting(var), self.subs), "adding type"); + let layout = self .layout_cache .from_var(self.arena, var, self.subs) From f1b5705b063051437fd260b47730ee9067fcc9d8 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 7 Sep 2022 12:42:54 -0500 Subject: [PATCH 06/26] Re-use existing ID of added anonymous type, when possible --- crates/glue/src/types.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index 45fc1ff953..e815c5943f 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -359,6 +359,12 @@ impl Types { typ: RocType, layout: Layout<'a>, ) -> TypeId { + for (id, existing_type) in self.types.iter().enumerate() { + if self.is_equivalent(&typ, &existing_type) { + return TypeId(id); + } + } + let id = TypeId(self.types.len()); assert!(id.0 <= TypeId::MAX.0); From ea527f627f08f3f04448700c7ab32aa73a862841 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 7 Sep 2022 12:46:03 -0500 Subject: [PATCH 07/26] Add test for multiple modules and anonymous string --- .../tests/fixtures/multiple-modules/app.roc | 6 ++ .../tests/fixtures/multiple-modules/dep1.roc | 5 + .../tests/fixtures/multiple-modules/dep2.roc | 5 + .../fixtures/multiple-modules/platform.roc | 11 +++ .../fixtures/multiple-modules/src/lib.rs | 99 +++++++++++++++++++ crates/glue/tests/test_glue_cli.rs | 3 + 6 files changed, 129 insertions(+) create mode 100644 crates/glue/tests/fixtures/multiple-modules/app.roc create mode 100644 crates/glue/tests/fixtures/multiple-modules/dep1.roc create mode 100644 crates/glue/tests/fixtures/multiple-modules/dep2.roc create mode 100644 crates/glue/tests/fixtures/multiple-modules/platform.roc create mode 100644 crates/glue/tests/fixtures/multiple-modules/src/lib.rs diff --git a/crates/glue/tests/fixtures/multiple-modules/app.roc b/crates/glue/tests/fixtures/multiple-modules/app.roc new file mode 100644 index 0000000000..5ff6397dc0 --- /dev/null +++ b/crates/glue/tests/fixtures/multiple-modules/app.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "platform.roc" } + imports [pf.Dep1, pf.Dep2] + provides [main] to pf + +main = {s1: Dep1.string "hello", s2: Dep2.string "world"} diff --git a/crates/glue/tests/fixtures/multiple-modules/dep1.roc b/crates/glue/tests/fixtures/multiple-modules/dep1.roc new file mode 100644 index 0000000000..c698bb66f2 --- /dev/null +++ b/crates/glue/tests/fixtures/multiple-modules/dep1.roc @@ -0,0 +1,5 @@ +interface Dep1 exposes [DepStr1, string] imports [] + +DepStr1 := [ S Str ] + +string = \s -> @DepStr1 (S s) diff --git a/crates/glue/tests/fixtures/multiple-modules/dep2.roc b/crates/glue/tests/fixtures/multiple-modules/dep2.roc new file mode 100644 index 0000000000..830a8d8a96 --- /dev/null +++ b/crates/glue/tests/fixtures/multiple-modules/dep2.roc @@ -0,0 +1,5 @@ +interface Dep2 exposes [DepStr2, string] imports [] + +DepStr2 := [ R Str ] + +string = \s -> @DepStr2 (R s) diff --git a/crates/glue/tests/fixtures/multiple-modules/platform.roc b/crates/glue/tests/fixtures/multiple-modules/platform.roc new file mode 100644 index 0000000000..e7dcc2c38a --- /dev/null +++ b/crates/glue/tests/fixtures/multiple-modules/platform.roc @@ -0,0 +1,11 @@ +platform "test-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [Dep1, Dep2] + provides [mainForHost] + +Combined : {s1: Dep1.DepStr1, s2: Dep2.DepStr2} + +mainForHost : Combined +mainForHost = main diff --git a/crates/glue/tests/fixtures/multiple-modules/src/lib.rs b/crates/glue/tests/fixtures/multiple-modules/src/lib.rs new file mode 100644 index 0000000000..0789c36e98 --- /dev/null +++ b/crates/glue/tests/fixtures/multiple-modules/src/lib.rs @@ -0,0 +1,99 @@ +mod test_glue; + +use indoc::indoc; +use test_glue::Combined; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut Combined); +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use std::cmp::Ordering; + use std::collections::hash_set::HashSet; + + let tag_union = unsafe { + let mut ret: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); + + roc_main(ret.as_mut_ptr()); + + ret.assume_init() + }; + + // Verify that it has all the expected traits. + + assert!(tag_union == tag_union); // PartialEq + assert!(tag_union.clone() == tag_union.clone()); // Clone + + assert!(tag_union.partial_cmp(&tag_union) == Some(Ordering::Equal)); // PartialOrd + assert!(tag_union.cmp(&tag_union) == Ordering::Equal); // Ord + + print!( + indoc!( + r#" + combined was: {:?} + "# + ), + tag_union, + ); // Debug + + let mut set = HashSet::new(); + + set.insert(tag_union.clone()); // Eq, Hash + set.insert(tag_union); + + assert_eq!(set.len(), 1); + + // Exit code + 0 +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/crates/glue/tests/test_glue_cli.rs b/crates/glue/tests/test_glue_cli.rs index 66140c02ae..0f16b28dfe 100644 --- a/crates/glue/tests/test_glue_cli.rs +++ b/crates/glue/tests/test_glue_cli.rs @@ -124,6 +124,9 @@ mod glue_cli_run { list_recursive_union:"list-recursive-union" => indoc!(r#" rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { args: [], tool: Tool::SystemTool("test") }), inputFiles: ["foo"], job: [] }) } "#), + multiple_modules:"multiple-modules" => indoc!(r#" + combined was: Combined { s1: DepStr1::S("hello"), s2: DepStr2::R("world") } + "#), } fn check_for_tests(all_fixtures: &mut roc_collections::VecSet) { From 9f3a44d3237a0f669afbc9aa3daaa3274fe060c0 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 7 Sep 2022 14:41:47 -0500 Subject: [PATCH 08/26] Remove redundant reference --- crates/glue/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index e815c5943f..ea93e77b59 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -360,7 +360,7 @@ impl Types { layout: Layout<'a>, ) -> TypeId { for (id, existing_type) in self.types.iter().enumerate() { - if self.is_equivalent(&typ, &existing_type) { + if self.is_equivalent(&typ, existing_type) { return TypeId(id); } } From 9eb1c170f3a9b7cbbadcbdf642b72385fba685cb Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 7 Sep 2022 17:44:56 -0500 Subject: [PATCH 09/26] Print unreachable id --- crates/glue/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index ea93e77b59..3b093ec3a7 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -385,7 +385,7 @@ impl Types { pub fn get_type(&self, id: TypeId) -> &RocType { match self.types.get(id.0) { Some(typ) => typ, - None => unreachable!(), + None => unreachable!("{:?}", id), } } From 564d9f795abe6f38d61060da23dc4b062dad1352 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 12 Sep 2022 12:39:47 -0400 Subject: [PATCH 10/26] Deal with equivalence in the presence of unresolved recursive pointers --- crates/glue/src/types.rs | 87 +++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index 3b093ec3a7..2050eb6da7 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -65,24 +65,45 @@ impl Types { } pub fn is_equivalent(&self, a: &RocType, b: &RocType) -> bool { + self.is_equivalent_help(RocTypeOrPending::Type(a), RocTypeOrPending::Type(b)) + } + + fn is_equivalent_help(&self, a: RocTypeOrPending, b: RocTypeOrPending) -> bool { use RocType::*; + let (a, b) = match (a, b) { + (RocTypeOrPending::Type(a), RocTypeOrPending::Type(b)) => (a, b), + (RocTypeOrPending::Pending, RocTypeOrPending::Pending) => return true, + _ => return false, + }; + match (a, b) { (RocStr, RocStr) | (Bool, Bool) | (EmptyTagUnion, EmptyTagUnion) | (Unit, Unit) => true, (RocResult(ok_a, err_a), RocResult(ok_b, err_b)) => { - self.is_equivalent(self.get_type(*ok_a), self.get_type(*ok_b)) - && self.is_equivalent(self.get_type(*err_a), self.get_type(*err_b)) + self.is_equivalent_help( + self.get_type_or_pending(*ok_a), + self.get_type_or_pending(*ok_b), + ) && self.is_equivalent_help( + self.get_type_or_pending(*err_a), + self.get_type_or_pending(*err_b), + ) } (Num(num_a), Num(num_b)) => num_a == num_b, (RocList(elem_a), RocList(elem_b)) | (RocSet(elem_a), RocSet(elem_b)) | (RocBox(elem_a), RocBox(elem_b)) - | (RecursivePointer(elem_a), RecursivePointer(elem_b)) => { - self.is_equivalent(self.get_type(*elem_a), self.get_type(*elem_b)) - } + | (RecursivePointer(elem_a), RecursivePointer(elem_b)) => self.is_equivalent_help( + self.get_type_or_pending(*elem_a), + self.get_type_or_pending(*elem_b), + ), (RocDict(key_a, val_a), RocDict(key_b, val_b)) => { - self.is_equivalent(self.get_type(*key_a), self.get_type(*key_b)) - && self.is_equivalent(self.get_type(*val_a), self.get_type(*val_b)) + self.is_equivalent_help( + self.get_type_or_pending(*key_a), + self.get_type_or_pending(*key_b), + ) && self.is_equivalent_help( + self.get_type_or_pending(*val_a), + self.get_type_or_pending(*val_b), + ) } (TagUnion(union_a), TagUnion(union_b)) => { use RocTagUnion::*; @@ -113,8 +134,10 @@ impl Types { }, ) => { tag_name_a == tag_name_b - && self - .is_equivalent(self.get_type(*payload_a), self.get_type(*payload_b)) + && self.is_equivalent_help( + self.get_type_or_pending(*payload_a), + self.get_type_or_pending(*payload_b), + ) } (Enumeration { tags: tags_a, .. }, Enumeration { tags: tags_b, .. }) => { tags_a == tags_b @@ -157,9 +180,9 @@ impl Types { |((name_a, opt_id_a), (name_b, opt_id_b))| { name_a == name_b && match (opt_id_a, opt_id_b) { - (Some(id_a), Some(id_b)) => self.is_equivalent( - self.get_type(*id_a), - self.get_type(*id_b), + (Some(id_a), Some(id_b)) => self.is_equivalent_help( + self.get_type_or_pending(*id_a), + self.get_type_or_pending(*id_b), ), (None, None) => true, (None, Some(_)) | (Some(_), None) => false, @@ -179,9 +202,9 @@ impl Types { |((name_a, opt_id_a), (name_b, opt_id_b))| { name_a == name_b && match (opt_id_a, opt_id_b) { - (Some(id_a), Some(id_b)) => self.is_equivalent( - self.get_type(*id_a), - self.get_type(*id_b), + (Some(id_a), Some(id_b)) => self.is_equivalent_help( + self.get_type_or_pending(*id_a), + self.get_type_or_pending(*id_b), ), (None, None) => true, (None, Some(_)) | (Some(_), None) => false, @@ -241,7 +264,10 @@ impl Types { .zip(fields_b.iter()) .all(|((name_a, id_a), (name_b, id_b))| { name_a == name_b - && self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b)) + && self.is_equivalent_help( + self.get_type_or_pending(*id_a), + self.get_type_or_pending(*id_b), + ) }) } else { false @@ -261,7 +287,10 @@ impl Types { .zip(fields_b.iter()) .all(|((name_a, id_a), (name_b, id_b))| { name_a == name_b - && self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b)) + && self.is_equivalent_help( + self.get_type_or_pending(*id_a), + self.get_type_or_pending(*id_b), + ) }) } else { false @@ -283,10 +312,16 @@ impl Types { // with the same type could have completely different implementations! if name_a == name_b && args_a.len() == args_b.len() - && self.is_equivalent(self.get_type(*ret_a), self.get_type(*ret_b)) + && self.is_equivalent_help( + self.get_type_or_pending(*ret_a), + self.get_type_or_pending(*ret_b), + ) { args_a.iter().zip(args_b.iter()).all(|(id_a, id_b)| { - self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b)) + self.is_equivalent_help( + self.get_type_or_pending(*id_a), + self.get_type_or_pending(*id_b), + ) }) } else { false @@ -389,6 +424,14 @@ impl Types { } } + fn get_type_or_pending(&self, id: TypeId) -> RocTypeOrPending { + match self.types.get(id.0) { + Some(typ) => RocTypeOrPending::Type(typ), + None if id == TypeId::PENDING => RocTypeOrPending::Pending, + None => unreachable!("{:?}", id), + } + } + /// Contrast this with the size_ignoring_alignment method pub fn size_rounded_to_alignment(&self, id: TypeId) -> u32 { let size_ignoring_alignment = self.size_ignoring_alignment(id); @@ -448,6 +491,12 @@ impl Types { } } +enum RocTypeOrPending<'a> { + Type(&'a RocType), + /// A pending recursive pointer + Pending, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum RocType { RocStr, From 0b2cea8c42bec0e3c0c220c80947271fa8fdff97 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 12 Sep 2022 13:25:01 -0400 Subject: [PATCH 11/26] Move --- .../glue/tests/fixtures/multiple-modules/{dep1.roc => Dep1.roc} | 0 .../glue/tests/fixtures/multiple-modules/{dep2.roc => Dep2.roc} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename crates/glue/tests/fixtures/multiple-modules/{dep1.roc => Dep1.roc} (100%) rename crates/glue/tests/fixtures/multiple-modules/{dep2.roc => Dep2.roc} (100%) diff --git a/crates/glue/tests/fixtures/multiple-modules/dep1.roc b/crates/glue/tests/fixtures/multiple-modules/Dep1.roc similarity index 100% rename from crates/glue/tests/fixtures/multiple-modules/dep1.roc rename to crates/glue/tests/fixtures/multiple-modules/Dep1.roc diff --git a/crates/glue/tests/fixtures/multiple-modules/dep2.roc b/crates/glue/tests/fixtures/multiple-modules/Dep2.roc similarity index 100% rename from crates/glue/tests/fixtures/multiple-modules/dep2.roc rename to crates/glue/tests/fixtures/multiple-modules/Dep2.roc From d5cc75907fab78c38f7a23e1f5e7f23ff0776f5a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 10:02:23 -0400 Subject: [PATCH 12/26] Update file I/O example --- examples/interactive/.gitignore | 1 + examples/interactive/file.roc | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/interactive/.gitignore b/examples/interactive/.gitignore index cb41e998d8..6eacf36982 100644 --- a/examples/interactive/.gitignore +++ b/examples/interactive/.gitignore @@ -4,3 +4,4 @@ effects form tui http-get +file-io diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index 4f30de7065..e6f9b3a3fd 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -1,17 +1,21 @@ -app "file" +app "file-io" packages { pf: "cli-platform/main.roc" } imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path] provides [main] to pf -main : Task.Task {} [] [Write [File, Stdout, Stderr]] +main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File]] main = + path = Path.fromStr "out.txt" task = _ <- Stdout.line "Writing a string to out.txt" |> Task.await - File.writeUtf8 (Path.fromStr "out.txt") "a string!\n" + _ <- File.writeUtf8 path "a string!" |> Task.await + contents <- File.readUtf8 path |> Task.await + Stdout.line "I read the file back. Its contents: \"\(contents)\"" Task.attempt task \result -> when result is Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied" Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported" Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)" + Err (FileReadErr _ _) -> Stderr.line "Error reading file" _ -> Stdout.line "Successfully wrote a string to out.txt" From 81d11e4edd1a253d1d1dc9ef4869268c797772b0 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 10:03:35 -0400 Subject: [PATCH 13/26] Add File.delete to CLI platform --- examples/interactive/cli-platform/Effect.roc | 2 + examples/interactive/cli-platform/File.roc | 41 +++++++++++++++----- examples/interactive/cli-platform/src/lib.rs | 10 +++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/examples/interactive/cli-platform/Effect.roc b/examples/interactive/cli-platform/Effect.roc index 634bc7eeeb..92a6fc49ae 100644 --- a/examples/interactive/cli-platform/Effect.roc +++ b/examples/interactive/cli-platform/Effect.roc @@ -11,6 +11,7 @@ hosted Effect stdinLine, sendRequest, fileReadBytes, + fileDelete, fileWriteUtf8, fileWriteBytes, ] @@ -23,6 +24,7 @@ stdinLine : Effect Str fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr) fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr) +fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr) fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr) sendRequest : Box Request -> Effect Response diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index f6e84107ab..107892285b 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -1,6 +1,6 @@ interface File - exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes] - imports [Effect, Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath] + exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete] + imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }] ReadErr : InternalFile.ReadErr @@ -41,10 +41,7 @@ write = \path, val, fmt -> ## To format data before writing it to a file, you can use [File.write] instead. writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* writeBytes = \path, bytes -> - InternalPath.toBytes path - |> Effect.fileWriteBytes bytes - |> InternalTask.fromEffect - |> Task.mapFail \err -> FileWriteErr path err + toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes ## Write a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## @@ -56,10 +53,27 @@ writeBytes = \path, bytes -> ## To write unformatted bytes to a file, you can use [File.writeBytes] instead. writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* writeUtf8 = \path, str -> - InternalPath.toBytes path - |> Effect.fileWriteUtf8 str - |> InternalTask.fromEffect - |> Task.mapFail \err -> FileWriteErr path err + toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str + +## Delete a file from the filesystem. +## +## # Deletes the file named +## File.delete (Path.fromStr "myfile.dat") [1, 2, 3] +## +## Note that this does not securely erase the file's contents from disk; instead, the operating +## system marks the space it was occupying as safe to write over in the future. Also, the operating +## system may not immediately mark the space as free; for example, on Windows it will wait until +## the last file handle to it is closed, and on UNIX, it will not remove it until the last +## [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted. +## +## This performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile) +## on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems. +## +## On Windows, this will fail when attempting to delete a readonly file; the file's +## readonly permission must be disabled before it can be successfully deleted. +delete : Path -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* +delete = \path -> + toWriteTask path \bytes -> Effect.fileDelete bytes ## Read all the bytes in a file. ## @@ -119,3 +133,10 @@ readUtf8 = \path -> # Err decodingErr -> Err (FileReadDecodeErr decodingErr) # Err readErr -> Err (FileReadErr readErr) # InternalTask.fromEffect effect + +toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]* [Write [File]*]* +toWriteTask = \path, toEffect -> + InternalPath.toBytes path + |> toEffect + |> InternalTask.fromEffect + |> Task.mapFail \err -> FileWriteErr path err diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index cf7f4337e1..09faea6e8c 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -217,6 +217,16 @@ pub extern "C" fn roc_fx_fileReadBytes(path: &RocList) -> RocResult) -> RocResult<(), ReadErr> { + match std::fs::remove_file(path_from_roc_path(roc_path)) { + Ok(()) => RocResult::ok(()), + Err(_) => { + todo!("Report a file write error"); + } + } +} + #[no_mangle] pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response { let mut builder = reqwest::blocking::ClientBuilder::new(); From 4ccd7261417e44adba91e5b0fda3781c0a9b69d6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 10:03:51 -0400 Subject: [PATCH 14/26] Fix File.readBytes --- examples/interactive/cli-platform/File.roc | 12 ++++++++---- examples/interactive/cli-platform/src/lib.rs | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index 107892285b..257218b082 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -85,10 +85,7 @@ delete = \path -> ## To read and decode data from a file, you can use `File.read` instead. readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]* readBytes = \path -> - InternalPath.toBytes path - |> Effect.fileReadBytes - |> InternalTask.fromEffect - |> Task.mapFail \err -> FileReadErr path err + toReadTask path \bytes -> Effect.fileReadBytes bytes ## Read a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## @@ -140,3 +137,10 @@ toWriteTask = \path, toEffect -> |> toEffect |> InternalTask.fromEffect |> Task.mapFail \err -> FileWriteErr path err + +toReadTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileReadErr Path err]* [Read [File]*]* +toReadTask = \path, toEffect -> + InternalPath.toBytes path + |> toEffect + |> InternalTask.fromEffect + |> Task.mapFail \err -> FileReadErr path err diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index 09faea6e8c..58b7a89cfc 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -210,11 +210,22 @@ pub fn os_str_from_list(bytes: &RocList) -> &OsStr { } #[no_mangle] -pub extern "C" fn roc_fx_fileReadBytes(path: &RocList) -> RocResult, ReadErr> { - let path = path_from_roc_path(path); - println!("TODO read bytes from {:?}", path); +pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList) -> RocResult, ReadErr> { + use std::io::Read; - RocResult::ok(RocList::empty()) + let mut bytes = Vec::new(); + + match File::open(path_from_roc_path(roc_path)) { + Ok(mut file) => match file.read_to_end(&mut bytes) { + Ok(_bytes_read) => RocResult::ok(RocList::from(bytes.as_slice())), + Err(_) => { + todo!("Report a file write error"); + } + }, + Err(_) => { + todo!("Report a file open error"); + } + } } #[no_mangle] From 21b74f6dbbe8fe3dc309dd2cb0c94fd6d5b04a65 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 16:12:36 -0400 Subject: [PATCH 15/26] Add Dir and Env to CLI platform --- examples/interactive/cli-platform/Dir.roc | 25 +++++++++++ examples/interactive/cli-platform/Effect.roc | 7 ++- examples/interactive/cli-platform/Env.roc | 15 +++++++ examples/interactive/cli-platform/File.roc | 12 ++--- .../interactive/cli-platform/FileMetadata.roc | 35 +++++++++++++++ .../interactive/cli-platform/InternalDir.roc | 41 +++++++++++++++++ .../interactive/cli-platform/InternalPath.roc | 10 +++++ examples/interactive/cli-platform/Path.roc | 25 ++++++----- examples/interactive/cli-platform/src/lib.rs | 44 +++++++++++++++++++ examples/interactive/file.roc | 13 ++++-- 10 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 examples/interactive/cli-platform/Dir.roc create mode 100644 examples/interactive/cli-platform/Env.roc create mode 100644 examples/interactive/cli-platform/FileMetadata.roc create mode 100644 examples/interactive/cli-platform/InternalDir.roc diff --git a/examples/interactive/cli-platform/Dir.roc b/examples/interactive/cli-platform/Dir.roc new file mode 100644 index 0000000000..cf5a615815 --- /dev/null +++ b/examples/interactive/cli-platform/Dir.roc @@ -0,0 +1,25 @@ +interface Dir + exposes [ReadErr, DeleteErr, DirEntry, deleteEmptyDir, deleteRecursive, list] + imports [Effect, Task.{ Task }, InternalTask, Path.{ Path }, InternalPath, InternalDir] + +ReadErr : InternalDir.ReadErr + +DeleteErr : InternalDir.DeleteErr + +DirEntry : InternalDir.DirEntry + +## Lists the files and directories inside the directory. +list : Path -> Task (List Path) [DirReadErr Path ReadErr]* [Read [File]*]* +list = \path -> + effect = Effect.map (Effect.dirList (InternalPath.toBytes path)) \result -> + when result is + Ok entries -> Ok (List.map entries InternalPath.fromOsBytes) + Err err -> Err (DirReadErr path err) + + InternalTask.fromEffect effect + +## Deletes a directory if it's empty. +deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* + +## Recursively deletes the directory as well as all files and directories inside it. +deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* \ No newline at end of file diff --git a/examples/interactive/cli-platform/Effect.roc b/examples/interactive/cli-platform/Effect.roc index 92a6fc49ae..26505ddc07 100644 --- a/examples/interactive/cli-platform/Effect.roc +++ b/examples/interactive/cli-platform/Effect.roc @@ -6,6 +6,8 @@ hosted Effect always, forever, loop, + dirList, + cwd, stdoutLine, stderrLine, stdinLine, @@ -15,7 +17,7 @@ hosted Effect fileWriteUtf8, fileWriteBytes, ] - imports [InternalHttp.{ Request, Response }, InternalFile] + imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir] generates Effect with [after, map, always, forever, loop] stdoutLine : Str -> Effect {} @@ -26,5 +28,8 @@ fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr) fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr) fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr) fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr) +dirList : List U8 -> Effect (Result (List (List U8)) InternalDir.ReadErr) + +cwd : Effect (List U8) sendRequest : Box Request -> Effect Response diff --git a/examples/interactive/cli-platform/Env.roc b/examples/interactive/cli-platform/Env.roc new file mode 100644 index 0000000000..72a10e8d61 --- /dev/null +++ b/examples/interactive/cli-platform/Env.roc @@ -0,0 +1,15 @@ +interface Env + exposes [cwd] + imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask] + +## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory) +## from the environment. +cwd : Task Path [CwdUnavailable]* [Env]* +cwd = + effect = Effect.map Effect.cwd \bytes -> + if List.isEmpty bytes then + Err CwdUnavailable + else + Ok (InternalPath.fromArbitraryBytes bytes) + + InternalTask.fromEffect effect diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index 257218b082..4fd0b03e61 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -6,6 +6,8 @@ ReadErr : InternalFile.ReadErr WriteErr : InternalFile.WriteErr +## Encodes a value using the given `EncodingFormat` and writes it to a file. +## ## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON) ## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format ## to write some encodable data to a file as JSON, like so: @@ -31,7 +33,7 @@ write = \path, val, fmt -> # TODO handle encoding errors here, once they exist writeBytes path bytes -## Write bytes to a file. +## Writes bytes to a file. ## ## # Writes the bytes 1, 2, 3 to the file `myfile.dat`. ## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3] @@ -43,7 +45,7 @@ writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File writeBytes = \path, bytes -> toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes -## Write a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). +## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## ## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. ## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!" @@ -55,7 +57,7 @@ writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* writeUtf8 = \path, str -> toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str -## Delete a file from the filesystem. +## Deletes a file from the filesystem. ## ## # Deletes the file named ## File.delete (Path.fromStr "myfile.dat") [1, 2, 3] @@ -75,7 +77,7 @@ delete : Path -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* delete = \path -> toWriteTask path \bytes -> Effect.fileDelete bytes -## Read all the bytes in a file. +## Reads all the bytes in a file. ## ## # Read all the bytes in `myfile.txt`. ## File.readBytes (Path.fromStr "myfile.txt") @@ -87,7 +89,7 @@ readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]* readBytes = \path -> toReadTask path \bytes -> Effect.fileReadBytes bytes -## Read a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. +## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## ## # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`. ## File.readUtf8 (Path.fromStr "myfile.txt") diff --git a/examples/interactive/cli-platform/FileMetadata.roc b/examples/interactive/cli-platform/FileMetadata.roc new file mode 100644 index 0000000000..89386f259f --- /dev/null +++ b/examples/interactive/cli-platform/FileMetadata.roc @@ -0,0 +1,35 @@ +interface FileMetadata + exposes [FileMetadata, bytes, type, isReadonly, mode] + imports [] + +# Design note: this is an opaque type rather than a type alias so that +# we can add new operating system info if new OS releases introduce them, +# as a backwards-compatible change. + +FileMetadata := { + bytes : U64, + type : [File, Dir, Symlink], + isReadonly : Bool, + mode : [Unix U32, NonUnix], +} + +bytes : FileMetadata -> U64 +bytes = \@FileMetadata info -> info.bytes + +isReadonly : FileMetadata -> Bool +isReadonly = \@FileMetadata info -> info.isReadonly + +type : FileMetadata -> [File, Dir, Symlink] +type = \@FileMetadata info -> info.type + +mode : FileMetadata -> [Unix U32, NonUnix] +mode = \@FileMetadata info -> info.mode + +# TODO need to create a Time module and return something like Time.Utc here. +# lastModified : FileMetadata -> Utc + +# TODO need to create a Time module and return something like Time.Utc here. +# lastAccessed : FileMetadata -> Utc + +# TODO need to create a Time module and return something like Time.Utc here. +# created : FileMetadata -> Utc \ No newline at end of file diff --git a/examples/interactive/cli-platform/InternalDir.roc b/examples/interactive/cli-platform/InternalDir.roc new file mode 100644 index 0000000000..6163125e84 --- /dev/null +++ b/examples/interactive/cli-platform/InternalDir.roc @@ -0,0 +1,41 @@ +interface InternalDir + exposes [ReadErr, DeleteErr, DirEntry] + imports [FileMetadata.{ FileMetadata }, Path.{ Path }] + +DirEntry : { + path : Path, + type : [File, Dir, Symlink], + metadata : FileMetadata, +} + +ReadErr : [ + NotFound, + Interrupted, + InvalidFilename, + PermissionDenied, + TooManySymlinks, # aka FilesystemLoop + TooManyHardlinks, + TimedOut, + StaleNetworkFileHandle, + NotADirectory, + OutOfMemory, + Unsupported, + Unrecognized I32 Str, +] + +DeleteErr : [ + NotFound, + Interrupted, + InvalidFilename, + PermissionDenied, + TooManySymlinks, # aka FilesystemLoop + TooManyHardlinks, + TimedOut, + StaleNetworkFileHandle, + NotADirectory, + ReadOnlyFilesystem, + DirectoryNotEmpty, + OutOfMemory, + Unsupported, + Unrecognized I32 Str, +] diff --git a/examples/interactive/cli-platform/InternalPath.roc b/examples/interactive/cli-platform/InternalPath.roc index b27ff8e5e5..6731b1a92a 100644 --- a/examples/interactive/cli-platform/InternalPath.roc +++ b/examples/interactive/cli-platform/InternalPath.roc @@ -5,6 +5,8 @@ interface InternalPath wrap, unwrap, toBytes, + fromArbitraryBytes, + fromOsBytes, ] imports [] @@ -61,3 +63,11 @@ toBytes = \@InternalPath path -> FromOperatingSystem bytes -> bytes ArbitraryBytes bytes -> bytes FromStr str -> Str.toUtf8 str + +fromArbitraryBytes : List U8 -> InternalPath +fromArbitraryBytes = \bytes -> + @InternalPath (ArbitraryBytes bytes) + +fromOsBytes : List U8 -> InternalPath +fromOsBytes = \bytes -> + @InternalPath (FromOperatingSystem bytes) \ No newline at end of file diff --git a/examples/interactive/cli-platform/Path.roc b/examples/interactive/cli-platform/Path.roc index 12de18ee32..dad8f989f6 100644 --- a/examples/interactive/cli-platform/Path.roc +++ b/examples/interactive/cli-platform/Path.roc @@ -6,6 +6,7 @@ interface Path WindowsRoot, # toComponents, # walkComponents, + display, fromStr, fromBytes, withExtension, @@ -85,10 +86,10 @@ fromBytes = \bytes -> ## with the given [Charset]. (Use [Env.charset] to get the current system charset.) ## ## For a conversion to [Str] that is lossy but does not return a [Result], see -## [displayUtf8]. +## [display]. # toInner : Path -> [Str Str, Bytes (List U8)] ## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), -## and converts it to a string using [Str.displayUtf8]. +## and converts it to a string using [Str.display]. ## ## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens, ## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) @@ -103,17 +104,21 @@ fromBytes = \bytes -> ## Converting paths to strings can be an unreliable operation, because operating systems ## don't record the paths' encodings. This means it's possible for the path to have been ## encoded with a different character set than UTF-8 even if UTF-8 is the system default, -## which means when [displayUtf8] converts them to a string, the string may include gibberish. +## which means when [display] converts them to a string, the string may include gibberish. ## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863) ## ## If you happen to know the [Charset] that was used to encode the path, you can use -## [toStrUsingCharset] instead of [displayUtf8]. -# displayUtf8 : Path -> Str -# displayUtf8 = \path -> -# when InternalPath.unwrap path is -# FromStr str -> str -# FromOperatingSystem bytes | ArbitraryBytes bytes -> -# Str.displayUtf8 bytes +## [toStrUsingCharset] instead of [display]. +display : Path -> Str +display = \path -> + when InternalPath.unwrap path is + FromStr str -> str + FromOperatingSystem bytes | ArbitraryBytes bytes -> + when Str.fromUtf8 bytes is + Ok str -> str + # TODO: this should use the builtin Str.display to display invalid UTF-8 chars in just the right spots, but that does not exist yet! + Err _ -> "�" + # isEq : Path, Path -> Bool # isEq = \p1, p2 -> # when InternalPath.unwrap p1 is diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index 58b7a89cfc..d801fcd8da 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -238,6 +238,50 @@ pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList) -> RocResult<(), Rea } } +#[no_mangle] +pub extern "C" fn roc_fx_cwd() -> RocList { + // TODO instead, call getcwd on UNIX and GetCurrentDirectory on Windows + match std::env::current_dir() { + Ok(path_buf) => os_str_to_roc_path(path_buf.into_os_string().as_os_str()), + Err(_) => { + // Default to empty path + RocList::empty() + } + } +} + +#[no_mangle] +pub extern "C" fn roc_fx_dirList( + // TODO: this RocResult should use Dir.WriteErr - but right now it's File.WriteErr + // because glue doesn't have Dir.WriteErr yet. + roc_path: &RocList, +) -> RocResult>, WriteErr> { + println!("Dir.list..."); + match std::fs::read_dir(path_from_roc_path(roc_path)) { + Ok(dir_entries) => RocResult::ok( + dir_entries + .map(|opt_dir_entry| match opt_dir_entry { + Ok(entry) => os_str_to_roc_path(entry.path().into_os_string().as_os_str()), + Err(_) => { + todo!("handle dir_entry path didn't resolve") + } + }) + .collect::>>(), + ), + Err(_) => { + todo!("handle Dir.list error"); + } + } +} + +#[cfg(target_family = "unix")] +/// TODO convert from EncodeWide to RocPath on Windows +fn os_str_to_roc_path(os_str: &OsStr) -> RocList { + use std::os::unix::ffi::OsStrExt; + + RocList::from(os_str.as_bytes()) +} + #[no_mangle] pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response { let mut builder = reqwest::blocking::ClientBuilder::new(); diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index e6f9b3a3fd..d491ec9477 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -1,12 +1,18 @@ app "file-io" packages { pf: "cli-platform/main.roc" } - imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path] + imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path, pf.Env, pf.Dir] provides [main] to pf -main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File]] +main : Task.Task {} [] [Write [File, Stdout, Stderr], Read [File], Env] main = path = Path.fromStr "out.txt" task = + cwd <- Env.cwd |> Task.await + cwdStr = Path.display cwd + _ <- Stdout.line "cwd: \(cwdStr)" |> Task.await + dirEntries <- Dir.list cwd |> Task.await + contentsStr = Str.joinWith (List.map dirEntries Path.display) "\n " + _ <- Stdout.line "Directory contents:\n \(contentsStr)\n" |> Task.await _ <- Stdout.line "Writing a string to out.txt" |> Task.await _ <- File.writeUtf8 path "a string!" |> Task.await contents <- File.readUtf8 path |> Task.await @@ -18,4 +24,5 @@ main = Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported" Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)" Err (FileReadErr _ _) -> Stderr.line "Error reading file" - _ -> Stdout.line "Successfully wrote a string to out.txt" + Err _ -> Stderr.line "Uh oh, there was an error!" + Ok _ -> Stdout.line "Successfully wrote a string to out.txt" From 75a0eeacc7c1ef274d3c1210e86745006ec3c586 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:26:30 -0400 Subject: [PATCH 16/26] Don't hardcode "roc/builtins 1.0.0" in docs --- crates/docs/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index 9e2a95f030..b74ae10d45 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -29,8 +29,8 @@ pub fn generate_docs_html(filenames: Vec) { // TODO: get info from a package module; this is all hardcoded for now. let mut package = roc_load::docs::Documentation { - name: "roc/builtins".to_string(), - version: "1.0.0".to_string(), + name: "documentation".to_string(), + version: "".to_string(), docs: "Package introduction or README.".to_string(), modules: loaded_modules, }; From 0a3a9584c478556491401dc08182c3d897db96c5 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:26:48 -0400 Subject: [PATCH 17/26] Don't put spaces in types in docs --- crates/docs/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index b74ae10d45..6b39b539f5 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -502,8 +502,6 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ for (index, tag) in tags.iter().enumerate() { if is_multiline { indent(buf, next_indent_level); - } else { - buf.push(' '); } buf.push_str(tag.name.as_str()); @@ -524,8 +522,6 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ if is_multiline { indent(buf, tag_union_indent); - } else { - buf.push(' '); } buf.push(']'); From 791340e2ae3384c3b09f310dd73068fdedcc25ef Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:32:15 -0400 Subject: [PATCH 18/26] Don't have docs print unnecessary parens or spaces --- crates/docs/src/lib.rs | 224 ++++++++++++++++++++++------------------- 1 file changed, 121 insertions(+), 103 deletions(-) diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index 6b39b539f5..5dc681bdd1 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -239,7 +239,7 @@ fn render_module_documentation( } } - type_annotation_to_html(0, &mut content, type_ann); + type_annotation_to_html(0, &mut content, type_ann, false); buf.push_str( html_to_string( @@ -477,56 +477,65 @@ fn new_line(buf: &mut String) { } // html is written to buf -fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &TypeAnnotation) { +fn type_annotation_to_html( + indent_level: usize, + buf: &mut String, + type_ann: &TypeAnnotation, + needs_parens: bool, +) { let is_multiline = should_be_multiline(type_ann); match type_ann { TypeAnnotation::TagUnion { tags, extension } => { - let tags_len = tags.len(); + if tags.is_empty() { + buf.push_str("[]"); + } else { + let tags_len = tags.len(); - let tag_union_indent = indent_level + 1; - - if is_multiline { - new_line(buf); - - indent(buf, tag_union_indent); - } - - buf.push('['); - - if is_multiline { - new_line(buf); - } - - let next_indent_level = tag_union_indent + 1; - - for (index, tag) in tags.iter().enumerate() { - if is_multiline { - indent(buf, next_indent_level); - } - - buf.push_str(tag.name.as_str()); - - for type_value in &tag.values { - buf.push(' '); - type_annotation_to_html(next_indent_level, buf, type_value); - } + let tag_union_indent = indent_level + 1; if is_multiline { - if index < (tags_len - 1) { - buf.push(','); - } + new_line(buf); + indent(buf, tag_union_indent); + } + + buf.push('['); + + if is_multiline { new_line(buf); } + + let next_indent_level = tag_union_indent + 1; + + for (index, tag) in tags.iter().enumerate() { + if is_multiline { + indent(buf, next_indent_level); + } + + buf.push_str(tag.name.as_str()); + + for type_value in &tag.values { + buf.push(' '); + type_annotation_to_html(next_indent_level, buf, type_value, true); + } + + if is_multiline { + if index < (tags_len - 1) { + buf.push(','); + } + + new_line(buf); + } + } + + if is_multiline { + indent(buf, tag_union_indent); + } + + buf.push(']'); } - if is_multiline { - indent(buf, tag_union_indent); - } - - buf.push(']'); - - type_annotation_to_html(indent_level, buf, extension); + type_annotation_to_html(indent_level, buf, extension, true); } TypeAnnotation::BoundVariable(var_name) => { buf.push_str(var_name); @@ -535,82 +544,91 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ if parts.is_empty() { buf.push_str(name); } else { - buf.push('('); + if needs_parens { + buf.push('('); + } + buf.push_str(name); for part in parts { buf.push(' '); - type_annotation_to_html(indent_level, buf, part); + type_annotation_to_html(indent_level, buf, part, true); + } + + if needs_parens { + buf.push(')'); } - buf.push(')'); } } TypeAnnotation::Record { fields, extension } => { - let fields_len = fields.len(); + if fields.is_empty() { + buf.push_str("{}"); + } else { + let fields_len = fields.len(); + let record_indent = indent_level + 1; - let record_indent = indent_level + 1; - - if is_multiline { - new_line(buf); - indent(buf, record_indent); - } - - buf.push('{'); - - if is_multiline { - new_line(buf); - } - - let next_indent_level = record_indent + 1; - - for (index, field) in fields.iter().enumerate() { if is_multiline { - indent(buf, next_indent_level); + new_line(buf); + indent(buf, record_indent); + } + + buf.push('{'); + + if is_multiline { + new_line(buf); + } + + let next_indent_level = record_indent + 1; + + for (index, field) in fields.iter().enumerate() { + if is_multiline { + indent(buf, next_indent_level); + } else { + buf.push(' '); + } + + let fields_name = match field { + RecordField::RecordField { name, .. } => name, + RecordField::OptionalField { name, .. } => name, + RecordField::LabelOnly { name } => name, + }; + + buf.push_str(fields_name.as_str()); + + match field { + RecordField::RecordField { + type_annotation, .. + } => { + buf.push_str(" : "); + type_annotation_to_html(next_indent_level, buf, type_annotation, false); + } + RecordField::OptionalField { + type_annotation, .. + } => { + buf.push_str(" ? "); + type_annotation_to_html(next_indent_level, buf, type_annotation, false); + } + RecordField::LabelOnly { .. } => {} + } + + if is_multiline { + if index < (fields_len - 1) { + buf.push(','); + } + + new_line(buf); + } + } + + if is_multiline { + indent(buf, record_indent); } else { buf.push(' '); } - let fields_name = match field { - RecordField::RecordField { name, .. } => name, - RecordField::OptionalField { name, .. } => name, - RecordField::LabelOnly { name } => name, - }; - - buf.push_str(fields_name.as_str()); - - match field { - RecordField::RecordField { - type_annotation, .. - } => { - buf.push_str(" : "); - type_annotation_to_html(next_indent_level, buf, type_annotation); - } - RecordField::OptionalField { - type_annotation, .. - } => { - buf.push_str(" ? "); - type_annotation_to_html(next_indent_level, buf, type_annotation); - } - RecordField::LabelOnly { .. } => {} - } - - if is_multiline { - if index < (fields_len - 1) { - buf.push(','); - } - - new_line(buf); - } + buf.push('}'); } - if is_multiline { - indent(buf, record_indent); - } else { - buf.push(' '); - } - - buf.push('}'); - - type_annotation_to_html(indent_level, buf, extension); + type_annotation_to_html(indent_level, buf, extension, true); } TypeAnnotation::Function { args, output } => { let mut peekable_args = args.iter().peekable(); @@ -622,7 +640,7 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ indent(buf, indent_level + 1); } - type_annotation_to_html(indent_level, buf, arg); + type_annotation_to_html(indent_level, buf, arg, false); if peekable_args.peek().is_some() { buf.push_str(", "); @@ -642,7 +660,7 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ next_indent_level += 1; } - type_annotation_to_html(next_indent_level, buf, output); + type_annotation_to_html(next_indent_level, buf, output, false); } TypeAnnotation::Ability { members: _ } => { // TODO(abilities): fill me in From 33ca6147c9802fd6e8a2457aeb8ea50fad08977f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:21:19 -0400 Subject: [PATCH 19/26] Fix CLI platform docs --- examples/interactive/cli-platform/Path.roc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/interactive/cli-platform/Path.roc b/examples/interactive/cli-platform/Path.roc index dad8f989f6..7a9453df5e 100644 --- a/examples/interactive/cli-platform/Path.roc +++ b/examples/interactive/cli-platform/Path.roc @@ -83,13 +83,13 @@ fromBytes = \bytes -> ## have been encoded with the same charset as the operating system's curent locale (which ## typically does not change after it is set during installation of the OS), so ## this should convert a [Path] to a valid string as long as the path was created -## with the given [Charset]. (Use [Env.charset] to get the current system charset.) +## with the given `Charset`. (Use `Env.charset` to get the current system charset.) ## ## For a conversion to [Str] that is lossy but does not return a [Result], see ## [display]. # toInner : Path -> [Str Str, Bytes (List U8)] ## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), -## and converts it to a string using [Str.display]. +## and converts it to a string using `Str.display`. ## ## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens, ## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) @@ -107,8 +107,8 @@ fromBytes = \bytes -> ## which means when [display] converts them to a string, the string may include gibberish. ## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863) ## -## If you happen to know the [Charset] that was used to encode the path, you can use -## [toStrUsingCharset] instead of [display]. +## If you happen to know the `Charset` that was used to encode the path, you can use +## `toStrUsingCharset` instead of [display]. display : Path -> Str display = \path -> when InternalPath.unwrap path is From 3f467459f94034e567a371bd2cb97980a94ab764 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 20:15:46 -0400 Subject: [PATCH 20/26] roc format --- examples/interactive/cli-platform/Dir.roc | 2 +- examples/interactive/cli-platform/File.roc | 1 - examples/interactive/cli-platform/FileMetadata.roc | 5 +---- examples/interactive/cli-platform/InternalPath.roc | 2 +- examples/interactive/file.roc | 2 ++ 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/interactive/cli-platform/Dir.roc b/examples/interactive/cli-platform/Dir.roc index cf5a615815..6b657600b3 100644 --- a/examples/interactive/cli-platform/Dir.roc +++ b/examples/interactive/cli-platform/Dir.roc @@ -22,4 +22,4 @@ list = \path -> deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* ## Recursively deletes the directory as well as all files and directories inside it. -deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* \ No newline at end of file +deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]* [Write [File]*]* diff --git a/examples/interactive/cli-platform/File.roc b/examples/interactive/cli-platform/File.roc index 4fd0b03e61..de0181ecce 100644 --- a/examples/interactive/cli-platform/File.roc +++ b/examples/interactive/cli-platform/File.roc @@ -132,7 +132,6 @@ readUtf8 = \path -> # Err decodingErr -> Err (FileReadDecodeErr decodingErr) # Err readErr -> Err (FileReadErr readErr) # InternalTask.fromEffect effect - toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]* [Write [File]*]* toWriteTask = \path, toEffect -> InternalPath.toBytes path diff --git a/examples/interactive/cli-platform/FileMetadata.roc b/examples/interactive/cli-platform/FileMetadata.roc index 89386f259f..4c071e6fa4 100644 --- a/examples/interactive/cli-platform/FileMetadata.roc +++ b/examples/interactive/cli-platform/FileMetadata.roc @@ -5,7 +5,6 @@ interface FileMetadata # Design note: this is an opaque type rather than a type alias so that # we can add new operating system info if new OS releases introduce them, # as a backwards-compatible change. - FileMetadata := { bytes : U64, type : [File, Dir, Symlink], @@ -27,9 +26,7 @@ mode = \@FileMetadata info -> info.mode # TODO need to create a Time module and return something like Time.Utc here. # lastModified : FileMetadata -> Utc - # TODO need to create a Time module and return something like Time.Utc here. # lastAccessed : FileMetadata -> Utc - # TODO need to create a Time module and return something like Time.Utc here. -# created : FileMetadata -> Utc \ No newline at end of file +# created : FileMetadata -> Utc diff --git a/examples/interactive/cli-platform/InternalPath.roc b/examples/interactive/cli-platform/InternalPath.roc index 6731b1a92a..10ebe7f0e5 100644 --- a/examples/interactive/cli-platform/InternalPath.roc +++ b/examples/interactive/cli-platform/InternalPath.roc @@ -70,4 +70,4 @@ fromArbitraryBytes = \bytes -> fromOsBytes : List U8 -> InternalPath fromOsBytes = \bytes -> - @InternalPath (FromOperatingSystem bytes) \ No newline at end of file + @InternalPath (FromOperatingSystem bytes) diff --git a/examples/interactive/file.roc b/examples/interactive/file.roc index d491ec9477..c022f2e1f9 100644 --- a/examples/interactive/file.roc +++ b/examples/interactive/file.roc @@ -9,9 +9,11 @@ main = task = cwd <- Env.cwd |> Task.await cwdStr = Path.display cwd + _ <- Stdout.line "cwd: \(cwdStr)" |> Task.await dirEntries <- Dir.list cwd |> Task.await contentsStr = Str.joinWith (List.map dirEntries Path.display) "\n " + _ <- Stdout.line "Directory contents:\n \(contentsStr)\n" |> Task.await _ <- Stdout.line "Writing a string to out.txt" |> Task.await _ <- File.writeUtf8 path "a string!" |> Task.await From c5b6aef21ab1505cfefac0f9704075b7c994bbd7 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Mon, 12 Sep 2022 00:42:13 -0600 Subject: [PATCH 21/26] Add `Str.replaceFirst` builtin --- crates/compiler/builtins/roc/Str.roc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 805da5407c..da19e71ac5 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -33,6 +33,7 @@ interface Str toU8, toI8, toScalars, + replaceFirst, splitFirst, splitLast, walkUtf8WithIndex, @@ -276,6 +277,17 @@ countUtf8Bytes : Str -> Nat ## string slice that does not do bounds checking or utf-8 verification substringUnsafe : Str, Nat, Nat -> Str +## Returns the string with the first occurrence of a substring replaced with a replacement. +## If the substring is not found, returns `Err NotFound`. +## +## Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz" +replaceFirst : Str, Str, Str -> Result Str [NotFound]* +replaceFirst = \haystack, needle, flower -> + when splitFirst haystack needle is + Ok { before, after } -> + "\(before)\(flower)\(after)" + err -> err + ## Returns the string before the first occurrence of a delimiter, as well as the ## rest of the string after that occurrence. If the delimiter is not found, returns `Err`. ## From 2b65659a111b63ec51c1bcd3b0d099fbc4adc8b1 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Mon, 12 Sep 2022 00:42:38 -0600 Subject: [PATCH 22/26] Add `Str.replaceLast` builtin --- crates/compiler/builtins/roc/Str.roc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index da19e71ac5..7764cb2724 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -34,6 +34,7 @@ interface Str toI8, toScalars, replaceFirst, + replaceLast, splitFirst, splitLast, walkUtf8WithIndex, @@ -288,6 +289,17 @@ replaceFirst = \haystack, needle, flower -> "\(before)\(flower)\(after)" err -> err +## Returns the string with the last occurrence of a substring replaced with a replacement. +## If the substring is not found, returns `Err NotFound`. +## +## Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz" +replaceLast : Str, Str, Str -> Result Str [NotFound]* +replaceLast = \haystack, needle, flower -> + when splitLast haystack needle is + Ok { before, after } -> + "\(before)\(flower)\(after)" + err -> err + ## Returns the string before the first occurrence of a delimiter, as well as the ## rest of the string after that occurrence. If the delimiter is not found, returns `Err`. ## From 9ef57f86ac1a4a9121331953a15801f37dd28066 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Mon, 12 Sep 2022 00:43:12 -0600 Subject: [PATCH 23/26] Add first draft of `Str.replaceEach` builtin --- crates/compiler/builtins/roc/Str.roc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 7764cb2724..61ff5bc046 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -33,6 +33,7 @@ interface Str toU8, toI8, toScalars, + replaceEach, replaceFirst, replaceLast, splitFirst, @@ -278,6 +279,19 @@ countUtf8Bytes : Str -> Nat ## string slice that does not do bounds checking or utf-8 verification substringUnsafe : Str, Nat, Nat -> Str +## Returns the string with each occurrence of a substring replaced with a replacement. +## If the substring is not found, returns `Err NotFound`. +## +## Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz" +replaceEach : Str, Str, Str -> Result Str [NotFound]* +replaceEach = \haystack, needle, flower -> + when replaceFirst haystack needle flower is + Ok progress -> + when replaceEach progress needle flower is + Ok finished -> finished + Err -> progress + err -> err + ## Returns the string with the first occurrence of a substring replaced with a replacement. ## If the substring is not found, returns `Err NotFound`. ## From c64874937f905671972d064c85291e47d89f69f5 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 22:24:20 -0400 Subject: [PATCH 24/26] Add entries for Str.replace___ functions to Symbol --- crates/compiler/module/src/symbol.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 79cd220c37..f6d5560f24 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1243,6 +1243,9 @@ define_builtins! { 47 STR_TO_NUM: "strToNum" 48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel" 49 STR_CAPACITY: "capacity" + 50 STR_REPLACE_EACH: "replaceEach" + 51 STR_REPLACE_FIRST: "replaceFirst" + 52 STR_REPLACE_LAST: "replaceLast" } 6 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias From 4ed63c548b03bdecd7e1aac6510bba05d761fed9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 22:30:02 -0400 Subject: [PATCH 25/26] Fix type mismatches in Str.replace___ functions ...and fix/finish the draft `Str.replaceEach` implementation! Don't be so modest, Richard :) --- crates/compiler/builtins/roc/Str.roc | 38 ++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 61ff5bc046..81656d09b7 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -285,12 +285,28 @@ substringUnsafe : Str, Nat, Nat -> Str ## Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz" replaceEach : Str, Str, Str -> Result Str [NotFound]* replaceEach = \haystack, needle, flower -> - when replaceFirst haystack needle flower is - Ok progress -> - when replaceEach progress needle flower is - Ok finished -> finished - Err -> progress - err -> err + when splitFirst haystack needle is + Ok { before, after } -> + # We found at least one needle, so start the buffer off with + # `before` followed by the first replacement flower. + Str.reserve "" (Str.countUtf8Bytes haystack) + |> Str.concat before + |> Str.concat flower + |> replaceEachHelp after needle flower + |> Ok + + Err err -> Err err + +replaceEachHelp : Str, Str, Str, Str -> Str +replaceEachHelp = \buf, haystack, needle, flower -> + when splitFirst haystack needle is + Ok { before, after } -> + buf + |> Str.concat before + |> Str.concat flower + |> replaceEachHelp after needle flower + + Err NotFound -> Str.concat buf haystack ## Returns the string with the first occurrence of a substring replaced with a replacement. ## If the substring is not found, returns `Err NotFound`. @@ -300,8 +316,9 @@ replaceFirst : Str, Str, Str -> Result Str [NotFound]* replaceFirst = \haystack, needle, flower -> when splitFirst haystack needle is Ok { before, after } -> - "\(before)\(flower)\(after)" - err -> err + Ok "\(before)\(flower)\(after)" + + Err err -> Err err ## Returns the string with the last occurrence of a substring replaced with a replacement. ## If the substring is not found, returns `Err NotFound`. @@ -311,8 +328,9 @@ replaceLast : Str, Str, Str -> Result Str [NotFound]* replaceLast = \haystack, needle, flower -> when splitLast haystack needle is Ok { before, after } -> - "\(before)\(flower)\(after)" - err -> err + Ok "\(before)\(flower)\(after)" + + Err err -> Err err ## Returns the string before the first occurrence of a delimiter, as well as the ## rest of the string after that occurrence. If the delimiter is not found, returns `Err`. From 620a3feb750c605d523c1f210123e457361234e7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 12 Sep 2022 22:49:44 -0400 Subject: [PATCH 26/26] Add some tests for Str.replace___ functions --- crates/compiler/builtins/roc/Str.roc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 81656d09b7..f595d56250 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -308,6 +308,8 @@ replaceEachHelp = \buf, haystack, needle, flower -> Err NotFound -> Str.concat buf haystack +expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi" + ## Returns the string with the first occurrence of a substring replaced with a replacement. ## If the substring is not found, returns `Err NotFound`. ## @@ -320,6 +322,8 @@ replaceFirst = \haystack, needle, flower -> Err err -> Err err +expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi" + ## Returns the string with the last occurrence of a substring replaced with a replacement. ## If the substring is not found, returns `Err NotFound`. ## @@ -332,6 +336,8 @@ replaceLast = \haystack, needle, flower -> Err err -> Err err +expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi" + ## Returns the string before the first occurrence of a delimiter, as well as the ## rest of the string after that occurrence. If the delimiter is not found, returns `Err`. ##