diff --git a/.github/workflows/nightly_macos_x86_64.yml b/.github/workflows/nightly_macos_x86_64.yml index 413538587a..48eb4249c8 100644 --- a/.github/workflows/nightly_macos_x86_64.yml +++ b/.github/workflows/nightly_macos_x86_64.yml @@ -6,7 +6,7 @@ name: Nightly Release macOS x86_64 env: ZIG_VERSION: 0.9.1 - LLVM_SYS_130_PREFIX: /usr/local/opt/llvm + LLVM_SYS_130_PREFIX: /usr/local/opt/llvm@13 jobs: test-build-upload: diff --git a/.github/workflows/test_nightly_macos_apple_silicon.yml b/.github/workflows/test_nightly_macos_apple_silicon.yml index 3f2dc315cf..10ec5dbcde 100644 --- a/.github/workflows/test_nightly_macos_apple_silicon.yml +++ b/.github/workflows/test_nightly_macos_apple_silicon.yml @@ -28,7 +28,7 @@ jobs: run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf - name: test roc hello world - run: ./roc examples/hello-world/main.roc + run: ./roc examples/helloWorld.roc diff --git a/.github/workflows/test_nightly_many_os.yml b/.github/workflows/test_nightly_many_os.yml index 20002356c3..6bd5a4e5e8 100644 --- a/.github/workflows/test_nightly_many_os.yml +++ b/.github/workflows/test_nightly_many_os.yml @@ -41,7 +41,7 @@ jobs: run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf - name: test roc hello world - run: ./roc examples/hello-world/main.roc + run: ./roc examples/helloWorld.roc diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 39c7104cf4..e2b5963c54 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -28,13 +28,7 @@ sh <(curl -L https://nixos.org/nix/install) --no-daemon sh <(curl -L https://nixos.org/nix/install) --daemon ``` -Open a new terminal and install nixFlakes in your environment: - -```sh -nix-env -iA nixpkgs.nixFlakes -``` - -Edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add: +Open a new terminal and edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add: ```text experimental-features = nix-command flakes diff --git a/Cargo.lock b/Cargo.lock index 15800eb10a..71d01961c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,15 +75,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "approx" version = "0.4.0" @@ -253,34 +244,13 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - [[package]] name = "block-buffer" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "generic-array", ] [[package]] @@ -301,12 +271,6 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "bytecheck" version = "0.6.8" @@ -620,10 +584,10 @@ dependencies = [ [[package]] name = "confy" -version = "0.4.0" -source = "git+https://github.com/rust-cli/confy#c6b62039281b8643539b436440bcea1b0d634bc7" +version = "0.5.0" +source = "git+https://github.com/rust-cli/confy#fd069f062aa3373c846f0d8c6e3b5e2a5cd0096b" dependencies = [ - "directories-next", + "directories", "serde", "serde_yaml", "thiserror", @@ -1023,7 +987,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" dependencies = [ - "generic-array 0.14.5", + "generic-array", "typenum", ] @@ -1129,22 +1093,13 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer", "crypto-common", ] @@ -1160,13 +1115,13 @@ dependencies = [ ] [[package]] -name = "directories-next" -version = "2.0.0" +name = "directories" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", + "cfg-if 0.1.10", + "dirs-sys", ] [[package]] @@ -1179,6 +1134,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1231,9 +1197,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dunce" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "dynasm" @@ -1367,12 +1333,6 @@ dependencies = [ "str-buf", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1564,15 +1524,6 @@ dependencies = [ "cfg-if 0.1.10", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.5" @@ -2438,6 +2389,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09f1f8e5676e1a1f2ee8b21f38238e1243c827531c9435624c7bfb305102cee4" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2606,12 +2567,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "ordered-float" version = "3.0.0" @@ -2636,6 +2591,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owned_ttf_parser" version = "0.15.0" @@ -2800,9 +2761,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "502b62a6d0245378b04ffe0a7fb4f4419a4815fce813bd8a0ec89a56e07d67b1" dependencies = [ "pest", "pest_generator", @@ -2810,9 +2771,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "451e629bf49b750254da26132f1a5a9d11fd8a95a3df51d15c4abd1ba154cb6c" dependencies = [ "pest", "pest_meta", @@ -2823,13 +2784,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "bcec162c71c45e269dfc3fc2916eaeb97feab22993a21bcce4721d08cd7801a6" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1", + "sha1 0.10.4", ] [[package]] @@ -3467,8 +3428,6 @@ dependencies = [ "roc_tracing", "serial_test", "signal-hook", - "strum", - "strum_macros", "target-lexicon", "tempfile", "ven_pretty", @@ -4450,18 +4409,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug", -] - [[package]] name = "sha1" version = "0.6.1" @@ -4471,6 +4418,17 @@ dependencies = [ "sha1_smol", ] +[[package]] +name = "sha1" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + [[package]] name = "sha1_smol" version = "1.0.0" @@ -4485,7 +4443,7 @@ checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest", ] [[package]] @@ -4728,7 +4686,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha1", + "sha1 0.6.1", "syn", ] @@ -4767,9 +4725,9 @@ checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", "proc-macro2", @@ -5106,9 +5064,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -5127,12 +5085,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -5155,7 +5113,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "rand", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index e1ce6b2323..ac055aca3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,9 +57,10 @@ members = [ "crates/wasi-libc-sys", ] exclude = [ - # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those. - "examples", "ci/bench-runner", + # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those. + "crates/cli_testing_examples", + "examples", # Ignore building these normally. They are only imported by tests. # The tests will still correctly build them. "crates/cli_utils", diff --git a/Earthfile b/Earthfile index d67866328b..7c59196695 100644 --- a/Earthfile +++ b/Earthfile @@ -50,7 +50,7 @@ install-zig-llvm-valgrind: copy-dirs: FROM +install-zig-llvm-valgrind - COPY --dir crates examples Cargo.toml Cargo.lock version.txt www ./ + COPY --dir crates Cargo.toml Cargo.lock version.txt www ./ # compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. prep-bench-folder: @@ -60,11 +60,11 @@ prep-bench-folder: ARG BENCH_SUFFIX=branch RUN cargo criterion -V RUN --mount=type=cache,target=$SCCACHE_DIR cd crates/cli && cargo criterion --no-run + RUN mkdir -p bench-folder/crates/cli_testing_examples/benchmarks RUN mkdir -p bench-folder/crates/compiler/builtins/bitcode/src RUN mkdir -p bench-folder/target/release/deps - RUN mkdir -p bench-folder/examples/benchmarks - RUN cp examples/benchmarks/*.roc bench-folder/examples/benchmarks/ - RUN cp -r examples/benchmarks/platform bench-folder/examples/benchmarks/ + RUN cp crates/cli_testing_examples/benchmarks/*.roc bench-folder/crates/cli_testing_examples/benchmarks/ + RUN cp -r crates/cli_testing_examples/benchmarks/platform bench-folder/crates/cli_testing_examples/benchmarks/ RUN cp crates/compiler/builtins/bitcode/src/str.zig bench-folder/crates/compiler/builtins/bitcode/src RUN cp target/release/roc bench-folder/target/release # copy the most recent time bench to bench-folder diff --git a/TUTORIAL.md b/TUTORIAL.md index 83056417f0..7ad755b7bf 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -115,7 +115,7 @@ Create a new file called `Hello.roc` and put this inside it: ```coffee app "hello" - packages { pf: "examples/interactive/cli-platform/main.roc" } + packages { pf: "examples/cli/cli-platform/main.roc" } imports [pf.Stdout, pf.Program] provides [main] to pf @@ -124,8 +124,8 @@ main = Stdout.line "I'm a Roc application!" |> Program.quick > **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc > source code. If you'd like to put it somewhere else, you'll need to replace -> `"examples/interactive/cli-platform/main.roc"` with the path to the -> `examples/interactive/cli-platform/main.roc` file in that source code. In the future, +> `"examples/cli/cli-platform/main.roc"` with the path to the +> `examples/cli/cli-platform/main.roc` file in that source code. In the future, > Roc will have the tutorial built in, and this aside will no longer be > necessary! @@ -1273,7 +1273,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`: ```coffee app "hello" - packages { pf: "examples/interactive/cli-platform/main.roc" } + packages { pf: "examples/cli/cli-platform/main.roc" } imports [pf.Stdout, pf.Program] provides main to pf ``` @@ -1291,14 +1291,14 @@ without running it by running `roc build Hello.roc`. The remaining lines all involve the *platform* this application is built on: ```coffee -packages { pf: "examples/interactive/cli-platform/main.roc" } +packages { pf: "examples/cli/cli-platform/main.roc" } imports [pf.Stdout, pf.Program] provides main to pf ``` -The `packages { pf: "examples/interactive/cli-platform/main.roc" }` part says two things: +The `packages { pf: "examples/cli/cli-platform/main.roc" }` part says two things: -- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform/main.roc"` +- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/cli-platform/main.roc"` - We're going to name that package `pf` so we can refer to it more concisely in the future. The `imports [pf.Stdout, pf.Program]` line says that we want to import the `Stdout` and `Program` modules @@ -1320,16 +1320,16 @@ which effectively makes it a simple Roc program. When we write `imports [pf.Stdout, pf.Program]`, it specifies that the `Stdout` and `Program` modules come from the `pf` package. -Since `pf` was the name we chose for the `examples/interactive/cli-platform/main.roc` -package (when we wrote `packages { pf: "examples/interactive/cli-platform/main.roc" }`), +Since `pf` was the name we chose for the `examples/cli/cli-platform/main.roc` +package (when we wrote `packages { pf: "examples/cli/cli-platform/main.roc" }`), this `imports` line tells the Roc compiler that when we call `Stdout.line`, it should look for that `line` function in the `Stdout` module of the -`examples/interactive/cli-platform/main.roc` package. +`examples/cli/cli-platform/main.roc` package. ## Tasks Tasks are technically not part of the Roc language, but they're very common in -platforms. Let's use the CLI platform in `examples/interactive/cli-platform/main.roc` as an example! +platforms. Let's use the CLI platform in `examples/cli/cli-platform/main.roc` as an example! In the CLI platform, we have four operations we can do: @@ -1344,7 +1344,7 @@ First, let's do a basic "Hello World" using the tutorial app. ```coffee app "cli-tutorial" - packages { pf: "examples/interactive/cli-platform/main.roc" } + packages { pf: "examples/cli/cli-platform/main.roc" } imports [pf.Stdout, pf.Program] provides [main] to pf @@ -1382,7 +1382,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai ```swift app "cli-tutorial" - packages { pf: "examples/interactive/cli-platform/main.roc" } + packages { pf: "examples/cli/cli-platform/main.roc" } imports [pf.Stdout, pf.Stdin, pf.Task, pf.Program] provides [main] to pf @@ -1434,7 +1434,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo ```haskell app "cli-tutorial" - packages { pf: "examples/interactive/cli-platform/main.roc" } + packages { pf: "examples/cli/cli-platform/main.roc" } imports [pf.Stdout, pf.Stdin, pf.Task.{ await }, pf.Program] provides [main] to pf diff --git a/ci/bench-runner/src/main.rs b/ci/bench-runner/src/main.rs index bb44247ea9..44d7e1c027 100644 --- a/ci/bench-runner/src/main.rs +++ b/ci/bench-runner/src/main.rs @@ -227,9 +227,10 @@ fn calc_hashes_for_folder(benches_path_str: &str) -> HashMap { } fn check_if_bench_executables_changed() -> bool { - let bench_folder_str = "/examples/benchmarks/"; + let bench_folder_str = "/crates/cli_testing_examples/benchmarks/"; let main_benches_path_str = [BENCH_FOLDER_MAIN, bench_folder_str].join(""); + let main_bench_hashes = calc_hashes_for_folder(&main_benches_path_str); let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join(""); diff --git a/ci/package_release.sh b/ci/package_release.sh index 6e17f0d150..4c03da969e 100755 --- a/ci/package_release.sh +++ b/ci/package_release.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash cp target/release/roc ./roc # to be able to exclude "target" later in the tar command cp -r target/release/lib ./lib -tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/hello-world crates/roc_std +tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/cli crates/roc_std diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d57b994a2f..ee7140fca0 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -100,8 +100,6 @@ indoc = "1.0.7" serial_test = "0.9.0" criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli_utils" } -strum = "0.24.0" -strum_macros = "0.24" once_cell = "1.14.0" parking_lot = "0.12" diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 75b379c2a6..ffd4466bf1 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -10,22 +10,16 @@ extern crate roc_module; #[cfg(test)] mod cli_run { use cli_utils::helpers::{ - example_file, examples_dir, extract_valgrind_errors, fixture_file, fixtures_dir, - known_bad_file, run_cmd, run_roc, run_with_valgrind, strip_colors, Out, ValgrindError, - ValgrindErrorXWhat, + extract_valgrind_errors, file_path_from_root, fixture_file, fixtures_dir, known_bad_file, + run_cmd, run_roc, run_with_valgrind, strip_colors, Out, ValgrindError, ValgrindErrorXWhat, }; use const_format::concatcp; use indoc::indoc; - use once_cell::sync::Lazy; - use parking_lot::{Mutex, RwLock}; use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN}; use roc_test_utils::assert_multiline_str_eq; use serial_test::serial; use std::iter; - use std::path::{Path, PathBuf}; - use std::sync::Once; - use strum::IntoEnumIterator; - use strum_macros::EnumIter; + use std::path::Path; const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE); const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER); @@ -34,30 +28,13 @@ mod cli_run { #[allow(dead_code)] const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET); - static BENCHMARKS_BUILD_PLATFORM: Once = Once::new(); - static POPULATED_EXAMPLE_LOCKS: Once = Once::new(); - - use std::collections::HashMap; - static EXAMPLE_PLATFORM_LOCKS: Lazy>>> = - once_cell::sync::Lazy::new(|| RwLock::new(HashMap::default())); - - fn populate_example_locks(examples: impl Iterator) { - let mut locks = EXAMPLE_PLATFORM_LOCKS.write(); - for example in examples { - locks.insert(example, Default::default()); - } - } - - #[derive(Debug, EnumIter)] + #[derive(Debug)] enum CliMode { - RocBuild, - RocRun, - Roc, + RocBuild, // buildOnly + RocRun, // buildAndRun + Roc, // buildAndRunIfNoErrors } - #[cfg(not(debug_assertions))] - use roc_collections::all::MutMap; - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] const TEST_LEGACY_LINKER: bool = true; @@ -82,7 +59,7 @@ mod cli_run { } #[derive(Debug, PartialEq, Eq)] - struct Example<'a> { + struct CliTest<'a> { filename: &'a str, executable_filename: &'a str, stdin: &'a [&'a str], @@ -93,7 +70,11 @@ mod cli_run { } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { - let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]); + let compile_out = run_roc( + [CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), + &[], + &[], + ); let err = compile_out.stdout.trim(); let err = strip_colors(err); @@ -105,7 +86,7 @@ mod cli_run { } fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { - let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]); + let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[], &[]); assert_eq!(out.status.success(), expects_success_exit_code); } @@ -114,15 +95,17 @@ mod cli_run { file: &'a Path, args: I, stdin: &[&str], - app_args: &[String], + roc_app_args: &[String], + env: &[(&str, &str)], ) -> Out { let compile_out = run_roc( // converting these all to String avoids lifetime issues args.into_iter() .map(|arg| arg.to_string()) .chain([file.to_str().unwrap().to_string(), "--".to_string()]) - .chain(app_args.iter().cloned()), + .chain(roc_app_args.iter().cloned()), stdin, + env, ); let ignorable = "🔨 Rebuilding platform...\n"; @@ -143,10 +126,11 @@ mod cli_run { stdin: &[&str], executable_filename: &str, flags: &[&str], - app_args: &[String], + roc_app_args: &[String], extra_env: &[(&str, &str)], expected_ending: &str, use_valgrind: bool, + test_many_cli_commands: bool, // buildOnly, buildAndRun and buildAndRunIfNoErrors ) { // valgrind does not yet support avx512 instructions, see #1963. // we can't enable this only when testing with valgrind because of host re-use between tests @@ -155,7 +139,13 @@ mod cli_run { std::env::set_var("NO_AVX512", "1"); } - for cli_mode in CliMode::iter() { + let cli_commands = if test_many_cli_commands { + vec![CliMode::RocBuild, CliMode::RocRun, CliMode::Roc] + } else { + vec![CliMode::Roc] + }; + + for cli_mode in cli_commands.iter() { let flags = { let mut vec = flags.to_vec(); @@ -166,7 +156,13 @@ mod cli_run { let out = match cli_mode { CliMode::RocBuild => { - run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], &[]); + run_roc_on( + file, + iter::once(CMD_BUILD).chain(flags.clone()), + &[], + &[], + &[], + ); if use_valgrind && ALLOW_VALGRIND { let mut valgrind_args = vec![file @@ -174,7 +170,7 @@ mod cli_run { .to_str() .unwrap() .to_string()]; - valgrind_args.extend(app_args.iter().cloned()); + valgrind_args.extend(roc_app_args.iter().cloned()); let (valgrind_out, raw_xml) = run_with_valgrind(stdin.iter().copied(), &valgrind_args); if valgrind_out.status.success() { @@ -216,32 +212,25 @@ mod cli_run { run_cmd( file.with_file_name(executable_filename).to_str().unwrap(), stdin.iter().copied(), - app_args, + roc_app_args, extra_env.iter().copied(), ) } } CliMode::Roc => { if !extra_env.is_empty() { - // TODO: environment is not currently forwarded by Roc to the target - // binary, so this would fail! + // TODO: `roc` and `roc dev` are currently buggy for `env.roc` 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, - ) + run_roc_on(file, flags.clone(), stdin, roc_app_args, extra_env) } + CliMode::RocRun => run_roc_on( + file, + iter::once(CMD_RUN).chain(flags.clone()), + stdin, + roc_app_args, + extra_env, + ), }; if !&out.stdout.ends_with(expected_ending) { @@ -261,773 +250,663 @@ mod cli_run { } } - #[cfg(feature = "wasm32-cli-run")] - fn check_wasm_output_with_stdin( - file: &Path, - stdin: &[&str], + // when you don't need args, stdin or extra_env + fn test_roc_app_slim( + dir_name: &str, + roc_filename: &str, executable_filename: &str, - flags: &[&str], - args: &[&Arg], expected_ending: &str, + use_valgrind: bool, ) { - assert!(input_paths.is_empty(), "Wasm does not support input files"); - let mut flags = flags.to_vec(); - flags.push(concatcp!(TARGET_FLAG, "=wasm32")); + test_roc_app( + dir_name, + roc_filename, + executable_filename, + &[], + &[], + &[], + expected_ending, + use_valgrind, + false, + ) + } - let compile_out = run_roc( - [CMD_BUILD, file.to_str().unwrap()] - .iter() - .chain(flags.as_slice()), - ); - if !compile_out.stderr.is_empty() { - panic!("{}", compile_out.stderr); + #[allow(clippy::too_many_arguments)] + fn test_roc_app( + dir_name: &str, + roc_filename: &str, + executable_filename: &str, + stdin: &[&str], + args: &[Arg], + extra_env: &[(&str, &str)], + expected_ending: &str, + use_valgrind: bool, + test_many_cli_commands: bool, // buildOnly, buildAndRun and buildAndRunIfNoErrors + ) { + let file_name = file_path_from_root(dir_name, roc_filename); + + let mut roc_app_args: Vec = vec![]; + for arg in args { + match arg { + Arg::ExamplePath(file) => { + roc_app_args.push( + file_path_from_root(dir_name, file) + .to_str() + .unwrap() + .to_string(), + ); + } + Arg::PlainText(arg) => { + roc_app_args.push(arg.to_string()); + } + } } - assert!(compile_out.status.success(), "bad status {:?}", compile_out); + // workaround for surgical linker issue, see PR #3990 + let mut custom_flags: Vec<&str> = vec![]; - let path = file.with_file_name(executable_filename); - let stdout = crate::run_with_wasmer(&path, stdin); + match executable_filename { + "form" | "hello-gui" | "breakout" | "ruby" => { + // Since these require things the build system often doesn't have + // (e.g. GUIs open a window, Ruby needs ruby installed, WASM needs a browser) + // we do `roc build` on them but don't run them. + run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[], &[]); + return; + } + "swiftui" | "rocLovesSwift" => { + if cfg!(not(target_os = "macos")) { + eprintln!( + "WARNING: skipping testing example {} because it only works on MacOS.", + roc_filename + ); + return; + } else { + run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[], &[]); + return; + } + } + "rocLovesWebAssembly" => { + // this is a web assembly example, but we don't test with JS at the moment + eprintln!( + "WARNING: skipping testing example {} because it only works in a browser!", + roc_filename + ); + return; + } + "args" => { + custom_flags = vec![LINKER_FLAG, "legacy"]; + } + _ => {} + } - if !stdout.ends_with(expected_ending) { - panic!( - "expected output to end with {:?} but instead got {:#?}", - expected_ending, stdout + // Check with and without optimizations + check_output_with_stdin( + &file_name, + stdin, + executable_filename, + &custom_flags, + &roc_app_args, + extra_env, + expected_ending, + use_valgrind, + test_many_cli_commands, + ); + + custom_flags.push(OPTIMIZE_FLAG); + // This is mostly because the false interpreter is still very slow - + // 25s for the cli tests is just not acceptable during development! + #[cfg(not(debug_assertions))] + check_output_with_stdin( + &file_name, + stdin, + executable_filename, + &custom_flags, + &roc_app_args, + extra_env, + expected_ending, + use_valgrind, + test_many_cli_commands, + ); + + // Also check with the legacy linker. + + if TEST_LEGACY_LINKER { + check_output_with_stdin( + &file_name, + stdin, + executable_filename, + &[LINKER_FLAG, "legacy"], + &roc_app_args, + extra_env, + expected_ending, + use_valgrind, + test_many_cli_commands, ); } } - /// This macro does two things. - /// - /// First, it generates and runs a separate test for each of the given - /// Example expressions. Each of these should test a particular .roc file - /// in the examples/ directory. - /// - /// Second, it generates an extra test which (non-recursively) traverses the - /// examples/ directory and verifies that each of the .roc files in there - /// has had a corresponding test generated in the previous step. This test - /// will fail if we ever add a new .roc file to examples/ and forget to - /// add a test for it here! - macro_rules! examples { - ($($test_name:ident:$name:expr => $example:expr,)+) => { - static EXAMPLE_NAMES: &[&str] = &[$($name,)+]; - - $( - #[test] - #[allow(non_snake_case)] - fn $test_name() { - POPULATED_EXAMPLE_LOCKS.call_once( || { - populate_example_locks(EXAMPLE_NAMES.iter().map(|name| examples_dir(name))) - }); - - let dir_name = $name; - let example = $example; - let example_dir = examples_dir(dir_name); - let file_name = example_file(dir_name, example.filename); - - let mut app_args: Vec = vec![]; - for arg in example.arguments { - match arg { - Arg::ExamplePath(file) => { - app_args.push(example_file(dir_name, file).to_str().unwrap().to_string()); - } - Arg::PlainText(arg) => { - app_args.push(arg.to_string()); - } - } - } - - // workaround for surgical linker issue, see PR #3990 - let mut custom_flags : Vec<&str> = vec![]; - - match example.executable_filename { - "form" | "hello-gui" | "breakout" | "ruby" => { - // Since these require things the build system often doesn't have - // (e.g. GUIs open a window, Ruby needs ruby installed, WASM needs a browser) - // we do `roc build` on them but don't run them. - run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[]); - return; - } - "swiftui" | "rocLovesSwift" => { - if cfg!(not(target_os = "macos")) { - eprintln!("WARNING: skipping testing example {} because it only works on MacOS.", example.filename); - return; - } else { - run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[]); - return; - } - } - "rocLovesWebAssembly" => { - // this is a web assembly example, but we don't test with JS at the moment - eprintln!("WARNING: skipping testing example {} because it only works in a browser!", example.filename); - return; - } - "args" => { - custom_flags = vec![LINKER_FLAG, "legacy"]; - } - _ => {} - } - - // To avoid concurrent examples tests overwriting produced host binaries, lock - // on the example's directory, so that only one example per directory runs at a - // time. - // NOTE: we are assuming that each example corresponds to one platform, under - // the subdirectory. This is not necessarily true, and moreover is too - // restrictive. To increase throughput we only need to lock the produced host - // file, however, it is not trivial to recover what that file is today (without - // enumerating all examples and their platforms). - let locks = EXAMPLE_PLATFORM_LOCKS.read(); - let _example_guard = locks.get(&example_dir).unwrap().lock(); - - // Check with and without optimizations - check_output_with_stdin( - &file_name, - example.stdin, - example.executable_filename, - &custom_flags, - &app_args, - example.env, - example.expected_ending, - example.use_valgrind, - ); - - custom_flags.push(OPTIMIZE_FLAG); - // This is mostly because the false interpreter is still very slow - - // 25s for the cli tests is just not acceptable during development! - #[cfg(not(debug_assertions))] - check_output_with_stdin( - &file_name, - example.stdin, - example.executable_filename, - &custom_flags, - &app_args, - example.env, - example.expected_ending, - example.use_valgrind, - ); - - // Also check with the legacy linker. - - if TEST_LEGACY_LINKER { - check_output_with_stdin( - &file_name, - example.stdin, - example.executable_filename, - &[LINKER_FLAG, "legacy"], - &app_args, - example.env, - example.expected_ending, - example.use_valgrind, - ); - } - } - )* - - #[test] - #[cfg(not(debug_assertions))] - fn all_examples_have_tests() { - let mut all_examples: MutMap<&str, Example<'_>> = MutMap::default(); - - $( - all_examples.insert($name, $example); - )* - - check_for_tests("../../examples", &mut all_examples); - } - } + #[test] + #[serial(cli_platform)] + fn hello_world() { + test_roc_app_slim( + "examples", + "helloWorld.roc", + "helloWorld", + "Hello, World!\n", + true, + ) } - // examples! macro format: - // - // "name-of-subdirectory-inside-examples-dir" => [ - // test_name_1: Example { - // ... - // }, - // test_name_2: Example { - // ... - // }, - // ] - examples! { - helloWorld:"hello-world" => Example { - filename: "main.roc", - executable_filename: "helloWorld", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"Hello, World!\n", - use_valgrind: true, - }, - platformSwitching:"platform-switching" => Example { - filename: "main.roc", - executable_filename: "rocLovesPlatforms", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"Which platform am I running on now?\n", - use_valgrind: true, - }, - // We exclude the C platforming switching example - // because the main platform switching example runs the c platform. - // If we don't a race condition leads to test flakiness. - // platformSwitchingC:"platform-switching" => Example { - // filename: "rocLovesC.roc", - // executable_filename: "rocLovesC", - // stdin: &[], - // arguments: &[], - // expected_ending:"Roc <3 C!\n", - // use_valgrind: true, - // }, - platformSwitchingRust:"platform-switching" => Example { - filename: "rocLovesRust.roc", - executable_filename: "rocLovesRust", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"Roc <3 Rust!\n", - use_valgrind: true, - }, - platformSwitchingSwift:"platform-switching" => Example { - filename: "rocLovesSwift.roc", - executable_filename: "rocLovesSwift", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"Roc <3 Swift!\n", - use_valgrind: true, - }, - platformSwitchingWebAssembly:"platform-switching" => Example { - filename: "rocLovesWebAssembly.roc", - executable_filename: "rocLovesWebAssembly", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"Roc <3 Web Assembly!\n", - use_valgrind: true, - }, - platformSwitchingZig:"platform-switching" => Example { - filename: "rocLovesZig.roc", - executable_filename: "rocLovesZig", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"Roc <3 Zig!\n", - use_valgrind: true, - }, - ruby:"ruby-interop" => Example { - filename: "main.roc", - executable_filename: "libhello", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"", - use_valgrind: true, - }, - fib:"algorithms" => Example { - filename: "fibonacci.roc", - executable_filename: "fibonacci", - stdin: &[], - arguments: &[], - env: &[], - expected_ending:"55\n", - use_valgrind: true, - }, - gui:"gui" => Example { - filename: "Hello.roc", - executable_filename: "hello-gui", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "", - use_valgrind: false, - }, - breakout:"breakout" => Example { - filename: "breakout.roc", - executable_filename: "breakout", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "", - use_valgrind: false, - }, - quicksort:"algorithms" => Example { - filename: "quicksort.roc", - 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, - }, - // shared_quicksort:"shared-quicksort" => Example { - // filename: "Quicksort.roc", - // executable_filename: "quicksort", - // stdin: &[], - // arguments: &[], - // 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, - // }, - cli_args:"interactive" => Example { - filename: "args.roc", - 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, - }, - // tui_tea:"tea" => Example { - // filename: "Main.roc", - // executable_filename: "tea-example", - // stdin: &[], - // arguments: &[], - // expected_ending: "", - // use_valgrind: true, - // }, - cli_form:"interactive" => Example { - filename: "form.roc", - executable_filename: "form", - stdin: &["Giovanni\n", "Giorgio\n"], - arguments: &[], - env: &[], - expected_ending: "Hi, Giovanni Giorgio! 👋\n", - use_valgrind: false, - }, - tui:"interactive" => Example { - filename: "tui.roc", - executable_filename: "tui", - stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks - arguments: &[], - env: &[], - expected_ending: "Hello Worldfoo!\n", - use_valgrind: true, - }, - // custom_malloc:"custom-malloc" => Example { - // filename: "Main.roc", - // executable_filename: "custom-malloc-example", - // stdin: &[], - // arguments: &[], - // expected_ending: "ms!\nThe list was small!\n", - // use_valgrind: true, - // }, - // task:"task" => Example { - // filename: "Main.roc", - // executable_filename: "task-example", - // stdin: &[], - // arguments: &[], - // expected_ending: "successfully wrote to file\n", - // use_valgrind: true, - // }, - false_interpreter:"false-interpreter" => { - Example { - filename: "False.roc", - executable_filename: "false", - stdin: &[], - arguments: &[Arg::ExamplePath("examples/hello.false")], - env: &[], - expected_ending:"Hello, World!\n", - use_valgrind: false, - } - }, - swiftui:"swiftui" => Example { - filename: "main.roc", - executable_filename: "swiftui", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "", - use_valgrind: false, - }, - static_site_gen: "static-site-gen" => { - Example { - filename: "static-site.roc", - 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, - } - }, - parse_movies_csv: "parser" => { - Example { - filename: "parse-movies-csv.roc", - executable_filename: "parse-movies-csv", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "Parse success!\n", - use_valgrind: false, - } - }, + #[test] + // uses C platform + fn platform_switching_main() { + test_roc_app_slim( + "crates/cli_testing_examples/platform-switching", + "main.roc", + "rocLovesPlatforms", + "Which platform am I running on now?\n", + true, + ) } - macro_rules! benchmarks { - ($($test_name:ident => $benchmark:expr,)+) => { + // We exclude the C platforming switching example + // because the main platform switching example runs the c platform. + // If we don't, a race condition leads to test flakiness. - $( - #[test] - #[cfg_attr(not(debug_assertions), serial(benchmark))] - #[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))] - fn $test_name() { - let benchmark = $benchmark; - let file_name = examples_dir("benchmarks").join(benchmark.filename); + #[test] + fn platform_switching_rust() { + test_roc_app_slim( + "crates/cli_testing_examples/platform-switching", + "rocLovesRust.roc", + "rocLovesRust", + "Roc <3 Rust!\n", + true, + ) + } - // TODO fix QuicksortApp and then remove this! - match benchmark.filename { - "QuicksortApp.roc" => { - eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename); - return; - } - _ => {} - } + #[test] + fn platform_switching_zig() { + test_roc_app_slim( + "crates/cli_testing_examples/platform-switching", + "rocLovesZig.roc", + "rocLovesZig", + "Roc <3 Zig!\n", + true, + ) + } - let mut ran_without_optimizations = false; + #[test] + fn platform_switching_wasm() { + test_roc_app_slim( + "crates/cli_testing_examples/platform-switching", + "rocLovesWebAssembly.roc", + "rocLovesWebAssembly", + "Roc <3 Web Assembly!\n", + true, + ) + } - let mut app_args: Vec = vec![]; - for arg in benchmark.arguments { - match arg { - Arg::ExamplePath(file) => { - app_args.push(examples_dir("benchmarks").join(file).to_str().unwrap().to_string()); - } - Arg::PlainText(arg) => { - app_args.push(arg.to_string()); - } - } - } + #[test] + fn platform_switching_swift() { + test_roc_app_slim( + "crates/cli_testing_examples/platform-switching", + "rocLovesSwift.roc", + "rocLovesSwift", + "Roc <3 Swift!\n", + true, + ) + } - BENCHMARKS_BUILD_PLATFORM.call_once( || { - // Check with and without optimizations - check_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - &[], - &app_args, - benchmark.env, - benchmark.expected_ending, - benchmark.use_valgrind, - ); + #[test] + fn ruby_interop() { + test_roc_app_slim("examples/ruby-interop", "main.roc", "libhello", "", true) + } - ran_without_optimizations = true; - }); + #[test] + fn fibonacci() { + test_roc_app_slim( + "crates/cli_testing_examples/algorithms", + "fibonacci.roc", + "fibonacci", + "", + true, + ) + } - // now we can pass the `PREBUILT_PLATFORM` flag, because the - // `call_once` will have built the platform + #[test] + fn hello_gui() { + test_roc_app_slim("examples/gui", "hello.roc", "hello-gui", "", false) + } - if !ran_without_optimizations { - // Check with and without optimizations - check_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - &[PREBUILT_PLATFORM], - &app_args, - benchmark.env, - benchmark.expected_ending, - benchmark.use_valgrind, - ); - } + #[test] + fn breakout() { + test_roc_app_slim( + "examples/gui/breakout", + "breakout.roc", + "breakout", + "", + false, + ) + } - check_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - &[PREBUILT_PLATFORM, OPTIMIZE_FLAG], - &app_args, - benchmark.env, - benchmark.expected_ending, - benchmark.use_valgrind, - ); - } + #[test] + fn quicksort() { + test_roc_app_slim( + "crates/cli_testing_examples/algorithms", + "quicksort.roc", + "quicksort", + "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", + true, + ) + } - )* + #[test] + #[serial(cli_platform)] + fn cli_args() { + test_roc_app( + "examples/cli", + "args.roc", + "args", + &[], + &[ + Arg::PlainText("log"), + Arg::PlainText("-b"), + Arg::PlainText("3"), + Arg::PlainText("--num"), + Arg::PlainText("81"), + ], + &[], + "4\n", + false, + false, + ) + } + + #[test] + fn interactive_effects() { + test_roc_app( + "examples/cli", + "effects.roc", + "effects", + &["hi there!"], + &[], + &[], + "hi there!\nIt is known\n", + true, + false, + ) + } + + #[test] + // tea = The Elm Architecture + fn terminal_ui_tea() { + test_roc_app( + "examples/cli", + "tui.roc", + "tui", + &["foo\n"], // NOTE: adding more lines leads to memory leaks + &[], + &[], + "Hello Worldfoo!\n", + true, + false, + ) + } + + #[test] + fn false_interpreter() { + test_roc_app( + "examples/cli/false-interpreter", + "False.roc", + "false", + &[], + &[Arg::ExamplePath("examples/hello.false")], + &[], + "Hello, World!\n", + false, + true, + ) + } + + #[test] + fn swift_ui() { + test_roc_app_slim("examples/swiftui", "main.roc", "swiftui", "", false) + } + + #[test] + fn static_site_gen() { + test_roc_app( + "examples/static-site-gen", + "static-site.roc", + "static-site", + &[], + &[Arg::ExamplePath("input"), Arg::ExamplePath("output")], + &[], + "Processed 3 files with 3 successes and 0 errors\n", + false, + false, + ) + } + + #[test] + #[serial(cli_platform)] + fn with_env_vars() { + test_roc_app( + "examples/cli", + "env.roc", + "env", + &[], + &[], + &[ + ("EDITOR", "roc-editor"), + ("SHLVL", "3"), + ("LETTERS", "a,c,e,j"), + ], + "Your favorite editor is roc-editor!\n\ + Your current shell level is 3!\n\ + Your favorite letters are: a c e j\n", + false, + false, + ) + } + + #[test] + fn parse_movies_csv() { + test_roc_app_slim( + "examples/parser", + "parse-movies-csv.roc", + "parse-movies-csv", + "Parse success!\n", + false, + ) + } + + // TODO not sure if this cfg should still be here: #[cfg(not(debug_assertions))] + // this is for testing the benchmarks, to perform proper benchmarks see crates/cli/benches/README.md + mod test_benchmarks { + use cli_utils::helpers::cli_testing_dir; + + use super::{check_output_with_stdin, OPTIMIZE_FLAG, PREBUILT_PLATFORM}; + + use std::{path::Path, sync::Once}; + + static BENCHMARKS_BUILD_PLATFORM: Once = Once::new(); + + fn test_benchmark( + roc_filename: &str, + executable_filename: &str, + stdin: &[&str], + expected_ending: &str, + use_valgrind: bool, + ) { + let file_name = cli_testing_dir("benchmarks").join(roc_filename); + + // TODO fix QuicksortApp and then remove this! + if roc_filename == "QuicksortApp.roc" { + eprintln!( + "WARNING: skipping testing benchmark {} because the test is broken right now!", + roc_filename + ); + return; + } + + #[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))] + check_output_regular( + &file_name, + stdin, + executable_filename, + expected_ending, + use_valgrind, + ); #[cfg(feature = "wasm32-cli-run")] - mod wasm32 { - use super::*; - $( - #[test] - #[cfg_attr(not(debug_assertions), serial(benchmark))] - fn $test_name() { - let benchmark = $benchmark; - let file_name = examples_dir("benchmarks").join(benchmark.filename); - - // TODO fix QuicksortApp and then remove this! - match benchmark.filename { - "QuicksortApp.roc" => { - eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename); - return; - } - _ => {} - } - - // Check with and without optimizations - check_wasm_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - &[], - benchmark.input_paths.iter().map(|file| examples_dir("benchmarks").join(file)), - benchmark.expected_ending, - ); - - check_wasm_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - &[OPTIMIZE_FLAG], - benchmark.input_paths.iter().map(|file| examples_dir("benchmarks").join(file)), - benchmark.expected_ending, - ); - } - )* - } + check_output_wasm(&file_name, stdin, executable_filename, expected_ending); #[cfg(feature = "i386-cli-run")] - mod i386 { - use super::*; - $( - #[test] - #[cfg_attr(not(debug_assertions), serial(benchmark))] - fn $test_name() { - let benchmark = $benchmark; - let file_name = examples_dir("benchmarks").join(benchmark.filename); - - // TODO fix QuicksortApp and then remove this! - match benchmark.filename { - "QuicksortApp.roc" => { - eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename); - return; - } - _ => {} - } - - // Check with and without optimizations - check_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - [concatcp!(TARGET_FLAG, "=x86_32")], - benchmark.input_paths.iter().map(|file| Some(examples_dir("benchmarks").join(file))), - benchmark.expected_ending, - benchmark.use_valgrind, - ); - - check_output_with_stdin( - &file_name, - benchmark.stdin, - benchmark.executable_filename, - [concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG], - benchmark.input_paths.iter().map(|file| Some(examples_dir("benchmarks").join(file))), - benchmark.expected_ending, - benchmark.use_valgrind, - ); - } - )* - } - - #[test] - #[cfg(not(debug_assertions))] - fn all_benchmarks_have_tests() { - let mut all_benchmarks: MutMap<&str, Example<'_>> = MutMap::default(); - - $( - let benchmark = $benchmark; - - all_benchmarks.insert(benchmark.filename, benchmark); - )* - - check_for_benchmarks("../../examples/benchmarks", &mut all_benchmarks); - } - } - } - - benchmarks! { - nqueens => Example { - filename: "NQueens.roc", - executable_filename: "nqueens", - stdin: &["6"], - arguments: &[], - env: &[], - expected_ending: "4\n", - use_valgrind: true, - }, - cfold => Example { - filename: "CFold.roc", - executable_filename: "cfold", - stdin: &["3"], - arguments: &[], - env: &[], - expected_ending: "11 & 11\n", - use_valgrind: true, - }, - deriv => Example { - filename: "Deriv.roc", - executable_filename: "deriv", - stdin: &["2"], - arguments: &[], - env: &[], - expected_ending: "1 count: 6\n2 count: 22\n", - use_valgrind: true, - }, - rbtree_ck => Example { - filename: "RBTreeCk.roc", - executable_filename: "rbtree-ck", - stdin: &["100"], - arguments: &[], - env: &[], - expected_ending: "10\n", - use_valgrind: true, - }, - rbtree_insert => Example { - filename: "RBTreeInsert.roc", - executable_filename: "rbtree-insert", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "Node Black 0 {} Empty Empty\n", - use_valgrind: true, - }, - // rbtree_del => Example { - // filename: "RBTreeDel.roc", - // executable_filename: "rbtree-del", - // stdin: &["420"], - // arguments: &[], - // env: &[], - // expected_ending: "30\n", - // use_valgrind: true, - // }, - astar => Example { - filename: "TestAStar.roc", - executable_filename: "test-astar", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "True\n", - use_valgrind: false, - }, - base64 => Example { - filename: "TestBase64.roc", - executable_filename: "test-base64", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", - use_valgrind: true, - }, - closure => Example { - filename: "Closure.roc", - executable_filename: "closure", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "", - use_valgrind: false, - }, - issue2279 => Example { - filename: "Issue2279.roc", - executable_filename: "issue2279", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "Hello, world!\n", - use_valgrind: true, - }, - quicksort_app => Example { - filename: "QuicksortApp.roc", - executable_filename: "quicksortapp", - stdin: &[], - arguments: &[], - env: &[], - expected_ending: "todo put the correct quicksort answer here", - use_valgrind: true, - }, - } - - #[cfg(not(debug_assertions))] - fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { - let entries = std::fs::read_dir(examples_dir).unwrap_or_else(|err| { - panic!( - "Error trying to read {} as an examples directory: {}", - examples_dir, err + check_output_i386( + &file_name, + stdin, + executable_filename, + expected_ending, + use_valgrind, ); - }); - - for entry in entries { - let entry = entry.unwrap(); - - if entry.file_type().unwrap().is_dir() { - let example_dir_name = entry.file_name().into_string().unwrap(); - - // We test benchmarks separately - if example_dir_name != "benchmarks" { - all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| { - panic!("The example directory {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", examples_dir, example_dir_name); - }); - } - } } - assert_eq!(all_examples, &mut MutMap::default()); - } + #[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))] + fn check_output_regular( + file_name: &Path, + stdin: &[&str], + executable_filename: &str, + expected_ending: &str, + use_valgrind: bool, + ) { + let mut ran_without_optimizations = false; - #[cfg(not(debug_assertions))] - fn check_for_benchmarks(benchmarks_dir: &str, all_benchmarks: &mut MutMap<&str, Example<'_>>) { - use std::ffi::OsStr; - use std::fs::File; - use std::io::Read; + BENCHMARKS_BUILD_PLATFORM.call_once(|| { + // Check with and without optimizations + check_output_with_stdin( + file_name, + stdin, + executable_filename, + &[], + &[], + &[], + expected_ending, + use_valgrind, + false, + ); - let entries = std::fs::read_dir(benchmarks_dir).unwrap_or_else(|err| { - panic!( - "Error trying to read {} as a benchmark directory: {}", - benchmarks_dir, err + ran_without_optimizations = true; + }); + + // now we can pass the `PREBUILT_PLATFORM` flag, because the + // `call_once` will have built the platform + + if !ran_without_optimizations { + // Check with and without optimizations + check_output_with_stdin( + file_name, + stdin, + executable_filename, + &[PREBUILT_PLATFORM], + &[], + &[], + expected_ending, + use_valgrind, + false, + ); + } + + check_output_with_stdin( + file_name, + stdin, + executable_filename, + &[PREBUILT_PLATFORM, OPTIMIZE_FLAG], + &[], + &[], + expected_ending, + use_valgrind, + false, ); - }); + } - for entry in entries { - let entry = entry.unwrap(); - let path = entry.path(); + #[cfg(feature = "wasm32-cli-run")] + fn check_output_wasm( + file_name: &Path, + stdin: &[&str], + executable_filename: &str, + expected_ending: &str, + ) { + // Check with and without optimizations + check_wasm_output_with_stdin( + file_name, + stdin, + executable_filename, + &[], + expected_ending, + ); - if let Some("roc") = path.extension().and_then(OsStr::to_str) { - let benchmark_file_name = entry.file_name().into_string().unwrap(); + check_wasm_output_with_stdin( + file_name, + stdin, + executable_filename, + &[OPTIMIZE_FLAG], + expected_ending, + ); + } - // Verify that this is an app module by reading the first 3 - // bytes of the file. - let buf: &mut [u8] = &mut [0, 0, 0]; - let mut file = File::open(path).unwrap(); + #[cfg(feature = "wasm32-cli-run")] + fn check_wasm_output_with_stdin( + file: &Path, + stdin: &[&str], + executable_filename: &str, + flags: &[&str], + expected_ending: &str, + ) { + let mut flags = flags.to_vec(); + flags.push(concatcp!(TARGET_FLAG, "=wasm32")); - file.read_exact(buf).unwrap(); + let compile_out = run_roc( + [CMD_BUILD, file.to_str().unwrap()] + .iter() + .chain(flags.as_slice()), + &[], + ); + if !compile_out.stderr.is_empty() { + panic!("{}", compile_out.stderr); + } - // Only app modules in this directory are considered benchmarks. - if "app".as_bytes() == buf && !benchmark_file_name.contains("RBTreeDel") { - all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| { - panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); - }); - } + assert!(compile_out.status.success(), "bad status {:?}", compile_out); + + let path = file.with_file_name(executable_filename); + let stdout = crate::run_with_wasmer(&path, stdin); + + if !stdout.ends_with(expected_ending) { + panic!( + "expected output to end with {:?} but instead got {:#?}", + expected_ending, stdout + ); } } - assert_eq!(all_benchmarks, &mut MutMap::default()); + #[cfg(feature = "i386-cli-run")] + fn check_output_i386( + file_name: &Path, + stdin: &[&str], + executable_filename: &str, + expected_ending: &str, + use_valgrind: bool, + ) { + check_output_with_stdin( + &file_name, + stdin, + executable_filename, + &[concatcp!(TARGET_FLAG, "=x86_32")], + &[], + expected_ending, + use_valgrind, + false, + ); + + check_output_with_stdin( + &file_name, + stdin, + executable_filename, + &[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG], + &[], + expected_ending, + use_valgrind, + false, + ); + } + + #[test] + fn nqueens() { + test_benchmark("NQueens.roc", "nqueens", &["6"], "4\n", true) + } + + #[test] + fn cfold() { + test_benchmark("CFold.roc", "cfold", &["3"], "11 & 11\n", true) + } + + #[test] + fn deriv() { + test_benchmark( + "Deriv.roc", + "deriv", + &["2"], + "1 count: 6\n2 count: 22\n", + true, + ) + } + + #[test] + fn rbtree_ck() { + test_benchmark("RBTreeCk.roc", "rbtree-ck", &["100"], "10\n", true) + } + + #[test] + fn rbtree_insert() { + test_benchmark( + "RBTreeInsert.roc", + "rbtree-insert", + &[], + "Node Black 0 {} Empty Empty\n", + true, + ) + } + + /* + // rbtree_del does not work + #[test] + fn rbtree_del() { + test_benchmark( + "RBTreeDel.roc", + "rbtree-del", + &["420"], + &[], + "30\n", + true + ) + }*/ + + #[test] + fn astar() { + test_benchmark("TestAStar.roc", "test-astar", &[], "True\n", false) + } + + #[test] + fn base64() { + test_benchmark( + "TestBase64.roc", + "test-base64", + &[], + "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", + true, + ) + } + + #[test] + fn closure() { + test_benchmark("Closure.roc", "closure", &[], "", false) + } + + #[test] + fn issue2279() { + test_benchmark("Issue2279.roc", "issue2279", &[], "Hello, world!\n", true) + } + + #[test] + fn quicksort_app() { + test_benchmark( + "QuicksortApp.roc", + "quicksortapp", + &[], + "todo put the correct quicksort answer here", + true, + ) + } } #[test] @@ -1042,6 +921,7 @@ mod cli_run { &[], "I am Dep2.str2\n", true, + false, ); } @@ -1057,6 +937,7 @@ mod cli_run { &[], "I am Dep2.str2\n", true, + false, ); } @@ -1072,6 +953,7 @@ mod cli_run { &[], "I am Dep2.value2\n", true, + false, ); } @@ -1087,6 +969,7 @@ mod cli_run { &[], "I am Dep2.value2\n", true, + false, ); } @@ -1097,7 +980,7 @@ mod cli_run { &[], indoc!( r#" - ── TYPE MISMATCH ─ ...d/../../../../examples/interactive/cli-platform/main.roc ─ + ── TYPE MISMATCH ─ ...known_bad/../../../../examples/cli/cli-platform/main.roc ─ Something is off with the type annotation of the main required symbol: @@ -1118,7 +1001,7 @@ mod cli_run { an instance of this opaque type by doing @Age 23. - ── TYPE MISMATCH ─ ...d/../../../../examples/interactive/cli-platform/main.roc ─ + ── TYPE MISMATCH ─ ...known_bad/../../../../examples/cli/cli-platform/main.roc ─ This 1st argument to toEffect has an unexpected type: diff --git a/crates/cli/tests/known_bad/TypeError.roc b/crates/cli/tests/known_bad/TypeError.roc index fa6060e81a..0d7e15f6d7 100644 --- a/crates/cli/tests/known_bad/TypeError.roc +++ b/crates/cli/tests/known_bad/TypeError.roc @@ -1,5 +1,5 @@ app "type-error" - packages { pf: "../../../../examples/interactive/cli-platform/main.roc" } + packages { pf: "../../../../examples/cli/cli-platform/main.roc" } imports [pf.Stdout.{ line }, pf.Task.{ await }, pf.Program] provides [main] to pf diff --git a/crates/cli_testing_examples/.gitignore b/crates/cli_testing_examples/.gitignore new file mode 100644 index 0000000000..58cb449bb9 --- /dev/null +++ b/crates/cli_testing_examples/.gitignore @@ -0,0 +1,6 @@ +*.dSYM +libhost.a +libapp.so +dynhost +preprocessedhost +metadata diff --git a/examples/algorithms/.gitignore b/crates/cli_testing_examples/algorithms/.gitignore similarity index 100% rename from examples/algorithms/.gitignore rename to crates/cli_testing_examples/algorithms/.gitignore diff --git a/examples/algorithms/README.md b/crates/cli_testing_examples/algorithms/README.md similarity index 100% rename from examples/algorithms/README.md rename to crates/cli_testing_examples/algorithms/README.md diff --git a/examples/algorithms/fibonacci-platform/host.zig b/crates/cli_testing_examples/algorithms/fibonacci-platform/host.zig similarity index 100% rename from examples/algorithms/fibonacci-platform/host.zig rename to crates/cli_testing_examples/algorithms/fibonacci-platform/host.zig diff --git a/examples/algorithms/fibonacci-platform/main.roc b/crates/cli_testing_examples/algorithms/fibonacci-platform/main.roc similarity index 100% rename from examples/algorithms/fibonacci-platform/main.roc rename to crates/cli_testing_examples/algorithms/fibonacci-platform/main.roc diff --git a/examples/algorithms/fibonacci.roc b/crates/cli_testing_examples/algorithms/fibonacci.roc similarity index 100% rename from examples/algorithms/fibonacci.roc rename to crates/cli_testing_examples/algorithms/fibonacci.roc diff --git a/examples/algorithms/quicksort-platform/host.zig b/crates/cli_testing_examples/algorithms/quicksort-platform/host.zig similarity index 100% rename from examples/algorithms/quicksort-platform/host.zig rename to crates/cli_testing_examples/algorithms/quicksort-platform/host.zig diff --git a/examples/algorithms/quicksort-platform/main.roc b/crates/cli_testing_examples/algorithms/quicksort-platform/main.roc similarity index 100% rename from examples/algorithms/quicksort-platform/main.roc rename to crates/cli_testing_examples/algorithms/quicksort-platform/main.roc diff --git a/examples/algorithms/quicksort.roc b/crates/cli_testing_examples/algorithms/quicksort.roc similarity index 100% rename from examples/algorithms/quicksort.roc rename to crates/cli_testing_examples/algorithms/quicksort.roc diff --git a/examples/benchmarks/.gitignore b/crates/cli_testing_examples/benchmarks/.gitignore similarity index 100% rename from examples/benchmarks/.gitignore rename to crates/cli_testing_examples/benchmarks/.gitignore diff --git a/examples/benchmarks/AStar.roc b/crates/cli_testing_examples/benchmarks/AStar.roc similarity index 100% rename from examples/benchmarks/AStar.roc rename to crates/cli_testing_examples/benchmarks/AStar.roc diff --git a/examples/benchmarks/Base64.roc b/crates/cli_testing_examples/benchmarks/Base64.roc similarity index 100% rename from examples/benchmarks/Base64.roc rename to crates/cli_testing_examples/benchmarks/Base64.roc diff --git a/examples/benchmarks/Base64/Decode.roc b/crates/cli_testing_examples/benchmarks/Base64/Decode.roc similarity index 100% rename from examples/benchmarks/Base64/Decode.roc rename to crates/cli_testing_examples/benchmarks/Base64/Decode.roc diff --git a/examples/benchmarks/Base64/Encode.roc b/crates/cli_testing_examples/benchmarks/Base64/Encode.roc similarity index 100% rename from examples/benchmarks/Base64/Encode.roc rename to crates/cli_testing_examples/benchmarks/Base64/Encode.roc diff --git a/examples/benchmarks/Bytes/Decode.roc b/crates/cli_testing_examples/benchmarks/Bytes/Decode.roc similarity index 100% rename from examples/benchmarks/Bytes/Decode.roc rename to crates/cli_testing_examples/benchmarks/Bytes/Decode.roc diff --git a/examples/benchmarks/Bytes/Encode.roc b/crates/cli_testing_examples/benchmarks/Bytes/Encode.roc similarity index 100% rename from examples/benchmarks/Bytes/Encode.roc rename to crates/cli_testing_examples/benchmarks/Bytes/Encode.roc diff --git a/examples/benchmarks/CFold.roc b/crates/cli_testing_examples/benchmarks/CFold.roc similarity index 100% rename from examples/benchmarks/CFold.roc rename to crates/cli_testing_examples/benchmarks/CFold.roc diff --git a/examples/benchmarks/Closure.roc b/crates/cli_testing_examples/benchmarks/Closure.roc similarity index 100% rename from examples/benchmarks/Closure.roc rename to crates/cli_testing_examples/benchmarks/Closure.roc diff --git a/examples/benchmarks/Deriv.roc b/crates/cli_testing_examples/benchmarks/Deriv.roc similarity index 100% rename from examples/benchmarks/Deriv.roc rename to crates/cli_testing_examples/benchmarks/Deriv.roc diff --git a/examples/benchmarks/Issue2279.roc b/crates/cli_testing_examples/benchmarks/Issue2279.roc similarity index 100% rename from examples/benchmarks/Issue2279.roc rename to crates/cli_testing_examples/benchmarks/Issue2279.roc diff --git a/examples/benchmarks/Issue2279Help.roc b/crates/cli_testing_examples/benchmarks/Issue2279Help.roc similarity index 100% rename from examples/benchmarks/Issue2279Help.roc rename to crates/cli_testing_examples/benchmarks/Issue2279Help.roc diff --git a/examples/benchmarks/NQueens.roc b/crates/cli_testing_examples/benchmarks/NQueens.roc similarity index 100% rename from examples/benchmarks/NQueens.roc rename to crates/cli_testing_examples/benchmarks/NQueens.roc diff --git a/examples/benchmarks/Quicksort.roc b/crates/cli_testing_examples/benchmarks/Quicksort.roc similarity index 100% rename from examples/benchmarks/Quicksort.roc rename to crates/cli_testing_examples/benchmarks/Quicksort.roc diff --git a/examples/benchmarks/QuicksortApp.roc b/crates/cli_testing_examples/benchmarks/QuicksortApp.roc similarity index 100% rename from examples/benchmarks/QuicksortApp.roc rename to crates/cli_testing_examples/benchmarks/QuicksortApp.roc diff --git a/examples/benchmarks/RBTreeCk.roc b/crates/cli_testing_examples/benchmarks/RBTreeCk.roc similarity index 100% rename from examples/benchmarks/RBTreeCk.roc rename to crates/cli_testing_examples/benchmarks/RBTreeCk.roc diff --git a/examples/benchmarks/RBTreeDel.roc b/crates/cli_testing_examples/benchmarks/RBTreeDel.roc similarity index 100% rename from examples/benchmarks/RBTreeDel.roc rename to crates/cli_testing_examples/benchmarks/RBTreeDel.roc diff --git a/examples/benchmarks/RBTreeInsert.roc b/crates/cli_testing_examples/benchmarks/RBTreeInsert.roc similarity index 100% rename from examples/benchmarks/RBTreeInsert.roc rename to crates/cli_testing_examples/benchmarks/RBTreeInsert.roc diff --git a/examples/benchmarks/TestAStar.roc b/crates/cli_testing_examples/benchmarks/TestAStar.roc similarity index 100% rename from examples/benchmarks/TestAStar.roc rename to crates/cli_testing_examples/benchmarks/TestAStar.roc diff --git a/examples/benchmarks/TestBase64.roc b/crates/cli_testing_examples/benchmarks/TestBase64.roc similarity index 100% rename from examples/benchmarks/TestBase64.roc rename to crates/cli_testing_examples/benchmarks/TestBase64.roc diff --git a/examples/benchmarks/platform/Effect.roc b/crates/cli_testing_examples/benchmarks/platform/Effect.roc similarity index 100% rename from examples/benchmarks/platform/Effect.roc rename to crates/cli_testing_examples/benchmarks/platform/Effect.roc diff --git a/examples/benchmarks/platform/Task.roc b/crates/cli_testing_examples/benchmarks/platform/Task.roc similarity index 100% rename from examples/benchmarks/platform/Task.roc rename to crates/cli_testing_examples/benchmarks/platform/Task.roc diff --git a/examples/benchmarks/platform/host.zig b/crates/cli_testing_examples/benchmarks/platform/host.zig similarity index 100% rename from examples/benchmarks/platform/host.zig rename to crates/cli_testing_examples/benchmarks/platform/host.zig diff --git a/examples/benchmarks/platform/main.roc b/crates/cli_testing_examples/benchmarks/platform/main.roc similarity index 100% rename from examples/benchmarks/platform/main.roc rename to crates/cli_testing_examples/benchmarks/platform/main.roc diff --git a/examples/platform-switching/.gitignore b/crates/cli_testing_examples/platform-switching/.gitignore similarity index 100% rename from examples/platform-switching/.gitignore rename to crates/cli_testing_examples/platform-switching/.gitignore diff --git a/examples/platform-switching/README.md b/crates/cli_testing_examples/platform-switching/README.md similarity index 100% rename from examples/platform-switching/README.md rename to crates/cli_testing_examples/platform-switching/README.md diff --git a/examples/hello-world/platform/host.c b/crates/cli_testing_examples/platform-switching/c-platform/host.c similarity index 100% rename from examples/hello-world/platform/host.c rename to crates/cli_testing_examples/platform-switching/c-platform/host.c diff --git a/examples/platform-switching/c-platform/main.roc b/crates/cli_testing_examples/platform-switching/c-platform/main.roc similarity index 100% rename from examples/platform-switching/c-platform/main.roc rename to crates/cli_testing_examples/platform-switching/c-platform/main.roc diff --git a/examples/platform-switching/main.roc b/crates/cli_testing_examples/platform-switching/main.roc similarity index 100% rename from examples/platform-switching/main.roc rename to crates/cli_testing_examples/platform-switching/main.roc diff --git a/examples/platform-switching/rocLovesC.roc b/crates/cli_testing_examples/platform-switching/rocLovesC.roc similarity index 100% rename from examples/platform-switching/rocLovesC.roc rename to crates/cli_testing_examples/platform-switching/rocLovesC.roc diff --git a/examples/platform-switching/rocLovesRust.roc b/crates/cli_testing_examples/platform-switching/rocLovesRust.roc similarity index 100% rename from examples/platform-switching/rocLovesRust.roc rename to crates/cli_testing_examples/platform-switching/rocLovesRust.roc diff --git a/examples/platform-switching/rocLovesSwift.roc b/crates/cli_testing_examples/platform-switching/rocLovesSwift.roc similarity index 100% rename from examples/platform-switching/rocLovesSwift.roc rename to crates/cli_testing_examples/platform-switching/rocLovesSwift.roc diff --git a/examples/platform-switching/rocLovesWebAssembly.roc b/crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc similarity index 100% rename from examples/platform-switching/rocLovesWebAssembly.roc rename to crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc diff --git a/examples/platform-switching/rocLovesZig.roc b/crates/cli_testing_examples/platform-switching/rocLovesZig.roc similarity index 100% rename from examples/platform-switching/rocLovesZig.roc rename to crates/cli_testing_examples/platform-switching/rocLovesZig.roc diff --git a/examples/platform-switching/rust-platform/Cargo.lock b/crates/cli_testing_examples/platform-switching/rust-platform/Cargo.lock similarity index 100% rename from examples/platform-switching/rust-platform/Cargo.lock rename to crates/cli_testing_examples/platform-switching/rust-platform/Cargo.lock diff --git a/examples/platform-switching/rust-platform/Cargo.toml b/crates/cli_testing_examples/platform-switching/rust-platform/Cargo.toml similarity index 85% rename from examples/platform-switching/rust-platform/Cargo.toml rename to crates/cli_testing_examples/platform-switching/rust-platform/Cargo.toml index 8d10ce6eea..29083ca183 100644 --- a/examples/platform-switching/rust-platform/Cargo.toml +++ b/crates/cli_testing_examples/platform-switching/rust-platform/Cargo.toml @@ -16,7 +16,7 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } +roc_std = { path = "../../../../crates/roc_std" } libc = "0.2" [workspace] diff --git a/examples/breakout/platform/build.rs b/crates/cli_testing_examples/platform-switching/rust-platform/build.rs similarity index 100% rename from examples/breakout/platform/build.rs rename to crates/cli_testing_examples/platform-switching/rust-platform/build.rs diff --git a/examples/breakout/platform/host.c b/crates/cli_testing_examples/platform-switching/rust-platform/host.c similarity index 100% rename from examples/breakout/platform/host.c rename to crates/cli_testing_examples/platform-switching/rust-platform/host.c diff --git a/examples/platform-switching/rust-platform/main.roc b/crates/cli_testing_examples/platform-switching/rust-platform/main.roc similarity index 100% rename from examples/platform-switching/rust-platform/main.roc rename to crates/cli_testing_examples/platform-switching/rust-platform/main.roc diff --git a/examples/platform-switching/rust-platform/src/lib.rs b/crates/cli_testing_examples/platform-switching/rust-platform/src/lib.rs similarity index 100% rename from examples/platform-switching/rust-platform/src/lib.rs rename to crates/cli_testing_examples/platform-switching/rust-platform/src/lib.rs diff --git a/examples/breakout/platform/src/main.rs b/crates/cli_testing_examples/platform-switching/rust-platform/src/main.rs similarity index 100% rename from examples/breakout/platform/src/main.rs rename to crates/cli_testing_examples/platform-switching/rust-platform/src/main.rs diff --git a/examples/platform-switching/swift-platform/host.h b/crates/cli_testing_examples/platform-switching/swift-platform/host.h similarity index 100% rename from examples/platform-switching/swift-platform/host.h rename to crates/cli_testing_examples/platform-switching/swift-platform/host.h diff --git a/examples/platform-switching/swift-platform/host.swift b/crates/cli_testing_examples/platform-switching/swift-platform/host.swift similarity index 100% rename from examples/platform-switching/swift-platform/host.swift rename to crates/cli_testing_examples/platform-switching/swift-platform/host.swift diff --git a/examples/platform-switching/swift-platform/main.roc b/crates/cli_testing_examples/platform-switching/swift-platform/main.roc similarity index 100% rename from examples/platform-switching/swift-platform/main.roc rename to crates/cli_testing_examples/platform-switching/swift-platform/main.roc diff --git a/examples/platform-switching/web-assembly-platform/README.md b/crates/cli_testing_examples/platform-switching/web-assembly-platform/README.md similarity index 77% rename from examples/platform-switching/web-assembly-platform/README.md rename to crates/cli_testing_examples/platform-switching/web-assembly-platform/README.md index 76f5720e29..a1dba338dc 100644 --- a/examples/platform-switching/web-assembly-platform/README.md +++ b/crates/cli_testing_examples/platform-switching/web-assembly-platform/README.md @@ -3,12 +3,12 @@ To run this website, first compile either of these identical apps: ```bash -# Option A: Compile examples/platform-switching/rocLovesWebAssembly.roc -cargo run -- build --target=wasm32 examples/platform-switching/rocLovesWebAssembly.roc +# Option A: Compile crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc +cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/rocLovesWebAssembly.roc -# Option B: Compile examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result -cargo run -- build --target=wasm32 examples/platform-switching/main.roc -(cd examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm) +# Option B: Compile crates/cli_testing_examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result +cargo run -- build --target=wasm32 crates/cli_testing_examples/platform-switching/main.roc +(cd crates/cli_testing_examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm) ``` Then `cd` into the website directory @@ -16,7 +16,7 @@ and run any web server that can handle WebAssembly. For example, with `http-server`: ```bash -cd examples/platform-switching/web-assembly-platform +cd crates/cli_testing_examples/platform-switching/web-assembly-platform npm install -g http-server http-server ``` diff --git a/examples/platform-switching/web-assembly-platform/host.js b/crates/cli_testing_examples/platform-switching/web-assembly-platform/host.js similarity index 100% rename from examples/platform-switching/web-assembly-platform/host.js rename to crates/cli_testing_examples/platform-switching/web-assembly-platform/host.js diff --git a/examples/platform-switching/web-assembly-platform/host.test.js b/crates/cli_testing_examples/platform-switching/web-assembly-platform/host.test.js similarity index 100% rename from examples/platform-switching/web-assembly-platform/host.test.js rename to crates/cli_testing_examples/platform-switching/web-assembly-platform/host.test.js diff --git a/examples/platform-switching/web-assembly-platform/host.zig b/crates/cli_testing_examples/platform-switching/web-assembly-platform/host.zig similarity index 100% rename from examples/platform-switching/web-assembly-platform/host.zig rename to crates/cli_testing_examples/platform-switching/web-assembly-platform/host.zig diff --git a/examples/platform-switching/web-assembly-platform/index.html b/crates/cli_testing_examples/platform-switching/web-assembly-platform/index.html similarity index 100% rename from examples/platform-switching/web-assembly-platform/index.html rename to crates/cli_testing_examples/platform-switching/web-assembly-platform/index.html diff --git a/examples/platform-switching/web-assembly-platform/main.roc b/crates/cli_testing_examples/platform-switching/web-assembly-platform/main.roc similarity index 100% rename from examples/platform-switching/web-assembly-platform/main.roc rename to crates/cli_testing_examples/platform-switching/web-assembly-platform/main.roc diff --git a/examples/platform-switching/zig-platform/host.zig b/crates/cli_testing_examples/platform-switching/zig-platform/host.zig similarity index 100% rename from examples/platform-switching/zig-platform/host.zig rename to crates/cli_testing_examples/platform-switching/zig-platform/host.zig diff --git a/examples/platform-switching/zig-platform/main.roc b/crates/cli_testing_examples/platform-switching/zig-platform/main.roc similarity index 100% rename from examples/platform-switching/zig-platform/main.roc rename to crates/cli_testing_examples/platform-switching/zig-platform/main.roc diff --git a/crates/cli_utils/src/bench_utils.rs b/crates/cli_utils/src/bench_utils.rs index 8afb84be99..31da15a8e7 100644 --- a/crates/cli_utils/src/bench_utils.rs +++ b/crates/cli_utils/src/bench_utils.rs @@ -1,4 +1,4 @@ -use crate::helpers::{example_file, run_cmd, run_roc}; +use crate::helpers::{file_path_from_root, run_cmd, run_roc}; use criterion::{black_box, measurement::Measurement, BenchmarkGroup}; use std::{path::Path, thread}; @@ -16,6 +16,7 @@ fn exec_bench_w_input( let compile_out = run_roc( ["build", OPTIMIZE_FLAG, file.to_str().unwrap()], &[stdin_str], + &[], ); if !compile_out.stderr.is_empty() && compile_out.stderr != "🔨 Rebuilding platform...\n" { @@ -110,7 +111,7 @@ fn bench_cmd( pub fn bench_nqueens(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( - &example_file("benchmarks", "NQueens.roc"), + &file_path_from_root("crates/cli_testing_examples/benchmarks", "NQueens.roc"), "11", "nqueens", "2680\n", //2680-14200 @@ -120,7 +121,7 @@ pub fn bench_nqueens(bench_group_opt: Option<&mut BenchmarkGroup pub fn bench_cfold(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( - &example_file("benchmarks", "CFold.roc"), + &file_path_from_root("crates/cli_testing_examples/benchmarks", "CFold.roc"), "17", "cfold", "396354 & 396354\n", @@ -130,7 +131,7 @@ pub fn bench_cfold(bench_group_opt: Option<&mut BenchmarkGroup(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( - &example_file("benchmarks", "Deriv.roc"), + &file_path_from_root("crates/cli_testing_examples/benchmarks", "Deriv.roc"), "8", "deriv", "1 count: 6\n2 count: 22\n3 count: 90\n4 count: 420\n5 count: 2202\n6 count: 12886\n7 count: 83648\n8 count: 598592\n", @@ -140,7 +141,7 @@ pub fn bench_deriv(bench_group_opt: Option<&mut BenchmarkGroup(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( - &example_file("benchmarks", "RBTreeCk.roc"), + &file_path_from_root("crates/cli_testing_examples/benchmarks", "RBTreeCk.roc"), "80000", "rbtree-ck", "8000\n", @@ -151,7 +152,7 @@ pub fn bench_rbtree_ck(bench_group_opt: Option<&mut BenchmarkGro #[allow(dead_code)] pub fn bench_rbtree_delete(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( - &example_file("benchmarks", "RBTreeDel.roc"), + &file_path_from_root("crates/cli_testing_examples/benchmarks", "RBTreeDel.roc"), "100000", "rbtree-del", "7000\n", @@ -161,7 +162,7 @@ pub fn bench_rbtree_delete(bench_group_opt: Option<&mut Benchmar pub fn bench_quicksort(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( - &example_file("benchmarks", "QuicksortApp.roc"), + &file_path_from_root("crates/cli_testing_examples/benchmarks", "QuicksortApp.roc"), "1", // 1 for sorting large list, 0 for a small list "quicksortapp", "[0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 9, 10, 10, 11, 11, 12, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 18, 18, 19, 19, 19, 20, 21, 21, 21, 21, 22, 23, 23, 23, 25, 26, 27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 31, 32, 33, 34, 35, 35, 35, 36, 36, 36, 37, 38, 38, 39, 39, 39, 39, 39, 39, 40, 40, 41, 42, 42, 42, 42, 42, 43, 43, 44, 46, 47, 47, 47, 48, 50, 51, 51, 52, 52, 52, 53, 54, 54, 55, 55, 55, 56, 57, 57, 58, 58, 58, 58, 58, 59, 59, 60, 60, 61, 62, 63, 63, 63, 63, 64, 65, 65, 65, 66, 66, 66, 66, 67, 67, 68, 69, 69, 70, 70, 71, 71, 71, 72, 72, 73, 73, 73, 74, 75, 75, 75, 76, 78, 79, 79, 80, 81, 81, 82, 82, 83, 83, 84, 84, 86, 86, 87, 87, 88, 88, 88, 89, 89, 90, 90, 90, 91, 92, 92, 92, 93, 93, 93, 94, 95, 95, 96, 97, 98, 99, 100, 100, 101, 102, 102, 102, 104, 104, 105, 106, 106, 106, 106, 106, 106, 107, 107, 108, 108, 108, 109, 109, 109, 109, 110, 112, 112, 112, 113, 113, 113, 113, 113, 114, 115, 117, 117, 117, 118, 119, 119, 119, 120, 120, 121, 123, 124, 125, 125, 126, 126, 126, 126, 127, 129, 131, 131, 131, 131, 131, 131, 131, 132, 133, 133, 134, 134, 134, 135, 135, 135, 135, 135, 137, 138, 138, 138, 139, 139, 140, 141, 142, 142, 142, 144, 144, 145, 145, 145, 147, 147, 147, 147, 148, 149, 149, 149, 150, 150, 151, 151, 151, 151, 153, 155, 156, 159, 160, 160, 160, 161, 161, 162, 162, 162, 162, 162, 162, 163, 163, 163, 163, 163, 163, 164, 164, 164, 164, 164, 165, 165, 165, 165, 165, 166, 166, 166, 166, 166, 167, 167, 167, 167, 168, 169, 170, 170, 170, 170, 172, 172, 172, 173, 173, 173, 174, 175, 176, 177, 177, 178, 178, 178, 178, 179, 179, 180, 180, 181, 181, 182, 183, 183, 185, 186, 186, 186, 186, 186, 186, 186, 187, 187, 187, 188, 188, 188, 190, 190, 190, 190, 190, 192, 193, 194, 194, 194, 195, 195, 196, 197, 198, 198, 198, 199, 199, 199, 200, 200, 201, 201, 201, 204, 205, 205, 205, 207, 207, 207, 208, 208, 208, 208, 210, 210, 210, 210, 211, 211, 213, 214, 214, 214, 218, 218, 218, 218, 218, 218, 219, 221, 222, 223, 223, 223, 224, 224, 224, 224, 224, 224, 224, 225, 226, 226, 226, 226, 226, 227, 227, 228, 228, 229, 229, 229, 229, 230, 230, 230, 230, 232, 233, 233, 234, 235, 236, 236, 237, 237, 238, 240, 240, 242, 242, 243, 244, 246, 247, 247, 247, 247, 248, 248, 248, 249, 249, 249, 249, 249, 250, 250, 250, 251, 251, 252, 252, 253, 255, 255, 256, 256, 256, 257, 257, 257, 258, 258, 258, 258, 258, 259, 259, 260, 260, 260, 261, 261, 261, 262, 263, 265, 265, 266, 267, 267, 267, 268, 268, 268, 270, 270, 270, 271, 271, 273, 274, 274, 274, 275, 277, 277, 279, 279, 280, 281, 281, 282, 283, 283, 285, 286, 288, 288, 289, 289, 290, 290, 290, 290, 290, 291, 291, 291, 291, 292, 292, 292, 293, 294, 294, 295, 295, 295, 295, 295, 298, 298, 301, 301, 301, 302, 302, 303, 304, 305, 305, 306, 307, 307, 308, 308, 309, 309, 309, 309, 310, 310, 311, 311, 311, 312, 313, 313, 313, 314, 315, 316, 316, 316, 316, 317, 318, 318, 319, 319, 319, 320, 321, 321, 322, 322, 322, 322, 323, 323, 323, 324, 324, 324, 325, 326, 326, 328, 329, 329, 330, 330, 330, 331, 331, 331, 331, 332, 332, 333, 333, 333, 333, 334, 334, 334, 335, 336, 336, 337, 337, 337, 337, 339, 339, 340, 341, 341, 343, 344, 344, 345, 345, 345, 346, 346, 347, 348, 348, 348, 349, 350, 351, 351, 351, 352, 353, 354, 354, 354, 355, 356, 356, 357, 358, 358, 358, 359, 359, 360, 361, 361, 362, 362, 363, 364, 364, 365, 365, 365, 366, 366, 367, 367, 368, 368, 369, 369, 369, 370, 370, 370, 370, 370, 371, 372, 373, 373, 374, 374, 375, 375, 376, 377, 377, 378, 379, 381, 381, 383, 384, 385, 385, 385, 385, 386, 386, 387, 388, 388, 388, 389, 389, 390, 391, 391, 391, 392, 392, 393, 393, 394, 394, 394, 395, 395, 396, 396, 397, 397, 398, 399, 400, 400, 401, 401, 402, 402, 403, 404, 404, 405, 406, 406, 407, 407, 407, 408, 408, 408, 408, 408, 409, 409, 409, 411, 411, 412, 412, 413, 413, 413, 413, 413, 414, 414, 414, 415, 416, 416, 416, 416, 417, 417, 418, 418, 418, 418, 419, 420, 420, 420, 421, 421, 422, 422, 423, 423, 423, 424, 424, 424, 424, 425, 425, 425, 426, 426, 427, 427, 427, 428, 428, 429, 429, 429, 430, 430, 431, 432, 433, 433, 433, 434, 434, 434, 434, 437, 438, 438, 438, 438, 438, 439, 440, 441, 441, 442, 442, 443, 444, 444, 444, 445, 445, 445, 447, 447, 447, 448, 448, 449, 449, 450, 450, 450, 451, 452, 453, 453, 453, 453, 455, 455, 456, 456, 457, 458, 459, 459, 460, 460, 461, 461, 464, 465, 465, 465, 466, 466, 467, 467, 467, 467, 468, 469, 469, 470, 470, 471, 471, 471, 472, 473, 473, 473, 473, 474, 475, 475, 475, 476, 476, 476, 477, 477, 477, 478, 478, 479, 481, 481, 481, 482, 482, 482, 483, 483, 483, 484, 484, 485, 488, 488, 488, 488, 489, 490, 491, 491, 491, 492, 492, 493, 493, 493, 493, 493, 495, 495, 496, 496, 496, 496, 496, 496, 497, 497, 498, 498, 498, 498, 498, 499, 500, 500, 501, 501, 501, 502, 502, 502, 502, 503, 503, 503, 505, 505, 506, 507, 507, 507, 507, 508, 508, 510, 510, 510, 511, 511, 512, 512, 513, 513, 513, 513, 514, 514, 515, 516, 517, 518, 519, 519, 519, 520, 521, 521, 522, 522, 523, 523, 523, 525, 525, 526, 527, 527, 527, 528, 528, 528, 530, 531, 532, 532, 532, 532, 532, 535, 535, 537, 538, 538, 538, 540, 540, 540, 541, 541, 541, 541, 541, 542, 543, 543, 543, 543, 544, 544, 545, 545, 545, 546, 547, 547, 547, 548, 549, 549, 551, 552, 552, 553, 553, 553, 554, 554, 554, 555, 556, 557, 557, 557, 558, 558, 558, 559, 559, 559, 560, 560, 560, 561, 561, 561, 561, 562, 562, 562, 563, 563, 565, 566, 566, 567, 568, 569, 570, 570, 571, 571, 571, 571, 572, 572, 572, 574, 575, 576, 576, 577, 580, 581, 581, 582, 582, 582, 583, 583, 584, 585, 585, 585, 586, 587, 587, 588, 588, 588, 589, 591, 591, 591, 592, 592, 592, 593, 593, 593, 594, 594, 594, 594, 595, 595, 595, 596, 596, 596, 596, 596, 597, 597, 599, 599, 600, 600, 601, 601, 601, 602, 602, 603, 603, 604, 605, 605, 605, 606, 607, 608, 610, 612, 612, 613, 613, 614, 614, 615, 615, 615, 616, 616, 616, 617, 617, 619, 619, 619, 619, 620, 621, 621, 622, 624, 624, 624, 624, 625, 625, 628, 628, 628, 629, 629, 630, 630, 630, 630, 632, 633, 633, 634, 635, 638, 638, 639, 640, 641, 641, 643, 643, 644, 644, 644, 645, 645, 645, 646, 646, 646, 647, 647, 647, 647, 648, 648, 649, 650, 650, 650, 650, 650, 650, 651, 652, 652, 652, 653, 653, 653, 653, 654, 655, 655, 655, 655, 656, 657, 657, 657, 658, 658, 659, 659, 659, 659, 659, 660, 660, 661, 662, 663, 664, 665, 666, 666, 666, 667, 667, 667, 667, 667, 668, 668, 669, 670, 670, 670, 671, 672, 672, 672, 672, 672, 673, 673, 674, 674, 674, 675, 676, 676, 677, 678, 678, 679, 679, 680, 681, 681, 682, 683, 683, 684, 684, 685, 686, 686, 686, 686, 687, 687, 688, 690, 690, 691, 691, 693, 693, 694, 694, 697, 697, 698, 700, 701, 702, 702, 703, 703, 703, 704, 705, 706, 706, 707, 708, 708, 709, 709, 710, 710, 711, 712, 712, 712, 712, 712, 712, 713, 713, 714, 714, 716, 716, 716, 717, 717, 717, 718, 718, 718, 718, 719, 719, 719, 720, 720, 721, 721, 722, 723, 724, 725, 726, 726, 727, 729, 729, 729, 730, 730, 731, 731, 732, 732, 734, 734, 734, 735, 735, 736, 736, 736, 737, 737, 738, 739, 740, 740, 740, 741, 741, 742, 742, 742, 742, 744, 744, 744, 744, 745, 745, 745, 745, 746, 748, 749, 749, 749, 750, 750, 751, 751, 751, 752, 752, 753, 753, 754, 755, 756, 756, 756, 757, 757, 757, 757, 757, 761, 761, 762, 762, 762, 763, 763, 763, 763, 763, 764, 764, 764, 764, 765, 765, 766, 766, 766, 766, 767, 767, 767, 770, 770, 770, 770, 770, 771, 772, 772, 772, 773, 774, 775, 775, 775, 775, 776, 778, 778, 779, 779, 780, 780, 780, 781, 784, 784, 784, 786, 786, 786, 786, 787, 788, 789, 789, 789, 790, 791, 791, 792, 793, 793, 793, 794, 794, 795, 796, 797, 797, 798, 799, 799, 799, 800, 800, 800, 800, 801, 802, 802, 802, 802, 804, 806, 806, 806, 807, 807, 807, 807, 808, 809, 810, 810, 811, 812, 812, 812, 812, 812, 813, 813, 813, 814, 814, 814, 815, 816, 816, 817, 817, 817, 818, 818, 818, 819, 820, 820, 820, 820, 820, 821, 821, 823, 824, 824, 824, 825, 826, 826, 826, 826, 828, 828, 829, 829, 829, 829, 829, 830, 831, 831, 831, 831, 831, 832, 832, 833, 833, 833, 834, 834, 835, 835, 835, 835, 835, 836, 836, 836, 837, 839, 839, 839, 839, 839, 840, 840, 840, 841, 841, 842, 843, 844, 844, 844, 845, 845, 845, 845, 845, 846, 846, 846, 847, 847, 848, 848, 848, 849, 849, 850, 850, 851, 852, 852, 852, 852, 853, 855, 856, 857, 857, 858, 858, 858, 859, 860, 861, 861, 861, 861, 862, 863, 863, 863, 865, 865, 865, 866, 867, 867, 867, 868, 868, 870, 871, 872, 872, 873, 873, 873, 874, 874, 874, 875, 875, 875, 876, 877, 878, 878, 878, 878, 878, 879, 879, 879, 879, 880, 881, 881, 881, 882, 883, 885, 886, 886, 887, 887, 888, 888, 889, 889, 890, 890, 890, 892, 892, 892, 892, 893, 893, 894, 894, 894, 895, 896, 896, 896, 897, 899, 899, 900, 901, 901, 901, 901, 905, 905, 905, 905, 906, 907, 907, 907, 908, 908, 908, 908, 908, 908, 909, 909, 910, 910, 910, 912, 913, 913, 914, 914, 914, 915, 916, 916, 916, 916, 917, 917, 918, 919, 919, 919, 920, 920, 920, 920, 921, 921, 922, 923, 923, 923, 923, 923, 924, 925, 927, 927, 927, 928, 928, 929, 929, 929, 929, 930, 930, 931, 932, 932, 932, 933, 933, 934, 934, 935, 935, 936, 937, 937, 937, 939, 940, 940, 941, 941, 941, 941, 942, 942, 943, 943, 945, 946, 946, 946, 948, 949, 949, 951, 953, 953, 954, 954, 954, 954, 954, 955, 956, 956, 956, 957, 957, 957, 957, 959, 960, 960, 961, 961, 963, 963, 963, 964, 964, 964, 964, 965, 966, 967, 968, 969, 969, 970, 972, 972, 973, 973, 974, 975, 975, 975, 976, 977, 978, 978, 979, 979, 980, 980, 980, 980, 981, 982, 982, 984, 986, 986, 986, 986, 986, 987, 988, 988, 990, 990, 990, 990, 990, 991, 991, 991, 991, 991, 991, 992, 992, 992, 992, 992, 993, 993, 993, 993, 995, 996, 996, 996, 997, 997, 997, 997, 997, 998, 998, 998, 999, 999, 1000, 1001, 1001, 1002, 1003, 1003, 1004, 1004, 1004, 1006, 1007, 1007, 1007, 1008, 1008, 1008, 1009, 1010, 1010, 1011, 1011, 1012, 1012, 1012, 1013, 1013, 1013, 1014, 1014, 1014, 1016, 1016, 1016, 1017, 1017, 1017, 1018, 1018, 1018, 1019, 1019, 1020, 1020, 1021, 1021, 1021, 1022, 1023, 1023, 1023, 1024, 1024, 1024, 1025, 1026, 1026, 1027, 1028, 1028, 1028, 1028, 1029, 1029, 1029, 1030, 1031, 1031, 1032, 1033, 1034, 1034, 1035, 1035, 1036, 1038, 1039, 1039, 1040, 1040, 1040, 1040, 1040, 1040, 1042, 1042, 1043, 1043, 1043, 1043, 1044, 1045, 1045, 1045, 1045, 1047, 1047, 1048, 1048, 1049, 1049, 1050, 1050, 1051, 1051, 1053, 1053, 1053, 1054, 1054, 1055, 1055, 1056, 1056, 1057, 1057, 1058, 1058, 1058, 1058, 1059, 1059, 1059, 1061, 1061, 1061, 1061, 1062, 1062, 1062, 1063, 1063, 1063, 1063, 1064, 1064, 1064, 1064, 1064, 1065, 1065, 1066, 1066, 1067, 1067, 1069, 1069, 1069, 1070, 1071, 1071, 1072, 1072, 1072, 1073, 1073, 1074, 1074, 1074, 1075, 1076, 1077, 1077, 1078, 1078, 1078, 1079, 1079, 1079, 1081, 1082, 1082, 1083, 1084, 1084, 1084, 1084, 1085, 1085, 1086, 1086, 1087, 1087, 1088, 1088, 1089, 1089, 1090, 1090, 1090, 1091, 1093, 1093, 1093, 1094, 1094, 1094, 1094, 1095, 1095, 1095, 1095, 1095, 1095, 1096, 1097, 1098, 1098, 1098, 1098, 1100, 1102, 1102, 1103, 1103, 1103, 1104, 1104, 1105, 1105, 1105, 1105, 1106, 1106, 1106, 1106, 1107, 1107, 1107, 1108, 1110, 1111, 1111, 1112, 1113, 1113, 1113, 1113, 1115, 1115, 1115, 1115, 1115, 1116, 1116, 1117, 1117, 1119, 1119, 1119, 1121, 1122, 1122, 1122, 1122, 1123, 1124, 1124, 1125, 1125, 1127, 1127, 1127, 1128, 1129, 1129, 1129, 1130, 1130, 1131, 1131, 1132, 1132, 1132, 1132, 1134, 1135, 1137, 1137, 1138, 1138, 1138, 1138, 1139, 1140, 1140, 1140, 1140, 1142, 1142, 1142, 1142, 1142, 1142, 1143, 1143, 1145, 1145, 1148, 1148, 1150, 1150, 1151, 1151, 1151, 1152, 1152, 1152, 1153, 1153, 1154, 1155, 1156, 1156, 1156, 1156, 1157, 1158, 1158, 1158, 1159, 1159, 1159, 1160, 1160, 1161, 1161, 1161, 1162, 1162, 1163, 1163, 1163, 1164, 1164, 1165, 1165, 1167, 1167, 1167, 1168, 1168, 1168, 1169, 1170, 1170, 1171, 1171, 1171, 1172, 1172, 1172, 1173, 1173, 1173, 1174, 1174, 1174, 1174, 1176, 1176, 1176, 1176, 1176, 1177, 1178, 1178, 1178, 1179, 1179, 1179, 1180, 1180, 1181, 1181, 1182, 1182, 1182, 1183, 1183, 1184, 1184, 1184, 1184, 1184, 1185, 1186, 1186, 1188, 1188, 1189, 1189, 1190, 1190, 1191, 1191, 1191, 1192, 1192, 1193, 1193, 1195, 1197, 1197, 1198, 1198, 1198, 1199, 1199, 1199, 1200, 1201, 1201, 1201, 1202, 1202, 1202, 1202, 1204, 1204, 1205, 1205, 1205, 1205, 1205, 1206, 1206, 1206, 1207, 1207, 1207, 1207, 1207, 1207, 1209, 1210, 1210, 1211, 1212, 1213, 1213, 1214, 1214, 1215, 1215, 1216, 1216, 1217, 1217, 1217, 1219, 1219, 1219, 1219, 1220, 1220, 1222, 1222, 1223, 1224, 1224, 1225, 1225, 1226, 1226, 1226, 1227, 1227, 1227, 1227, 1227, 1227, 1228, 1228, 1228, 1229, 1230, 1230, 1232, 1232, 1232, 1232, 1232, 1232, 1233, 1234, 1235, 1235, 1235, 1236, 1237, 1238, 1239, 1240, 1240, 1240, 1240, 1240, 1240, 1241, 1241, 1242, 1243, 1243, 1243, 1243, 1244, 1244, 1246, 1246, 1247, 1247, 1249, 1250, 1251, 1251, 1252, 1252, 1252, 1252, 1252, 1252, 1253, 1253, 1253, 1253, 1254, 1254, 1255, 1256, 1257, 1257, 1257, 1259, 1259, 1261, 1261, 1262, 1263, 1263, 1264, 1265, 1265, 1265, 1266, 1266, 1268, 1268, 1269, 1270, 1270, 1270, 1270, 1271, 1271, 1271, 1271, 1272, 1272, 1273, 1273, 1274, 1274, 1274, 1274, 1275, 1275, 1275, 1275, 1276, 1276, 1276, 1276, 1276, 1277, 1278, 1279, 1279, 1280, 1280, 1281, 1282, 1282, 1283, 1283, 1284, 1284, 1284, 1286, 1286, 1289, 1290, 1290, 1290, 1291, 1292, 1292, 1293, 1293, 1294, 1296, 1296, 1296, 1296, 1297, 1297, 1297, 1298, 1299, 1300, 1300, 1301, 1302, 1303, 1304, 1304, 1305, 1305, 1306, 1306, 1307, 1307, 1307, 1307, 1307, 1308, 1308, 1308, 1308, 1309, 1309, 1310, 1311, 1312, 1312, 1313, 1313, 1313, 1314, 1315, 1316, 1316, 1316, 1317, 1319, 1320, 1320, 1320, 1320, 1321, 1322, 1322, 1323, 1323, 1323, 1324, 1324, 1325, 1327, 1328, 1329, 1329, 1330, 1330, 1330, 1330, 1332, 1332, 1332, 1333, 1333, 1334, 1335, 1335, 1336, 1336, 1336, 1338, 1338, 1338, 1339, 1339, 1340, 1340, 1340, 1341, 1341, 1341, 1342, 1343, 1343, 1345, 1345, 1345, 1346, 1346, 1346, 1346, 1346, 1346, 1347, 1348, 1349, 1349, 1349, 1349, 1351, 1352, 1353, 1353, 1353, 1354, 1354, 1355, 1355, 1356, 1356, 1356, 1356, 1358, 1358, 1359, 1359, 1359, 1359, 1359, 1360, 1360, 1360, 1361, 1361, 1361, 1362, 1362, 1363, 1363, 1363, 1365, 1365, 1366, 1367, 1367, 1370, 1371, 1371, 1372, 1372, 1373, 1373, 1373, 1374, 1375, 1375, 1375, 1377, 1377, 1378, 1378, 1378, 1380, 1380, 1381, 1381, 1381, 1382, 1382, 1382, 1382, 1382, 1382, 1383, 1383, 1383, 1384, 1384, 1384, 1385, 1385, 1385, 1385, 1386, 1386, 1387, 1387, 1388, 1388, 1388, 1389, 1389, 1389, 1392, 1393, 1393, 1394, 1394, 1395, 1395, 1395, 1396, 1397, 1398, 1398, 1398, 1399, 1399, 1399, 1400, 1401, 1402, 1402, 1402, 1403, 1404, 1405, 1406, 1406, 1406, 1406, 1407, 1407, 1407, 1407, 1409, 1409, 1409, 1410, 1410, 1410, 1410, 1410, 1411, 1411, 1412, 1413, 1413, 1413, 1414, 1414, 1415, 1415, 1415, 1416, 1416, 1416, 1417, 1417, 1417, 1417, 1417, 1419, 1420, 1420, 1420, 1421, 1422, 1422, 1422, 1422, 1425, 1426, 1427, 1427, 1428, 1428, 1430, 1431, 1431, 1432, 1432, 1432, 1433, 1433, 1434, 1434, 1434, 1434, 1434, 1435, 1436, 1436, 1436, 1436, 1436, 1437, 1438, 1438, 1438, 1438, 1439, 1439, 1440, 1440, 1440, 1440, 1441, 1441, 1442, 1443, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1445, 1446, 1446, 1446, 1447, 1448, 1449, 1449, 1450, 1450, 1450, 1451, 1451, 1452, 1452, 1452, 1453, 1454, 1455, 1456, 1458, 1459, 1459, 1459, 1459, 1460, 1460, 1461, 1461, 1461, 1462, 1462, 1462, 1462, 1462, 1462, 1463, 1463, 1465, 1465, 1465, 1466, 1467, 1468, 1469, 1470, 1472, 1472, 1473, 1474, 1474, 1474, 1474, 1475, 1476, 1477, 1477, 1477, 1477, 1478, 1478, 1480, 1481, 1481, 1481, 1481, 1481, 1481, 1482, 1482, 1482, 1483, 1484, 1485, 1485, 1486, 1486, 1486, 1488, 1488, 1489, 1489, 1489, 1491, 1491, 1492, 1492, 1493, 1495, 1495, 1495, 1496, 1496, 1497, 1497, 1497, 1497, 1497, 1498, 1498, 1499, 1500, 1500, 1501, 1501, 1501, 1501, 1502, 1503, 1503, 1503, 1503, 1503, 1503, 1504, 1505, 1505, 1505, 1506, 1506, 1506, 1506, 1509, 1509, 1509, 1510, 1510, 1511, 1511, 1511, 1511, 1512, 1513, 1513, 1513, 1514, 1514, 1515, 1516, 1516, 1517, 1517, 1518, 1518, 1519, 1519, 1520, 1521, 1522, 1522, 1524, 1525, 1525, 1525, 1525, 1526, 1526, 1526, 1526, 1526, 1526, 1528, 1528, 1528, 1529, 1532, 1532, 1532, 1534, 1534, 1535, 1536, 1536, 1536, 1537, 1537, 1538, 1538, 1538, 1539, 1539, 1539, 1539, 1540, 1541, 1542, 1542, 1543, 1544, 1544, 1544, 1545, 1545, 1545, 1546, 1547, 1547, 1547, 1547, 1547, 1548, 1550, 1551, 1551, 1551, 1551, 1552, 1552, 1552, 1552, 1553, 1554, 1554, 1554, 1555, 1555, 1555, 1555, 1556, 1556, 1557, 1558, 1559, 1559, 1559, 1560, 1560, 1560, 1560, 1561, 1561, 1562, 1562, 1563, 1564, 1564, 1565, 1565, 1565, 1566, 1567, 1567, 1568, 1568, 1569, 1569, 1570, 1570, 1570, 1571, 1571, 1571, 1571, 1572, 1572, 1572, 1573, 1573, 1573, 1573, 1574, 1574, 1575, 1575, 1575, 1575, 1575, 1576, 1576, 1576, 1576, 1576, 1578, 1578, 1578, 1579, 1579, 1579, 1580, 1581, 1581, 1581, 1581, 1581, 1582, 1582, 1582, 1582, 1583, 1583, 1586, 1586, 1586, 1586, 1586, 1587, 1588, 1589, 1590, 1591, 1591, 1591, 1594, 1595, 1595, 1595, 1596, 1598, 1598, 1599, 1600, 1600, 1601, 1601, 1601, 1602, 1602, 1602, 1603, 1603, 1605, 1605, 1606, 1607, 1608, 1608, 1608, 1609, 1609, 1609, 1609, 1611, 1611, 1612, 1612, 1612, 1612, 1612, 1612, 1614, 1615, 1615, 1615, 1615, 1616, 1618, 1618, 1619, 1620, 1621, 1621, 1621, 1622, 1623, 1623, 1624, 1624, 1624, 1624, 1625, 1625, 1625, 1626, 1626, 1627, 1627, 1627, 1629, 1629, 1630, 1630, 1631, 1631, 1634, 1634, 1634, 1634, 1634, 1634, 1635, 1636, 1639, 1639, 1640, 1641, 1641, 1641, 1642, 1642, 1643, 1645, 1645, 1645, 1646, 1647, 1647, 1647, 1648, 1649, 1649, 1649, 1649, 1649, 1651, 1652, 1653, 1653, 1655, 1655, 1655, 1655, 1655, 1655, 1657, 1657, 1657, 1658, 1658, 1659, 1659, 1659, 1659, 1660, 1660, 1660, 1660, 1662, 1663, 1663, 1664, 1664, 1666, 1666, 1666, 1666, 1668, 1669, 1669, 1669, 1671, 1671, 1672, 1672, 1673, 1673, 1673, 1673, 1674, 1674, 1675, 1675, 1675, 1677, 1677, 1677, 1677, 1678, 1678, 1678, 1679, 1679, 1679, 1679, 1680, 1680, 1680, 1681, 1681, 1681, 1682, 1682, 1682, 1683, 1683, 1683, 1684, 1684, 1684, 1685, 1685, 1686, 1687, 1688, 1688, 1688, 1689, 1689, 1691, 1691, 1691, 1692, 1693, 1693, 1693, 1696, 1697, 1697, 1698, 1699, 1700, 1700, 1701, 1702, 1703, 1703, 1705, 1705, 1705, 1707, 1708, 1708, 1708, 1709, 1711, 1712, 1712, 1712, 1714, 1714, 1714, 1714, 1715, 1716, 1716, 1717, 1718, 1718, 1719, 1719, 1719, 1720, 1720, 1720, 1721, 1721, 1722, 1722, 1722, 1722, 1722, 1723, 1723, 1724, 1724, 1725, 1726, 1726, 1727, 1727, 1728, 1728, 1730, 1731, 1731, 1734, 1735, 1735, 1735, 1736, 1737, 1737, 1738, 1738, 1738, 1739, 1739, 1739, 1739, 1739, 1740, 1740, 1740, 1740, 1740, 1741, 1741, 1741, 1741, 1741, 1742, 1743, 1744, 1744, 1744, 1745, 1746, 1746, 1747, 1748, 1749, 1749, 1749, 1749, 1751, 1751, 1751, 1752, 1752, 1752, 1752, 1753, 1754, 1755, 1755, 1755, 1756, 1756, 1757, 1757, 1757, 1757, 1758, 1759, 1759, 1759, 1760, 1760, 1762, 1764, 1766, 1766, 1767, 1767, 1768, 1769, 1769, 1770, 1770, 1770, 1771, 1772, 1773, 1774, 1775, 1775, 1775, 1776, 1776, 1776, 1777, 1777, 1778, 1778, 1779, 1779, 1780, 1780, 1781, 1782, 1784, 1784, 1784, 1785, 1785, 1785, 1785, 1787, 1788, 1789, 1789, 1789, 1790, 1790, 1790, 1791, 1791, 1791, 1791, 1791, 1792, 1792, 1793, 1793, 1793, 1793, 1794, 1794, 1795, 1795, 1796, 1796, 1797, 1797, 1798, 1798, 1798, 1799, 1799, 1800, 1800, 1800, 1801, 1801, 1802, 1802, 1804, 1804, 1804, 1806, 1806, 1808, 1809, 1810, 1810, 1811, 1811, 1814, 1814, 1814, 1815, 1815, 1816, 1816, 1816, 1816, 1817, 1817, 1818, 1819, 1819, 1819, 1820, 1820, 1820, 1821, 1821, 1822, 1823, 1823, 1824, 1824, 1824, 1825, 1825, 1825, 1826, 1826, 1826, 1827, 1827, 1827, 1828, 1828, 1830, 1831, 1832, 1832, 1832, 1832, 1833, 1833, 1833, 1833, 1835, 1837, 1838, 1839, 1840, 1840, 1840, 1840, 1840, 1840, 1841, 1842, 1842, 1843, 1843, 1844, 1844, 1844, 1844, 1844, 1845, 1846, 1847, 1847, 1847, 1848, 1849, 1849, 1849, 1850, 1850, 1850, 1851, 1851, 1851, 1852, 1852, 1853, 1853, 1853, 1854, 1854, 1855, 1855, 1855, 1855, 1855, 1855, 1856, 1856, 1856, 1856, 1857, 1857, 1857, 1857, 1858, 1859, 1859, 1860, 1860, 1860, 1860, 1861, 1861, 1863, 1863, 1865, 1865, 1866, 1866, 1866, 1866, 1866, 1867, 1867, 1867, 1867, 1867, 1868, 1869, 1869, 1869, 1869, 1869, 1869, 1870, 1870, 1870, 1870, 1871, 1872, 1873, 1874, 1875, 1875, 1876, 1876, 1876, 1876, 1877, 1877, 1878, 1878, 1878, 1879, 1879, 1880, 1880, 1880, 1881, 1881, 1883, 1883, 1885, 1885, 1885, 1885, 1885, 1885, 1886, 1886, 1886, 1887, 1887, 1887, 1887, 1888, 1888, 1890, 1891, 1891, 1891, 1892, 1894, 1894, 1894, 1894, 1896, 1896, 1896, 1896, 1897, 1899, 1899, 1900, 1900, 1901, 1901, 1902, 1903, 1904, 1905, 1905, 1905, 1906, 1906, 1906, 1907, 1907, 1908, 1908, 1909, 1910, 1910, 1911, 1912, 1912, 1912, 1913, 1914, 1914, 1914, 1915, 1915, 1915, 1916, 1916, 1916, 1917, 1918, 1918, 1919, 1919, 1920, 1920, 1920, 1920, 1921, 1921, 1922, 1923, 1925, 1925, 1925, 1925, 1926, 1928, 1929, 1929, 1930, 1930, 1931, 1931, 1931, 1931, 1932, 1932, 1932, 1932, 1932, 1933, 1933, 1933, 1933, 1934, 1934, 1934, 1934, 1934, 1935, 1935, 1935, 1936, 1937, 1938, 1938, 1938, 1938, 1940, 1941, 1941, 1941, 1942, 1942, 1943, 1943, 1944, 1944, 1944, 1944, 1945, 1946, 1946, 1947, 1948, 1948, 1948, 1949, 1949, 1949, 1949, 1949, 1950, 1950, 1950, 1951, 1951, 1951, 1951, 1951, 1952, 1953, 1955, 1955, 1956, 1956, 1956, 1957, 1957, 1957, 1958, 1958, 1960, 1960, 1960, 1960, 1961, 1963, 1965, 1965, 1965, 1967, 1967, 1968, 1968, 1969, 1969, 1969, 1969, 1970, 1970, 1971, 1971, 1971, 1972, 1972, 1973, 1973, 1973, 1973, 1973, 1974, 1974, 1975, 1975, 1976, 1976, 1976, 1976, 1977, 1978, 1978, 1979, 1979, 1979, 1980, 1980, 1981, 1981, 1982, 1982, 1982, 1983, 1983, 1983, 1984, 1984, 1986, 1986, 1987, 1989, 1989, 1989, 1989, 1989, 1990, 1990, 1990, 1991, 1991, 1991, 1991, 1992, 1992, 1994, 1994, 1994, 1995, 1995, 1995, 1995, 1995, 1996, 1996, 1996, 1997, 1997, 1998, 1998, 1998, 1998, 1999, 1999, 2000, 2000, 2002, 2003, 2003, 2005, 2009, 2010, 2010, 2011, 2012, 2013, 2014, 2014, 2015, 2016, 2016, 2016, 2016, 2016, 2017, 2018, 2019, 2020, 2020, 2021, 2021, 2021, 2021, 2024, 2026, 2027, 2027, 2028, 2028, 2029, 2029, 2030, 2031, 2032, 2032, 2033, 2034, 2035, 2035, 2036, 2036, 2036, 2036, 2036, 2037, 2037, 2037, 2037, 2038, 2038, 2039, 2039, 2039, 2040, 2041, 2041, 2042, 2042, 2042, 2042, 2042, 2042, 2042, 2043, 2044, 2044, 2045, 2045, 2045, 2046, 2047, 2047, 2048, 2048, 2049, 2051, 2051, 2052, 2052, 2054, 2054, 2054, 2054, 2055, 2056, 2056, 2057, 2058, 2058, 2059, 2059, 2062, 2063, 2063, 2063, 2063, 2063, 2063, 2064, 2064, 2065, 2065, 2065, 2065, 2066, 2066, 2067, 2067, 2068, 2068, 2068, 2068, 2068, 2069, 2070, 2070, 2071, 2071, 2071, 2072, 2073, 2073, 2073, 2075, 2075, 2075, 2076, 2077, 2077, 2078, 2078, 2079, 2079, 2079, 2079, 2080, 2080, 2080, 2081, 2082, 2082, 2082, 2082, 2083, 2083, 2083, 2084, 2084, 2084, 2085, 2085, 2086, 2086, 2086, 2087, 2087, 2087, 2088, 2088, 2088, 2088, 2088, 2089, 2089, 2089, 2089, 2089, 2089, 2089, 2090, 2091, 2091, 2091, 2091, 2092, 2093, 2093, 2094, 2094, 2095, 2096, 2096, 2097, 2097, 2097, 2097, 2098, 2098, 2098, 2098, 2099, 2100, 2102, 2102, 2102, 2102, 2102, 2104, 2104, 2104, 2105, 2105, 2106, 2106, 2107, 2108, 2109, 2109, 2110, 2110, 2111, 2111, 2112, 2114, 2115, 2115, 2116, 2117, 2117, 2118, 2119, 2119, 2119, 2120, 2121, 2121, 2121, 2122, 2122, 2122, 2123, 2124, 2124, 2125, 2125, 2125, 2125, 2127, 2127, 2127, 2127, 2128, 2128, 2128, 2128, 2128, 2129, 2129, 2130, 2131, 2131, 2131, 2132, 2132, 2132, 2133, 2133, 2133, 2133, 2133, 2133, 2133, 2133, 2134, 2135, 2136, 2137, 2137, 2137, 2138, 2138, 2139, 2140, 2140, 2140, 2140, 2142, 2143, 2144, 2144, 2145, 2145, 2145, 2145, 2146, 2146, 2146, 2147, 2147, 2147, 2147, 2147, 2148, 2148, 2148, 2148, 2149, 2149, 2149, 2150, 2151, 2151, 2153, 2153, 2153, 2153, 2154, 2154, 2154, 2155, 2155, 2156, 2157, 2157, 2157, 2157, 2158, 2158, 2158, 2158, 2158, 2159, 2159, 2160, 2160, 2160, 2160, 2161, 2162, 2162, 2162, 2162, 2162, 2163, 2164, 2164, 2167, 2168, 2169, 2169, 2169, 2170, 2172, 2172, 2172, 2172, 2172, 2173, 2173, 2174, 2174, 2175, 2175, 2176, 2176, 2176, 2176, 2177, 2177, 2179, 2179, 2180, 2180, 2180, 2183, 2183, 2183, 2183, 2184, 2185, 2185, 2185, 2185, 2186, 2186, 2186, 2187, 2187, 2188, 2189, 2189, 2189, 2190, 2190, 2191, 2191, 2191, 2191, 2191, 2192, 2193, 2194, 2194, 2195, 2195, 2195, 2195, 2196, 2196, 2197, 2197, 2197, 2198, 2198, 2198, 2199, 2199, 2199, 2200, 2200, 2201, 2201, 2202, 2202, 2202, 2203, 2203, 2204, 2205, 2205, 2205, 2205, 2205, 2206, 2206, 2206, 2207, 2207, 2207, 2210, 2210, 2212, 2213, 2214, 2214, 2215, 2216, 2216, 2216, 2217, 2217, 2219, 2219, 2219, 2219, 2220, 2220, 2221, 2221, 2222, 2222, 2223, 2223, 2224, 2224, 2225, 2225, 2226, 2226, 2226, 2226, 2227, 2228, 2228, 2228, 2229, 2229, 2229, 2230, 2230, 2231, 2231, 2232, 2232, 2232, 2234, 2234, 2234, 2235, 2235, 2236, 2237, 2237, 2238, 2238, 2239, 2239, 2239, 2240, 2240, 2241, 2241, 2241, 2242, 2244, 2244, 2245, 2245, 2245, 2245, 2246, 2248, 2249, 2250, 2251, 2251, 2251, 2251, 2252, 2252, 2253, 2254, 2254, 2255, 2256, 2256, 2256, 2258, 2258, 2258, 2259, 2259, 2259, 2259, 2260, 2260, 2261, 2261, 2262, 2262, 2262, 2263, 2265, 2265, 2265, 2265, 2266, 2266, 2267, 2268, 2269, 2269, 2270, 2270, 2271, 2271, 2272, 2273, 2273, 2273, 2275, 2275, 2276, 2276, 2277, 2277, 2278, 2278, 2280, 2280, 2281, 2282, 2282, 2282, 2282, 2284, 2284, 2284, 2284, 2285, 2285, 2286, 2287, 2287, 2288, 2288, 2289, 2291, 2292, 2292, 2293, 2294, 2295, 2296, 2296, 2297, 2298, 2298, 2299, 2299, 2299, 2300, 2300, 2301, 2301, 2301, 2302, 2302, 2302, 2302, 2303, 2303, 2303, 2304, 2304, 2306, 2306, 2307, 2307, 2307, 2307, 2309, 2309, 2309, 2310, 2310, 2310, 2310, 2311, 2311, 2311, 2312, 2312, 2312, 2313, 2313, 2316, 2317, 2317, 2317, 2317, 2317, 2317, 2317, 2318, 2318, 2319, 2319, 2319, 2320, 2322, 2323, 2323, 2324, 2324, 2324, 2325, 2326, 2327, 2327, 2328, 2329, 2330, 2331, 2332, 2333, 2333, 2334, 2334, 2336, 2336, 2337, 2337, 2338, 2338, 2339, 2339, 2339, 2340, 2340, 2340, 2341, 2342, 2343, 2344, 2345, 2345, 2345, 2345, 2346, 2346, 2347, 2347, 2347, 2347, 2349, 2349, 2349, 2350, 2350, 2351, 2351, 2351, 2351, 2352, 2352, 2353, 2354, 2355, 2356, 2356, 2358, 2359, 2360, 2361, 2362, 2362, 2362, 2363, 2363, 2363, 2364, 2365, 2365, 2365, 2365, 2366, 2367, 2367, 2367, 2367, 2368, 2370, 2370, 2370, 2372, 2372, 2372, 2372, 2372, 2373, 2373, 2373, 2374, 2374, 2375, 2375, 2375, 2376, 2376, 2377, 2377, 2377, 2377, 2378, 2379, 2379, 2380, 2380, 2380, 2381, 2382, 2382, 2382, 2382, 2384, 2384, 2384, 2385, 2387, 2387, 2387, 2388, 2389, 2389, 2389, 2389, 2389, 2390, 2391, 2391, 2392, 2392, 2392, 2394, 2394, 2395, 2395, 2395, 2396, 2396, 2397, 2397, 2397, 2397, 2398, 2398, 2398, 2399, 2400, 2401, 2402, 2404, 2404, 2405, 2405, 2405, 2407, 2408, 2409, 2409, 2409, 2409, 2410, 2410, 2410, 2410, 2410, 2410, 2410, 2411, 2411, 2412, 2412, 2414, 2414, 2415, 2415, 2416, 2416, 2417, 2417, 2418, 2418, 2420, 2421, 2422, 2424, 2424, 2424, 2425, 2425, 2426, 2426, 2426, 2426, 2427, 2427, 2427, 2427, 2427, 2428, 2430, 2432, 2432, 2432, 2432, 2433, 2433, 2433, 2433, 2433, 2434, 2435, 2435, 2435, 2435, 2436, 2437, 2437, 2437, 2437, 2438, 2438, 2439, 2439, 2439, 2440, 2440, 2441, 2441, 2441, 2442, 2443, 2443, 2444, 2444, 2444, 2444, 2447, 2447, 2448, 2448, 2448, 2449, 2449, 2449, 2450, 2451, 2451, 2451, 2453, 2453, 2454, 2454, 2454, 2454, 2455, 2456, 2456, 2457, 2457, 2457, 2458, 2458, 2458, 2459, 2459, 2459, 2459, 2460, 2460, 2461, 2461, 2462, 2463, 2463, 2463, 2463, 2464, 2464, 2464, 2464, 2464, 2465, 2465, 2465, 2466, 2467, 2467, 2467, 2467, 2469, 2470, 2471, 2471, 2472, 2472, 2473, 2473, 2473, 2474, 2474, 2474, 2474, 2475, 2475, 2476, 2476, 2477, 2478, 2479, 2482, 2482, 2483, 2483, 2485, 2485, 2485, 2485, 2486, 2487, 2488, 2489, 2489, 2490, 2490, 2491, 2491, 2491, 2493, 2494, 2494, 2495, 2495, 2495, 2495, 2495, 2495, 2496, 2496, 2496, 2496, 2497, 2497, 2497, 2498, 2498, 2499, 2501, 2502, 2503, 2504, 2504, 2505, 2506, 2506, 2507, 2508, 2508, 2508, 2509, 2509, 2513, 2513, 2513, 2513, 2514, 2514, 2515, 2515, 2516, 2516, 2516, 2518, 2518, 2519, 2519, 2519, 2519, 2520, 2520, 2520, 2520, 2521, 2521, 2521, 2522, 2523, 2523, 2523, 2524, 2524, 2524, 2524, 2525, 2525, 2527, 2527, 2527, 2527, 2527, 2528, 2528, 2529, 2531, 2531, 2532, 2532, 2532, 2533, 2534, 2534, 2535, 2535, 2535, 2536, 2537, 2537, 2537, 2538, 2538, 2539, 2539, 2539, 2539, 2539, 2541, 2541, 2541, 2542, 2542, 2543, 2544, 2544, 2544, 2544, 2545, 2545, 2545, 2546, 2546, 2546, 2546, 2547, 2547, 2547, 2548, 2548, 2548, 2550, 2550, 2550, 2550, 2550, 2551, 2552, 2552, 2553, 2554, 2554, 2554, 2555, 2555, 2556, 2556, 2557, 2557, 2557, 2558, 2560, 2561, 2561, 2561, 2561, 2562, 2563, 2563, 2564, 2564, 2564, 2566, 2566, 2566, 2566, 2566, 2566, 2567, 2567, 2567, 2568, 2569, 2569, 2569, 2571, 2572, 2573, 2573, 2574, 2574, 2576, 2576, 2577, 2577, 2578, 2580, 2580, 2581, 2581, 2581, 2581, 2584, 2584, 2585, 2586, 2587, 2587, 2588, 2588, 2588, 2589, 2589, 2590, 2590, 2591, 2591, 2591, 2592, 2592, 2592, 2593, 2593, 2593, 2594, 2594, 2594, 2596, 2596, 2597, 2598, 2599, 2599, 2599, 2600, 2601, 2601, 2602, 2603, 2603, 2604, 2604, 2604, 2605, 2607, 2608, 2608, 2609, 2609, 2609, 2609, 2611, 2611, 2612, 2612, 2613, 2613, 2613, 2613, 2613, 2614, 2614, 2615, 2615, 2615, 2615, 2615, 2616, 2616, 2617, 2617, 2617, 2618, 2619, 2619, 2620, 2621, 2622, 2622, 2622, 2623, 2624, 2625, 2627, 2628, 2628, 2628, 2628, 2629, 2630, 2630, 2630, 2630, 2631, 2632, 2632, 2632, 2632, 2633, 2633, 2633, 2633, 2633, 2634, 2634, 2635, 2636, 2636, 2636, 2636, 2637, 2637, 2637, 2637, 2637, 2638, 2638, 2638, 2638, 2640, 2640, 2644, 2646, 2646, 2647, 2648, 2649, 2650, 2650, 2650, 2651, 2651, 2651, 2651, 2652, 2652, 2653, 2654, 2654, 2654, 2654, 2655, 2655, 2656, 2656, 2657, 2657, 2657, 2659, 2659, 2660, 2660, 2660, 2660, 2661, 2661, 2662, 2662, 2663, 2663, 2663, 2664, 2665, 2665, 2665, 2666, 2667, 2668, 2670, 2670, 2670, 2670, 2672, 2672, 2673, 2673, 2674, 2674, 2675, 2676, 2676, 2676, 2676, 2677, 2677, 2677, 2677, 2677, 2677, 2679, 2680, 2681, 2683, 2683, 2684, 2684, 2684, 2684, 2685, 2686, 2687, 2688, 2688, 2688, 2689, 2689, 2689, 2689, 2690, 2690, 2690, 2690, 2691, 2691, 2692, 2692, 2692, 2692, 2693, 2693, 2694, 2694, 2694, 2694, 2694, 2695, 2695, 2695, 2696, 2697, 2698, 2699, 2700, 2700, 2701, 2702, 2702, 2704, 2704, 2704, 2705, 2705, 2705, 2705, 2706, 2707, 2707, 2708, 2708, 2710, 2710, 2710, 2711, 2711, 2711, 2711, 2711, 2711, 2712, 2713, 2713, 2714, 2715, 2716, 2717, 2717, 2718, 2718, 2718, 2718, 2719, 2719, 2720, 2722, 2723, 2723, 2724, 2724, 2725, 2726, 2726, 2727, 2728, 2729, 2729, 2729, 2729, 2730, 2731, 2732, 2733, 2734, 2734, 2734, 2735, 2735, 2736, 2736, 2736, 2737, 2738, 2739, 2739, 2740, 2740, 2741, 2741, 2742, 2742, 2743, 2743, 2743, 2744, 2744, 2746, 2747, 2748, 2748, 2748, 2748, 2749, 2749, 2749, 2750, 2750, 2750, 2752, 2752, 2754, 2754, 2754, 2755, 2755, 2756, 2756, 2757, 2757, 2758, 2758, 2759, 2759, 2759, 2759, 2761, 2762, 2762, 2762, 2762, 2762, 2763, 2763, 2763, 2764, 2764, 2764, 2765, 2766, 2766, 2766, 2766, 2767, 2767, 2768, 2769, 2770, 2770, 2770, 2770, 2771, 2771, 2771, 2772, 2772, 2772, 2772, 2774, 2776, 2776, 2776, 2776, 2776, 2777, 2778, 2779, 2779, 2779, 2780, 2780, 2780, 2781, 2781, 2782, 2783, 2783, 2784, 2784, 2784, 2785, 2785, 2786, 2786, 2786, 2787, 2787, 2787, 2787, 2788, 2788, 2789, 2789, 2789, 2789, 2790, 2790, 2790, 2790, 2791, 2791, 2791, 2791, 2792, 2792, 2792, 2792, 2792, 2793, 2793, 2794, 2795, 2795, 2796, 2796, 2797, 2797, 2798, 2800, 2800, 2801, 2801, 2801, 2802, 2802, 2803, 2803, 2804, 2804, 2805, 2805, 2805, 2805, 2805, 2805, 2806, 2806, 2806, 2807, 2808, 2809, 2809, 2809, 2809, 2809, 2809, 2810, 2810, 2811, 2811, 2811, 2812, 2812, 2812, 2813, 2816, 2816, 2816, 2817, 2817, 2818, 2818, 2818, 2818, 2818, 2819, 2819, 2819, 2820, 2820, 2820, 2821, 2821, 2821, 2822, 2823, 2823, 2823, 2824, 2824, 2824, 2825, 2826, 2826, 2826, 2827, 2827, 2827, 2827, 2827, 2827, 2828, 2828, 2830, 2830, 2830, 2831, 2831, 2833, 2833, 2833, 2833, 2835, 2836, 2838, 2838, 2838, 2839, 2839, 2840, 2840, 2841, 2842, 2842, 2843, 2844, 2845, 2845, 2846, 2846, 2848, 2848, 2848, 2849, 2850, 2851, 2852, 2852, 2852, 2853, 2853, 2853, 2854, 2854, 2855, 2855, 2856, 2856, 2857, 2857, 2857, 2857, 2858, 2858, 2859, 2859, 2859, 2860, 2861, 2861, 2861, 2862, 2862, 2863, 2863, 2863, 2864, 2865, 2868, 2868, 2868, 2868, 2868, 2869, 2869, 2870, 2870, 2870, 2871, 2871, 2871, 2872, 2873, 2874, 2875, 2875, 2876, 2876, 2877, 2877, 2878, 2879, 2880, 2880, 2881, 2882, 2884, 2884, 2884, 2885, 2885, 2886, 2887, 2887, 2887, 2887, 2887, 2888, 2888, 2888, 2888, 2889, 2889, 2889, 2890, 2890, 2890, 2891, 2893, 2894, 2895, 2896, 2896, 2897, 2897, 2898, 2898, 2898, 2900, 2900, 2901, 2901, 2902, 2902, 2902, 2902, 2903, 2904, 2904, 2904, 2904, 2905, 2905, 2905, 2906, 2907, 2907, 2908, 2908, 2908, 2908, 2909, 2909, 2910, 2911, 2911, 2911, 2912, 2913, 2914, 2915, 2916, 2916, 2918, 2918, 2919, 2919, 2919, 2920, 2921, 2921, 2922, 2922, 2922, 2923, 2923, 2923, 2924, 2925, 2926, 2926, 2926, 2927, 2927, 2927, 2928, 2929, 2930, 2931, 2931, 2932, 2932, 2932, 2934, 2934, 2934, 2935, 2935, 2935, 2936, 2937, 2938, 2939, 2940, 2940, 2941, 2942, 2942, 2943, 2943, 2943, 2944, 2944, 2944, 2944, 2944, 2945, 2946, 2946, 2947, 2947, 2948, 2949, 2950, 2950, 2951, 2952, 2954, 2954, 2954, 2955, 2955, 2956, 2957, 2958, 2958, 2959, 2959, 2960, 2960, 2960, 2962, 2962, 2964, 2964, 2965, 2965, 2965, 2966, 2966, 2967, 2967, 2968, 2969, 2969, 2969, 2970, 2970, 2971, 2972, 2972, 2972, 2972, 2972, 2974, 2974, 2974, 2976, 2976, 2977, 2978, 2979, 2980, 2980, 2980, 2980, 2981, 2981, 2982, 2982, 2983, 2984, 2984, 2986, 2987, 2987, 2988, 2988, 2988, 2989, 2989, 2989, 2990, 2990, 2991, 2991, 2991, 2992, 2993, 2994, 2995, 2995, 2995, 2995, 2996, 2996, 2997, 2997, 2997, 2998, 2999, 2999, 2999, 2999, 2999, 2999, 3000, 3000, 3000, 3000, 3001, 3001, 3002, 3003, 3003, 3004, 3005, 3005, 3005, 3007, 3007, 3008, 3008, 3009, 3009, 3009, 3010, 3010, 3010, 3010, 3011, 3011, 3013, 3013, 3014, 3015, 3015, 3016, 3016, 3016, 3016, 3017, 3018, 3018, 3018, 3018, 3019, 3020, 3020, 3021, 3021, 3021, 3022, 3024, 3026, 3026, 3026, 3026, 3027, 3028, 3028, 3028, 3028, 3030, 3030, 3031, 3035, 3036, 3036, 3036, 3037, 3037, 3038, 3038, 3039, 3039, 3041, 3041, 3041, 3042, 3043, 3043, 3044, 3044, 3045, 3045, 3045, 3045, 3045, 3046, 3047, 3048, 3048, 3048, 3049, 3049, 3049, 3050, 3050, 3051, 3051, 3051, 3051, 3052, 3052, 3052, 3053, 3054, 3054, 3054, 3054, 3055, 3055, 3055, 3055, 3057, 3057, 3057, 3058, 3059, 3060, 3060, 3060, 3060, 3061, 3062, 3063, 3063, 3063, 3064, 3065, 3065, 3066, 3068, 3068, 3068, 3068, 3068, 3068, 3069, 3071, 3072, 3072, 3072, 3073, 3074, 3074, 3074, 3075, 3077, 3077, 3078, 3078, 3079, 3079, 3079, 3079, 3081, 3081, 3081, 3082, 3082, 3082, 3082, 3083, 3083, 3084, 3084, 3084, 3086, 3086, 3087, 3087, 3087, 3087, 3088, 3089, 3089, 3090, 3091, 3092, 3092, 3093, 3093, 3094, 3094, 3094, 3095, 3095, 3096, 3097, 3097, 3098, 3099, 3100, 3101, 3101, 3102, 3102, 3104, 3104, 3105, 3107, 3108, 3108, 3109, 3109, 3109, 3110, 3110, 3111, 3111, 3111, 3112, 3112, 3112, 3112, 3112, 3113, 3113, 3113, 3113, 3113, 3114, 3115, 3116, 3116, 3116, 3117, 3117, 3117, 3118, 3118, 3119, 3119, 3119, 3120, 3120, 3120, 3121, 3121, 3121, 3122, 3122, 3122, 3122, 3123, 3123, 3124, 3126, 3127, 3127, 3127, 3127, 3128, 3128, 3128, 3128, 3129, 3130, 3130, 3131, 3131, 3131, 3131, 3131, 3132, 3132, 3132, 3133, 3133, 3134, 3135, 3136, 3136, 3136, 3137, 3138, 3140, 3140, 3141, 3142, 3142, 3143, 3143, 3143, 3143, 3143, 3144, 3145, 3146, 3146, 3146, 3147, 3148, 3149, 3149, 3150, 3150, 3150, 3150, 3150, 3150, 3151, 3151, 3152, 3152, 3154, 3154, 3155, 3155, 3155, 3156, 3156, 3157, 3158, 3158, 3159, 3160, 3160, 3161, 3161, 3161, 3162, 3162, 3163, 3164, 3164, 3165, 3165, 3166, 3166, 3166, 3167, 3167, 3168, 3168, 3168, 3169, 3169, 3170, 3170, 3170, 3170, 3171, 3172, 3172, 3173, 3175, 3175, 3177, 3177, 3178, 3178, 3179, 3180, 3180, 3180, 3181, 3182, 3182, 3182, 3183, 3184, 3184, 3184, 3185, 3186, 3187, 3187, 3188, 3189, 3189, 3189, 3190, 3190, 3191, 3192, 3192, 3193, 3193, 3193, 3194, 3194, 3194, 3194, 3195, 3195, 3196, 3196, 3196, 3196, 3198, 3198, 3198, 3198, 3198, 3199, 3199, 3199, 3200, 3200, 3202, 3202, 3203, 3203, 3203, 3205, 3206, 3207, 3207, 3207, 3208, 3208, 3208, 3208, 3209, 3209, 3210, 3210, 3211, 3211, 3211, 3212, 3212, 3213, 3213, 3213, 3214, 3214, 3215, 3216, 3216, 3217, 3218, 3218, 3219, 3219, 3220, 3222, 3223, 3223, 3223, 3224, 3224, 3224, 3224, 3225, 3225, 3225, 3225, 3226, 3227, 3228, 3228, 3228, 3228, 3228, 3228, 3229, 3230, 3230, 3231, 3233, 3234, 3234, 3234, 3235, 3235, 3236, 3236, 3237, 3237, 3239, 3239, 3239, 3240, 3240, 3241, 3241, 3241, 3241, 3243, 3243, 3243, 3243, 3243, 3243, 3243, 3243, 3245, 3245, 3246, 3246, 3246, 3247, 3247, 3247, 3247, 3248, 3248, 3249, 3250, 3250, 3251, 3251, 3252, 3252, 3253, 3253, 3254, 3254, 3255, 3256, 3257, 3257, 3257, 3259, 3259, 3260, 3260, 3261, 3262, 3263, 3263, 3263, 3264, 3266, 3266, 3266, 3267, 3267, 3267, 3267, 3267, 3268, 3268, 3268, 3269, 3269, 3269, 3270, 3270, 3270, 3270, 3271, 3272, 3272, 3272, 3272, 3273, 3273, 3273, 3274, 3274, 3275, 3275, 3276, 3276, 3276, 3278, 3278, 3279, 3280, 3280, 3280, 3280, 3281, 3282, 3284, 3284, 3284, 3285, 3285, 3285, 3285, 3286, 3286, 3287, 3288, 3288, 3289, 3289, 3289, 3289, 3290, 3292, 3292, 3292, 3293, 3293, 3293, 3293, 3294, 3294, 3297, 3297, 3298, 3299, 3301, 3301, 3302, 3302, 3302, 3302, 3303, 3304, 3305, 3305, 3305, 3305, 3306, 3306, 3306, 3306, 3306, 3306, 3308, 3308, 3308, 3308, 3309, 3309, 3310, 3310, 3311, 3311, 3311, 3311, 3312, 3313, 3313, 3313, 3314, 3314, 3315, 3315, 3316, 3318, 3320, 3320, 3321, 3321, 3321, 3322, 3322, 3323, 3323, 3323, 3324, 3324, 3327, 3329, 3329, 3330, 3330, 3330, 3331, 3331, 3331, 3331, 3331, 3333, 3334, 3335, 3336, 3336, 3336, 3337, 3337, 3337, 3338, 3338, 3339, 3339, 3339, 3339, 3340, 3340, 3340, 3340, 3341, 3341, 3341, 3344, 3345, 3345, 3346, 3347, 3347, 3347, 3347, 3347, 3348, 3348, 3348, 3348, 3349, 3349, 3349, 3350, 3350, 3351, 3351, 3352, 3352, 3352, 3352, 3353, 3354, 3357, 3358, 3358, 3358, 3358, 3359, 3359, 3359, 3360, 3360, 3361, 3361, 3361, 3362, 3363, 3363, 3363, 3365, 3365, 3367, 3367, 3367, 3368, 3369, 3369, 3369, 3370, 3370, 3371, 3372, 3372, 3373, 3374, 3374, 3377, 3377, 3377, 3377, 3378, 3379, 3379, 3380, 3380, 3381, 3381, 3382, 3383, 3383, 3383, 3384, 3384, 3385, 3385, 3385, 3386, 3386, 3387, 3387, 3388, 3388, 3388, 3389, 3389, 3389, 3390, 3392, 3393, 3394, 3394, 3394, 3395, 3396, 3397, 3397, 3397, 3398, 3398, 3398, 3398, 3399, 3399, 3400, 3400, 3400, 3401, 3401, 3402, 3402, 3402, 3402, 3403, 3403, 3405, 3405, 3405, 3405, 3405, 3406, 3407, 3407, 3408, 3410, 3410, 3411, 3411, 3411, 3412, 3412, 3412, 3413, 3414, 3414, 3414, 3414, 3415, 3415, 3417, 3419, 3419, 3420, 3420, 3420, 3421, 3421, 3421, 3422, 3422, 3423, 3423, 3423, 3423, 3424, 3425, 3425, 3425, 3426, 3427, 3427, 3428, 3428, 3429, 3429, 3430, 3431, 3431, 3431, 3432, 3432, 3432, 3434, 3435, 3435, 3435, 3436, 3437, 3438, 3438, 3438, 3439, 3439, 3439, 3440, 3440, 3441, 3441, 3442, 3443, 3443, 3443, 3444, 3444, 3444, 3445, 3445, 3445, 3446, 3446, 3447, 3447, 3447, 3448, 3448, 3449, 3449, 3449, 3450, 3450, 3450, 3451, 3452, 3452, 3453, 3453, 3454, 3454, 3454, 3454, 3455, 3456, 3456, 3456, 3457, 3457, 3460, 3461, 3461, 3461, 3462, 3462, 3462, 3463, 3463, 3463, 3463, 3463, 3464, 3464, 3464, 3466, 3467, 3467, 3467, 3468, 3468, 3469, 3470, 3471, 3472, 3473, 3473, 3473, 3474, 3475, 3475, 3475, 3476, 3476, 3476, 3478, 3479, 3479, 3480, 3481, 3481, 3481, 3482, 3483, 3484, 3484, 3485, 3485, 3486, 3486, 3486, 3486, 3487, 3487, 3487, 3487, 3489, 3489, 3490, 3490, 3490, 3491, 3491, 3491, 3492, 3492, 3493, 3493, 3494, 3494, 3494, 3495, 3495, 3495, 3495, 3495, 3495, 3495, 3496, 3497, 3497, 3498, 3498, 3499, 3499, 3499, 3499, 3500, 3501, 3501, 3503, 3503, 3503, 3504, 3504, 3504, 3504, 3504, 3505, 3505, 3505, 3506, 3507, 3508, 3508, 3508, 3511, 3511, 3511, 3511, 3511, 3511, 3511, 3512, 3512, 3512, 3512, 3513, 3514, 3514, 3514, 3515, 3515, 3516, 3517, 3517, 3518, 3518, 3518, 3518, 3519, 3520, 3520, 3520, 3520, 3521, 3521, 3521, 3521, 3521, 3524, 3525, 3527, 3528, 3528, 3530, 3530, 3531, 3532, 3532, 3533, 3534, 3534, 3534, 3535, 3535, 3535, 3535, 3536, 3537, 3537, 3538, 3539, 3539, 3539, 3539, 3540, 3540, 3540, 3541, 3541, 3541, 3543, 3544, 3544, 3547, 3548, 3548, 3549, 3549, 3550, 3551, 3551, 3551, 3551, 3552, 3553, 3553, 3553, 3553, 3554, 3554, 3554, 3554, 3555, 3555, 3556, 3556, 3557, 3558, 3558, 3558, 3558, 3559, 3559, 3560, 3560, 3560, 3561, 3561, 3562, 3562, 3563, 3565, 3566, 3566, 3566, 3566, 3567, 3567, 3567, 3567, 3568, 3569, 3569, 3570, 3570, 3571, 3572, 3572, 3573, 3573, 3573, 3574, 3574, 3575, 3575, 3576, 3577, 3578, 3579, 3581, 3581, 3582, 3582, 3582, 3583, 3583, 3583, 3583, 3583, 3584, 3584, 3585, 3586, 3586, 3587, 3587, 3588, 3588, 3588, 3589, 3591, 3591, 3593, 3594, 3594, 3595, 3596, 3596, 3597, 3599, 3599, 3599, 3600, 3600, 3600, 3601, 3601, 3602, 3602, 3602, 3603, 3604, 3605, 3607, 3608, 3609, 3609, 3609, 3609, 3610, 3610, 3611, 3612, 3612, 3613, 3614, 3614, 3615, 3615, 3615, 3615, 3615, 3616, 3617, 3617, 3617, 3617, 3619, 3619, 3619, 3621, 3621, 3621, 3622, 3623, 3624, 3624, 3625, 3627, 3628, 3628, 3628, 3628, 3629, 3630, 3630, 3630, 3631, 3631, 3631, 3631, 3632, 3633, 3633, 3633, 3634, 3634, 3634, 3636, 3637, 3638, 3638, 3638, 3639, 3639, 3639, 3639, 3641, 3642, 3642, 3642, 3643, 3643, 3643, 3643, 3644, 3644, 3645, 3646, 3646, 3647, 3647, 3647, 3647, 3648, 3648, 3649, 3649, 3650, 3650, 3651, 3652, 3652, 3653, 3653, 3654, 3655, 3656, 3656, 3657, 3658, 3659, 3660, 3661, 3662, 3663, 3664, 3664, 3664, 3665, 3666, 3667, 3667, 3668, 3669, 3669, 3669, 3670, 3670, 3671, 3671, 3672, 3672, 3673, 3677, 3678, 3678, 3678, 3678, 3679, 3679, 3679, 3681, 3681, 3681, 3682, 3682, 3683, 3683, 3684, 3684, 3685, 3685, 3685, 3687, 3687, 3687, 3688, 3688, 3688, 3688, 3688, 3689, 3690, 3690, 3690, 3693, 3693, 3694, 3694, 3695, 3695, 3696, 3698, 3698, 3699, 3699, 3700, 3702, 3703, 3704, 3705, 3705, 3705, 3705, 3706, 3706, 3706, 3706, 3706, 3707, 3707, 3707, 3708, 3708, 3710, 3710, 3710, 3711, 3712, 3713, 3713, 3713, 3713, 3714, 3714, 3714, 3715, 3715, 3716, 3716, 3717, 3717, 3717, 3717, 3718, 3718, 3718, 3718, 3719, 3719, 3719, 3720, 3720, 3721, 3721, 3722, 3722, 3722, 3722, 3722, 3723, 3724, 3725, 3726, 3727, 3727, 3728, 3728, 3729, 3729, 3731, 3731, 3731, 3731, 3731, 3732, 3734, 3734, 3734, 3734, 3735, 3735, 3736, 3736, 3736, 3736, 3737, 3738, 3739, 3739, 3739, 3740, 3740, 3740, 3741, 3741, 3741, 3742, 3742, 3743, 3744, 3744, 3744, 3745, 3745, 3745, 3746, 3746, 3747, 3747, 3747, 3748, 3748, 3749, 3751, 3751, 3751, 3751, 3751, 3752, 3753, 3753, 3753, 3753, 3754, 3755, 3756, 3757, 3757, 3758, 3758, 3758, 3759, 3759, 3759, 3762, 3763, 3763, 3763, 3763, 3764, 3765, 3765, 3766, 3766, 3766, 3766, 3767, 3767, 3768, 3768, 3769, 3769, 3770, 3770, 3770, 3770, 3771, 3771, 3772, 3772, 3773, 3773, 3774, 3775, 3775, 3776, 3776, 3776, 3776, 3776, 3777, 3777, 3779, 3779, 3779, 3779, 3780, 3780, 3781, 3781, 3782, 3783, 3783, 3784, 3785, 3785, 3787, 3787, 3787, 3788, 3788, 3788, 3788, 3789, 3789, 3790, 3790, 3791, 3792, 3792, 3792, 3793, 3793, 3794, 3794, 3795, 3795, 3796, 3797, 3797, 3797, 3797, 3798, 3798, 3799, 3800, 3800, 3800, 3800, 3801, 3801, 3801, 3802, 3802, 3802, 3802, 3803, 3804, 3805, 3806, 3806, 3807, 3808, 3808, 3809, 3809, 3811, 3813, 3814, 3814, 3816, 3816, 3816, 3817, 3818, 3819, 3820, 3820, 3821, 3821, 3821, 3822, 3822, 3822, 3825, 3825, 3825, 3825, 3826, 3828, 3828, 3828, 3829, 3830, 3830, 3830, 3830, 3831, 3831, 3831, 3832, 3832, 3833, 3833, 3833, 3833, 3834, 3835, 3835, 3836, 3837, 3837, 3837, 3837, 3838, 3838, 3838, 3839, 3841, 3841, 3842, 3842, 3842, 3842, 3843, 3843, 3843, 3843, 3843, 3844, 3844, 3844, 3845, 3846, 3847, 3847, 3848, 3849, 3850, 3850, 3851, 3851, 3851, 3854, 3854, 3854, 3855, 3855, 3856, 3857, 3858, 3858, 3858, 3859, 3859, 3859, 3859, 3860, 3860, 3861, 3861, 3861, 3861, 3862, 3862, 3862, 3862, 3863, 3863, 3865, 3865, 3865, 3865, 3866, 3866, 3867, 3867, 3867, 3867, 3868, 3868, 3869, 3869, 3870, 3871, 3871, 3871, 3872, 3873, 3873, 3873, 3874, 3874, 3874, 3875, 3875, 3876, 3877, 3878, 3878, 3878, 3879, 3879, 3879, 3880, 3880, 3881, 3881, 3881, 3881, 3883, 3883, 3884, 3884, 3884, 3884, 3884, 3886, 3887, 3887, 3887, 3887, 3888, 3888, 3889, 3890, 3890, 3891, 3891, 3891, 3891, 3892, 3892, 3892, 3892, 3893, 3893, 3893, 3893, 3894, 3894, 3894, 3895, 3895, 3895, 3895, 3897, 3897, 3897, 3899, 3899, 3900, 3901, 3902, 3904, 3904, 3905, 3905, 3906, 3906, 3906, 3907, 3907, 3907, 3908, 3909, 3910, 3911, 3911, 3912, 3913, 3914, 3915, 3915, 3915, 3915, 3916, 3917, 3917, 3917, 3919, 3919, 3919, 3920, 3921, 3921, 3922, 3922, 3923, 3923, 3923, 3924, 3924, 3925, 3925, 3926, 3926, 3926, 3928, 3928, 3928, 3929, 3929, 3930, 3930, 3930, 3931, 3931, 3931, 3932, 3932, 3932, 3932, 3932, 3933, 3933, 3933, 3934, 3934, 3934, 3935, 3935, 3935, 3935, 3936, 3937, 3937, 3937, 3938, 3938, 3939, 3942, 3942, 3943, 3943, 3943, 3945, 3945, 3945, 3946, 3947, 3947, 3948, 3948, 3948, 3948, 3948, 3951, 3952, 3952, 3952, 3952, 3953, 3954, 3954, 3956, 3957, 3957, 3957, 3957, 3958, 3958, 3958, 3959, 3960, 3961, 3961, 3961, 3962, 3963, 3964, 3964, 3964, 3965, 3965, 3965, 3965, 3967, 3968, 3969, 3969, 3970, 3970, 3971, 3972, 3972, 3973, 3973, 3974, 3974, 3975, 3975, 3976, 3976, 3977, 3977, 3977, 3977, 3978, 3978, 3979, 3979, 3979, 3979, 3979, 3980, 3980, 3981, 3981, 3981, 3981, 3982, 3982, 3982, 3982, 3983, 3984, 3984, 3984, 3984, 3984, 3984, 3986, 3986, 3986, 3987, 3988, 3988, 3988, 3988, 3989, 3989, 3989, 3990, 3990, 3991, 3992, 3993, 3994, 3995, 3996, 3996, 3998, 3998, 3998, 3999, 4000, 4000, 4000, 4001, 4001, 4001, 4001, 4002, 4002, 4002, 4002, 4003, 4004, 4004, 4004, 4005, 4006, 4006, 4007, 4007, 4008, 4008, 4008, 4009, 4010, 4010, 4010, 4010, 4011, 4011, 4013, 4014, 4015, 4016, 4017, 4018, 4018, 4019, 4020, 4020, 4020, 4021, 4021, 4022, 4022, 4022, 4023, 4023, 4023, 4024, 4024, 4025, 4025, 4025, 4026, 4026, 4027, 4027, 4028, 4028, 4028, 4029, 4030, 4031, 4031, 4031, 4031, 4032, 4032, 4032, 4032, 4033, 4033, 4033, 4033, 4035, 4035, 4035, 4035, 4035, 4037, 4038, 4038, 4038, 4038, 4038, 4039, 4039, 4039, 4040, 4040, 4040, 4041, 4041, 4041, 4041, 4041, 4041, 4041, 4042, 4042, 4043, 4043, 4043, 4043, 4044, 4044, 4045, 4045, 4045, 4047, 4047, 4048, 4048, 4049, 4050, 4050, 4050, 4051, 4052, 4052, 4053, 4053, 4054, 4055, 4055, 4056, 4056, 4057, 4058, 4058, 4059, 4059, 4060, 4060, 4060, 4061, 4061, 4061, 4062, 4063, 4063, 4064, 4065, 4065, 4065, 4066, 4067, 4068, 4068, 4068, 4069, 4069, 4069, 4070, 4070, 4070, 4071, 4071, 4072, 4072, 4072, 4072, 4072, 4073, 4073, 4074, 4074, 4075, 4076, 4076, 4076, 4076, 4077, 4077, 4078, 4078, 4078, 4080, 4081, 4081, 4082, 4082, 4082, 4083, 4083, 4085, 4085, 4085, 4085, 4085, 4086, 4086, 4086, 4087, 4087, 4087, 4088, 4088, 4089, 4089, 4090, 4090, 4091, 4091, 4092, 4093, 4093, 4093, 4094, 4095, 4096, 4096, 4096, 4097, 4097, 4098, 4099, 4099, 4099, 4099, 4100, 4100, 4101, 4101, 4102, 4102, 4103, 4104, 4104, 4104, 4104, 4104, 4105, 4106, 4106, 4106, 4106, 4107, 4108, 4108, 4109, 4109, 4109, 4109, 4110, 4111, 4112, 4112, 4112, 4112, 4112, 4113, 4113, 4114, 4114, 4114, 4115, 4115, 4116, 4116, 4117, 4117, 4117, 4118, 4118, 4118, 4119, 4119, 4121, 4121, 4122, 4122, 4122, 4123, 4124, 4125, 4125, 4126, 4127, 4129, 4129, 4130, 4131, 4131, 4132, 4132, 4132, 4134, 4134, 4134, 4135, 4135, 4135, 4135, 4135, 4135, 4135, 4135, 4136, 4136, 4136, 4136, 4136, 4136, 4137, 4137, 4137, 4139, 4140, 4140, 4140, 4141, 4141, 4142, 4142, 4142, 4143, 4144, 4144, 4144, 4144, 4145, 4145, 4145, 4145, 4146, 4147, 4147, 4147, 4148, 4148, 4148, 4149, 4149, 4149, 4149, 4149, 4150, 4150, 4151, 4153, 4153, 4154, 4155, 4155, 4156, 4156, 4156, 4157, 4158, 4158, 4159, 4159, 4160, 4160, 4161, 4161, 4161, 4161, 4163, 4163, 4163, 4164, 4164, 4164, 4164, 4165, 4165, 4165, 4166, 4166, 4166, 4167, 4168, 4169, 4170, 4170, 4170, 4171, 4171, 4172, 4173, 4173, 4173, 4173, 4174, 4175, 4175, 4176, 4176, 4176, 4177, 4177, 4177, 4177, 4178, 4178, 4179, 4179, 4179, 4179, 4179, 4179, 4179, 4180, 4180, 4180, 4181, 4181, 4181, 4181, 4182, 4182, 4183, 4184, 4185, 4186, 4187, 4187, 4187, 4188, 4188, 4188, 4189, 4189, 4189, 4189, 4190, 4190, 4190, 4190, 4190, 4191, 4192, 4192, 4192, 4192, 4194, 4195, 4196, 4196, 4196, 4196, 4197, 4197, 4198, 4198, 4198, 4198, 4200, 4200, 4201, 4202, 4202, 4203, 4203, 4204, 4205, 4205, 4207, 4210, 4210, 4210, 4210, 4211, 4211, 4213, 4214, 4214, 4215, 4215, 4215, 4216, 4216, 4216, 4217, 4217, 4218, 4218, 4218, 4218, 4218, 4219, 4220, 4220, 4220, 4221, 4222, 4223, 4223, 4223, 4225, 4225, 4226, 4226, 4227, 4227, 4228, 4228, 4228, 4228, 4229, 4230, 4230, 4232, 4233, 4233, 4233, 4233, 4234, 4235, 4235, 4235, 4235, 4236, 4236, 4236, 4237, 4238, 4238, 4238, 4238, 4238, 4239, 4239, 4239, 4239, 4239, 4240, 4240, 4241, 4243, 4243, 4243, 4243, 4243, 4243, 4244, 4244, 4244, 4244, 4244, 4245, 4245, 4245, 4247, 4247, 4247, 4248, 4248, 4249, 4249, 4249, 4250, 4250, 4250, 4251, 4251, 4252, 4252, 4253, 4253, 4253, 4253, 4253, 4254, 4255, 4255, 4255, 4256, 4256, 4257, 4257, 4257, 4258, 4258, 4258, 4258, 4259, 4259, 4260, 4260, 4261, 4261, 4261, 4261, 4261, 4261, 4262, 4262, 4262, 4263, 4264, 4265, 4265, 4265, 4266, 4267, 4268, 4269, 4269, 4269, 4270, 4270, 4271, 4271, 4271, 4272, 4272, 4273, 4273, 4273, 4273, 4274, 4274, 4274, 4275, 4275, 4277, 4279, 4279, 4280, 4280, 4280, 4281, 4281, 4282, 4282, 4282, 4282, 4282, 4283, 4283, 4283, 4283, 4284, 4285, 4285, 4285, 4285, 4285, 4286, 4286, 4287, 4287, 4287, 4287, 4288, 4289, 4290, 4290, 4291, 4291, 4291, 4292, 4293, 4293, 4293, 4293, 4294, 4294, 4295, 4296, 4297, 4297, 4297, 4297, 4297, 4297, 4298, 4299, 4299, 4299, 4299, 4300, 4300, 4301, 4301, 4302, 4302, 4302, 4302, 4303, 4304, 4305, 4306, 4306, 4306, 4307, 4307, 4308, 4308, 4309, 4309, 4309, 4309, 4310, 4311, 4311, 4312, 4312, 4312, 4312, 4312, 4313, 4314, 4315, 4315, 4316, 4316, 4317, 4317, 4317, 4318, 4318, 4319, 4319, 4320, 4320, 4321, 4322, 4322, 4323, 4325, 4327, 4327, 4327, 4327, 4327, 4328, 4328, 4330, 4330, 4330, 4331, 4331, 4332, 4332, 4334, 4335, 4335, 4336, 4336, 4337, 4337, 4338, 4338, 4338, 4338, 4339, 4339, 4339, 4340, 4340, 4340, 4340, 4340, 4340, 4341, 4341, 4341, 4343, 4343, 4344, 4344, 4344, 4345, 4346, 4347, 4347, 4348, 4348, 4348, 4352, 4353, 4355, 4356, 4356, 4357, 4357, 4358, 4358, 4358, 4358, 4359, 4360, 4360, 4360, 4361, 4361, 4361, 4361, 4362, 4362, 4363, 4363, 4364, 4365, 4365, 4365, 4368, 4370, 4370, 4370, 4371, 4371, 4371, 4372, 4372, 4372, 4372, 4374, 4374, 4374, 4375, 4375, 4376, 4376, 4377, 4377, 4380, 4380, 4381, 4381, 4381, 4382, 4382, 4384, 4386, 4386, 4387, 4387, 4389, 4389, 4389, 4389, 4389, 4389, 4390, 4391, 4391, 4392, 4393, 4393, 4394, 4394, 4394, 4394, 4395, 4395, 4396, 4398, 4398, 4398, 4399, 4400, 4400, 4400, 4401, 4401, 4402, 4403, 4403, 4403, 4404, 4405, 4405, 4405, 4407, 4408, 4408, 4408, 4409, 4410, 4410, 4410, 4410, 4411, 4411, 4412, 4412, 4413, 4413, 4414, 4414, 4414, 4414, 4414, 4415, 4415, 4419, 4419, 4419, 4419, 4420, 4420, 4420, 4421, 4421, 4421, 4421, 4423, 4424, 4425, 4426, 4427, 4427, 4428, 4429, 4429, 4430, 4430, 4430, 4431, 4431, 4431, 4431, 4432, 4432, 4432, 4432, 4432, 4432, 4433, 4433, 4434, 4434, 4435, 4435, 4435, 4435, 4436, 4436, 4436, 4436, 4437, 4437, 4438, 4438, 4438, 4438, 4438, 4439, 4439, 4440, 4440, 4441, 4441, 4442, 4443, 4444, 4444, 4446, 4446, 4447, 4447, 4447, 4448, 4448, 4448, 4449, 4450, 4451, 4452, 4453, 4453, 4454, 4454, 4455, 4455, 4455, 4456, 4456, 4456, 4457, 4457, 4457, 4457, 4457, 4457, 4457, 4457, 4459, 4460, 4460, 4461, 4462, 4462, 4462, 4462, 4465, 4465, 4466, 4467, 4468, 4468, 4469, 4470, 4470, 4471, 4471, 4471, 4471, 4471, 4471, 4471, 4471, 4472, 4472, 4472, 4473, 4473, 4474, 4474, 4474, 4475, 4475, 4476, 4477, 4479, 4479, 4479, 4479, 4481, 4481, 4481, 4481, 4482, 4482, 4482, 4483, 4483, 4484, 4484, 4486, 4487, 4487, 4487, 4487, 4487, 4488, 4488, 4488, 4489, 4491, 4491, 4492, 4492, 4493, 4493, 4494, 4494, 4494, 4494, 4494, 4495, 4495, 4495, 4496, 4496, 4496, 4497, 4498, 4499, 4500, 4500, 4501, 4501, 4503, 4503, 4503, 4504, 4504, 4506, 4509, 4509, 4509, 4509, 4510, 4510, 4511, 4511, 4511, 4512, 4513, 4513, 4514, 4514, 4514, 4515, 4517, 4518, 4521, 4521, 4521, 4521, 4522, 4523, 4523, 4524, 4524, 4525, 4525, 4525, 4525, 4525, 4526, 4526, 4527, 4527, 4528, 4528, 4528, 4529, 4529, 4529, 4529, 4530, 4531, 4532, 4533, 4533, 4534, 4535, 4536, 4536, 4536, 4536, 4537, 4537, 4538, 4539, 4539, 4542, 4542, 4542, 4543, 4543, 4543, 4544, 4544, 4546, 4547, 4547, 4548, 4548, 4549, 4549, 4550, 4550, 4551, 4552, 4552, 4552, 4553, 4553, 4554, 4554, 4554, 4554, 4554, 4555, 4555, 4556, 4556, 4557, 4557, 4558, 4558, 4559, 4559, 4559, 4560, 4560, 4562, 4563, 4563, 4564, 4565, 4566, 4566, 4566, 4567, 4567, 4567, 4567, 4567, 4568, 4568, 4568, 4569, 4569, 4570, 4571, 4572, 4572, 4572, 4572, 4573, 4574, 4574, 4574, 4575, 4575, 4575, 4575, 4575, 4575, 4576, 4576, 4577, 4577, 4578, 4578, 4578, 4579, 4579, 4579, 4579, 4580, 4580, 4580, 4580, 4580, 4581, 4581, 4582, 4583, 4584, 4584, 4586, 4586, 4587, 4588, 4589, 4590, 4590, 4592, 4592, 4592, 4593, 4594, 4594, 4594, 4595, 4595, 4595, 4596, 4597, 4597, 4597, 4598, 4598, 4600, 4600, 4600, 4600, 4601, 4601, 4602, 4602, 4602, 4603, 4604, 4604, 4605, 4605, 4605, 4606, 4607, 4608, 4608, 4608, 4609, 4609, 4609, 4610, 4611, 4611, 4612, 4612, 4614, 4614, 4614, 4614, 4615, 4615, 4616, 4616, 4616, 4616, 4617, 4617, 4617, 4617, 4618, 4618, 4618, 4618, 4620, 4621, 4621, 4621, 4622, 4623, 4623, 4623, 4624, 4624, 4625, 4625, 4626, 4626, 4627, 4627, 4627, 4629, 4629, 4630, 4630, 4631, 4631, 4631, 4631, 4631, 4631, 4632, 4633, 4634, 4634, 4634, 4635, 4635, 4635, 4635, 4636, 4636, 4636, 4636, 4637, 4637, 4638, 4639, 4639, 4640, 4640, 4640, 4641, 4641, 4642, 4643, 4643, 4643, 4644, 4644, 4645, 4646, 4646, 4647, 4648, 4649, 4649, 4649, 4649, 4651, 4651, 4653, 4654, 4655, 4655, 4656, 4656, 4657, 4658, 4658, 4658, 4659, 4659, 4659, 4659, 4659, 4660, 4661, 4662, 4662, 4663, 4663, 4664, 4664, 4665, 4665, 4666, 4666, 4666, 4667, 4667, 4668, 4669, 4669, 4669, 4669, 4670, 4670, 4670, 4671, 4673, 4673, 4674, 4674, 4674, 4674, 4675, 4675, 4675, 4676, 4677, 4678, 4678, 4679, 4679, 4679, 4679, 4680, 4680, 4681, 4681, 4683, 4683, 4683, 4683, 4684, 4684, 4685, 4685, 4686, 4686, 4687, 4687, 4688, 4690, 4690, 4690, 4690, 4691, 4691, 4693, 4693, 4693, 4693, 4693, 4695, 4695, 4697, 4697, 4698, 4699, 4699, 4700, 4700, 4700, 4701, 4701, 4701, 4702, 4703, 4703, 4704, 4704, 4704, 4705, 4705, 4705, 4706, 4707, 4707, 4707, 4708, 4708, 4709, 4709, 4710, 4710, 4710, 4711, 4711, 4712, 4712, 4714, 4715, 4716, 4716, 4717, 4718, 4718, 4718, 4718, 4719, 4719, 4720, 4720, 4720, 4720, 4721, 4721, 4721, 4722, 4722, 4725, 4725, 4726, 4726, 4727, 4728, 4728, 4728, 4728, 4728, 4729, 4729, 4730, 4730, 4731, 4731, 4732, 4732, 4733, 4733, 4733, 4733, 4733, 4734, 4734, 4734, 4734, 4735, 4735, 4735, 4736, 4737, 4738, 4738, 4738, 4738, 4738, 4739, 4739, 4740, 4740, 4741, 4741, 4743, 4743, 4743, 4744, 4744, 4744, 4744, 4744, 4746, 4746, 4746, 4746, 4747, 4747, 4747, 4748, 4748, 4748, 4749, 4749, 4749, 4750, 4751, 4751, 4751, 4752, 4753, 4753, 4753, 4755, 4755, 4756, 4756, 4757, 4757, 4757, 4758, 4758, 4760, 4760, 4760, 4761, 4761, 4762, 4762, 4762, 4763, 4764, 4764, 4765, 4766, 4767, 4767, 4768, 4768, 4769, 4770, 4771, 4771, 4771, 4773, 4774, 4774, 4774, 4774, 4775, 4778, 4779, 4780, 4780, 4780, 4781, 4781, 4782, 4782, 4782, 4783, 4785, 4785, 4786, 4787, 4787, 4787, 4787, 4788, 4788, 4788, 4788, 4788, 4789, 4790, 4790, 4790, 4791, 4791, 4791, 4792, 4792, 4792, 4792, 4792, 4792, 4793, 4794, 4794, 4796, 4796, 4796, 4796, 4797, 4798, 4798, 4799, 4799, 4799, 4799, 4801, 4801, 4802, 4802, 4802, 4803, 4805, 4805, 4808, 4808, 4808, 4810, 4810, 4810, 4811, 4811, 4811, 4811, 4812, 4812, 4813, 4814, 4815, 4815, 4816, 4816, 4816, 4816, 4816, 4817, 4817, 4817, 4818, 4818, 4818, 4819, 4819, 4820, 4822, 4822, 4822, 4822, 4822, 4822, 4823, 4823, 4823, 4824, 4824, 4825, 4826, 4826, 4827, 4827, 4828, 4828, 4828, 4829, 4829, 4830, 4830, 4830, 4831, 4831, 4831, 4832, 4832, 4833, 4834, 4834, 4834, 4834, 4835, 4835, 4835, 4836, 4837, 4838, 4838, 4838, 4838, 4838, 4838, 4839, 4839, 4839, 4839, 4840, 4840, 4841, 4842, 4842, 4842, 4843, 4843, 4843, 4843, 4843, 4844, 4844, 4845, 4846, 4846, 4847, 4847, 4847, 4847, 4847, 4848, 4848, 4849, 4849, 4849, 4849, 4849, 4850, 4850, 4851, 4853, 4853, 4853, 4854, 4854, 4856, 4856, 4857, 4857, 4857, 4858, 4858, 4859, 4859, 4859, 4859, 4860, 4860, 4861, 4862, 4862, 4863, 4863, 4863, 4863, 4864, 4864, 4864, 4864, 4865, 4865, 4866, 4866, 4867, 4867, 4869, 4870, 4870, 4870, 4870, 4870, 4870, 4871, 4871, 4871, 4872, 4873, 4873, 4874, 4874, 4875, 4875, 4876, 4876, 4876, 4876, 4877, 4879, 4879, 4879, 4881, 4882, 4882, 4883, 4883, 4883, 4884, 4884, 4886, 4888, 4888, 4888, 4889, 4890, 4890, 4890, 4891, 4891, 4892, 4892, 4892, 4892, 4893, 4893, 4893, 4894, 4894, 4894, 4894, 4894, 4894, 4895, 4898, 4899, 4899, 4900, 4901, 4901, 4901, 4901, 4902, 4902, 4903, 4904, 4904, 4904, 4904, 4904, 4905, 4906, 4908, 4908, 4909, 4910, 4910, 4911, 4911, 4912, 4912, 4913, 4913, 4914, 4914, 4914, 4915, 4915, 4916, 4917, 4917, 4918, 4918, 4920, 4921, 4921, 4921, 4921, 4922, 4922, 4922, 4922, 4923, 4923, 4924, 4924, 4924, 4925, 4926, 4926, 4926, 4927, 4928, 4928, 4928, 4928, 4928, 4928, 4929, 4930, 4930, 4931, 4932, 4934, 4934, 4935, 4935, 4936, 4936, 4937, 4937, 4937, 4937, 4937, 4938, 4939, 4939, 4939, 4939, 4939, 4939, 4942, 4943, 4943, 4944, 4944, 4944, 4944, 4944, 4945, 4946, 4946, 4946, 4947, 4948, 4948, 4948, 4949, 4949, 4950, 4950, 4950, 4950, 4951, 4951, 4952, 4956, 4956, 4957, 4957, 4957, 4957, 4958, 4960, 4960, 4960, 4961, 4961, 4961, 4962, 4962, 4962, 4963, 4963, 4964, 4964, 4964, 4965, 4966, 4967, 4967, 4968, 4968, 4968, 4971, 4971, 4972, 4972, 4974, 4975, 4975, 4975, 4976, 4976, 4977, 4978, 4978, 4979, 4979, 4979, 4980, 4980, 4980, 4981, 4981, 4981, 4981, 4982, 4982, 4982, 4982, 4982, 4983, 4983, 4983, 4983, 4984, 4985, 4985, 4986, 4986, 4986, 4986, 4987, 4987, 4988, 4989, 4989, 4989, 4990, 4991, 4992, 4992, 4993, 4993, 4994, 4995, 4995, 4996, 4997, 4997, 4997, 4997, 4997, 4998, 4998, 4999, 4999]\n", diff --git a/crates/cli_utils/src/helpers.rs b/crates/cli_utils/src/helpers.rs index 020e890c37..74618d6824 100644 --- a/crates/cli_utils/src/helpers.rs +++ b/crates/cli_utils/src/helpers.rs @@ -22,7 +22,7 @@ pub struct Out { pub status: ExitStatus, } -pub fn run_roc(args: I, stdin_vals: &[&str]) -> Out +pub fn run_roc(args: I, stdin_vals: &[&str], extra_env: &[(&str, &str)]) -> Out where I: IntoIterator, S: AsRef, @@ -62,7 +62,7 @@ where } } - run_with_stdin(&roc_binary_path, args, stdin_vals) + run_with_stdin_and_env(&roc_binary_path, args, stdin_vals, extra_env) } pub fn run_glue(args: I) -> Out @@ -118,6 +118,19 @@ pub fn strip_colors(str: &str) -> String { } pub fn run_with_stdin(path: &Path, args: I, stdin_vals: &[&str]) -> Out +where + I: IntoIterator, + S: AsRef, +{ + run_with_stdin_and_env(path, args, stdin_vals, &[]) +} + +pub fn run_with_stdin_and_env( + path: &Path, + args: I, + stdin_vals: &[&str], + extra_env: &[(&str, &str)], +) -> Out where I: IntoIterator, S: AsRef, @@ -128,6 +141,10 @@ where cmd.arg(arg); } + for (k, v) in extra_env { + cmd.env(k, v); + } + let mut child = cmd .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -364,20 +381,31 @@ pub fn root_dir() -> PathBuf { path } +// start the dir with crates/cli_testing_examples #[allow(dead_code)] -pub fn examples_dir(dir_name: &str) -> PathBuf { +pub fn cli_testing_dir(dir_name: &str) -> PathBuf { let mut path = root_dir(); // Descend into examples/{dir_name} - path.push("examples"); + path.push("crates"); + path.push("cli_testing_examples"); path.extend(dir_name.split("/")); // Make slashes cross-target path } #[allow(dead_code)] -pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { - let mut path = examples_dir(dir_name); +pub fn dir_path_from_root(dir_name: &str) -> PathBuf { + let mut path = root_dir(); + + path.extend(dir_name.split("/")); // Make slashes cross-target + + path +} + +#[allow(dead_code)] +pub fn file_path_from_root(dir_name: &str, file_name: &str) -> PathBuf { + let mut path = dir_path_from_root(dir_name); path.push(file_name); diff --git a/crates/compiler/build/src/link.rs b/crates/compiler/build/src/link.rs index 202524210c..fa4eb1dfb8 100644 --- a/crates/compiler/build/src/link.rs +++ b/crates/compiler/build/src/link.rs @@ -374,7 +374,7 @@ pub fn build_zig_host_wasm32( "c", "-target", zig_target, - // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", + // "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll", "-fPIC", "--strip", ]; @@ -635,6 +635,7 @@ pub fn rebuild_host( } else if cargo_host_src.exists() { // Compile and link Cargo.toml, if it exists let cargo_dir = host_input_path.parent().unwrap(); + let cargo_out_dir = cargo_dir.join("target").join( if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) { "release" @@ -1215,7 +1216,7 @@ fn link_wasm32( "-O", "ReleaseSmall", // useful for debugging - // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", + // "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll", ]) .spawn()?; diff --git a/crates/compiler/builtins/Cargo.toml b/crates/compiler/builtins/Cargo.toml index d824d44a64..840cce6f60 100644 --- a/crates/compiler/builtins/Cargo.toml +++ b/crates/compiler/builtins/Cargo.toml @@ -15,7 +15,7 @@ lazy_static = "1.4.0" [build-dependencies] # dunce can be removed once ziglang/zig#5109 is fixed -dunce = "1.0.2" +dunce = "1.0.3" [target.'cfg(target_os = "macos")'.build-dependencies] tempfile = "3.2.0" diff --git a/crates/compiler/builtins/bitcode/src/list.zig b/crates/compiler/builtins/bitcode/src/list.zig index 467ee2ff37..38e07218fd 100644 --- a/crates/compiler/builtins/bitcode/src/list.zig +++ b/crates/compiler/builtins/bitcode/src/list.zig @@ -93,20 +93,6 @@ pub const RocList = extern struct { return (ptr - 1)[0] == utils.REFCOUNT_ONE; } - pub fn allocate( - alignment: u32, - length: usize, - element_size: usize, - ) RocList { - const data_bytes = length * element_size; - - return RocList{ - .bytes = utils.allocateWithRefcount(data_bytes, alignment), - .length = length, - .capacity = length, - }; - } - pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) RocList { if (update_mode == .InPlace) { return self; @@ -140,11 +126,117 @@ pub const RocList = extern struct { return new_list; } + // We follow roughly the [fbvector](https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md) when it comes to growing a RocList. + // Here is [their growth strategy](https://github.com/facebook/folly/blob/3e0525988fd444201b19b76b390a5927c15cb697/folly/FBVector.h#L1128) for push_back: + // + // (1) initial size + // Instead of growing to size 1 from empty, fbvector allocates at least + // 64 bytes. You may still use reserve to reserve a lesser amount of + // memory. + // (2) 1.5x + // For medium-sized vectors, the growth strategy is 1.5x. See the docs + // for details. + // This does not apply to very small or very large fbvectors. This is a + // heuristic. + // + // In our case, we exposed allocate and reallocate, which will use a smart growth stategy. + // We also expose allocateExact and reallocateExact for case where a specific number of elements is requested. + + // calculateCapacity should only be called in cases the list will be growing. + // requested_length should always be greater than old_capacity. + inline fn calculateCapacity( + old_capacity: usize, + requested_length: usize, + element_width: usize, + ) usize { + // TODO: there are two adjustments that would likely lead to better results for Roc. + // 1. Deal with the fact we allocate an extra u64 for refcount. + // This may lead to allocating page size + 8 bytes. + // That could mean allocating an entire page for 8 bytes of data which isn't great. + // 2. Deal with the fact that we can request more than 1 element at a time. + // fbvector assumes just appending 1 element at a time when using this algorithm. + // As such, they will generally grow in a way that should better match certain memory multiple. + // This is also the normal case for roc, but we could also grow by a much larger amount. + // We may want to round to multiples of 2 or something similar. + var new_capacity: usize = 0; + if (element_width == 0) { + return requested_length; + } else if (old_capacity == 0) { + new_capacity = 64 / element_width; + } else if (old_capacity < 4096 / element_width) { + new_capacity = old_capacity * 2; + } else if (old_capacity > 4096 * 32 / element_width) { + new_capacity = old_capacity * 2; + } else { + new_capacity = (old_capacity * 3 + 1) / 2; + } + return @maximum(new_capacity, requested_length); + } + + pub fn allocate( + alignment: u32, + length: usize, + element_width: usize, + ) RocList { + if (length == 0) { + return empty(); + } + + const capacity = calculateCapacity(0, length, element_width); + const data_bytes = capacity * element_width; + return RocList{ + .bytes = utils.allocateWithRefcount(data_bytes, alignment), + .length = length, + .capacity = capacity, + }; + } + + pub fn allocateExact( + alignment: u32, + length: usize, + element_width: usize, + ) RocList { + if (length == 0) { + return empty(); + } + + const data_bytes = length * element_width; + return RocList{ + .bytes = utils.allocateWithRefcount(data_bytes, alignment), + .length = length, + .capacity = length, + }; + } + pub fn reallocate( self: RocList, alignment: u32, new_length: usize, element_width: usize, + ) RocList { + if (self.bytes) |source_ptr| { + if (self.isUnique()) { + if (self.capacity >= new_length) { + return RocList{ .bytes = self.bytes, .length = new_length, .capacity = self.capacity }; + } else { + const new_capacity = calculateCapacity(self.capacity, new_length, element_width); + const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_capacity, element_width); + return RocList{ .bytes = new_source, .length = new_length, .capacity = new_capacity }; + } + } + // TODO: Investigate the performance of this. + // Maybe we should just always reallocate to the new_length instead of expanding capacity? + const new_capacity = if (self.capacity >= new_length) self.capacity else calculateCapacity(self.capacity, new_length, element_width); + return self.reallocateFresh(alignment, new_length, new_capacity, element_width); + } + return RocList.allocate(alignment, new_length, element_width); + } + + pub fn reallocateExact( + self: RocList, + alignment: u32, + new_length: usize, + element_width: usize, ) RocList { if (self.bytes) |source_ptr| { if (self.isUnique()) { @@ -155,9 +247,9 @@ pub const RocList = extern struct { return RocList{ .bytes = new_source, .length = new_length, .capacity = new_length }; } } + return self.reallocateFresh(alignment, new_length, new_length, element_width); } - - return self.reallocateFresh(alignment, new_length, element_width); + return RocList.allocateExact(alignment, new_length, element_width); } /// reallocate by explicitly making a new allocation and copying elements over @@ -165,16 +257,16 @@ pub const RocList = extern struct { self: RocList, alignment: u32, new_length: usize, + new_capacity: usize, element_width: usize, ) RocList { const old_length = self.length; const delta_length = new_length - old_length; - const data_bytes = new_length * element_width; + const data_bytes = new_capacity * element_width; const first_slot = utils.allocateWithRefcount(data_bytes, alignment); // transfer the memory - if (self.bytes) |source_ptr| { const dest_ptr = first_slot; @@ -185,7 +277,7 @@ pub const RocList = extern struct { const result = RocList{ .bytes = first_slot, .length = new_length, - .capacity = new_length, + .capacity = new_capacity, }; utils.decref(self.bytes, old_length * element_width, alignment); @@ -412,7 +504,7 @@ pub fn listWithCapacity( alignment: u32, element_width: usize, ) callconv(.C) RocList { - var output = RocList.allocate(alignment, capacity, element_width); + var output = RocList.allocateExact(alignment, capacity, element_width); output.length = 0; return output; } @@ -517,17 +609,25 @@ pub fn listSublist( len: usize, dec: Dec, ) callconv(.C) RocList { - if (len == 0) { + const size = list.len(); + if (len == 0 or start >= size) { + if (list.isUnique()) { + // Decrement the reference counts of all elements. + if (list.bytes) |source_ptr| { + var i: usize = 0; + while (i < size) : (i += 1) { + const element = source_ptr + i * element_width; + dec(element); + } + var output = list; + output.length = 0; + return output; + } + } return RocList.empty(); } if (list.bytes) |source_ptr| { - const size = list.len(); - - if (start >= size) { - return RocList.empty(); - } - const keep_len = std.math.min(len, size - start); const drop_start_len = start; const drop_end_len = size - (start + keep_len); @@ -546,10 +646,17 @@ pub fn listSublist( dec(element); } - if (start == 0 and list.isUnique()) { + if (list.isUnique()) { var output = list; output.length = keep_len; - return output; + if (start == 0) { + return output; + } else { + // We want memmove due to aliasing. Zig does not expose it directly. + // Instead use copy which can write to aliases as long as the dest is before the source. + mem.copy(u8, source_ptr[0 .. keep_len * element_width], source_ptr[start * element_width .. (start + keep_len) * element_width]); + return output; + } } else { const output = RocList.allocate(alignment, keep_len, element_width); const target_ptr = output.bytes orelse unreachable; diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index eff2e48c97..f88d159479 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -144,6 +144,7 @@ comptime { exportStrFn(str.strTrimLeft, "trim_left"); exportStrFn(str.strTrimRight, "trim_right"); exportStrFn(str.strCloneTo, "clone_to"); + exportStrFn(str.withCapacity, "with_capacity"); inline for (INTEGERS) |T| { str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int."); diff --git a/crates/compiler/builtins/bitcode/src/str.zig b/crates/compiler/builtins/bitcode/src/str.zig index 930fd26cf5..6feacda78e 100644 --- a/crates/compiler/builtins/bitcode/src/str.zig +++ b/crates/compiler/builtins/bitcode/src/str.zig @@ -2596,6 +2596,10 @@ pub fn reserve(string: RocStr, capacity: usize) callconv(.C) RocStr { } } +pub fn withCapacity(capacity: usize) callconv(.C) RocStr { + return RocStr.allocate(0, capacity); +} + pub fn getScalarUnsafe(string: RocStr, index: usize) callconv(.C) extern struct { bytesParsed: usize, scalar: u32 } { const slice = string.asSlice(); const bytesParsed = @intCast(usize, std.unicode.utf8ByteSequenceLength(slice[index]) catch unreachable); diff --git a/crates/compiler/builtins/roc/Decode.roc b/crates/compiler/builtins/roc/Decode.roc index 46574421ad..55021ca86e 100644 --- a/crates/compiler/builtins/roc/Decode.roc +++ b/crates/compiler/builtins/roc/Decode.roc @@ -30,6 +30,23 @@ interface Decode ] imports [ List, + Result.{ Result }, + Num.{ + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + F32, + F64, + Dec, + }, + Bool.{ Bool }, ] DecodeError : [TooShort] diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index ef012f7f21..3a90eef8f9 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -20,6 +20,7 @@ interface Dict Bool.{ Bool }, Result.{ Result }, List, + Num.{ Nat }, ] ## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values. diff --git a/crates/compiler/builtins/roc/Encode.roc b/crates/compiler/builtins/roc/Encode.roc index 5941723871..ec0d10ef0a 100644 --- a/crates/compiler/builtins/roc/Encode.roc +++ b/crates/compiler/builtins/roc/Encode.roc @@ -27,7 +27,24 @@ interface Encode append, toBytes, ] - imports [] + imports [ + Num.{ + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + F32, + F64, + Dec, + }, + Bool.{ Bool }, + ] Encoder fmt := List U8, fmt -> List U8 | fmt has EncoderFormatting diff --git a/crates/compiler/builtins/roc/Hash.roc b/crates/compiler/builtins/roc/Hash.roc index a9fe6ecbc4..59cf8f476c 100644 --- a/crates/compiler/builtins/roc/Hash.roc +++ b/crates/compiler/builtins/roc/Hash.roc @@ -20,6 +20,7 @@ interface Hash ] imports [ List, Str, + Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128 }, ] ## A value that can hashed. diff --git a/crates/compiler/builtins/roc/Json.roc b/crates/compiler/builtins/roc/Json.roc index 3dca470cf5..ed39e33819 100644 --- a/crates/compiler/builtins/roc/Json.roc +++ b/crates/compiler/builtins/roc/Json.roc @@ -18,6 +18,23 @@ interface Json DecoderFormatting, DecodeResult, }, + Num.{ + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + F32, + F64, + Dec, + }, + Bool.{ Bool }, + Result, ] Json := {} has [ @@ -187,9 +204,8 @@ takeWhile = \list, predicate -> helper { taken: [], rest: list } -asciiByte = \b -> Num.toU8 b - -digits = List.range (asciiByte '0') (asciiByte '9' + 1) +digits : List U8 +digits = List.range '0' ('9' + 1) takeDigits = \bytes -> takeWhile bytes \n -> List.contains digits n @@ -198,10 +214,10 @@ takeFloat = \bytes -> { taken: intPart, rest } = takeDigits bytes when List.get rest 0 is - Ok 46 -> # 46 = . + Ok '.' -> { taken: floatPart, rest: afterAll } = takeDigits (List.split rest 1).others builtFloat = - List.concat (List.append intPart (asciiByte '.')) floatPart + List.concat (List.append intPart '.') floatPart { taken: builtFloat, rest: afterAll } @@ -305,14 +321,14 @@ decodeBool = Decode.custom \bytes, @Json {} -> # Note: this could be more performant by traversing both branches char-by-char. # Doing that would also make `rest` more correct in the erroring case. if - maybeFalse == [asciiByte 'f', asciiByte 'a', asciiByte 'l', asciiByte 's', asciiByte 'e'] + maybeFalse == ['f', 'a', 'l', 's', 'e'] then { result: Ok Bool.false, rest: afterFalse } else { before: maybeTrue, others: afterTrue } = List.split bytes 4 if - maybeTrue == [asciiByte 't', asciiByte 'r', asciiByte 'u', asciiByte 'e'] + maybeTrue == ['t', 'r', 'u', 'e'] then { result: Ok Bool.true, rest: afterTrue } else @@ -323,10 +339,10 @@ jsonString = \bytes -> { before, others: afterStartingQuote } = List.split bytes 1 if - before == [asciiByte '"'] + before == ['"'] then # TODO: handle escape sequences - { taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != asciiByte '"' + { taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"' when Str.fromUtf8 strSequence is Ok s -> @@ -351,7 +367,7 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} -> { before: afterElem, others } = List.split rest 1 if - afterElem == [asciiByte ','] + afterElem == [','] then decodeElems others (List.append accum val) else @@ -362,7 +378,7 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} -> { before, others: afterStartingBrace } = List.split bytes 1 if - before == [asciiByte '['] + before == ['['] then # TODO: empty lists when decodeElems afterStartingBrace [] is @@ -371,7 +387,7 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} -> { before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1 if - maybeEndingBrace == [asciiByte ']'] + maybeEndingBrace == [']'] then { result: Ok vals, rest: afterEndingBrace } else @@ -393,10 +409,10 @@ parseExactChar = \bytes, char -> Err _ -> { result: Err TooShort, rest: bytes } openBrace : List U8 -> DecodeResult {} -openBrace = \bytes -> parseExactChar bytes (asciiByte '{') +openBrace = \bytes -> parseExactChar bytes '{' closingBrace : List U8 -> DecodeResult {} -closingBrace = \bytes -> parseExactChar bytes (asciiByte '}') +closingBrace = \bytes -> parseExactChar bytes '}' recordKey : List U8 -> DecodeResult Str recordKey = \bytes -> jsonString bytes @@ -405,10 +421,10 @@ anything : List U8 -> DecodeResult {} anything = \bytes -> { result: Err TooShort, rest: bytes } colon : List U8 -> DecodeResult {} -colon = \bytes -> parseExactChar bytes (asciiByte ':') +colon = \bytes -> parseExactChar bytes ':' comma : List U8 -> DecodeResult {} -comma = \bytes -> parseExactChar bytes (asciiByte ',') +comma = \bytes -> parseExactChar bytes ',' tryDecode : DecodeResult a, ({ val : a, rest : List U8 } -> DecodeResult b) -> DecodeResult b tryDecode = \{ result, rest }, mapper -> diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index f5b5734767..ac65a8c5f0 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -39,6 +39,7 @@ interface List max, map4, mapTry, + walkTry, dropFirst, joinMap, any, @@ -60,9 +61,12 @@ interface List sortAsc, sortDesc, reserve, + walkBackwardsUntil, ] imports [ Bool.{ Bool }, + Result.{ Result }, + Num.{ Nat, Num, Int }, ] ## Types @@ -85,9 +89,8 @@ interface List ## ## ## Performance Details ## -## Under the hood, a list is a record containing a `len : Nat` field as well -## as a pointer to a reference count and a flat array of bytes. Unique lists -## store a capacity #Nat instead of a reference count. +## Under the hood, a list is a record containing a `len : Nat` field, a `capacity : Nat` +## field, and a pointer to a reference count and a flat array of bytes. ## ## ## Shared Lists ## @@ -109,9 +112,8 @@ interface List ## begins with a refcount of 1, because so far only `ratings` is referencing it. ## ## The second line alters this refcount. `{ foo: ratings` references -## the `ratings` list, which will result in its refcount getting incremented -## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list, -## which will result in its refcount getting incremented from 1 to 2. +## the `ratings` list, and so does `bar: ratings }`. This will result in its +## refcount getting incremented from 1 to 3. ## ## Let's turn this example into a function. ## @@ -129,11 +131,11 @@ interface List ## ## Since `ratings` represented a way to reference the list, and that way is no ## longer accessible, the list's refcount gets decremented when `ratings` goes -## out of scope. It will decrease from 2 back down to 1. +## out of scope. It will decrease from 3 back down to 2. ## ## Putting these together, when we call `getRatings 5`, what we get back is ## a record with two fields, `foo`, and `bar`, each of which refers to the same -## list, and that list has a refcount of 1. +## list, and that list has a refcount of 2. ## ## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: ## @@ -433,6 +435,13 @@ walkUntil = \list, initial, step -> Continue new -> new Break new -> new +## Same as [List.walkUntil], but does it from the end of the list instead. +walkBackwardsUntil : List elem, state, (state, elem -> [Continue state, Break state]) -> state +walkBackwardsUntil = \list, initial, func -> + when List.iterateBackwards list initial func is + Continue new -> new + Break new -> new + sum : List (Num a) -> Num a sum = \list -> List.walk list 0 Num.add @@ -957,9 +966,8 @@ mapTry = \list, toResult -> Result.map (toResult elem) \ok -> List.append state ok -## This is the same as `iterate` but with Result instead of [Continue, Break]. +## This is the same as `iterate` but with [Result] instead of `[Continue, Break]`. ## Using `Result` saves a conditional in `mapTry`. -## It might be useful to expose this in userspace? walkTry : List elem, state, (state, elem -> Result state err) -> Result state err walkTry = \list, init, func -> walkTryHelp list init func 0 (List.len list) diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 9280d49003..0b95884eb6 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -145,6 +145,7 @@ interface Num ] imports [ Bool.{ Bool }, + Result.{ Result }, ] ## Represents a number that could be either an [Int] or a [Frac]. @@ -574,7 +575,6 @@ isGte : Num a, Num a -> Bool ## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise. isZero : Num a -> Bool -isZero = \x -> x == 0 ## A number is even if dividing it by 2 gives a remainder of 0. ## diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index 381c94d383..c1fe50a5ad 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -14,7 +14,7 @@ interface Set intersection, difference, ] - imports [List, Bool.{ Bool }, Dict.{ Dict }] + imports [List, Bool.{ Bool }, Dict.{ Dict }, Num.{ Nat }] Set k := Dict.Dict k {} diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index fd8d3fcc30..d789a01353 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -43,8 +43,14 @@ interface Str appendScalar, walkScalars, walkScalarsUntil, + withCapacity, + ] + imports [ + Bool.{ Bool }, + Result.{ Result }, + List, + Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec }, ] - imports [Bool.{ Bool }, Result.{ Result }, List] ## # Types ## @@ -139,6 +145,9 @@ Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem } isEmpty : Str -> Bool concat : Str, Str -> Str +## Returns a string of the specified capacity without any content +withCapacity : Nat -> Str + ## Combine a list of strings into a single string, with a separator ## string in between each. ## diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 2cde884fe5..be9af47fe2 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -361,6 +361,7 @@ pub const STR_RESERVE: &str = "roc_builtins.str.reserve"; pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar"; pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe"; pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to"; +pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity"; pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP2: &str = "roc_builtins.list.map2"; diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index d381f591cc..461eaeada9 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -55,6 +55,8 @@ macro_rules! map_symbol_to_lowlevel_and_arity { Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)), Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)), + Symbol::NUM_IS_ZERO => Some(to_num_is_zero(Symbol::NUM_IS_ZERO, var_store)), + _ => None, } } @@ -121,6 +123,7 @@ map_symbol_to_lowlevel_and_arity! { StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2, StrToNum; STR_TO_NUM; 1, StrGetCapacity; STR_CAPACITY; 1, + StrWithCapacity; STR_WITH_CAPACITY; 1, ListLen; LIST_LEN; 1, ListWithCapacity; LIST_WITH_CAPACITY; 1, @@ -535,3 +538,33 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) ret_var, ) } + +fn to_num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var = var_store.fresh(); + + let body = Expr::RunLowLevel { + op: LowLevel::Eq, + args: vec![ + (num_var, Var(Symbol::ARG_1)), + ( + num_var, + Num( + var_store.fresh(), + "0".to_string().into_boxed_str(), + crate::expr::IntValue::I128(0i128.to_ne_bytes()), + roc_types::num::NumBound::None, + ), + ), + ], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1)], + var_store, + body, + bool_var, + ) +} diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index 5722dcaf27..787e3a1741 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -60,8 +60,6 @@ trait CopyEnv { fn clone_name(&mut self, name: SubsIndex) -> SubsIndex; - fn clone_tag_name(&mut self, tag_name: SubsIndex) -> SubsIndex; - fn clone_field_names(&mut self, field_names: SubsSlice) -> SubsSlice; fn clone_tag_names(&mut self, tag_names: SubsSlice) -> SubsSlice; @@ -95,11 +93,6 @@ impl CopyEnv for Subs { name } - #[inline(always)] - fn clone_tag_name(&mut self, tag_name: SubsIndex) -> SubsIndex { - tag_name - } - #[inline(always)] fn clone_field_names(&mut self, field_names: SubsSlice) -> SubsSlice { field_names @@ -150,11 +143,6 @@ impl<'a> CopyEnv for AcrossSubs<'a> { SubsIndex::push_new(&mut self.target.field_names, self.source[name].clone()) } - #[inline(always)] - fn clone_tag_name(&mut self, tag_name: SubsIndex) -> SubsIndex { - SubsIndex::push_new(&mut self.target.tag_names, self.source[tag_name].clone()) - } - #[inline(always)] fn clone_field_names(&mut self, field_names: SubsSlice) -> SubsSlice { SubsSlice::extend_new( @@ -259,7 +247,7 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound), Float(v1, v2, str, val, bound) => Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound), Str(str) => Str(str.clone()), - SingleQuote(char) => SingleQuote(*char), + SingleQuote(v1, v2, char, bound) => SingleQuote(sub!(*v1), sub!(*v2), *char, *bound), List { elem_var, loc_elems, @@ -725,7 +713,7 @@ fn deep_copy_pattern_help( FloatLiteral(sub!(*v1), sub!(*v2), s.clone(), *n, *bound) } StrLiteral(s) => StrLiteral(s.clone()), - SingleQuote(c) => SingleQuote(*c), + SingleQuote(v1, v2, c, bound) => SingleQuote(sub!(*v1), sub!(*v2), *c, *bound), Underscore => Underscore, AbilityMemberSpecialization { ident, specializes } => AbilityMemberSpecialization { ident: *ident, @@ -935,12 +923,13 @@ fn deep_copy_type_vars( Structure(RecursiveTagUnion(new_rec_var, new_union_tags, new_ext_var)) }) } - FunctionOrTagUnion(tag_name, symbol, ext_var) => { + FunctionOrTagUnion(tag_names, symbols, ext_var) => { let new_ext_var = descend_var!(ext_var); - let new_tag_name = env.clone_tag_name(tag_name); + let new_tag_names = env.clone_tag_names(tag_names); + let new_symbols = env.clone_lambda_names(symbols); perform_clone!(Structure(FunctionOrTagUnion( - new_tag_name, - symbol, + new_tag_names, + new_symbols, new_ext_var ))) } diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 978e8a2f61..3084daeeaa 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -1884,7 +1884,7 @@ fn pattern_to_vars_by_symbol( | IntLiteral(..) | FloatLiteral(..) | StrLiteral(_) - | SingleQuote(_) + | SingleQuote(..) | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) diff --git a/crates/compiler/can/src/exhaustive.rs b/crates/compiler/can/src/exhaustive.rs index 1fcc73e470..b094d6b7a5 100644 --- a/crates/compiler/can/src/exhaustive.rs +++ b/crates/compiler/can/src/exhaustive.rs @@ -253,7 +253,7 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern { } &FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))), StrLiteral(v) => SP::Literal(Literal::Str(v.clone())), - &SingleQuote(c) => SP::Literal(Literal::Byte(c as u8)), + &SingleQuote(_, _, c, _) => SP::Literal(Literal::Byte(c as u8)), RecordDestructure { destructs, .. } => { let tag_id = TagId(0); let mut patterns = std::vec::Vec::with_capacity(destructs.len()); diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 65bfe3f083..1483e190db 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -22,6 +22,7 @@ use roc_parse::ast::{self, Defs, EscapedChar, StrLiteral}; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; +use roc_types::num::SingleQuoteBound; use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable}; use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type}; use std::fmt::{Debug, Display}; @@ -91,7 +92,8 @@ pub enum Expr { Int(Variable, Variable, Box, IntValue, IntBound), Float(Variable, Variable, Box, f64, FloatBound), Str(Box), - SingleQuote(char), + // Number variable, precision variable, value, bound + SingleQuote(Variable, Variable, char, SingleQuoteBound), List { elem_var: Variable, loc_elems: Vec>, @@ -637,7 +639,15 @@ pub fn canonicalize_expr<'a>( let mut it = string.chars().peekable(); if let Some(char) = it.next() { if it.peek().is_none() { - (Expr::SingleQuote(char), Output::default()) + ( + Expr::SingleQuote( + var_store.fresh(), + var_store.fresh(), + char, + SingleQuoteBound::from_char(char), + ), + Output::default(), + ) } else { // multiple chars is found let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region); @@ -1642,7 +1652,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> | other @ Int(..) | other @ Float(..) | other @ Str { .. } - | other @ SingleQuote(_) + | other @ SingleQuote(..) | other @ RuntimeError(_) | other @ EmptyRecord | other @ Accessor { .. } @@ -2703,7 +2713,7 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var | Expr::Str(_) | Expr::ZeroArgumentTag { .. } | Expr::Accessor(_) - | Expr::SingleQuote(_) + | Expr::SingleQuote(..) | Expr::EmptyRecord | Expr::TypedHole(_) | Expr::RuntimeError(_) diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index e6c71422e0..6a44341fe5 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -899,7 +899,7 @@ fn fix_values_captured_in_closure_pattern( | IntLiteral(..) | FloatLiteral(..) | StrLiteral(_) - | SingleQuote(_) + | SingleQuote(..) | Underscore | Shadowed(..) | MalformedPattern(_, _) @@ -1038,7 +1038,7 @@ fn fix_values_captured_in_closure_expr( | Int(..) | Float(..) | Str(_) - | SingleQuote(_) + | SingleQuote(..) | Var(_) | AbilityMember(..) | EmptyRecord diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index 9fc18f35ed..e15899ab6e 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -12,6 +12,7 @@ use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; +use roc_types::num::SingleQuoteBound; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type}; @@ -59,7 +60,7 @@ pub enum Pattern { IntLiteral(Variable, Variable, Box, IntValue, IntBound), FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), - SingleQuote(char), + SingleQuote(Variable, Variable, char, SingleQuoteBound), Underscore, /// An identifier that marks a specialization of an ability member. @@ -95,7 +96,7 @@ impl Pattern { IntLiteral(var, ..) => Some(*var), FloatLiteral(var, ..) => Some(*var), StrLiteral(_) => None, - SingleQuote(_) => None, + SingleQuote(..) => None, Underscore => None, AbilityMemberSpecialization { .. } => None, @@ -148,7 +149,7 @@ impl Pattern { IntLiteral(..) => C::Int, FloatLiteral(..) => C::Float, StrLiteral(_) => C::Str, - SingleQuote(_) => C::Character, + SingleQuote(..) => C::Character, Underscore => C::PatternDefault, AbilityMemberSpecialization { .. } => C::PatternDefault, @@ -456,7 +457,12 @@ pub fn canonicalize_pattern<'a>( let mut it = string.chars().peekable(); if let Some(char) = it.next() { if it.peek().is_none() { - Pattern::SingleQuote(char) + Pattern::SingleQuote( + var_store.fresh(), + var_store.fresh(), + char, + SingleQuoteBound::from_char(char), + ) } else { // multiple chars is found let problem = MalformedPatternProblem::MultipleCharsInSingleQuote; @@ -724,7 +730,7 @@ impl<'a> BindingsFromPattern<'a> { | IntLiteral(..) | FloatLiteral(..) | StrLiteral(_) - | SingleQuote(_) + | SingleQuote(..) | Underscore | Shadowed(_, _, _) | MalformedPattern(_, _) diff --git a/crates/compiler/constrain/src/builtins.rs b/crates/compiler/constrain/src/builtins.rs index e0925dcbb5..d26dc58b34 100644 --- a/crates/compiler/constrain/src/builtins.rs +++ b/crates/compiler/constrain/src/builtins.rs @@ -4,7 +4,7 @@ use roc_can::expected::Expected::{self, *}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand}; use roc_module::symbol::Symbol; use roc_region::all::Region; -use roc_types::num::NumericRange; +use roc_types::num::{NumericRange, SingleQuoteBound}; use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; @@ -99,6 +99,42 @@ pub fn int_literal( constraints.exists([num_var], and_constraint) } +pub fn single_quote_literal( + constraints: &mut Constraints, + num_var: Variable, + precision_var: Variable, + expected: Expected, + region: Region, + bound: SingleQuoteBound, +) -> Constraint { + let reason = Reason::IntLiteral; + + // Always add the bound first; this improves the resolved type quality in case it's an alias like "U8". + let mut constrs = ArrayVec::<_, 3>::new(); + let num_type = add_numeric_bound_constr( + constraints, + &mut constrs, + num_var, + precision_var, + bound, + region, + Category::Character, + ); + + constrs.extend([ + constraints.equal_types( + num_type.clone(), + ForReason(reason, num_int(Type::Variable(precision_var)), region), + Category::Character, + region, + ), + constraints.equal_types(num_type, expected, Category::Character, region), + ]); + + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var], and_constraint) +} + #[inline(always)] pub fn float_literal( constraints: &mut Constraints, @@ -332,6 +368,16 @@ impl TypedNumericBound for NumBound { } } +impl TypedNumericBound for SingleQuoteBound { + fn numeric_bound(&self) -> NumericBound { + match self { + &SingleQuoteBound::AtLeast { width } => { + NumericBound::Range(NumericRange::IntAtLeastEitherSign(width)) + } + } + } +} + /// A bound placed on a number because of its literal value. /// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8 #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 9eb996941b..869b063901 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -1,7 +1,8 @@ use std::ops::Range; use crate::builtins::{ - empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type, + empty_list_type, float_literal, int_literal, list_type, num_literal, single_quote_literal, + str_type, }; use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; @@ -292,7 +293,14 @@ pub fn constrain_expr( constraints.exists(vars, and_constraint) } Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region), - SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region), + SingleQuote(num_var, precision_var, _, bound) => single_quote_literal( + constraints, + *num_var, + *precision_var, + expected, + region, + *bound, + ), List { elem_var, loc_elems, diff --git a/crates/compiler/constrain/src/pattern.rs b/crates/compiler/constrain/src/pattern.rs index 31ff602be5..babcbba774 100644 --- a/crates/compiler/constrain/src/pattern.rs +++ b/crates/compiler/constrain/src/pattern.rs @@ -71,7 +71,7 @@ fn headers_from_annotation_help( | NumLiteral(..) | IntLiteral(..) | FloatLiteral(..) - | SingleQuote(_) + | SingleQuote(..) | StrLiteral(_) => true, RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { @@ -320,9 +320,32 @@ pub fn constrain_pattern( )); } - SingleQuote(_) => { + &SingleQuote(num_var, precision_var, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + let num_type = builtins::add_numeric_bound_constr( + constraints, + &mut state.constraints, + num_var, + num_var, + bound, + region, + Category::Int, + ); + + // Link the free num var with the int var and our expectation. + let int_type = builtins::num_int(Type::Variable(precision_var)); + + state.constraints.push(constraints.equal_types( + num_type.clone(), // TODO check me if something breaks! + Expected::NoExpectation(int_type), + Category::Int, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. state.constraints.push(constraints.equal_pattern_types( - builtins::num_u32(), + num_type, expected, PatternCategory::Character, region, diff --git a/crates/compiler/derive/src/hash.rs b/crates/compiler/derive/src/hash.rs index ab1f079e98..be8e35aa3b 100644 --- a/crates/compiler/derive/src/hash.rs +++ b/crates/compiler/derive/src/hash.rs @@ -1,14 +1,191 @@ //! Derivers for the `Hash` ability. +use std::iter::once; + +use roc_can::{ + expr::{AnnotatedMark, ClosureData, Expr, Recursive}, + pattern::Pattern, +}; use roc_derive_key::hash::FlatHashKey; -use roc_module::symbol::Symbol; +use roc_module::{called_via::CalledVia, ident::Lowercase, symbol::Symbol}; +use roc_region::all::Loc; +use roc_types::{ + subs::{ + Content, FlatType, LambdaSet, OptVariable, RecordFields, SubsSlice, UnionLambdas, Variable, + VariableSubsSlice, + }, + types::RecordField, +}; -use crate::{util::Env, DerivedBody}; +use crate::{synth_var, util::Env, DerivedBody}; -pub(crate) fn derive_hash( - _env: &mut Env<'_>, - key: FlatHashKey, - _def_symbol: Symbol, -) -> DerivedBody { - match key {} +pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody { + let (body, body_type) = match key { + FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields), + }; + + let specialization_lambda_sets = + env.get_specialization_lambda_sets(body_type, Symbol::HASH_HASH); + + DerivedBody { + body, + body_type, + specialization_lambda_sets, + } +} + +fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec) -> (Expr, Variable) { + // Suppose rcd = { f1, ..., fn }. + // Build a generalized type t_rcd = { f1: t1, ..., fn: tn }, with fresh t1, ..., tn, + // so that we can re-use the derived impl for many records of the same fields. + let (record_var, record_fields) = { + let flex_fields = fields + .into_iter() + .map(|name| { + ( + name, + RecordField::Required(env.subs.fresh_unnamed_flex_var()), + ) + }) + .collect::>(); + let fields = RecordFields::insert_into_subs(env.subs, flex_fields); + let record_var = synth_var( + env.subs, + Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)), + ); + + (record_var, fields) + }; + + // Now, a hasher for this record is + // + // hash_rcd : hasher, { f1: t1, ..., fn: tn } -> hasher | hasher has Hasher + // hash_rcd = \hasher, rcd -> + // Hash.hash ( + // Hash.hash + // ... + // (Hash.hash hasher rcd.f1) + // ... + // rcd.f_n1) + // rcd.fn + // + // So, just a build a fold travelling up the fields. + let rcd_sym = env.new_symbol("rcd"); + + let hasher_sym = env.new_symbol("hasher"); + let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER)); + + let (body, body_var) = record_fields.iter_all().fold( + (Expr::Var(hasher_sym), hasher_var), + |(body, body_var), (field_name, field_var, _)| { + let field_name = env.subs[field_name].clone(); + let field_var = env.subs[field_var]; + + let field_access = Expr::Access { + record_var, + field_var, + ext_var: env.subs.fresh_unnamed_flex_var(), + loc_expr: Box::new(Loc::at_zero(Expr::Var(rcd_sym))), + field: field_name, + }; + + let (hash_fn_data, returned_hasher_var) = { + // build `Hash.hash ...` function type + // + // hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash + let exposed_hash_fn_var = env.import_builtin_symbol_var(Symbol::HASH_HASH); + + // (typeof body), (typeof field) -[clos]-> hasher_result + let this_arguments_slice = + VariableSubsSlice::insert_into_subs(env.subs, [body_var, field_var]); + let this_hash_clos_var = env.subs.fresh_unnamed_flex_var(); + let this_hasher_result_var = env.subs.fresh_unnamed_flex_var(); + let this_hash_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + this_arguments_slice, + this_hash_clos_var, + this_hasher_result_var, + )), + ); + + // hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash + // ~ (typeof body), (typeof field) -[clos]-> hasher_result + env.unify(exposed_hash_fn_var, this_hash_fn_var); + + // Hash.hash : hasher, (typeof field) -[clos]-> hasher | hasher has Hasher, (typeof field) has Hash + let hash_fn_head = Expr::AbilityMember(Symbol::HASH_HASH, None, this_hash_fn_var); + let hash_fn_data = Box::new(( + this_hash_fn_var, + Loc::at_zero(hash_fn_head), + this_hash_clos_var, + this_hasher_result_var, + )); + + (hash_fn_data, this_hasher_result_var) + }; + + let hash_arguments = vec![ + (body_var, Loc::at_zero(body)), + (field_var, Loc::at_zero(field_access)), + ]; + let call_hash = Expr::Call(hash_fn_data, hash_arguments, CalledVia::Space); + + (call_hash, returned_hasher_var) + }, + ); + + // Finally, build the closure + // \hasher, rcd -> body + + let (fn_var, fn_clos_var) = { + // Create fn_var for ambient capture; we fix it up below. + let fn_var = synth_var(env.subs, Content::Error); + + // -[fn_name]-> + let fn_captures = vec![]; + let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, fn_captures))); + let fn_clos_var = synth_var( + env.subs, + Content::LambdaSet(LambdaSet { + solved: fn_name_labels, + recursion_var: OptVariable::NONE, + unspecialized: SubsSlice::default(), + ambient_function: fn_var, + }), + ); + + // hasher, rcd_var -[fn_name]-> (hasher = body_var) + let args_slice = SubsSlice::insert_into_subs(env.subs, [hasher_var, record_var]); + env.subs.set_content( + fn_var, + Content::Structure(FlatType::Func(args_slice, fn_clos_var, body_var)), + ); + + (fn_var, fn_clos_var) + }; + + let clos_expr = Expr::Closure(ClosureData { + function_type: fn_var, + closure_type: fn_clos_var, + return_type: body_var, + name: fn_name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments: vec![ + ( + hasher_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(hasher_sym)), + ), + ( + record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(rcd_sym)), + ), + ], + loc_body: Box::new(Loc::at_zero(body)), + }); + + (clos_expr, fn_var) } diff --git a/crates/compiler/derive_key/src/encoding.rs b/crates/compiler/derive_key/src/encoding.rs index d582d0bdd7..4e29a44336 100644 --- a/crates/compiler/derive_key/src/encoding.rs +++ b/crates/compiler/derive_key/src/encoding.rs @@ -2,7 +2,7 @@ use roc_module::{ ident::{Lowercase, TagName}, symbol::Symbol, }; -use roc_types::subs::{Content, FlatType, Subs, Variable}; +use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable}; use crate::{ util::{check_derivable_ext_var, debug_name_record}, @@ -107,9 +107,14 @@ impl FlatEncodable { Ok(Key(FlatEncodableKey::TagUnion(tag_names_and_payload_sizes))) } - FlatType::FunctionOrTagUnion(name_index, _, _) => Ok(Key( - FlatEncodableKey::TagUnion(vec![(subs[name_index].clone(), 0)]), - )), + FlatType::FunctionOrTagUnion(names_index, _, _) => { + Ok(Key(FlatEncodableKey::TagUnion( + subs.get_subs_slice(names_index) + .iter() + .map(|t| (t.clone(), 0)) + .collect(), + ))) + } FlatType::EmptyRecord => Ok(Key(FlatEncodableKey::Record(vec![]))), FlatType::EmptyTagUnion => Ok(Key(FlatEncodableKey::TagUnion(vec![]))), // diff --git a/crates/compiler/derive_key/src/hash.rs b/crates/compiler/derive_key/src/hash.rs index 04c8c6c574..52c8cbc79e 100644 --- a/crates/compiler/derive_key/src/hash.rs +++ b/crates/compiler/derive_key/src/hash.rs @@ -1,7 +1,10 @@ -use roc_module::symbol::Symbol; +use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_types::subs::{Content, FlatType, Subs, Variable}; -use crate::DeriveError; +use crate::{ + util::{check_derivable_ext_var, debug_name_record}, + DeriveError, +}; #[derive(Hash)] pub enum FlatHash { @@ -12,11 +15,16 @@ pub enum FlatHash { } #[derive(Hash, PartialEq, Eq, Debug, Clone)] -pub enum FlatHashKey {} +pub enum FlatHashKey { + // Unfortunate that we must allocate here, c'est la vie + Record(Vec), +} impl FlatHashKey { pub(crate) fn debug_name(&self) -> String { - unreachable!() // yet + match self { + FlatHashKey::Record(fields) => debug_name_record(fields), + } } } @@ -31,8 +39,26 @@ impl FlatHash { Symbol::STR_STR => Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_STR_BYTES)), _ => Err(Underivable), }, - FlatType::Record(_fields, _ext) => { - Err(Underivable) // yet + FlatType::Record(fields, ext) => { + let (fields_iter, ext) = fields.unsorted_iterator_and_ext(subs, ext); + + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyRecord)) + })?; + + let mut field_names = Vec::with_capacity(fields.len()); + for (field_name, record_field) in fields_iter { + if record_field.is_optional() { + // Can't derive a concrete decoder for optional fields, since those are + // compile-time-polymorphic + return Err(Underivable); + } + field_names.push(field_name.clone()); + } + + field_names.sort(); + + Ok(Key(FlatHashKey::Record(field_names))) } FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => { Err(Underivable) // yet @@ -40,7 +66,7 @@ impl FlatHash { FlatType::FunctionOrTagUnion(_name_index, _, _) => { Err(Underivable) // yet } - FlatType::EmptyRecord => Err(Underivable), // yet + FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))), FlatType::EmptyTagUnion => { Err(Underivable) // yet } diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 4a1e3c3c63..d705e5b7ef 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -718,7 +718,7 @@ pub fn construct_optimization_passes<'a>( OptLevel::Optimize => { pmb.set_optimization_level(OptimizationLevel::Aggressive); // this threshold seems to do what we want - pmb.set_inliner_with_threshold(275); + pmb.set_inliner_with_threshold(750); } } @@ -6049,6 +6049,20 @@ fn run_low_level<'a, 'ctx, 'env>( bitcode::STR_TRIM_RIGHT, ) } + StrWithCapacity => { + // Str.withCapacity : Nat -> Str + debug_assert_eq!(args.len(), 1); + + let str_len = load_symbol(scope, &args[0]); + + call_str_bitcode_fn( + env, + &[], + &[str_len], + BitcodeReturns::Str, + bitcode::STR_WITH_CAPACITY, + ) + } ListLen => { // List.len : List * -> Nat debug_assert_eq!(args.len(), 1); @@ -6171,7 +6185,7 @@ fn run_low_level<'a, 'ctx, 'env>( list_prepend(env, original_wrapper, elem, elem_layout) } StrGetUnsafe => { - // List.getUnsafe : Str, Nat -> u8 + // Str.getUnsafe : Str, Nat -> u8 debug_assert_eq!(args.len(), 2); let wrapper_struct = load_symbol(scope, &args[0]); diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index a0a763c589..f23c487664 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -302,6 +302,7 @@ impl<'a> LowLevelCall<'a> { StrSubstringUnsafe => { self.load_args_and_call_zig(backend, bitcode::STR_SUBSTRING_UNSAFE) } + StrWithCapacity => self.load_args_and_call_zig(backend, bitcode::STR_WITH_CAPACITY), // List ListLen => match backend.storage.get(&self.arguments[0]) { diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 70334f4baf..df9d5804f2 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -2239,24 +2239,15 @@ fn update<'a>( // add the prelude let mut header = header; - if ![ModuleId::RESULT, ModuleId::BOOL].contains(&header.module_id) { - extend_header_with_builtin(&mut header, ModuleId::RESULT); - } - - if ![ModuleId::NUM, ModuleId::BOOL, ModuleId::RESULT].contains(&header.module_id) { - extend_header_with_builtin(&mut header, ModuleId::NUM); - } - - if ![ModuleId::BOOL].contains(&header.module_id) { - extend_header_with_builtin(&mut header, ModuleId::BOOL); - } - if !header.module_id.is_builtin() { - extend_header_with_builtin(&mut header, ModuleId::BOX); + extend_header_with_builtin(&mut header, ModuleId::NUM); + extend_header_with_builtin(&mut header, ModuleId::BOOL); extend_header_with_builtin(&mut header, ModuleId::STR); + extend_header_with_builtin(&mut header, ModuleId::LIST); + extend_header_with_builtin(&mut header, ModuleId::RESULT); extend_header_with_builtin(&mut header, ModuleId::DICT); extend_header_with_builtin(&mut header, ModuleId::SET); - extend_header_with_builtin(&mut header, ModuleId::LIST); + extend_header_with_builtin(&mut header, ModuleId::BOX); extend_header_with_builtin(&mut header, ModuleId::ENCODE); extend_header_with_builtin(&mut header, ModuleId::DECODE); extend_header_with_builtin(&mut header, ModuleId::HASH); diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index a45b965377..57ac2ac823 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -30,6 +30,7 @@ pub enum LowLevel { StrAppendScalar, StrGetScalarUnsafe, StrGetCapacity, + StrWithCapacity, ListLen, ListWithCapacity, ListReserve, @@ -249,6 +250,7 @@ map_symbol_to_lowlevel! { StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE, StrToNum <= STR_TO_NUM, StrGetCapacity <= STR_CAPACITY, + StrWithCapacity <= STR_WITH_CAPACITY, ListLen <= LIST_LEN, ListGetCapacity <= LIST_CAPACITY, ListWithCapacity <= LIST_WITH_CAPACITY, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index df6acd2b7f..50712f1764 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1296,6 +1296,7 @@ define_builtins! { 50 STR_REPLACE_EACH: "replaceEach" 51 STR_REPLACE_FIRST: "replaceFirst" 52 STR_REPLACE_LAST: "replaceLast" + 53 STR_WITH_CAPACITY: "withCapacity" } 6 LIST: "List" => { 0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias @@ -1373,6 +1374,8 @@ define_builtins! { 72 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel" 73 LIST_CAPACITY: "capacity" 74 LIST_MAP_TRY: "mapTry" + 75 LIST_WALK_TRY: "walkTry" + 76 LIST_WALK_BACKWARDS_UNTIL: "walkBackwardsUntil" } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index d08835727f..a81139c125 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -881,7 +881,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { Unreachable => arena.alloc_slice_copy(&[irrelevant]), ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes | StrGetCapacity | ListGetCapacity => arena.alloc_slice_copy(&[borrowed]), - ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]), + ListWithCapacity | StrWithCapacity => arena.alloc_slice_copy(&[irrelevant]), ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListConcat => arena.alloc_slice_copy(&[owned, owned]), diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 80b08ebb3a..4bb5c799ed 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4027,12 +4027,18 @@ pub fn with_hole<'a>( hole, ), - SingleQuote(character) => Stmt::Let( - assigned, - Expr::Literal(Literal::Int((character as i128).to_ne_bytes())), - Layout::int_width(IntWidth::I32), - hole, - ), + SingleQuote(_, _, character, _) => { + let layout = layout_cache + .from_var(env.arena, variable, env.subs) + .unwrap(); + + Stmt::Let( + assigned, + Expr::Literal(Literal::Int((character as i128).to_ne_bytes())), + layout, + hole, + ) + } LetNonRec(def, cont) => from_can_let( env, procs, @@ -8883,10 +8889,12 @@ fn from_can_pattern_help<'a>( IntOrFloatValue::Float(*float), )), StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), - SingleQuote(c) => Ok(Pattern::IntLiteral( - (*c as i128).to_ne_bytes(), - IntWidth::I32, - )), + SingleQuote(var, _, c, _) => match layout_cache.from_var(env.arena, *var, env.subs) { + Ok(Layout::Builtin(Builtin::Int(width))) => { + Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width)) + } + o => internal_error!("an integer width was expected, but we found {:?}", o), + }, Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { original_region: *region, shadow: ident.clone(), diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 2f9ec538a0..0786c33e3c 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -12,7 +12,7 @@ use roc_problem::can::RuntimeError; use roc_target::{PtrWidth, TargetInfo}; use roc_types::num::NumericRange; use roc_types::subs::{ - self, Content, FlatType, GetSubsSlice, Label, OptVariable, RecordFields, Subs, UnionTags, + self, Content, FlatType, GetSubsSlice, Label, OptVariable, RecordFields, Subs, UnsortedUnionLabels, Variable, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError}; @@ -3152,16 +3152,18 @@ fn layout_from_flat_type<'a>( layout_from_non_recursive_union(env, &tags).map(Ok) } - FunctionOrTagUnion(tag_name, _, ext_var) => { + FunctionOrTagUnion(tag_names, _, ext_var) => { debug_assert!( ext_var_is_empty_tag_union(subs, ext_var), "If ext_var wasn't empty, this wouldn't be a FunctionOrTagUnion!" ); - let union_tags = UnionTags::from_tag_name_index(tag_name); - let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); + let tag_names = subs.get_subs_slice(tag_names); + let unsorted_tags = UnsortedUnionLabels { + tags: tag_names.iter().map(|t| (t, &[] as &[Variable])).collect(), + }; - layout_from_non_recursive_union(env, &tags).map(Ok) + layout_from_non_recursive_union(env, &unsorted_tags).map(Ok) } RecursiveTagUnion(rec_var, tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); diff --git a/crates/compiler/parse/src/blankspace.rs b/crates/compiler/parse/src/blankspace.rs index 3e56027cd6..549990af96 100644 --- a/crates/compiler/parse/src/blankspace.rs +++ b/crates/compiler/parse/src/blankspace.rs @@ -345,7 +345,12 @@ fn eat_spaces<'a>( state = state.advance(1); return eat_line_comment(state, comments_and_newlines); } - _ => break, + _ => { + if !comments_and_newlines.is_empty() { + state = state.mark_current_indent(); + } + break; + } } } @@ -398,7 +403,10 @@ fn eat_line_comment<'a>( index += 1; continue 'outer; } - _ => break, + _ => { + state = state.mark_current_indent(); + break; + } } index += 1; @@ -490,7 +498,10 @@ fn eat_line_comment<'a>( index += 1; continue 'outer; } - _ => break, + _ => { + state = state.mark_current_indent(); + break; + } } index += 1; @@ -554,7 +565,10 @@ fn eat_line_comment<'a>( index += 1; continue 'outer; } - _ => break, + _ => { + state = state.mark_current_indent(); + break; + } } index += 1; diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 0666dd4e25..f51433e3da 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -8,8 +8,8 @@ use crate::blankspace::{ use crate::ident::{lowercase_ident, parse_ident, Ident}; use crate::keyword; use crate::parser::{ - self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then, - trailing_sep_by0, word1, word2, EExpect, EExpr, EIf, EInParens, ELambda, EList, ENumber, + self, backtrackable, optional, parse_word1, sep_by1, sep_by1_e, specialize, specialize_ref, + then, trailing_sep_by0, word1, word2, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, }; use crate::pattern::{loc_closure_param, loc_has_parser}; @@ -205,7 +205,10 @@ fn parse_loc_term_or_underscore_or_conditional<'a>( loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), - loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), + loc!(specialize( + EExpr::Closure, + closure_help(min_indent, options) + )), loc!(underscore_expression()), loc!(record_literal_help(min_indent)), loc!(specialize(EExpr::List, list_literal_help(min_indent))), @@ -230,7 +233,10 @@ fn parse_loc_term_or_underscore<'a>( loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), - loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), + loc!(specialize( + EExpr::Closure, + closure_help(min_indent, options) + )), loc!(underscore_expression()), loc!(record_literal_help(min_indent)), loc!(specialize(EExpr::List, list_literal_help(min_indent))), @@ -253,7 +259,10 @@ fn parse_loc_term<'a>( loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), - loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), + loc!(specialize( + EExpr::Closure, + closure_help(min_indent, options) + )), loc!(record_literal_help(min_indent)), loc!(specialize(EExpr::List, list_literal_help(min_indent))), loc!(map_with_arena!( @@ -344,7 +353,6 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { fn parse_expr_start<'a>( min_indent: u32, options: ExprParseOptions, - start_column: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -355,8 +363,11 @@ fn parse_expr_start<'a>( when::expr_help(min_indent, options) )), loc!(specialize(EExpr::Expect, expect_help(min_indent, options))), - loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), - loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start_column, a, s)), + loc!(specialize( + EExpr::Closure, + closure_help(min_indent, options) + )), + loc!(move |a, s| parse_expr_operator_chain(min_indent, options, a, s)), fail_expr_start_e() ] .parse(arena, state) @@ -365,10 +376,11 @@ fn parse_expr_start<'a>( fn parse_expr_operator_chain<'a>( min_indent: u32, options: ExprParseOptions, - start_column: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { + let min_indent = state.check_indent(min_indent, EExpr::IndentStart)?; + let (_, expr, state) = loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state)?; @@ -387,7 +399,7 @@ fn parse_expr_operator_chain<'a>( end, }; - parse_expr_end(min_indent, options, start_column, expr_state, arena, state) + parse_expr_end(min_indent, options, expr_state, arena, state) } } } @@ -604,13 +616,11 @@ fn numeric_negate_expression<'a, T>( fn parse_defs_end<'a>( _options: ExprParseOptions, - start_column: u32, + min_indent: u32, mut defs: Defs<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Defs<'a>, EExpr<'a>> { - let min_indent = start_column; - let mut global_state = state; loop { @@ -723,7 +733,7 @@ fn parse_defs_end<'a>( loc_has_parser(min_indent).parse(arena, state.clone()) { let (_, (type_def, def_region), state) = finish_parsing_ability_def_help( - start_column, + min_indent, Loc::at(name_region, name), args, loc_has, @@ -967,14 +977,12 @@ fn parse_defs_end<'a>( fn parse_defs_expr<'a>( options: ExprParseOptions, - start_column: u32, + min_indent: u32, defs: Defs<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { - let min_indent = start_column; - - match parse_defs_end(options, start_column, defs, arena, state) { + match parse_defs_end(options, min_indent, defs, arena, state) { Err(bad) => Err(bad), Ok((_, def_state, state)) => { // this is no def, because there is no `=` or `:`; parse as an expr @@ -1050,7 +1058,6 @@ enum AliasOrOpaque { fn finish_parsing_alias_or_opaque<'a>( min_indent: u32, options: ExprParseOptions, - start_column: u32, expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -1059,7 +1066,7 @@ fn finish_parsing_alias_or_opaque<'a>( kind: AliasOrOpaque, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { let expr_region = expr_state.expr.region; - let indented_more = start_column + 1; + let indented_more = min_indent + 1; let (expr, arguments) = expr_state .validate_is_type_def(arena, loc_op, kind) @@ -1175,7 +1182,7 @@ fn finish_parsing_alias_or_opaque<'a>( } }; - parse_defs_expr(options, start_column, defs, arena, state) + parse_defs_expr(options, min_indent, defs, arena, state) } mod ability { @@ -1364,7 +1371,6 @@ fn finish_parsing_ability_def_help<'a>( fn parse_expr_operator<'a>( min_indent: u32, options: ExprParseOptions, - start_column: u32, mut expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -1405,11 +1411,11 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; expr_state.end = new_end; - parse_expr_end(min_indent, options, start_column, expr_state, arena, state) + parse_expr_end(min_indent, options, expr_state, arena, state) } BinOp::Assignment => { let expr_region = expr_state.expr.region; - let indented_more = start_column + 1; + let indented_more = min_indent + 1; let call = expr_state .validate_assignment_or_backpassing(arena, loc_op, EExpr::ElmStyleFunction) @@ -1448,11 +1454,11 @@ fn parse_expr_operator<'a>( let mut defs = Defs::default(); defs.push_value_def(value_def, def_region, &[], &[]); - parse_defs_expr(options, start_column, defs, arena, state) + parse_defs_expr(options, min_indent, defs, arena, state) } BinOp::Backpassing => { let expr_region = expr_state.expr.region; - let indented_more = start_column + 1; + let indented_more = min_indent + 1; let call = expr_state .validate_assignment_or_backpassing(arena, loc_op, |_, pos| { @@ -1502,7 +1508,6 @@ fn parse_expr_operator<'a>( BinOp::IsAliasType | BinOp::IsOpaqueType => finish_parsing_alias_or_opaque( min_indent, options, - start_column, expr_state, loc_op, arena, @@ -1552,7 +1557,7 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; // TODO new start? - parse_expr_end(min_indent, options, start_column, expr_state, arena, state) + parse_expr_end(min_indent, options, expr_state, arena, state) } } } @@ -1566,7 +1571,6 @@ fn parse_expr_operator<'a>( fn parse_expr_end<'a>( min_indent: u32, options: ExprParseOptions, - start_column: u32, mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, @@ -1626,13 +1630,13 @@ fn parse_expr_end<'a>( let args = arguments.into_bump_slice(); let (_, (type_def, def_region), state) = - finish_parsing_ability_def_help(start_column, name, args, has, arena, state)?; + finish_parsing_ability_def_help(min_indent, name, args, has, arena, state)?; let mut defs = Defs::default(); defs.push_type_def(type_def, def_region, &[], &[]); - parse_defs_expr(options, start_column, defs, arena, state) + parse_defs_expr(options, min_indent, defs, arena, state) } Ok((_, mut arg, state)) => { let new_end = state.pos(); @@ -1660,7 +1664,7 @@ fn parse_expr_end<'a>( expr_state.end = new_end; expr_state.spaces_after = new_spaces; - parse_expr_end(min_indent, options, start_column, expr_state, arena, state) + parse_expr_end(min_indent, options, expr_state, arena, state) } } } @@ -1672,15 +1676,7 @@ fn parse_expr_end<'a>( Ok((_, loc_op, state)) => { expr_state.consume_spaces(arena); expr_state.initial = before_op; - parse_expr_operator( - min_indent, - options, - start_column, - expr_state, - loc_op, - arena, - state, - ) + parse_expr_operator(min_indent, options, expr_state, loc_op, arena, state) } Err((NoProgress, _, mut state)) => { // try multi-backpassing @@ -1714,8 +1710,6 @@ fn parse_expr_end<'a>( match word2(b'<', b'-', EExpr::BackpassArrow).parse(arena, state) { Err((_, fail, state)) => Err((MadeProgress, fail, state)), Ok((_, _, state)) => { - let min_indent = start_column; - let parse_body = space0_before_e( move |a, s| parse_loc_expr(min_indent + 1, a, s), min_indent, @@ -1793,8 +1787,7 @@ fn parse_loc_expr_with_options<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { - let column = state.column(); - parse_expr_start(min_indent, options, column, arena, state) + parse_expr_start(min_indent, options, arena, state) } /// If the given Expr would parse the same way as a valid Pattern, convert it. @@ -1973,47 +1966,67 @@ pub fn toplevel_defs<'a>(min_indent: u32) -> impl Parser<'a, Defs<'a>, EExpr<'a> fn closure_help<'a>( min_indent: u32, options: ExprParseOptions, -) -> impl Parser<'a, Expr<'a>, ELambda<'a>> { - map_with_arena!( - skip_first!( - // All closures start with a '\' - e.g. (\x -> x + 1) - word1(b'\\', ELambda::Start), - // Once we see the '\', we're committed to parsing this as a closure. - // It may turn out to be malformed, but it is definitely a closure. - and!( - // Parse the params - // Params are comma-separated - sep_by1_e( - word1(b',', ELambda::Comma), - space0_around_ee( - specialize(ELambda::Pattern, loc_closure_param(min_indent)), - min_indent, - ELambda::IndentArg, - ELambda::IndentArrow - ), - ELambda::Arg, - ), - skip_first!( - // Parse the -> which separates params from body - word2(b'-', b'>', ELambda::Arrow), - // Parse the body - space0_before_e( - specialize_ref(ELambda::Body, move |arena, state| { - parse_loc_expr_with_options(min_indent, options, arena, state) - }), - min_indent, - ELambda::IndentBody - ) - ) - ) - ), - |arena: &'a Bump, (params, loc_body)| { - let params: Vec<'a, Loc>> = params; - let params: &'a [Loc>] = params.into_bump_slice(); +) -> impl Parser<'a, Expr<'a>, EClosure<'a>> { + move |arena, state| parse_closure_help(arena, state, min_indent, options) +} - Expr::Closure(params, arena.alloc(loc_body)) - } +fn parse_closure_help<'a>( + arena: &'a Bump, + state: State<'a>, + _min_indent: u32, + options: ExprParseOptions, +) -> ParseResult<'a, Expr<'a>, EClosure<'a>> { + let start_indent = state.line_indent(); + + let min_indent = start_indent; + + // All closures start with a '\' - e.g. (\x -> x + 1) + let (_, (), state) = parse_word1(state, min_indent, b'\\', EClosure::Start)?; + + // After the first token, all other tokens must be indented past the start of the line + let min_indent = min_indent + 1; + + // Once we see the '\', we're committed to parsing this as a closure. + // It may turn out to be malformed, but it is definitely a closure. + + // Parse the params + // Params are comma-separated + let (_, params, state) = sep_by1_e( + word1(b',', EClosure::Comma), + space0_around_ee( + specialize(EClosure::Pattern, loc_closure_param(min_indent)), + min_indent, + EClosure::IndentArg, + EClosure::IndentArrow, + ), + EClosure::Arg, ) + .parse(arena, state) + .map_err(|(_p, e, s)| (MadeProgress, e, s))?; + + let (_, loc_body, state) = skip_first!( + // Parse the -> which separates params from body + word2(b'-', b'>', EClosure::Arrow), + // Parse the body + space0_before_e( + specialize_ref(EClosure::Body, move |arena, state| { + parse_loc_expr_with_options(min_indent, options, arena, state) + }), + min_indent, + EClosure::IndentBody + ) + ) + .parse(arena, state) + .map_err(|(_p, e, s)| (MadeProgress, e, s))?; + + let params: Vec<'a, Loc>> = params; + let params: &'a [Loc>] = params.into_bump_slice(); + + Ok(( + MadeProgress, + Expr::Closure(params, arena.alloc(loc_body)), + state, + )) } mod when { diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index e66293b7b7..8769607efa 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -89,7 +89,7 @@ impl_space_problem! { EIf<'a>, EImports, EInParens<'a>, - ELambda<'a>, + EClosure<'a>, EList<'a>, EPackageEntry<'a>, EPackages<'a>, @@ -354,7 +354,7 @@ pub enum EExpr<'a> { Expect(EExpect<'a>, Position), - Lambda(ELambda<'a>, Position), + Closure(EClosure<'a>, Position), Underscore(Position), InParens(EInParens<'a>, Position), @@ -428,7 +428,7 @@ pub enum EInParens<'a> { } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum ELambda<'a> { +pub enum EClosure<'a> { Space(BadInputError, Position), Start(Position), Arrow(Position), @@ -1452,6 +1452,31 @@ where } } +pub fn parse_word1<'a, ToError, E>( + state: State<'a>, + min_indent: u32, + word: u8, + to_error: ToError, +) -> ParseResult<'a, (), E> +where + ToError: Fn(Position) -> E, + E: 'a, +{ + debug_assert_ne!(word, b'\n'); + + if min_indent > state.column() { + return Err((NoProgress, to_error(state.pos()), state)); + } + + match state.bytes().first() { + Some(x) if *x == word => { + let state = state.advance(1); + Ok((MadeProgress, (), state)) + } + _ => Err((NoProgress, to_error(state.pos()), state)), + } +} + pub fn word2<'a, ToError, E>(word_1: u8, word_2: u8, to_error: ToError) -> impl Parser<'a, (), E> where ToError: Fn(Position) -> E, diff --git a/crates/compiler/parse/src/state.rs b/crates/compiler/parse/src/state.rs index d6e37a1f77..0b86c801bf 100644 --- a/crates/compiler/parse/src/state.rs +++ b/crates/compiler/parse/src/state.rs @@ -1,13 +1,15 @@ use roc_region::all::{Position, Region}; use std::fmt; +use crate::parser::Progress; + /// A position in a source file. // NB: [Copy] is explicitly NOT derived to reduce the chance of bugs due to accidentally re-using // parser state. #[derive(Clone)] pub struct State<'a> { /// The raw input bytes from the file. - /// Beware: original_bytes[0] always points the the start of the file. + /// Beware: original_bytes[0] always points at the start of the file. /// Use bytes()[0] to access the current byte the parser is inspecting original_bytes: &'a [u8], @@ -16,6 +18,9 @@ pub struct State<'a> { /// Position of the start of the current line pub(crate) line_start: Position, + + /// Position of the first non-whitespace character on the current line + pub(crate) line_start_after_whitespace: Position, } impl<'a> State<'a> { @@ -24,6 +29,10 @@ impl<'a> State<'a> { original_bytes: bytes, offset: 0, line_start: Position::zero(), + + // Technically not correct. + // We don't know the position of the first non-whitespace character yet. + line_start_after_whitespace: Position::zero(), } } @@ -39,6 +48,24 @@ impl<'a> State<'a> { self.pos().offset - self.line_start.offset } + pub fn line_indent(&self) -> u32 { + self.line_start_after_whitespace.offset - self.line_start.offset + } + + /// Check that the indent is at least `indent` spaces. + /// Return a new indent if the current indent is greater than `indent`. + pub fn check_indent( + &self, + indent: u32, + e: impl Fn(Position) -> E, + ) -> Result)> { + if self.column() < indent { + Err((Progress::NoProgress, e(self.pos()), self.clone())) + } else { + Ok(std::cmp::max(indent, self.line_indent())) + } + } + /// Mutably advance the state by a given offset #[inline(always)] pub(crate) fn advance_mut(&mut self, offset: usize) { @@ -70,6 +97,18 @@ impl<'a> State<'a> { pub(crate) const fn advance_newline(mut self) -> State<'a> { self.offset += 1; self.line_start = self.pos(); + + // WARNING! COULD CAUSE BUGS IF WE FORGET TO CALL mark_current_ident LATER! + // We really need to be stricter about this. + self.line_start_after_whitespace = self.line_start; + + self + } + + #[must_use] + #[inline(always)] + pub(crate) const fn mark_current_indent(mut self) -> State<'a> { + self.line_start_after_whitespace = self.pos(); self } diff --git a/crates/compiler/parse/tests/snapshots/fail/lambda_extra_comma.expr.result-ast b/crates/compiler/parse/tests/snapshots/fail/lambda_extra_comma.expr.result-ast new file mode 100644 index 0000000000..5ef400a9ca --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/fail/lambda_extra_comma.expr.result-ast @@ -0,0 +1 @@ +Expr(Closure(Arg(@1), @1), @0) \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/fail/lambda_extra_comma.expr.roc b/crates/compiler/parse/tests/snapshots/fail/lambda_extra_comma.expr.roc new file mode 100644 index 0000000000..e687cafe45 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/fail/lambda_extra_comma.expr.roc @@ -0,0 +1 @@ +\,x -> 1 \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/fail/lambda_missing_indent.expr.result-ast b/crates/compiler/parse/tests/snapshots/fail/lambda_missing_indent.expr.result-ast new file mode 100644 index 0000000000..41cd968fe5 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/fail/lambda_missing_indent.expr.result-ast @@ -0,0 +1 @@ +Expr(Closure(IndentBody(@5), @3), @0) \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/fail/lambda_missing_indent.expr.roc b/crates/compiler/parse/tests/snapshots/fail/lambda_missing_indent.expr.roc new file mode 100644 index 0000000000..c98ae00db3 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/fail/lambda_missing_indent.expr.roc @@ -0,0 +1,2 @@ +\x -> +1 \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/lambda_in_chain.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/lambda_in_chain.expr.result-ast new file mode 100644 index 0000000000..5d1bc68b66 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/lambda_in_chain.expr.result-ast @@ -0,0 +1,71 @@ +BinOps( + [ + ( + @0-10 SpaceAfter( + Str( + PlainLine( + "a string", + ), + ), + [ + Newline, + ], + ), + @11-13 Pizza, + ), + ( + @14-24 SpaceAfter( + Var { + module_name: "Str", + ident: "toUtf8", + }, + [ + Newline, + ], + ), + @25-27 Pizza, + ), + ( + @28-54 SpaceAfter( + Apply( + @28-36 Var { + module_name: "List", + ident: "map", + }, + [ + @37-54 Closure( + [ + @38-42 Identifier( + "byte", + ), + ], + @46-54 BinOps( + [ + ( + @46-50 Var { + module_name: "", + ident: "byte", + }, + @51-52 Plus, + ), + ], + @53-54 Num( + "1", + ), + ), + ), + ], + Space, + ), + [ + Newline, + ], + ), + @55-57 Pizza, + ), + ], + @58-70 Var { + module_name: "List", + ident: "reverse", + }, +) diff --git a/crates/compiler/parse/tests/snapshots/pass/lambda_in_chain.expr.roc b/crates/compiler/parse/tests/snapshots/pass/lambda_in_chain.expr.roc new file mode 100644 index 0000000000..921b42c296 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/lambda_in_chain.expr.roc @@ -0,0 +1,4 @@ +"a string" +|> Str.toUtf8 +|> List.map \byte -> byte + 1 +|> List.reverse \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/lambda_indent.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/lambda_indent.expr.result-ast new file mode 100644 index 0000000000..426711c040 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/lambda_indent.expr.result-ast @@ -0,0 +1,15 @@ +Closure( + [ + @1-2 Identifier( + "x", + ), + ], + @8-9 SpaceBefore( + Num( + "1", + ), + [ + Newline, + ], + ), +) diff --git a/crates/compiler/parse/tests/snapshots/pass/lambda_indent.expr.roc b/crates/compiler/parse/tests/snapshots/pass/lambda_indent.expr.roc new file mode 100644 index 0000000000..9c7a43a9ff --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/lambda_indent.expr.roc @@ -0,0 +1,2 @@ +\x -> + 1 \ No newline at end of file diff --git a/crates/compiler/parse/tests/test_parse.rs b/crates/compiler/parse/tests/test_parse.rs index 7d6722ca5c..c7816273ef 100644 --- a/crates/compiler/parse/tests/test_parse.rs +++ b/crates/compiler/parse/tests/test_parse.rs @@ -120,6 +120,8 @@ mod test_parse { // see tests/snapshots to see test input(.roc) and expected output(.result-ast) snapshot_tests! { + fail/lambda_extra_comma.expr, + fail/lambda_missing_indent.expr, fail/type_argument_no_arrow.expr, fail/type_double_comma.expr, pass/ability_demand_signature_is_multiline.expr, @@ -157,6 +159,7 @@ mod test_parse { pass/empty_string.expr, pass/equals_with_spaces.expr, pass/equals.expr, + pass/expect_fx.module, pass/expect.expr, pass/float_with_underscores.expr, pass/full_app_header_trailing_commas.header, @@ -167,6 +170,8 @@ mod test_parse { pass/if_def.expr, pass/int_with_underscore.expr, pass/interface_with_newline.header, + pass/lambda_in_chain.expr, + pass/lambda_indent.expr, pass/list_closing_indent_not_enough.expr, pass/list_closing_same_indent_no_trailing_comma.expr, pass/list_closing_same_indent_with_trailing_comma.expr, @@ -181,6 +186,7 @@ mod test_parse { pass/module_def_newline.module, pass/multi_backpassing.expr, pass/multi_char_string.expr, + pass/multiline_string.expr, pass/multiline_type_signature_with_comment.expr, pass/multiline_type_signature.expr, pass/multiple_fields.expr, @@ -204,7 +210,6 @@ mod test_parse { pass/not_docs.expr, pass/number_literal_suffixes.expr, pass/one_backpassing.expr, - pass/multiline_string.expr, pass/one_char_string.expr, pass/one_def.expr, pass/one_minus_two.expr, @@ -251,7 +256,6 @@ mod test_parse { pass/spaced_singleton_list.expr, pass/spaces_inside_empty_list.expr, pass/standalone_module_defs.module, - pass/expect_fx.module, pass/string_without_escape.expr, pass/sub_var_with_spaces.expr, pass/sub_with_spaces.expr, @@ -277,9 +281,9 @@ mod test_parse { pass/var_minus_two.expr, pass/var_then.expr, pass/var_when.expr, + pass/when_if_guard.expr, pass/when_in_assignment.expr, pass/when_in_function.expr, - pass/when_if_guard.expr, pass/when_in_parens_indented.expr, pass/when_in_parens.expr, pass/when_with_alternative_patterns.expr, diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 19a5651f3c..475ba772a0 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -1522,8 +1522,9 @@ fn solve( symbols.iter().any(|(s, _)| { let var = env.get_var_by_symbol(s).expect("Symbol not solved!"); - let content = subs.get_content_without_compacting(var); - !matches!(content, Error | Structure(FlatType::Func(..))) + let (_, underlying_content) = chase_alias_content(subs, var); + + !matches!(underlying_content, Error | Structure(FlatType::Func(..))) }) }; @@ -1555,6 +1556,17 @@ fn solve( state } +fn chase_alias_content(subs: &Subs, mut var: Variable) -> (Variable, &Content) { + loop { + match subs.get_content_without_compacting(var) { + Content::Alias(_, _, real_var, _) => { + var = *real_var; + } + content => return (var, content), + } + } +} + #[allow(clippy::too_many_arguments)] fn compact_lambdas_and_check_obligations( arena: &Bump, @@ -2299,10 +2311,11 @@ fn type_to_variable<'a>( unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); } - let slice = SubsIndex::new(subs.tag_names.len() as u32); - subs.tag_names.push(tag_name.clone()); + let tag_names = SubsSlice::extend_new(&mut subs.tag_names, [tag_name.clone()]); + let symbols = SubsSlice::extend_new(&mut subs.closure_names, [*symbol]); - let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); + let content = + Content::Structure(FlatType::FunctionOrTagUnion(tag_names, symbols, ext)); register_with_known_var(subs, destination, rank, pools, content) } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 2abc12d6cc..95050db875 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -7841,4 +7841,135 @@ mod solve_expr { "hasher -> hasher | hasher has Hasher", ); } + + #[test] + fn dispatch_tag_union_function_inferred() { + infer_eq_without_problem( + indoc!( + r#" + g = if Bool.true then A else B + + g "" + "# + ), + "[A Str, B Str]*", + ); + } + + #[test] + fn check_char_as_u8() { + infer_eq_without_problem( + indoc!( + r#" + x : U8 + x = '.' + + x + "# + ), + "U8", + ); + } + + #[test] + fn check_char_as_u16() { + infer_eq_without_problem( + indoc!( + r#" + x : U16 + x = '.' + + x + "# + ), + "U16", + ); + } + + #[test] + fn check_char_as_u32() { + infer_eq_without_problem( + indoc!( + r#" + x : U32 + x = '.' + + x + "# + ), + "U32", + ); + } + + #[test] + fn check_char_pattern_as_u8() { + infer_eq_without_problem( + indoc!( + r#" + f : U8 -> _ + f = \c -> + when c is + '.' -> 'A' + c1 -> c1 + + f + "# + ), + "U8 -> U8", + ); + } + + #[test] + fn check_char_pattern_as_u16() { + infer_eq_without_problem( + indoc!( + r#" + f : U16 -> _ + f = \c -> + when c is + '.' -> 'A' + c1 -> c1 + + f + "# + ), + "U16 -> U16", + ); + } + + #[test] + fn check_char_pattern_as_u32() { + infer_eq_without_problem( + indoc!( + r#" + f : U32 -> _ + f = \c -> + when c is + '.' -> 'A' + c1 -> c1 + + f + "# + ), + "U32 -> U32", + ); + } + + #[test] + fn issue_4246_admit_recursion_between_opaque_functions() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [b] to "./platform" + + O := {} -> {} + + a = @O \{} -> ((\@O f -> f {}) b) + + b = a + "# + ), + "O", + ); + } } diff --git a/crates/compiler/test_derive/src/hash.rs b/crates/compiler/test_derive/src/hash.rs index cce7969e3b..842e8004ec 100644 --- a/crates/compiler/test_derive/src/hash.rs +++ b/crates/compiler/test_derive/src/hash.rs @@ -4,11 +4,39 @@ // For the `v!` macro we use uppercase variables when constructing tag unions. #![allow(non_snake_case)] -use crate::{util::check_single_lset_immediate, v}; +use crate::{ + test_key_eq, test_key_neq, + util::{check_derivable, check_single_lset_immediate, check_underivable, derive_test}, + v, +}; +use insta::assert_snapshot; use roc_module::symbol::Symbol; use roc_types::subs::Variable; -use roc_derive_key::DeriveBuiltin::Hash; +use roc_derive_key::{hash::FlatHashKey, DeriveBuiltin::Hash, DeriveError, DeriveKey}; + +test_key_eq! { + Hash, + + same_record: + v!({ a: v!(U8), }), v!({ a: v!(U8), }) + same_record_fields_diff_types: + v!({ a: v!(U8), }), v!({ a: v!(STR), }) + same_record_fields_any_order: + v!({ a: v!(U8), b: v!(U8), c: v!(U8), }), + v!({ c: v!(U8), a: v!(U8), b: v!(U8), }) + explicit_empty_record_and_implicit_empty_record: + v!(EMPTY_RECORD), v!({}) +} + +test_key_neq! { + Hash, + + different_record_fields: + v!({ a: v!(U8), }), v!({ b: v!(U8), }) + record_empty_vs_nonempty: + v!(EMPTY_RECORD), v!({ a: v!(U8), }) +} #[test] fn immediates() { @@ -26,3 +54,98 @@ fn immediates() { check_single_lset_immediate(Hash, v!(Symbol::LIST_LIST v!(U8)), Symbol::HASH_HASH_LIST); check_single_lset_immediate(Hash, v!(Symbol::LIST_LIST v!(STR)), Symbol::HASH_HASH_LIST); } + +#[test] +fn optional_record_field_derive_error() { + check_underivable(Hash, v!({ ?a: v!(U8), }), DeriveError::Underivable); +} + +#[test] +fn derivable_record_ext_flex_var() { + check_derivable( + Hash, + v!({ a: v!(STR), }* ), + DeriveKey::Hash(FlatHashKey::Record(vec!["a".into()])), + ); +} + +#[test] +fn derivable_record_ext_flex_able_var() { + check_derivable( + Hash, + v!({ a: v!(STR), }a has Symbol::DECODE_DECODER ), + DeriveKey::Hash(FlatHashKey::Record(vec!["a".into()])), + ); +} + +#[test] +fn derivable_record_with_record_ext() { + check_derivable( + Hash, + v!({ b: v!(STR), }{ a: v!(STR), } ), + DeriveKey::Hash(FlatHashKey::Record(vec!["a".into(), "b".into()])), + ); +} + +#[test] +fn empty_record() { + derive_test(Hash, v!(EMPTY_RECORD), |golden| { + assert_snapshot!(golden, @r###" + # derived for {} + # hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher + # hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher + # Specialization lambda sets: + # @<1>: [[hash_{}(0)]] + #Derived.hash_{} = \#Derived.hasher, #Derived.rcd -> #Derived.hasher + "### + ) + }) +} + +#[test] +fn zero_field_record() { + derive_test(Hash, v!({}), |golden| { + assert_snapshot!(golden, @r###" + # derived for {} + # hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher + # hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher + # Specialization lambda sets: + # @<1>: [[hash_{}(0)]] + #Derived.hash_{} = \#Derived.hasher, #Derived.rcd -> #Derived.hasher + "### + ) + }) +} + +#[test] +fn one_field_record() { + derive_test(Hash, v!({ a: v!(U8), }), |golden| { + assert_snapshot!(golden, @r###" + # derived for { a : U8 } + # hasher, { a : a } -[[hash_{a}(0)]]-> hasher | a has Hash, hasher has Hasher + # hasher, { a : a } -[[hash_{a}(0)]]-> hasher | a has Hash, hasher has Hasher + # Specialization lambda sets: + # @<1>: [[hash_{a}(0)]] + #Derived.hash_{a} = + \#Derived.hasher, #Derived.rcd -> Hash.hash #Derived.hasher #Derived.rcd.a + "### + ) + }) +} + +#[test] +fn two_field_record() { + derive_test(Hash, v!({ a: v!(U8), b: v!(STR), }), |golden| { + assert_snapshot!(golden, @r###" + # derived for { a : U8, b : Str } + # hasher, { a : a, b : a1 } -[[hash_{a,b}(0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher + # hasher, { a : a, b : a1 } -[[hash_{a,b}(0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher + # Specialization lambda sets: + # @<1>: [[hash_{a,b}(0)]] + #Derived.hash_{a,b} = + \#Derived.hasher, #Derived.rcd -> + Hash.hash (Hash.hash #Derived.hasher #Derived.rcd.a) #Derived.rcd.b + "### + ) + }) +} diff --git a/crates/compiler/test_derive/src/pretty_print.rs b/crates/compiler/test_derive/src/pretty_print.rs index 953ea05057..8a8a02da4e 100644 --- a/crates/compiler/test_derive/src/pretty_print.rs +++ b/crates/compiler/test_derive/src/pretty_print.rs @@ -58,7 +58,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, match e { Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n), Str(s) => f.text(format!(r#""{}""#, s)), - SingleQuote(c) => f.text(format!("'{}'", c)), + SingleQuote(_, _, c, _) => f.text(format!("'{}'", c)), List { elem_var: _, loc_elems, @@ -366,7 +366,7 @@ fn pattern<'a>( f.text(&**n) } StrLiteral(s) => f.text(format!(r#""{}""#, s)), - SingleQuote(c) => f.text(format!("'{}'", c)), + SingleQuote(_, _, c, _) => f.text(format!("'{}'", c)), Underscore => f.text("_"), Shadowed(_, _, _) => todo!(), diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index 40ae81efd9..bad342a99d 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -1330,4 +1330,47 @@ mod hash { ) } } + + mod derived { + use super::{assert_evals_to, build_test}; + use roc_std::RocList; + + #[test] + fn empty_record() { + assert_evals_to!( + &build_test(r#"{}"#), + RocList::from_slice(&[] as &[u8]), + RocList + ) + } + + #[test] + fn record_of_u8_and_str() { + assert_evals_to!( + &build_test(r#"{ a: 15u8, b: "bc" }"#), + RocList::from_slice(&[15, 98, 99]), + RocList + ) + } + + #[test] + fn record_of_records() { + assert_evals_to!( + &build_test(r#"{ a: { b: 15u8, c: "bc" }, d: { b: 23u8, e: "ef" } }"#), + RocList::from_slice(&[15, 98, 99, 23, 101, 102]), + RocList + ) + } + + #[test] + fn record_of_list_of_records() { + assert_evals_to!( + &build_test( + r#"{ a: [ { b: 15u8 }, { b: 23u8 } ], b: [ { c: 45u8 }, { c: 73u8 } ] }"# + ), + RocList::from_slice(&[15, 23, 45, 73]), + RocList + ) + } + } } diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 753feddfcc..91c43b1282 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -3395,3 +3395,59 @@ fn list_let_generalization() { usize ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn list_walk_backwards_until_sum() { + assert_evals_to!( + r#"List.walkBackwardsUntil [1, 2] 0 \a,b -> Continue (a + b)"#, + 3, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn list_walk_backwards_implements_position() { + assert_evals_to!( + r#" + Option a : [Some a, None] + + find : List a, a -> Option Nat + find = \list, needle -> + findHelp list needle + |> .v + + findHelp = \list, needle -> + List.walkBackwardsUntil list { n: 0, v: None } \{ n, v }, element -> + if element == needle then + Break { n, v: Some n } + else + Continue { n: n + 1, v } + + when find [1, 2, 3] 3 is + None -> 0 + Some v -> v + "#, + 0, + usize + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn list_walk_backwards_until_even_prefix_sum() { + assert_evals_to!( + r#" + helper = \a, b -> + if Num.isEven b then + Continue (a + b) + + else + Break a + + List.walkBackwardsUntil [9, 8, 4, 2] 0 helper"#, + 2 + 4 + 8, + i64 + ); +} diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 01179bf886..5a65af1f17 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -4067,3 +4067,21 @@ fn int_let_generalization() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn pattern_match_char() { + assert_evals_to!( + indoc!( + r#" + c = 'A' + + when c is + 'A' -> "okay" + _ -> "FAIL" + "# + ), + RocStr::from("okay"), + RocStr + ); +} diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 6b06bf4a57..d38e8e0fcb 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1930,3 +1930,31 @@ fn when_on_strings() { i64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn with_capacity() { + assert_evals_to!( + indoc!( + r#" + Str.withCapacity 10 + "# + ), + RocStr::from(""), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn with_capacity_concat() { + assert_evals_to!( + indoc!( + r#" + Str.withCapacity 10 |> Str.concat "Forty-two" + "# + ), + RocStr::from("Forty-two"), + RocStr + ); +} diff --git a/crates/compiler/test_gen/src/gen_tags.rs b/crates/compiler/test_gen/src/gen_tags.rs index 7070031e3b..c28a38a50d 100644 --- a/crates/compiler/test_gen/src/gen_tags.rs +++ b/crates/compiler/test_gen/src/gen_tags.rs @@ -2004,3 +2004,21 @@ fn match_on_result_with_uninhabited_error_branch() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn dispatch_tag_union_function_inferred() { + assert_evals_to!( + indoc!( + r#" + g = \b -> if b then H else J + + when P ((g Bool.true) "") ((g Bool.false) "") is + P (H _) (J _) -> "okay" + _ -> "FAIL" + "# + ), + RocStr::from("okay"), + RocStr + ); +} diff --git a/crates/compiler/types/src/num.rs b/crates/compiler/types/src/num.rs index 73b52860f7..74cce37dcf 100644 --- a/crates/compiler/types/src/num.rs +++ b/crates/compiler/types/src/num.rs @@ -328,6 +328,26 @@ pub enum NumBound { }, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SingleQuoteBound { + AtLeast { width: IntLitWidth }, +} + +impl SingleQuoteBound { + pub fn from_char(c: char) -> Self { + let n = c as u32; + let width = if n > u16::MAX as _ { + IntLitWidth::U32 + } else if n > u8::MAX as _ { + IntLitWidth::U16 + } else { + IntLitWidth::U8 + }; + + Self::AtLeast { width } + } +} + pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable { match w { IntLitWidth::U8 => Variable::U8, diff --git a/crates/compiler/types/src/pretty_print.rs b/crates/compiler/types/src/pretty_print.rs index b937672b90..0c930cf25f 100644 --- a/crates/compiler/types/src/pretty_print.rs +++ b/crates/compiler/types/src/pretty_print.rs @@ -1156,11 +1156,15 @@ fn write_flat_type<'a>( ) } - FunctionOrTagUnion(tag_name, _, ext_var) => { + FunctionOrTagUnion(tag_names, _, ext_var) => { buf.push('['); let mut tags: MutMap = MutMap::default(); - tags.insert(subs[*tag_name].clone(), vec![]); + tags.extend( + subs.get_subs_slice(*tag_names) + .iter() + .map(|t| (t.clone(), vec![])), + ); let ext_content = write_sorted_tags(env, ctx, subs, buf, &tags, *ext_var); buf.push(']'); @@ -1241,8 +1245,12 @@ pub fn chase_ext_tag_union( push_union(subs, tags, fields); chase_ext_tag_union(subs, *ext_var, fields) } - Content::Structure(FunctionOrTagUnion(tag_name, _, ext_var)) => { - fields.push((subs[*tag_name].clone(), vec![])); + Content::Structure(FunctionOrTagUnion(tag_names, _, ext_var)) => { + fields.extend( + subs.get_subs_slice(*tag_names) + .iter() + .map(|t| (t.clone(), vec![])), + ); chase_ext_tag_union(subs, *ext_var, fields) } diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index b0e4042c01..88dab303f3 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -962,13 +962,13 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f write!(f, "]<{:?}>", new_ext) } - FlatType::FunctionOrTagUnion(tagname_index, symbol, ext) => { - let tagname: &TagName = &subs[*tagname_index]; + FlatType::FunctionOrTagUnion(tagnames, symbol, ext) => { + let tagnames: &[TagName] = subs.get_subs_slice(*tagnames); write!( f, "FunctionOrTagUnion({:?}, {:?}, {:?})", - tagname, symbol, ext + tagnames, symbol, ext ) } FlatType::RecursiveTagUnion(rec, tags, ext) => { @@ -2424,7 +2424,12 @@ pub enum FlatType { Func(VariableSubsSlice, Variable, Variable), Record(RecordFields, Variable), TagUnion(UnionTags, Variable), - FunctionOrTagUnion(SubsIndex, Symbol, Variable), + + /// `A` might either be a function + /// x -> A x : a -> [A a, B a, C a] + /// or a tag `[A, B, C]` + FunctionOrTagUnion(SubsSlice, SubsSlice, Variable), + RecursiveTagUnion(Variable, UnionTags, Variable), Erroneous(SubsIndex), EmptyRecord, @@ -3881,12 +3886,12 @@ fn flat_type_to_err_type( } } - FunctionOrTagUnion(tag_name, _, ext_var) => { - let tag_name = subs[tag_name].clone(); + FunctionOrTagUnion(tag_names, _, ext_var) => { + let tag_names = subs.get_subs_slice(tag_names); - let mut err_tags = SendMap::default(); + let mut err_tags: SendMap> = SendMap::default(); - err_tags.insert(tag_name, vec![]); + err_tags.extend(tag_names.iter().map(|t| (t.clone(), vec![]))); match var_to_err_type(subs, state, ext_var).unwrap_structural_alias() { ErrorType::TagUnion(sub_tags, sub_ext) => { @@ -4202,8 +4207,8 @@ impl StorageSubs { Self::offset_tag_union(offsets, *union_tags), Self::offset_variable(offsets, *ext), ), - FlatType::FunctionOrTagUnion(tag_name, symbol, ext) => FlatType::FunctionOrTagUnion( - Self::offset_tag_name_index(offsets, *tag_name), + FlatType::FunctionOrTagUnion(tag_names, symbol, ext) => FlatType::FunctionOrTagUnion( + Self::offset_tag_name_slice(offsets, *tag_names), *symbol, Self::offset_variable(offsets, *ext), ), @@ -4295,13 +4300,13 @@ impl StorageSubs { record_fields } - fn offset_tag_name_index( + fn offset_tag_name_slice( offsets: &StorageSubsOffsets, - mut tag_name: SubsIndex, - ) -> SubsIndex { - tag_name.index += offsets.tag_names; + mut tag_names: SubsSlice, + ) -> SubsSlice { + tag_names.start += offsets.tag_names; - tag_name + tag_names } fn offset_variable(offsets: &StorageSubsOffsets, variable: Variable) -> Variable { @@ -4542,12 +4547,22 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) -> TagUnion(union_tags, new_ext) } - FunctionOrTagUnion(tag_name, symbol, ext_var) => { - let new_tag_name = SubsIndex::new(env.target.tag_names.len() as u32); + FunctionOrTagUnion(tag_names, symbols, ext_var) => { + let new_tag_names = SubsSlice::extend_new( + &mut env.target.tag_names, + env.source.get_subs_slice(tag_names).iter().cloned(), + ); - env.target.tag_names.push(env.source[tag_name].clone()); + let new_symbols = SubsSlice::extend_new( + &mut env.target.closure_names, + env.source.get_subs_slice(symbols).iter().cloned(), + ); - FunctionOrTagUnion(new_tag_name, symbol, storage_copy_var_to_help(env, ext_var)) + FunctionOrTagUnion( + new_tag_names, + new_symbols, + storage_copy_var_to_help(env, ext_var), + ) } RecursiveTagUnion(rec_var, tags, ext_var) => { @@ -4981,14 +4996,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl TagUnion(union_tags, new_ext) } - FunctionOrTagUnion(tag_name, symbol, ext_var) => { - let new_tag_name = SubsIndex::new(env.target.tag_names.len() as u32); + FunctionOrTagUnion(tag_names, symbols, ext_var) => { + let new_tag_names = SubsSlice::extend_new( + &mut env.target.tag_names, + env.source.get_subs_slice(tag_names).iter().cloned(), + ); - env.target.tag_names.push(env.source[tag_name].clone()); + let new_symbols = SubsSlice::extend_new( + &mut env.target.closure_names, + env.source.get_subs_slice(symbols).iter().cloned(), + ); FunctionOrTagUnion( - new_tag_name, - symbol, + new_tag_names, + new_symbols, copy_import_to_help(env, max_rank, ext_var), ) } diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 8098c0857c..383cf5e080 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -2657,13 +2657,13 @@ fn unify_flat_type( outcome } - (FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => { + (FunctionOrTagUnion(tag_names, tag_symbols, ext), Func(args, closure, ret)) => { unify_function_or_tag_union_and_func( env, pool, ctx, - tag_name, - *tag_symbol, + *tag_names, + *tag_symbols, *ext, *args, *ret, @@ -2671,13 +2671,13 @@ fn unify_flat_type( true, ) } - (Func(args, closure, ret), FunctionOrTagUnion(tag_name, tag_symbol, ext)) => { + (Func(args, closure, ret), FunctionOrTagUnion(tag_names, tag_symbols, ext)) => { unify_function_or_tag_union_and_func( env, pool, ctx, - tag_name, - *tag_symbol, + *tag_names, + *tag_symbols, *ext, *args, *ret, @@ -2685,50 +2685,61 @@ fn unify_flat_type( false, ) } - (FunctionOrTagUnion(tag_name_1, _, ext1), FunctionOrTagUnion(tag_name_2, _, ext2)) => { - let tag_name_1_ref = &env.subs[*tag_name_1]; - let tag_name_2_ref = &env.subs[*tag_name_2]; - - if tag_name_1_ref == tag_name_2_ref { - let outcome = unify_pool(env, pool, *ext1, *ext2, ctx.mode); - if outcome.mismatches.is_empty() { - let content = *env.subs.get_content_without_compacting(ctx.second); - merge(env, ctx, content) - } else { - outcome - } - } else { - let tags1 = UnionTags::from_tag_name_index(*tag_name_1); - let tags2 = UnionTags::from_tag_name_index(*tag_name_2); - - unify_tag_unions(env, pool, ctx, tags1, *ext1, tags2, *ext2, Rec::None) - } - } - (TagUnion(tags1, ext1), FunctionOrTagUnion(tag_name, _, ext2)) => { - let tags2 = UnionTags::from_tag_name_index(*tag_name); + ( + FunctionOrTagUnion(tag_names_1, tag_symbols_1, ext1), + FunctionOrTagUnion(tag_names_2, tag_symbols_2, ext2), + ) => unify_two_function_or_tag_unions( + env, + pool, + ctx, + *tag_names_1, + *tag_symbols_1, + *ext1, + *tag_names_2, + *tag_symbols_2, + *ext2, + ), + (TagUnion(tags1, ext1), FunctionOrTagUnion(tag_names, _, ext2)) => { + let empty_tag_var_slices = SubsSlice::extend_new( + &mut env.subs.variable_slices, + std::iter::repeat(Default::default()).take(tag_names.len()), + ); + let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2, Rec::None) } - (FunctionOrTagUnion(tag_name, _, ext1), TagUnion(tags2, ext2)) => { - let tags1 = UnionTags::from_tag_name_index(*tag_name); + (FunctionOrTagUnion(tag_names, _, ext1), TagUnion(tags2, ext2)) => { + let empty_tag_var_slices = SubsSlice::extend_new( + &mut env.subs.variable_slices, + std::iter::repeat(Default::default()).take(tag_names.len()), + ); + let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2, Rec::None) } - (RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_name, _, ext2)) => { + (RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_names, _, ext2)) => { // this never happens in type-correct programs, but may happen if there is a type error debug_assert!(is_recursion_var(env.subs, *recursion_var)); - let tags2 = UnionTags::from_tag_name_index(*tag_name); + let empty_tag_var_slices = SubsSlice::extend_new( + &mut env.subs.variable_slices, + std::iter::repeat(Default::default()).take(tag_names.len()), + ); + let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); let rec = Rec::Left(*recursion_var); unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2, rec) } - (FunctionOrTagUnion(tag_name, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => { + (FunctionOrTagUnion(tag_names, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => { debug_assert!(is_recursion_var(env.subs, *recursion_var)); - let tags1 = UnionTags::from_tag_name_index(*tag_name); + let empty_tag_var_slices = SubsSlice::extend_new( + &mut env.subs.variable_slices, + std::iter::repeat(Default::default()).take(tag_names.len()), + ); + let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); let rec = Rec::Right(*recursion_var); unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2, rec) @@ -3133,17 +3144,20 @@ fn unify_function_or_tag_union_and_func( env: &mut Env, pool: &mut Pool, ctx: &Context, - tag_name_index: &SubsIndex, - tag_symbol: Symbol, + tag_names_slice: SubsSlice, + tag_fn_lambdas: SubsSlice, tag_ext: Variable, function_arguments: VariableSubsSlice, function_return: Variable, function_lambda_set: Variable, left: bool, ) -> Outcome { - let tag_name = env.subs[*tag_name_index].clone(); + let tag_names = env.subs.get_subs_slice(tag_names_slice).to_vec(); - let union_tags = UnionTags::insert_slices_into_subs(env.subs, [(tag_name, function_arguments)]); + let union_tags = UnionTags::insert_slices_into_subs( + env.subs, + tag_names.into_iter().map(|tag| (tag, function_arguments)), + ); let content = Content::Structure(FlatType::TagUnion(union_tags, tag_ext)); let new_tag_union_var = fresh(env, pool, ctx, content); @@ -3155,7 +3169,14 @@ fn unify_function_or_tag_union_and_func( }; { - let union_tags = UnionLambdas::tag_without_arguments(env.subs, tag_symbol); + let lambda_names = env.subs.get_subs_slice(tag_fn_lambdas).to_vec(); + let new_lambda_names = SubsSlice::extend_new(&mut env.subs.closure_names, lambda_names); + let empty_captures_slices = SubsSlice::extend_new( + &mut env.subs.variable_slices, + std::iter::repeat(Default::default()).take(new_lambda_names.len()), + ); + let union_tags = UnionLambdas::from_slices(new_lambda_names, empty_captures_slices); + let ambient_function_var = if left { ctx.first } else { ctx.second }; let lambda_set_content = LambdaSet(self::LambdaSet { solved: union_tags, @@ -3196,3 +3217,53 @@ fn unify_function_or_tag_union_and_func( outcome } + +#[allow(clippy::too_many_arguments)] +fn unify_two_function_or_tag_unions( + env: &mut Env, + pool: &mut Pool, + ctx: &Context, + tag_names_1: SubsSlice, + tag_symbols_1: SubsSlice, + ext1: Variable, + tag_names_2: SubsSlice, + tag_symbols_2: SubsSlice, + ext2: Variable, +) -> Outcome { + let merged_tags = { + let mut all_tags: Vec<_> = (env.subs.get_subs_slice(tag_names_1).iter()) + .chain(env.subs.get_subs_slice(tag_names_2)) + .cloned() + .collect(); + all_tags.sort(); + all_tags.dedup(); + SubsSlice::extend_new(&mut env.subs.tag_names, all_tags) + }; + let merged_lambdas = { + let mut all_lambdas: Vec<_> = (env.subs.get_subs_slice(tag_symbols_1).iter()) + .chain(env.subs.get_subs_slice(tag_symbols_2)) + .cloned() + .collect(); + all_lambdas.sort(); + all_lambdas.dedup(); + SubsSlice::extend_new(&mut env.subs.closure_names, all_lambdas) + }; + + let mut outcome = unify_pool(env, pool, ext1, ext2, ctx.mode); + if !outcome.mismatches.is_empty() { + return outcome; + } + + let merge_outcome = merge( + env, + ctx, + Content::Structure(FlatType::FunctionOrTagUnion( + merged_tags, + merged_lambdas, + ext1, + )), + ); + + outcome.union(merge_outcome); + outcome +} diff --git a/crates/docs/src/static/search.js b/crates/docs/src/static/search.js index f19eb4f168..7204fc2afd 100644 --- a/crates/docs/src/static/search.js +++ b/crates/docs/src/static/search.js @@ -44,4 +44,21 @@ searchBox.addEventListener("input", search); search(); + + // Capture '/' keypress for quick search + window.addEventListener("keyup", (e) => { + + if (e.code === "Slash") { + e.preventDefault; + searchBox.focus(); + searchBox.value = ""; + } + + if (e.code === "Escape" && document.activeElement === searchBox) { + e.preventDefault; + searchBox.blur(); + } + + }); + })(); diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index ca4fbcc736..fc9d32a2c9 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -47,7 +47,7 @@ cgmath = "0.18.0" snafu = { version = "0.7.1", features = ["backtraces"] } colored = "2.0.0" pest = "2.3.1" -pest_derive = "2.1.0" +pest_derive = "2.3.1" copypasta = "0.8.1" palette = "0.6.1" confy = { git = 'https://github.com/rust-cli/confy', features = [ diff --git a/crates/editor/src/editor/main.rs b/crates/editor/src/editor/main.rs index 9b2c55a4e0..b345815db5 100644 --- a/crates/editor/src/editor/main.rs +++ b/crates/editor/src/editor/main.rs @@ -521,7 +521,7 @@ fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathBuf, String) // returns path and content of app file fn init_new_roc_project(project_dir_path: &Path) -> (PathBuf, String) { - let orig_platform_path = Path::new("./examples/hello-world").join(PLATFORM_DIR_NAME); + let orig_platform_path = Path::new("./examples/cli").join(PLATFORM_DIR_NAME); let roc_file_path = Path::new("./new-roc-project/main.roc"); diff --git a/crates/glue/tests/test_glue_cli.rs b/crates/glue/tests/test_glue_cli.rs index fda37ba5e3..424ae04ed0 100644 --- a/crates/glue/tests/test_glue_cli.rs +++ b/crates/glue/tests/test_glue_cli.rs @@ -215,6 +215,7 @@ mod glue_cli_run { .map(|arg| arg.to_string()) .chain([app_file.to_str().unwrap().to_string()]), &[], + &[], ); let ignorable = "🔨 Rebuilding platform...\n"; diff --git a/crates/highlight/tests/peg_grammar.rs b/crates/highlight/tests/peg_grammar.rs index 655088e8cf..294c992b84 100644 --- a/crates/highlight/tests/peg_grammar.rs +++ b/crates/highlight/tests/peg_grammar.rs @@ -730,16 +730,24 @@ test1 = file_to_string(&file_path) } + fn cli_testing_path(sub_path: &str) -> String { + let examples_dir = "../cli_testing_examples/".to_string(); + + let file_path = examples_dir + sub_path; + + file_to_string(&file_path) + } + #[test] fn test_hello() { - let tokens = tokenize(&example_path("hello-world/main.roc")); + let tokens = tokenize(&example_path("helloWorld.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } #[test] fn test_fibo() { - let tokens = tokenize(&example_path("algorithms/fibonacci.roc")); + let tokens = tokenize(&cli_testing_path("algorithms/fibonacci.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -831,14 +839,14 @@ test1 = #[test] fn test_base64() { - let tokens = tokenize(&example_path("benchmarks/Base64.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/Base64.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } #[test] fn test_base64_test() { - let tokens = tokenize(&example_path("benchmarks/TestBase64.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/TestBase64.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -874,14 +882,14 @@ test1 = #[test] fn test_astar_test() { - let tokens = tokenize(&example_path("benchmarks/TestAStar.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/TestAStar.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } #[test] fn test_cli_echo() { - let tokens = tokenize(&example_path("interactive/echo.roc")); + let tokens = tokenize(&example_path("cli/echo.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1106,7 +1114,7 @@ test1 = #[test] fn test_closure_file() { - let tokens = tokenize(&example_path("benchmarks/Closure.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/Closure.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1126,7 +1134,7 @@ test1 = #[test] fn test_nqueens() { - let tokens = tokenize(&example_path("benchmarks/NQueens.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/NQueens.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1149,7 +1157,7 @@ test1 = #[test] fn test_quicksort() { - let tokens = tokenize(&example_path("benchmarks/Quicksort.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/Quicksort.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1176,7 +1184,7 @@ test1 = #[test] fn test_task() { - let tokens = tokenize(&example_path("benchmarks/platform/Task.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/platform/Task.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1221,7 +1229,7 @@ test1 = #[test] fn test_cfold() { - let tokens = tokenize(&example_path("benchmarks/CFold.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/CFold.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1291,7 +1299,7 @@ balance = \color -> #[test] fn test_rbtree_insert() { - let tokens = tokenize(&example_path("benchmarks/RBTreeInsert.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/RBTreeInsert.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1357,7 +1365,7 @@ balance = \color -> #[test] fn test_rbtree_ck() { - let tokens = tokenize(&example_path("benchmarks/RBTreeCk.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/RBTreeCk.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1390,7 +1398,7 @@ balance = \color -> #[test] fn test_astar() { - let tokens = tokenize(&example_path("benchmarks/AStar.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/AStar.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1409,7 +1417,7 @@ balance = \color -> #[test] fn test_false_interpreter_context() { - let tokens = tokenize(&example_path("false-interpreter/Context.roc")); + let tokens = tokenize(&example_path("cli/false-interpreter/Context.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -1437,7 +1445,7 @@ balance = \color -> #[test] fn test_deriv() { - let tokens = tokenize(&example_path("benchmarks/Deriv.roc")); + let tokens = tokenize(&cli_testing_path("benchmarks/Deriv.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } diff --git a/crates/repl_cli/src/lib.rs b/crates/repl_cli/src/lib.rs index cefee73f99..b120c07b15 100644 --- a/crates/repl_cli/src/lib.rs +++ b/crates/repl_cli/src/lib.rs @@ -20,7 +20,7 @@ use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_load::{EntryPoint, MonomorphizedModule}; use roc_mono::ir::OptLevel; use roc_parse::ast::Expr; -use roc_parse::parser::{EExpr, ELambda, SyntaxError}; +use roc_parse::parser::{EClosure, EExpr, SyntaxError}; use roc_repl_eval::eval::jit_to_ast; use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; use roc_repl_eval::{ReplApp, ReplAppMemory}; @@ -136,7 +136,7 @@ impl Validator for InputValidator { // Special case some syntax errors to allow for multi-line inputs Err((_, EExpr::DefMissingFinalExpr(_), _)) | Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) - | Err((_, EExpr::Lambda(ELambda::Body(_, _), _), _)) => { + | Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => { Ok(ValidationResult::Incomplete) } _ => Ok(ValidationResult::Valid(None)), diff --git a/crates/repl_eval/src/eval.rs b/crates/repl_eval/src/eval.rs index d782fa59c9..dc76afcd91 100644 --- a/crates/repl_eval/src/eval.rs +++ b/crates/repl_eval/src/eval.rs @@ -450,8 +450,8 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( payload_vars, )) } - Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { - let tag_name = &env.subs[*tag_name]; + Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _, _)) => { + let tag_name = &env.subs.get_subs_slice(*tag_names)[0]; Ok(single_tag_union_to_ast( env, @@ -631,8 +631,8 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, payload_vars) } - Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { - let tag_name = &env.subs[*tag_name]; + Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _, _)) => { + let tag_name = &env.subs.get_subs_slice(*tag_names)[0]; single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { @@ -1200,6 +1200,19 @@ fn bool_to_ast<'a, M: ReplAppMemory>( tag_name_to_expr(env, tag_name) } + FlatType::FunctionOrTagUnion(tags, _, _) if tags.len() == 2 => { + let tags = env.subs.get_subs_slice(*tags); + let tag_name_1 = &tags[0]; + let tag_name_2 = &tags[1]; + + let tag_name = if value { + max_by_key(tag_name_1, tag_name_2, |n| n.as_ident_str()) + } else { + min_by_key(tag_name_1, tag_name_2, |n| n.as_ident_str()) + }; + + tag_name_to_expr(env, tag_name) + } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); } @@ -1293,6 +1306,37 @@ fn byte_to_ast<'a, M: ReplAppMemory>( _ => unreachable!("invalid union variant for a Byte!"), } } + FlatType::FunctionOrTagUnion(tags, _, _) => { + // anything with fewer tags is not a byte + debug_assert!(tags.len() > 2); + + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = env + .subs + .get_subs_slice(*tags) + .iter() + .map(|t| (t.clone(), vec![])) + .collect(); + + let union_variant = { + let mut layout_env = layout::Env::from_components( + &mut env.layout_cache, + env.subs, + env.arena, + env.target_info, + ); + union_sorted_tags_pub(&mut layout_env, tags_vec, None) + }; + + match union_variant { + UnionVariant::ByteUnion(tagnames) => { + let tag_name = &tagnames[value as usize].expect_tag_ref(); + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = Loc::at_zero(tag_expr); + Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space) + } + _ => unreachable!("invalid union variant for a Byte!"), + } + } other => { unreachable!("Unexpected FlatType {:?} in byte_to_ast", other); } diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index dbd5935c95..4e5151a72a 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -178,7 +178,7 @@ fn to_expr_report<'a>( match parse_problem { EExpr::If(if_, pos) => to_if_report(alloc, lines, filename, context, if_, *pos), EExpr::When(when, pos) => to_when_report(alloc, lines, filename, context, when, *pos), - EExpr::Lambda(lambda, pos) => { + EExpr::Closure(lambda, pos) => { to_lambda_report(alloc, lines, filename, context, lambda, *pos) } EExpr::List(list, pos) => to_list_report(alloc, lines, filename, context, list, *pos), @@ -557,13 +557,13 @@ fn to_lambda_report<'a>( lines: &LineInfo, filename: PathBuf, _context: Context, - parse_problem: &roc_parse::parser::ELambda<'a>, + parse_problem: &roc_parse::parser::EClosure<'a>, start: Position, ) -> Report<'a> { - use roc_parse::parser::ELambda; + use roc_parse::parser::EClosure; match *parse_problem { - ELambda::Arrow(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + EClosure::Arrow(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); @@ -610,7 +610,7 @@ fn to_lambda_report<'a>( } }, - ELambda::Comma(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + EClosure::Comma(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); @@ -657,7 +657,7 @@ fn to_lambda_report<'a>( } }, - ELambda::Arg(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + EClosure::Arg(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); @@ -702,17 +702,17 @@ fn to_lambda_report<'a>( } }, - ELambda::Start(_pos) => unreachable!("another branch would have been taken"), + EClosure::Start(_pos) => unreachable!("another branch would have been taken"), - ELambda::Body(expr, pos) => { + EClosure::Body(expr, pos) => { to_expr_report(alloc, lines, filename, Context::InDef(start), expr, pos) } - ELambda::Pattern(ref pattern, pos) => { + EClosure::Pattern(ref pattern, pos) => { to_pattern_report(alloc, lines, filename, pattern, pos) } - ELambda::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), + EClosure::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), - ELambda::IndentArrow(pos) => to_unfinished_lambda_report( + EClosure::IndentArrow(pos) => to_unfinished_lambda_report( alloc, lines, filename, @@ -725,7 +725,7 @@ fn to_lambda_report<'a>( ]), ), - ELambda::IndentBody(pos) => to_unfinished_lambda_report( + EClosure::IndentBody(pos) => to_unfinished_lambda_report( alloc, lines, filename, @@ -738,7 +738,7 @@ fn to_lambda_report<'a>( ]), ), - ELambda::IndentArg(pos) => to_unfinished_lambda_report( + EClosure::IndentArg(pos) => to_unfinished_lambda_report( alloc, lines, filename, diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 96749a5018..27f86c654b 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -11072,4 +11072,64 @@ All branches in an `if` must have the same type! U8 "### ); + + test_report!( + big_char_does_not_fit_in_u8, + indoc!( + r#" + digits : List U8 + digits = List.range '0' '9' + + List.contains digits '☃' + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This 2nd argument to `contains` has an unexpected type: + + 7│ List.contains digits '☃' + ^^^^^ + + The argument is a Unicode scalar value of type: + + U16, I32, U32, I64, Nat, U64, I128, or U128 + + But `contains` needs its 2nd argument to be: + + U8 + "### + ); + + test_report!( + big_char_does_not_fit_in_u8_pattern, + indoc!( + r#" + x : U8 + + when x is + '☃' -> "" + _ -> "" + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The branches of this `when` expression don't match the condition: + + 6│> when x is + 7│ '☃' -> "" + 8│ _ -> "" + + This `x` value is a: + + U8 + + But the branch patterns have type: + + U16, I32, U32, I64, Nat, U64, I128, or U128 + + The branches must be cases of the `when` condition's type! + "### + ); } diff --git a/crates/tracing/Cargo.toml b/crates/tracing/Cargo.toml index 9cdbe41419..ee153b5aa8 100644 --- a/crates/tracing/Cargo.toml +++ b/crates/tracing/Cargo.toml @@ -8,5 +8,5 @@ description = "Utilities for setting up tracing at various executable entry poin [dependencies] tracing = { version = "0.1.36", features = ["release_max_level_off"] } -tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-appender = "0.2.2" diff --git a/examples/.gitignore b/examples/.gitignore index 58cb449bb9..9fb0c881bf 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -4,3 +4,5 @@ libapp.so dynhost preprocessedhost metadata + +helloWorld diff --git a/examples/README.md b/examples/README.md index 7097cb5397..0358683fdc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -14,7 +14,7 @@ Run examples as follows: roc run hello-world/main.roc ``` -`examples/benchmarks/` contains some larger examples. +`crates/cli_testing_examples/benchmarks/` contains some larger examples. -Some examples like `examples/benchmarks/NQueens.roc` require input after running. +Some examples like `crates/cli_testing_examples/benchmarks/NQueens.roc` require input after running. For NQueens, input 10 in the terminal and press enter. diff --git a/examples/interactive/.gitignore b/examples/cli/.gitignore similarity index 100% rename from examples/interactive/.gitignore rename to examples/cli/.gitignore diff --git a/examples/interactive/README.md b/examples/cli/README.md similarity index 96% rename from examples/interactive/README.md rename to examples/cli/README.md index 545fd58f53..fd939f8b4e 100644 --- a/examples/interactive/README.md +++ b/examples/cli/README.md @@ -1,4 +1,4 @@ -# Interactive examples +# CLI examples These are examples of how to make basic CLI (command-line interface) and TUI (terminal user interface) apps in Roc. diff --git a/examples/interactive/args.roc b/examples/cli/args.roc similarity index 93% rename from examples/interactive/args.roc rename to examples/cli/args.roc index 97cb8ca8cc..50f4ca08a3 100644 --- a/examples/interactive/args.roc +++ b/examples/cli/args.roc @@ -10,7 +10,7 @@ main = Program.withArgs \args -> Arg.succeed (\dividend -> \divisor -> Div (Num.toF64 dividend) (Num.toF64 divisor)) |> Arg.withParser ( - Arg.i64 { + Arg.i64Option { long: "dividend", short: "n", help: "the number to divide; corresponds to a numerator", @@ -18,7 +18,7 @@ main = Program.withArgs \args -> ) |> Arg.withParser ( - Arg.i64 { + Arg.i64Option { long: "divisor", short: "d", help: "the number to divide by; corresponds to a denominator", @@ -30,7 +30,7 @@ main = Program.withArgs \args -> Arg.succeed (\base -> \num -> Log (Num.toF64 base) (Num.toF64 num)) |> Arg.withParser ( - Arg.i64 { + Arg.i64Option { long: "base", short: "b", help: "base of the logarithm", @@ -38,7 +38,7 @@ main = Program.withArgs \args -> ) |> Arg.withParser ( - Arg.i64 { + Arg.i64Option { long: "num", help: "the number to take the logarithm of", } diff --git a/examples/cli/cli-platform/Arg.roc b/examples/cli/cli-platform/Arg.roc new file mode 100644 index 0000000000..bdb7ab00e7 --- /dev/null +++ b/examples/cli/cli-platform/Arg.roc @@ -0,0 +1,1067 @@ +interface Arg + exposes [ + Parser, + NamedParser, + parse, + toHelp, + parseFormatted, + succeed, + boolOption, + strOption, + i64Option, + str, + subCommand, + choice, + withParser, + program, + ] + imports [] + +## A parser for a command-line application. +## A [NamedParser] is usually built from a [Parser] using [program]. +NamedParser a := { + name : Str, + help : Str, + parser : Parser a, +} + +## Describes how to parse a slice of command-line arguments. +## [Parser]s can be composed in various ways, including via [withParser] and +## [subCommand]. +## Once you have a [Parser] that describes your application's entire parsing +## needs, consider transforming it into a [NamedParser]. +Parser a := [ + Succeed a, + Option OptionConfig (MarkedArgs -> Result { newlyTaken : Taken, val : a } (ParseError [])), + Positional PositionalConfig (MarkedArgs -> Result { newlyTaken : Taken, val : a } (ParseError [])), + # TODO: hiding the record behind an alias currently causes a panic + SubCommand + (List { + name : Str, + parser : Parser a, + }), + + # Constructed during transformations of the above variants + WithConfig (Parser a) Config, + Lazy ({} -> a), +] + +## Indices in an arguments list that have already been parsed. +Taken : Set Nat + +## A representation of parsed and unparsed arguments in a constant list of +## command-line arguments. +## Used only internally, for efficient representation of parsed and unparsed +## arguments. +MarkedArgs : { args : List Str, taken : Taken } + +## Enumerates errors that can occur during parsing a list of command line arguments. +ParseError a : [ + ## The program name was not found as the first argument to be parsed. + ProgramNameNotProvided Str, + ## A positional argument (inherently required) was not found. + MissingPositional Str, + ## An option argument is required, but it was not found. + MissingRequiredOption Str, + ## An argument was found, but it didn't have the expected [OptionType]. + WrongOptionType + { + arg : Str, + expected : OptionType, + }, + ## A subcommand is required, but it was not found. + SubCommandNotFound + { + choices : List Str, + }, + ## A subcommand was found, but it was not the expected one. + IncorrectSubCommand + { + found : Str, + choices : List Str, + }, +]a + +## Expected type of an option, in an argument list being parsed. +## Describes how a string option should be interpreted as a certain type. +OptionType : [ + Str, + Bool, + I64, +] + +## Help metadata extracted from a [Parser]. +Help : [ + SubCommands (List { name : Str, help : Help }), + Config (List Config), +] + +OptionConfig : { + long : Str, + short : Str, + help : Str, + type : OptionType, +} + +PositionalConfig : { + name : Str, + help : Str, +} + +Config : [Option OptionConfig, Positional PositionalConfig] + +## Generates help metadata from a [Parser]. +## +## This is useful if you would like to use this metadata to generate your own +## human-readable help or hint menus. +## +## A default help menu can be generated with [formatHelp]. +toHelp : Parser * -> Help +toHelp = \parser -> + toHelpHelper parser [] + +## A parser that immediately succeeds with its given input. +succeed : a -> Parser a +succeed = \val -> @Parser (Succeed val) + +# TODO: check overflows when this annotation is included +# toHelpHelper : Parser *, List Config -> Help +toHelpHelper = \@Parser parser, configs -> + when parser is + Succeed _ -> Config configs + Lazy _ -> Config configs + WithConfig innerParser config -> + toHelpHelper innerParser (List.append configs config) + + Option config _ -> + List.append configs (Option config) + |> Config + + SubCommand commands -> + List.map + commands + (\{ name, parser: innerParser } -> { name, help: toHelpHelper innerParser [] }) + |> SubCommands + + Positional config _ -> + List.append configs (Positional config) + |> Config + +findOneArg : Str, Str, MarkedArgs -> Result { val : Str, newlyTaken : Taken } [NotFound]* +findOneArg = \long, short, { args, taken } -> + argMatches = \{ index, found: _ }, arg -> + if Set.contains taken index || Set.contains taken (index + 1) then + Continue { index: index + 1, found: Bool.false } + else if arg == "--\(long)" then + Break { index, found: Bool.true } + else if Bool.not (Str.isEmpty short) && arg == "-\(short)" then + Break { index, found: Bool.true } + else + Continue { index: index + 1, found: Bool.false } + + # TODO allow = as well, etc. + { index: argIndex, found } = List.walkUntil args { index: 0, found: Bool.false } argMatches + + if !found then + Err NotFound + else + # Return the next argument after the given one + List.get args (argIndex + 1) + |> Result.mapErr (\_ -> NotFound) + |> Result.map + (\val -> + newUsed = Set.fromList [argIndex, argIndex + 1] + + { val, newlyTaken: newUsed }) + +updateTaken : MarkedArgs, Taken -> MarkedArgs +updateTaken = \{ args, taken }, taken2 -> { args, taken: Set.union taken taken2 } + +# andMap : Parser a, Parser (a -> b) -> Parser b +andMap = \@Parser parser, @Parser mapper -> + unwrapped = + when mapper is + Succeed fn -> + when parser is + Succeed a -> + Lazy \{} -> fn a + + Lazy thunk -> + Lazy \{} -> fn (thunk {}) + + WithConfig parser2 config -> + parser2 + |> andMap (@Parser mapper) + |> WithConfig config + + Option config run -> + Option config \args -> + run args + |> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken }) + + Positional config run -> + Positional config \args -> + run args + |> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken }) + + SubCommand cmds -> + mapSubParser = \{ name, parser: parser2 } -> + { name, parser: andMap parser2 (@Parser mapper) } + + List.map cmds mapSubParser + |> SubCommand + + Option config run -> + when parser is + Succeed a -> + Option config \args -> + when run args is + Ok { val: fn, newlyTaken } -> Ok { val: fn a, newlyTaken } + Err err -> Err err + + Lazy thunk -> + Option config \args -> + when run args is + Ok { val: fn, newlyTaken } -> Ok { val: fn (thunk {}), newlyTaken } + Err err -> Err err + + WithConfig parser2 config2 -> + parser2 + |> andMap (@Parser mapper) + |> WithConfig config2 + + Option config2 run2 -> + # Parse first the one and then the other. + combinedParser = Option config2 \args -> + when run args is + Ok { val: fn, newlyTaken } -> + run2 (updateTaken args newlyTaken) + |> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 }) + + Err err -> Err err + + # Store the extra config. + @Parser combinedParser + |> WithConfig (Option config) + + Positional config2 run2 -> + combinedParser = Positional config2 \args -> + when run args is + Ok { val: fn, newlyTaken } -> + run2 (updateTaken args newlyTaken) + |> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 }) + + Err err -> Err err + + # Store the extra config. + @Parser combinedParser + |> WithConfig (Option config) + + SubCommand cmds -> + # For each subcommand, first run the subcommand, then + # push the result through the arg parser. + mapSubParser = \{ name, parser: parser2 } -> + { name, parser: andMap parser2 (@Parser mapper) } + + List.map cmds mapSubParser + |> SubCommand + + Positional config run -> + when parser is + Succeed a -> + Positional config \args -> + when run args is + Ok { val: fn, newlyTaken } -> Ok { val: fn a, newlyTaken } + Err err -> Err err + + Lazy thunk -> + Positional config \args -> + when run args is + Ok { val: fn, newlyTaken } -> Ok { val: fn (thunk {}), newlyTaken } + Err err -> Err err + + WithConfig parser2 config2 -> + parser2 + |> andMap (@Parser mapper) + |> WithConfig config2 + + Option config2 run2 -> + # Parse first the one and then the other. + combinedParser = Option config2 \args -> + when run args is + Ok { val: fn, newlyTaken } -> + run2 (updateTaken args newlyTaken) + |> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 }) + + Err err -> Err err + + # Store the extra config. + @Parser combinedParser + |> WithConfig (Positional config) + + Positional config2 run2 -> + combinedParser = Positional config2 \args -> + when run args is + Ok { val: fn, newlyTaken } -> + run2 (updateTaken args newlyTaken) + |> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 }) + + Err err -> Err err + + # Store the extra config. + @Parser combinedParser + |> WithConfig (Positional config) + + SubCommand cmds -> + # For each subcommand, first run the subcommand, then + # push the result through the arg parser. + mapSubParser = \{ name, parser: parser2 } -> + { name, parser: andMap parser2 (@Parser mapper) } + + List.map cmds mapSubParser + |> SubCommand + + Lazy thunk -> + fn = thunk {} + + when parser is + Succeed a -> + Lazy \{} -> fn a + + Lazy innerThunk -> + Lazy \{} -> fn (innerThunk {}) + + WithConfig parser2 config -> + parser2 + |> andMap (@Parser mapper) + |> WithConfig config + + Option config run -> + Option config \args -> + run args + |> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken }) + + Positional config run -> + Positional config \args -> + run args + |> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken }) + + SubCommand cmds -> + mapSubParser = \{ name, parser: parser2 } -> + { name, parser: andMap parser2 (@Parser mapper) } + + List.map cmds mapSubParser + |> SubCommand + + WithConfig mapper2 config -> + @Parser parser + |> andMap mapper2 + |> WithConfig config + + SubCommand cmds -> + mapSubParser = \{ name, parser: mapper2 } -> + { name, parser: andMap (@Parser parser) mapper2 } + + List.map cmds mapSubParser + |> SubCommand + + @Parser unwrapped + +## Marks a [Parser] as the entry point for parsing a command-line application, +## taking the program name and optionally a high-level help message for the +## application. +## +## The produced [NamedParser] can be used to parse arguments via [parse] or +## [parseFormatted]. +program = \parser, { name, help ? "" } -> + @NamedParser { name, help, parser } + +## Parses a list of command-line arguments with the given parser. The list of +## arguments is expected to contain the name of the program in the first +## position. +## +## If the arguments do not conform with what is expected by the parser, the +## first error seen will be returned. +# TODO panics in alias analysis when this annotation is included +# parse : NamedParser a, List Str -> Result a (ParseError*) +parse = \@NamedParser parser, args -> + # By convention the first string in the argument list is the program name. + if + List.isEmpty args + then + Err (ProgramNameNotProvided parser.name) + else + markedArgs = { args, taken: Set.single 0 } + + parseHelp parser.parser markedArgs + +parseHelp : Parser a, MarkedArgs -> Result a (ParseError []) +parseHelp = \@Parser parser, args -> + when parser is + Succeed val -> Ok val + Option _ run -> + run args + |> Result.map .val + + Positional _ run -> + run args + |> Result.map .val + + SubCommand cmds -> + when nextUnmarked args is + Ok { index, val: cmd } -> + argsRest = { args & taken: Set.insert args.taken index } + state = + List.walkUntil + cmds + (Err {}) + \st, { name, parser: subParser } -> + if + cmd == name + then + Break (Ok (parseHelp subParser argsRest)) + else + Continue st + + when state is + Ok result -> result + Err {} -> Err (IncorrectSubCommand { found: cmd, choices: List.map cmds .name }) + + Err OutOfBounds -> Err (SubCommandNotFound { choices: List.map cmds .name }) + + Lazy thunk -> Ok (thunk {}) + WithConfig parser2 _config -> + parseHelp parser2 args + +nextUnmarked : MarkedArgs -> Result { index : Nat, val : Str } [OutOfBounds] +nextUnmarked = \marked -> + help = \index -> + if Set.contains marked.taken index then + help (index + 1) + else + List.get marked.args index + |> Result.map \val -> { index, val } + + help 0 + +## Creates a parser for a boolean option argument. +## Options of value "true" and "false" will be parsed as [Bool.true] and [Bool.false], respectively. +## All other values will result in a `WrongOptionType` error. +boolOption : _ -> Parser Bool # TODO: panics if parameter annotation given +boolOption = \{ long, short ? "", help ? "" } -> + fn = \args -> + when findOneArg long short args is + Err NotFound -> Err (MissingRequiredOption long) + Ok { val, newlyTaken } -> + when val is + "true" -> Ok { val: Bool.true, newlyTaken } + "false" -> Ok { val: Bool.false, newlyTaken } + _ -> Err (WrongOptionType { arg: long, expected: Bool }) + + @Parser (Option { long, short, help, type: Bool } fn) + +## Creates a parser for a string option argument. +strOption : _ -> Parser Str # TODO: panics if parameter annotation given +strOption = \{ long, short ? "", help ? "" } -> + fn = \args -> + when findOneArg long short args is + Err NotFound -> Err (MissingRequiredOption long) + Ok { val, newlyTaken } -> Ok { val, newlyTaken } + + @Parser (Option { long, short, help, type: Str } fn) + +## Creates a parser for a 64-bit signed integer ([I64]) option argument. +i64Option : _ -> Parser I64 # TODO: panics if parameter annotation given +i64Option = \{ long, short ? "", help ? "" } -> + fn = \args -> + when findOneArg long short args is + Err NotFound -> Err (MissingRequiredOption long) + Ok { val, newlyTaken } -> + Str.toI64 val + |> Result.mapErr (\_ -> WrongOptionType { arg: long, expected: I64 }) + |> Result.map (\v -> { val: v, newlyTaken }) + + @Parser (Option { long, short, help, type: I64 } fn) + +## Parses a single positional argument as a string. +str : _ -> Parser Str +str = \{ name, help ? "" } -> + fn = \args -> + nextUnmarked args + |> Result.mapErr (\OutOfBounds -> MissingPositional name) + |> Result.map (\{ val, index } -> { val, newlyTaken: Set.insert args.taken index }) + + @Parser (Positional { name, help } fn) + +## Wraps a given parser as a subcommand parser. +## +## When parsing arguments, the subcommand name will be expected to be parsed +## first, and then the wrapped parser will be applied to the rest of the +## arguments. +## +## To support multiple subcommands, use [choice]. +subCommand : Parser a, Str -> { name : Str, parser : Parser a } +subCommand = \parser, name -> { name, parser } + +## Creates a parser that matches over a list of subcommands. +## +## The given list of subcommands is expected to be non-empty, and unique in the +## subcommand name. These invariants are not enforced today, but may be in the +## future. +## +## During argument parsing, the list of subcommands will be tried in-order. Due +## to the described invariant, at most one given subcommand will match any +## argument list. +choice : List { name : Str, parser : Parser a } -> Parser a +choice = \subCommands -> @Parser (SubCommand subCommands) + +## Like [parse], runs a parser to completion on a list of arguments. +## +## If the parser fails, a formatted error and help message is returned. +# TODO: mono panics in the args example if the type annotation is included +# parseFormatted : NamedParser a, List Str -> Result a Str +parseFormatted = \@NamedParser parser, args -> + Result.mapErr + (parse (@NamedParser parser) args) + \e -> + Str.concat (Str.concat (formatHelp (@NamedParser parser)) "\n\n") (formatError e) + +indent : Nat -> Str +indent = \n -> Str.repeat " " n + +indentLevel : Nat +indentLevel = 4 + +mapNonEmptyStr = \s, f -> if Str.isEmpty s then s else f s + +filterMap : List a, (a -> [Some b, None]) -> List b +filterMap = \lst, transform -> + List.walk lst [] \all, elem -> + when transform elem is + Some v -> List.append all v + None -> all + +# formatHelp : NamedParser a -> Str +formatHelp = \@NamedParser { name, help, parser } -> + fmtHelp = + mapNonEmptyStr help \helpStr -> "\n\(helpStr)" + + cmdHelp = toHelp parser + + fmtCmdHelp = formatHelpHelp 0 cmdHelp + + """ + \(name)\(fmtHelp) + \(fmtCmdHelp) + """ + +# formatHelpHelp : Nat, Help -> Str +formatHelpHelp = \n, cmdHelp -> + indented = indent n + + when cmdHelp is + SubCommands cmds -> + fmtCmdHelp = + Str.joinWith + (List.map cmds \subCmd -> formatSubCommand (n + indentLevel) subCmd) + "\n\n" + + """ + + \(indented)COMMANDS: + \(fmtCmdHelp) + """ + + Config configs -> + optionConfigs = + filterMap + configs + (\config -> + when config is + Option c -> Some c + _ -> None) + + positionalConfigs = + filterMap + configs + (\config -> + when config is + Positional c -> Some c + _ -> None) + + fmtOptionsHelp = + if List.isEmpty optionConfigs then + "" + else + helpStr = + optionConfigs + |> List.map (\c -> formatOptionConfig (n + indentLevel) c) + |> Str.joinWith "\n" + + """ + + \(indented)OPTIONS: + \(helpStr) + """ + + fmtPositionalsHelp = + if List.isEmpty positionalConfigs then + "" + else + helpStr = + positionalConfigs + |> List.map (\c -> formatPositionalConfig (n + indentLevel) c) + |> Str.joinWith "\n" + + """ + + \(indented)ARGS: + \(helpStr) + """ + + Str.concat fmtPositionalsHelp fmtOptionsHelp + +formatSubCommand = \n, { name, help } -> + indented = indent n + + fmtHelp = formatHelpHelp (n + indentLevel) help + + "\(indented)\(name)\(fmtHelp)" + +formatOptionConfig : Nat, OptionConfig -> Str +formatOptionConfig = \n, { long, short, help, type } -> + indented = indent n + + formattedShort = + mapNonEmptyStr short \s -> ", -\(s)" + + formattedType = formatOptionType type + + formattedHelp = + mapNonEmptyStr help \h -> " \(h)" + + "\(indented)--\(long)\(formattedShort)\(formattedHelp) (\(formattedType))" + +formatPositionalConfig : Nat, PositionalConfig -> Str +formatPositionalConfig = \n, { name, help } -> + indented = indent n + + formattedHelp = + mapNonEmptyStr help \h -> " \(h)" + + "\(indented)\(name)\(formattedHelp)" + +formatOptionType : OptionType -> Str +formatOptionType = \type -> + when type is + Bool -> "boolean" + Str -> "string" + I64 -> "integer, 64-bit signed" + +quote = \s -> "\"\(s)\"" + +formatError : ParseError [] -> Str +formatError = \err -> + when err is + ProgramNameNotProvided programName -> + "The program name \"\(programName)\" was not provided as a first argument!" + + MissingPositional arg -> + "The argument `\(arg)` is required but was not provided!" + + MissingRequiredOption arg -> + "The option `--\(arg)` is required but was not provided!" + + WrongOptionType { arg, expected } -> + formattedType = formatOptionType expected + + "The option `--\(arg)` expects a value of type \(formattedType)!" + + SubCommandNotFound { choices } -> + fmtChoices = + List.map choices quote + |> Str.joinWith ", " + + """ + A subcommand was expected, but not found! + The available subcommands are: + \t\(fmtChoices) + """ + + IncorrectSubCommand { found, choices } -> + fmtFound = quote found + + fmtChoices = + List.map choices quote + |> Str.joinWith ", " + + """ + The \(fmtFound) subcommand was found, but it's not expected in this context! + The available subcommands are: + \t\(fmtChoices) + """ + +## Applies one parser over another, mapping parser. +## +## `withParser mapper parser` produces a parser that will parse an argument list +## with `parser` first, then parse the remaining list with `mapper`, and feed +## the result of `parser` to `mapper`. +## +## This provides a way to chain the results of multiple parsers together. For +## example, to combine the results of two [strOption] arguments into a record, +## you could use +## +## ``` +## succeed (\host -> \port -> { host, port }) +## |> withParser (strOption { long: "host" }) +## |> withParser (strOption { long: "port" }) +## ``` +withParser = \arg1, arg2 -> andMap arg2 arg1 + +mark = \args -> { args, taken: Set.empty } + +# boolean undashed long optional is missing +expect + parser = boolOption { long: "foo" } + + parseHelp parser (mark ["foo"]) == Err (MissingRequiredOption "foo") + +# boolean dashed long optional without value is missing +expect + parser = boolOption { long: "foo" } + + parseHelp parser (mark ["--foo"]) == Err (MissingRequiredOption "foo") + +# boolean dashed long optional with value is determined true +expect + parser = boolOption { long: "foo" } + + parseHelp parser (mark ["--foo", "true"]) == Ok Bool.true + +# boolean dashed long optional with value is determined false +expect + parser = boolOption { long: "foo" } + + parseHelp parser (mark ["--foo", "false"]) == Ok Bool.false + +# boolean dashed long optional with value is determined wrong type +expect + parser = boolOption { long: "foo" } + + parseHelp parser (mark ["--foo", "not-a-boolean"]) == Err (WrongOptionType { arg: "foo", expected: Bool }) + +# boolean dashed short optional with value is determined true +expect + parser = boolOption { long: "foo", short: "F" } + + parseHelp parser (mark ["-F", "true"]) == Ok Bool.true + +# boolean dashed short optional with value is determined false +expect + parser = boolOption { long: "foo", short: "F" } + + parseHelp parser (mark ["-F", "false"]) == Ok Bool.false + +# boolean dashed short optional with value is determined wrong type +expect + parser = boolOption { long: "foo", short: "F" } + + parseHelp parser (mark ["-F", "not-a-boolean"]) == Err (WrongOptionType { arg: "foo", expected: Bool }) + +# string dashed long option without value is missing +expect + parser = strOption { long: "foo" } + + parseHelp parser (mark ["--foo"]) == Err (MissingRequiredOption "foo") + +# string dashed long option with value is determined +expect + parser = strOption { long: "foo" } + + parseHelp parser (mark ["--foo", "itsme"]) == Ok "itsme" + +# string dashed short option without value is missing +expect + parser = strOption { long: "foo", short: "F" } + + parseHelp parser (mark ["-F"]) == Err (MissingRequiredOption "foo") + +# string dashed short option with value is determined +expect + parser = strOption { long: "foo", short: "F" } + + parseHelp parser (mark ["-F", "itsme"]) == Ok "itsme" + +# i64 dashed long option without value is missing +expect + parser = i64Option { long: "foo" } + + parseHelp parser (mark ["--foo"]) == Err (MissingRequiredOption "foo") + +# i64 dashed long option with value is determined positive +expect + parser = i64Option { long: "foo" } + + parseHelp parser (mark ["--foo", "1234"]) == Ok 1234 + +# i64 dashed long option with value is determined negative +expect + parser = i64Option { long: "foo" } + + parseHelp parser (mark ["--foo", "-1234"]) == Ok -1234 + +# i64 dashed short option without value is missing +expect + parser = i64Option { long: "foo", short: "F" } + + parseHelp parser (mark ["-F"]) == Err (MissingRequiredOption "foo") + +# i64 dashed short option with value is determined +expect + parser = i64Option { long: "foo", short: "F" } + + parseHelp parser (mark ["-F", "1234"]) == Ok 1234 + +# two string parsers complete cases +expect + parser = + succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)") + |> withParser (strOption { long: "foo" }) + |> withParser (strOption { long: "bar" }) + + cases = [ + ["--foo", "true", "--bar", "baz"], + ["--bar", "baz", "--foo", "true"], + ["--foo", "true", "--bar", "baz", "--other", "something"], + ] + + List.all cases \args -> parseHelp parser (mark args) == Ok "foo: true bar: baz" + +# one argument is missing out of multiple +expect + parser = + succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)") + |> withParser (strOption { long: "foo" }) + |> withParser (strOption { long: "bar" }) + + List.all + [ + parseHelp parser (mark ["--foo", "zaz"]) == Err (MissingRequiredOption "bar"), + parseHelp parser (mark ["--bar", "zaz"]) == Err (MissingRequiredOption "foo"), + ] + (\b -> b) + +# string and boolean parsers build help +expect + parser = + succeed (\foo -> \bar -> \_bool -> "foo: \(foo) bar: \(bar)") + |> withParser (strOption { long: "foo", help: "the foo option" }) + |> withParser (strOption { long: "bar", short: "B" }) + |> withParser (boolOption { long: "boolean" }) + + toHelp parser + == Config [ + Option { long: "foo", short: "", help: "the foo option", type: Str }, + Option { long: "bar", short: "B", help: "", type: Str }, + Option { long: "boolean", short: "", help: "", type: Bool }, + ] + +# format option is missing +expect + parser = boolOption { long: "foo" } + + when parseHelp parser (mark ["foo"]) is + Ok _ -> Bool.false + Err e -> + err = formatError e + + err == "The option `--foo` is required but was not provided!" + +# format option has wrong type +expect + parser = boolOption { long: "foo" } + + when parseHelp parser (mark ["--foo", "12"]) is + Ok _ -> Bool.false + Err e -> + err = formatError e + + err == "The option `--foo` expects a value of type boolean!" + +# format help menu with only options +expect + parser = + succeed (\_foo -> \_bar -> \_baz -> \_bool -> "") + |> withParser (strOption { long: "foo", help: "the foo option" }) + |> withParser (strOption { long: "bar", short: "B" }) + |> withParser (strOption { long: "baz", short: "z", help: "the baz option" }) + |> withParser (boolOption { long: "boolean" }) + |> program { name: "test" } + + formatHelp parser + == + """ + test + + OPTIONS: + --foo the foo option (string) + --bar, -B (string) + --baz, -z the baz option (string) + --boolean (boolean) + """ + +# format help menu with subcommands +expect + parser = + choice [ + succeed (\user -> \pw -> "\(user)\(pw)") + |> withParser (strOption { long: "user" }) + |> withParser (strOption { long: "pw" }) + |> subCommand "login", + succeed (\file -> \url -> "\(file)\(url)") + |> withParser (strOption { long: "file" }) + |> withParser (strOption { long: "url" }) + |> subCommand "publish", + ] + |> program { name: "test" } + + formatHelp parser + == + """ + test + + COMMANDS: + login + OPTIONS: + --user (string) + --pw (string) + + publish + OPTIONS: + --file (string) + --url (string) + """ + +# format help menu with program help message +expect + parser = + choice [subCommand (succeed "") "login"] + |> program { name: "test", help: "a test cli app" } + + formatHelp parser + == + """ + test + a test cli app + + COMMANDS: + login + """ + +# subcommand parser +expect + parser = + choice [ + succeed (\user -> \pw -> "logging in \(user) with \(pw)") + |> withParser (strOption { long: "user" }) + |> withParser (strOption { long: "pw" }) + |> subCommand "login", + succeed (\file -> \url -> "\(file)\(url)") + |> withParser (strOption { long: "file" }) + |> withParser (strOption { long: "url" }) + |> subCommand "publish", + ] + |> program { name: "test" } + + when parse parser ["test", "login", "--pw", "123", "--user", "abc"] is + Ok result -> result == "logging in abc with 123" + Err _ -> Bool.false + +# subcommand of subcommand parser +expect + parser = + choice [ + choice [ + succeed (\user -> \pw -> "logging in \(user) with \(pw)") + |> withParser (strOption { long: "user" }) + |> withParser (strOption { long: "pw" }) + |> subCommand "login", + ] + |> subCommand "auth", + ] + |> program { name: "test" } + + when parse parser ["test", "auth", "login", "--pw", "123", "--user", "abc"] is + Ok result -> result == "logging in abc with 123" + Err _ -> Bool.false + +# subcommand not provided +expect + parser = + choice [subCommand (succeed "") "auth", subCommand (succeed "") "publish"] + + when parseHelp parser (mark []) is + Ok _ -> Bool.true + Err e -> + err = formatError e + + err + == + """ + A subcommand was expected, but not found! + The available subcommands are: + \t"auth", "publish" + """ + +# subcommand doesn't match choices +expect + parser = + choice [subCommand (succeed "") "auth", subCommand (succeed "") "publish"] + + when parseHelp parser (mark ["logs"]) is + Ok _ -> Bool.true + Err e -> + err = formatError e + + err + == + """ + The "logs" subcommand was found, but it's not expected in this context! + The available subcommands are: + \t"auth", "publish" + """ + +# parse positional +expect + parser = str { name: "foo" } + + parseHelp parser (mark ["myArg"]) == Ok "myArg" + +# parse positional with option +expect + parser = + succeed (\foo -> \bar -> "foo: \(foo), bar: \(bar)") + |> withParser (strOption { long: "foo" }) + |> withParser (str { name: "bar" }) + + cases = [ + ["--foo", "true", "baz"], + ["baz", "--foo", "true"], + ] + + List.all cases \args -> parseHelp parser (mark args) == Ok "foo: true, bar: baz" + +# parse positional with subcommand +expect + parser = choice [ + str { name: "bar" } + |> subCommand "hello", + ] + + parseHelp parser (mark ["hello", "foo"]) == Ok "foo" + +# missing positional +expect + parser = str { name: "bar" } + + parseHelp parser (mark []) == Err (MissingPositional "bar") diff --git a/examples/interactive/cli-platform/Cargo.lock b/examples/cli/cli-platform/Cargo.lock similarity index 76% rename from examples/interactive/cli-platform/Cargo.lock rename to examples/cli/cli-platform/Cargo.lock index 266b9b5e72..c1b2833f64 100644 --- a/examples/interactive/cli-platform/Cargo.lock +++ b/examples/cli/cli-platform/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "arrayvec" version = "0.7.2" @@ -14,6 +29,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.0" @@ -22,15 +52,15 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "bytes" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -61,52 +91,51 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-core", "futures-io", @@ -118,10 +147,16 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.13" +name = "gimli" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "h2" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -155,6 +190,7 @@ dependencies = [ name = "host" version = "0.0.1" dependencies = [ + "backtrace", "libc", "reqwest", "roc_std", @@ -184,9 +220,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -233,11 +269,10 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -260,30 +295,24 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" -version = "0.2.126" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "log" @@ -294,12 +323,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.5.0" @@ -312,6 +335,15 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.4" @@ -335,16 +367,25 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.13.0" +name = "object" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project-lite" @@ -360,27 +401,27 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64", "bytes", @@ -394,9 +435,9 @@ dependencies = [ "hyper-rustls", "ipnet", "js-sys", - "lazy_static", "log", "mime", + "once_cell", "percent-encoding", "pin-project-lite", "rustls", @@ -438,6 +479,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustls" version = "0.20.6" @@ -452,18 +499,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "sct" @@ -477,15 +524,15 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -515,9 +562,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -537,9 +584,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -563,9 +610,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "bytes", @@ -573,7 +620,6 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", "winapi", @@ -592,9 +638,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -612,9 +658,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -623,9 +669,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] @@ -644,15 +690,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -665,13 +711,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -693,9 +738,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -703,13 +748,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -718,9 +763,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if", "js-sys", @@ -730,9 +775,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -740,9 +785,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -753,15 +798,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -779,9 +824,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" dependencies = [ "webpki", ] diff --git a/examples/interactive/cli-platform/Cargo.toml b/examples/cli/cli-platform/Cargo.toml similarity index 95% rename from examples/interactive/cli-platform/Cargo.toml rename to examples/cli/cli-platform/Cargo.toml index 250b0419de..c50e3702ca 100644 --- a/examples/interactive/cli-platform/Cargo.toml +++ b/examples/cli/cli-platform/Cargo.toml @@ -20,5 +20,6 @@ path = "src/main.rs" roc_std = { path = "../../../crates/roc_std" } libc = "0.2" reqwest = { version="0.11.11", default-features=false, features=["blocking", "rustls-tls"] } +backtrace = "0.3" [workspace] diff --git a/examples/interactive/cli-platform/Dir.roc b/examples/cli/cli-platform/Dir.roc similarity index 100% rename from examples/interactive/cli-platform/Dir.roc rename to examples/cli/cli-platform/Dir.roc diff --git a/examples/interactive/cli-platform/Effect.roc b/examples/cli/cli-platform/Effect.roc similarity index 92% rename from examples/interactive/cli-platform/Effect.roc rename to examples/cli/cli-platform/Effect.roc index 518b34d074..9d20093613 100644 --- a/examples/interactive/cli-platform/Effect.roc +++ b/examples/cli/cli-platform/Effect.roc @@ -14,7 +14,9 @@ hosted Effect setCwd, exePath, stdoutLine, + stdoutWrite, stderrLine, + stderrWrite, stdinLine, sendRequest, fileReadBytes, @@ -26,7 +28,9 @@ hosted Effect generates Effect with [after, map, always, forever, loop] stdoutLine : Str -> Effect {} +stdoutWrite : Str -> Effect {} stderrLine : Str -> Effect {} +stderrWrite : Str -> Effect {} stdinLine : Effect Str fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr) diff --git a/examples/interactive/cli-platform/Env.roc b/examples/cli/cli-platform/Env.roc similarity index 100% rename from examples/interactive/cli-platform/Env.roc rename to examples/cli/cli-platform/Env.roc diff --git a/examples/interactive/cli-platform/EnvDecoding.roc b/examples/cli/cli-platform/EnvDecoding.roc similarity index 100% rename from examples/interactive/cli-platform/EnvDecoding.roc rename to examples/cli/cli-platform/EnvDecoding.roc diff --git a/examples/interactive/cli-platform/File.roc b/examples/cli/cli-platform/File.roc similarity index 100% rename from examples/interactive/cli-platform/File.roc rename to examples/cli/cli-platform/File.roc diff --git a/examples/interactive/cli-platform/FileMetadata.roc b/examples/cli/cli-platform/FileMetadata.roc similarity index 100% rename from examples/interactive/cli-platform/FileMetadata.roc rename to examples/cli/cli-platform/FileMetadata.roc diff --git a/examples/interactive/cli-platform/Http.roc b/examples/cli/cli-platform/Http.roc similarity index 100% rename from examples/interactive/cli-platform/Http.roc rename to examples/cli/cli-platform/Http.roc diff --git a/examples/interactive/cli-platform/InternalDir.roc b/examples/cli/cli-platform/InternalDir.roc similarity index 100% rename from examples/interactive/cli-platform/InternalDir.roc rename to examples/cli/cli-platform/InternalDir.roc diff --git a/examples/interactive/cli-platform/InternalFile.roc b/examples/cli/cli-platform/InternalFile.roc similarity index 100% rename from examples/interactive/cli-platform/InternalFile.roc rename to examples/cli/cli-platform/InternalFile.roc diff --git a/examples/interactive/cli-platform/InternalHttp.roc b/examples/cli/cli-platform/InternalHttp.roc similarity index 100% rename from examples/interactive/cli-platform/InternalHttp.roc rename to examples/cli/cli-platform/InternalHttp.roc diff --git a/examples/interactive/cli-platform/InternalPath.roc b/examples/cli/cli-platform/InternalPath.roc similarity index 100% rename from examples/interactive/cli-platform/InternalPath.roc rename to examples/cli/cli-platform/InternalPath.roc diff --git a/examples/interactive/cli-platform/InternalProgram.roc b/examples/cli/cli-platform/InternalProgram.roc similarity index 100% rename from examples/interactive/cli-platform/InternalProgram.roc rename to examples/cli/cli-platform/InternalProgram.roc diff --git a/examples/interactive/cli-platform/InternalTask.roc b/examples/cli/cli-platform/InternalTask.roc similarity index 100% rename from examples/interactive/cli-platform/InternalTask.roc rename to examples/cli/cli-platform/InternalTask.roc diff --git a/examples/interactive/cli-platform/Path.roc b/examples/cli/cli-platform/Path.roc similarity index 100% rename from examples/interactive/cli-platform/Path.roc rename to examples/cli/cli-platform/Path.roc diff --git a/examples/interactive/cli-platform/Program.roc b/examples/cli/cli-platform/Program.roc similarity index 100% rename from examples/interactive/cli-platform/Program.roc rename to examples/cli/cli-platform/Program.roc diff --git a/examples/cli/cli-platform/Stderr.roc b/examples/cli/cli-platform/Stderr.roc new file mode 100644 index 0000000000..80369212db --- /dev/null +++ b/examples/cli/cli-platform/Stderr.roc @@ -0,0 +1,15 @@ +interface Stderr + exposes [line, write] + imports [Effect, Task.{ Task }, InternalTask] + +line : Str -> Task {} * [Write [Stderr]*]* +line = \str -> + Effect.stderrLine str + |> Effect.map (\_ -> Ok {}) + |> InternalTask.fromEffect + +write : Str -> Task {} * [Write [Stderr]*]* +write = \str -> + Effect.stderrWrite str + |> Effect.map (\_ -> Ok {}) + |> InternalTask.fromEffect diff --git a/examples/interactive/cli-platform/Stdin.roc b/examples/cli/cli-platform/Stdin.roc similarity index 100% rename from examples/interactive/cli-platform/Stdin.roc rename to examples/cli/cli-platform/Stdin.roc diff --git a/examples/cli/cli-platform/Stdout.roc b/examples/cli/cli-platform/Stdout.roc new file mode 100644 index 0000000000..6c0692b668 --- /dev/null +++ b/examples/cli/cli-platform/Stdout.roc @@ -0,0 +1,15 @@ +interface Stdout + exposes [line, write] + imports [Effect, Task.{ Task }, InternalTask] + +line : Str -> Task {} * [Write [Stdout]*]* +line = \str -> + Effect.stdoutLine str + |> Effect.map (\_ -> Ok {}) + |> InternalTask.fromEffect + +write : Str -> Task {} * [Write [Stdout]*]* +write = \str -> + Effect.stdoutWrite str + |> Effect.map (\_ -> Ok {}) + |> InternalTask.fromEffect diff --git a/examples/interactive/cli-platform/Task.roc b/examples/cli/cli-platform/Task.roc similarity index 92% rename from examples/interactive/cli-platform/Task.roc rename to examples/cli/cli-platform/Task.roc index 1c223ac8b8..c5f439d60a 100644 --- a/examples/interactive/cli-platform/Task.roc +++ b/examples/cli/cli-platform/Task.roc @@ -1,5 +1,5 @@ interface Task - exposes [Task, succeed, fail, await, map, mapFail, onFail, attempt, forever, loop] + exposes [Task, succeed, fail, await, map, mapFail, onFail, attempt, forever, loop, fromResult] imports [Effect, InternalTask] Task ok err fx : InternalTask.Task ok err fx @@ -93,3 +93,10 @@ mapFail = \task, transform -> Err err -> Task.fail (transform err) |> InternalTask.toEffect InternalTask.fromEffect effect + +## Use a Result among other Tasks by converting it into a Task. +fromResult : Result ok err -> Task ok err * +fromResult = \result -> + when result is + Ok ok -> succeed ok + Err err -> fail err diff --git a/examples/interactive/cli-platform/Url.roc b/examples/cli/cli-platform/Url.roc similarity index 100% rename from examples/interactive/cli-platform/Url.roc rename to examples/cli/cli-platform/Url.roc diff --git a/examples/false-interpreter/platform/build.rs b/examples/cli/cli-platform/build.rs similarity index 100% rename from examples/false-interpreter/platform/build.rs rename to examples/cli/cli-platform/build.rs diff --git a/examples/interactive/cli-platform/host.c b/examples/cli/cli-platform/host.c similarity index 100% rename from examples/interactive/cli-platform/host.c rename to examples/cli/cli-platform/host.c diff --git a/examples/interactive/cli-platform/main.roc b/examples/cli/cli-platform/main.roc similarity index 100% rename from examples/interactive/cli-platform/main.roc rename to examples/cli/cli-platform/main.roc diff --git a/examples/interactive/cli-platform/src/file_glue.rs b/examples/cli/cli-platform/src/file_glue.rs similarity index 100% rename from examples/interactive/cli-platform/src/file_glue.rs rename to examples/cli/cli-platform/src/file_glue.rs diff --git a/examples/interactive/cli-platform/src/glue.rs b/examples/cli/cli-platform/src/glue.rs similarity index 100% rename from examples/interactive/cli-platform/src/glue.rs rename to examples/cli/cli-platform/src/glue.rs diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/cli/cli-platform/src/lib.rs similarity index 79% rename from examples/interactive/cli-platform/src/lib.rs rename to examples/cli/cli-platform/src/lib.rs index 57808a0a53..51abf949eb 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/cli/cli-platform/src/lib.rs @@ -12,6 +12,7 @@ use roc_std::{RocDict, RocList, RocResult, RocStr}; use std::borrow::Borrow; use std::ffi::{CStr, OsStr}; use std::fs::File; +use std::io::{self, Write}; use std::os::raw::c_char; use std::path::Path; use std::time::Duration; @@ -63,13 +64,110 @@ pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); let string = slice.to_str().unwrap(); - eprintln!("Roc hit a panic: {}", string); + eprintln!("Roc crashed with:\n\n\t{}\n", string); + + print_backtrace(); std::process::exit(1); } _ => todo!(), } } +fn print_backtrace() { + eprintln!("Here is the call stack that led to the crash:\n"); + + let mut entries = Vec::new(); + + #[derive(Default)] + struct Entry { + pub fn_name: String, + pub filename: Option, + pub line: Option, + pub col: Option, + } + + backtrace::trace(|frame| { + backtrace::resolve_frame(frame, |symbol| { + if let Some(fn_name) = symbol.name() { + let fn_name = fn_name.to_string(); + + if should_show_in_backtrace(&fn_name) { + let mut entry: Entry = Default::default(); + + entry.fn_name = format_fn_name(&fn_name); + + if let Some(path) = symbol.filename() { + entry.filename = Some(path.to_string_lossy().into_owned()); + }; + + entry.line = symbol.lineno(); + entry.col = symbol.colno(); + + entries.push(entry); + } + } else { + entries.push(Entry { + fn_name: "???".to_string(), + ..Default::default() + }); + } + }); + + true // keep going to the next frame + }); + + for entry in entries { + eprintln!("\t{}", entry.fn_name); + + if let Some(filename) = entry.filename { + eprintln!("\t\t{filename}"); + } + } + + eprintln!("\nOptimizations can make this list inaccurate! If it looks wrong, try running without `--optimize` and with `--linker=legacy`\n"); +} + +fn should_show_in_backtrace(fn_name: &str) -> bool { + let is_from_rust = fn_name.contains("::"); + let is_host_fn = fn_name.starts_with("roc_panic") + || fn_name.starts_with("_Effect_effect") + || fn_name.starts_with("_roc__") + || fn_name.starts_with("rust_main") + || fn_name == "_main"; + + !is_from_rust && !is_host_fn +} + +fn format_fn_name(fn_name: &str) -> String { + // e.g. convert "_Num_sub_a0c29024d3ec6e3a16e414af99885fbb44fa6182331a70ab4ca0886f93bad5" + // to ["Num", "sub", "a0c29024d3ec6e3a16e414af99885fbb44fa6182331a70ab4ca0886f93bad5"] + let mut pieces_iter = fn_name.split("_"); + + if let (_, Some(module_name), Some(name)) = + (pieces_iter.next(), pieces_iter.next(), pieces_iter.next()) + { + display_roc_fn(module_name, name) + } else { + "???".to_string() + } +} + +fn display_roc_fn(module_name: &str, fn_name: &str) -> String { + let module_name = if module_name == "#UserApp" { + "app" + } else { + module_name + }; + + let fn_name = if fn_name.parse::().is_ok() { + "(anonymous function)" + } else { + fn_name + }; + + format!("\u{001B}[36m{module_name}\u{001B}[39m.{fn_name}") +} + #[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) @@ -179,12 +277,26 @@ pub extern "C" fn roc_fx_stdoutLine(line: &RocStr) { println!("{}", string); } +#[no_mangle] +pub extern "C" fn roc_fx_stdoutWrite(text: &RocStr) { + let string = text.as_str(); + print!("{}", string); + io::stdout().flush().unwrap(); +} + #[no_mangle] pub extern "C" fn roc_fx_stderrLine(line: &RocStr) { let string = line.as_str(); eprintln!("{}", string); } +#[no_mangle] +pub extern "C" fn roc_fx_stderrWrite(text: &RocStr) { + let string = text.as_str(); + eprint!("{}", string); + io::stderr().flush().unwrap(); +} + // #[no_mangle] // pub extern "C" fn roc_fx_fileWriteUtf8( // roc_path: &RocList, diff --git a/examples/false-interpreter/platform/src/main.rs b/examples/cli/cli-platform/src/main.rs similarity index 100% rename from examples/false-interpreter/platform/src/main.rs rename to examples/cli/cli-platform/src/main.rs diff --git a/examples/interactive/countdown.roc b/examples/cli/countdown.roc similarity index 100% rename from examples/interactive/countdown.roc rename to examples/cli/countdown.roc diff --git a/examples/interactive/echo.roc b/examples/cli/echo.roc similarity index 100% rename from examples/interactive/echo.roc rename to examples/cli/echo.roc diff --git a/examples/interactive/effects-platform/Effect.roc b/examples/cli/effects-platform/Effect.roc similarity index 100% rename from examples/interactive/effects-platform/Effect.roc rename to examples/cli/effects-platform/Effect.roc diff --git a/examples/interactive/effects-platform/host.zig b/examples/cli/effects-platform/host.zig similarity index 100% rename from examples/interactive/effects-platform/host.zig rename to examples/cli/effects-platform/host.zig diff --git a/examples/interactive/effects-platform/main.roc b/examples/cli/effects-platform/main.roc similarity index 100% rename from examples/interactive/effects-platform/main.roc rename to examples/cli/effects-platform/main.roc diff --git a/examples/interactive/effects.roc b/examples/cli/effects.roc similarity index 100% rename from examples/interactive/effects.roc rename to examples/cli/effects.roc diff --git a/examples/interactive/env.roc b/examples/cli/env.roc similarity index 100% rename from examples/interactive/env.roc rename to examples/cli/env.roc diff --git a/examples/false-interpreter/.gitignore b/examples/cli/false-interpreter/.gitignore similarity index 100% rename from examples/false-interpreter/.gitignore rename to examples/cli/false-interpreter/.gitignore diff --git a/examples/false-interpreter/Context.roc b/examples/cli/false-interpreter/Context.roc similarity index 100% rename from examples/false-interpreter/Context.roc rename to examples/cli/false-interpreter/Context.roc diff --git a/examples/false-interpreter/False.roc b/examples/cli/false-interpreter/False.roc similarity index 100% rename from examples/false-interpreter/False.roc rename to examples/cli/false-interpreter/False.roc diff --git a/examples/false-interpreter/README.md b/examples/cli/false-interpreter/README.md similarity index 100% rename from examples/false-interpreter/README.md rename to examples/cli/false-interpreter/README.md diff --git a/examples/false-interpreter/Variable.roc b/examples/cli/false-interpreter/Variable.roc similarity index 100% rename from examples/false-interpreter/Variable.roc rename to examples/cli/false-interpreter/Variable.roc diff --git a/examples/false-interpreter/examples/bottles.false b/examples/cli/false-interpreter/examples/bottles.false similarity index 100% rename from examples/false-interpreter/examples/bottles.false rename to examples/cli/false-interpreter/examples/bottles.false diff --git a/examples/false-interpreter/examples/cksum.false b/examples/cli/false-interpreter/examples/cksum.false similarity index 100% rename from examples/false-interpreter/examples/cksum.false rename to examples/cli/false-interpreter/examples/cksum.false diff --git a/examples/false-interpreter/examples/copy.false b/examples/cli/false-interpreter/examples/copy.false similarity index 100% rename from examples/false-interpreter/examples/copy.false rename to examples/cli/false-interpreter/examples/copy.false diff --git a/examples/false-interpreter/examples/crc32.false b/examples/cli/false-interpreter/examples/crc32.false similarity index 100% rename from examples/false-interpreter/examples/crc32.false rename to examples/cli/false-interpreter/examples/crc32.false diff --git a/examples/false-interpreter/examples/hello.false b/examples/cli/false-interpreter/examples/hello.false similarity index 100% rename from examples/false-interpreter/examples/hello.false rename to examples/cli/false-interpreter/examples/hello.false diff --git a/examples/false-interpreter/examples/in.txt b/examples/cli/false-interpreter/examples/in.txt similarity index 100% rename from examples/false-interpreter/examples/in.txt rename to examples/cli/false-interpreter/examples/in.txt diff --git a/examples/false-interpreter/examples/odd_words.false b/examples/cli/false-interpreter/examples/odd_words.false similarity index 100% rename from examples/false-interpreter/examples/odd_words.false rename to examples/cli/false-interpreter/examples/odd_words.false diff --git a/examples/false-interpreter/examples/primes.false b/examples/cli/false-interpreter/examples/primes.false similarity index 100% rename from examples/false-interpreter/examples/primes.false rename to examples/cli/false-interpreter/examples/primes.false diff --git a/examples/false-interpreter/examples/queens.false b/examples/cli/false-interpreter/examples/queens.false similarity index 100% rename from examples/false-interpreter/examples/queens.false rename to examples/cli/false-interpreter/examples/queens.false diff --git a/examples/false-interpreter/examples/sqrt.false b/examples/cli/false-interpreter/examples/sqrt.false similarity index 100% rename from examples/false-interpreter/examples/sqrt.false rename to examples/cli/false-interpreter/examples/sqrt.false diff --git a/examples/false-interpreter/examples/test.false b/examples/cli/false-interpreter/examples/test.false similarity index 100% rename from examples/false-interpreter/examples/test.false rename to examples/cli/false-interpreter/examples/test.false diff --git a/examples/false-interpreter/platform/Cargo.lock b/examples/cli/false-interpreter/platform/Cargo.lock similarity index 100% rename from examples/false-interpreter/platform/Cargo.lock rename to examples/cli/false-interpreter/platform/Cargo.lock diff --git a/examples/false-interpreter/platform/Cargo.toml b/examples/cli/false-interpreter/platform/Cargo.toml similarity index 85% rename from examples/false-interpreter/platform/Cargo.toml rename to examples/cli/false-interpreter/platform/Cargo.toml index 9d98cfe0e8..eeeb74f517 100644 --- a/examples/false-interpreter/platform/Cargo.toml +++ b/examples/cli/false-interpreter/platform/Cargo.toml @@ -17,7 +17,7 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } +roc_std = { path = "../../../../crates/roc_std" } libc = "0.2" [workspace] diff --git a/examples/false-interpreter/platform/Effect.roc b/examples/cli/false-interpreter/platform/Effect.roc similarity index 100% rename from examples/false-interpreter/platform/Effect.roc rename to examples/cli/false-interpreter/platform/Effect.roc diff --git a/examples/false-interpreter/platform/File.roc b/examples/cli/false-interpreter/platform/File.roc similarity index 100% rename from examples/false-interpreter/platform/File.roc rename to examples/cli/false-interpreter/platform/File.roc diff --git a/examples/false-interpreter/platform/Stdin.roc b/examples/cli/false-interpreter/platform/Stdin.roc similarity index 100% rename from examples/false-interpreter/platform/Stdin.roc rename to examples/cli/false-interpreter/platform/Stdin.roc diff --git a/examples/false-interpreter/platform/Stdout.roc b/examples/cli/false-interpreter/platform/Stdout.roc similarity index 100% rename from examples/false-interpreter/platform/Stdout.roc rename to examples/cli/false-interpreter/platform/Stdout.roc diff --git a/examples/false-interpreter/platform/Task.roc b/examples/cli/false-interpreter/platform/Task.roc similarity index 100% rename from examples/false-interpreter/platform/Task.roc rename to examples/cli/false-interpreter/platform/Task.roc diff --git a/examples/interactive/cli-platform/build.rs b/examples/cli/false-interpreter/platform/build.rs similarity index 100% rename from examples/interactive/cli-platform/build.rs rename to examples/cli/false-interpreter/platform/build.rs diff --git a/examples/false-interpreter/platform/host.c b/examples/cli/false-interpreter/platform/host.c similarity index 100% rename from examples/false-interpreter/platform/host.c rename to examples/cli/false-interpreter/platform/host.c diff --git a/examples/false-interpreter/platform/main.roc b/examples/cli/false-interpreter/platform/main.roc similarity index 100% rename from examples/false-interpreter/platform/main.roc rename to examples/cli/false-interpreter/platform/main.roc diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/cli/false-interpreter/platform/src/lib.rs similarity index 100% rename from examples/false-interpreter/platform/src/lib.rs rename to examples/cli/false-interpreter/platform/src/lib.rs diff --git a/examples/interactive/cli-platform/src/main.rs b/examples/cli/false-interpreter/platform/src/main.rs similarity index 100% rename from examples/interactive/cli-platform/src/main.rs rename to examples/cli/false-interpreter/platform/src/main.rs diff --git a/examples/interactive/file.roc b/examples/cli/file.roc similarity index 100% rename from examples/interactive/file.roc rename to examples/cli/file.roc diff --git a/examples/interactive/form.roc b/examples/cli/form.roc similarity index 100% rename from examples/interactive/form.roc rename to examples/cli/form.roc diff --git a/examples/interactive/http-get.roc b/examples/cli/http-get.roc similarity index 100% rename from examples/interactive/http-get.roc rename to examples/cli/http-get.roc diff --git a/examples/interactive/tui-platform/Program.roc b/examples/cli/tui-platform/Program.roc similarity index 100% rename from examples/interactive/tui-platform/Program.roc rename to examples/cli/tui-platform/Program.roc diff --git a/examples/interactive/tui-platform/host.zig b/examples/cli/tui-platform/host.zig similarity index 100% rename from examples/interactive/tui-platform/host.zig rename to examples/cli/tui-platform/host.zig diff --git a/examples/interactive/tui-platform/main.roc b/examples/cli/tui-platform/main.roc similarity index 100% rename from examples/interactive/tui-platform/main.roc rename to examples/cli/tui-platform/main.roc diff --git a/examples/interactive/tui.roc b/examples/cli/tui.roc similarity index 100% rename from examples/interactive/tui.roc rename to examples/cli/tui.roc diff --git a/examples/breakout/.gitignore b/examples/gui/breakout/.gitignore similarity index 100% rename from examples/breakout/.gitignore rename to examples/gui/breakout/.gitignore diff --git a/examples/breakout/breakout.roc b/examples/gui/breakout/breakout.roc similarity index 100% rename from examples/breakout/breakout.roc rename to examples/gui/breakout/breakout.roc diff --git a/examples/breakout/hello.roc b/examples/gui/breakout/hello.roc similarity index 76% rename from examples/breakout/hello.roc rename to examples/gui/breakout/hello.roc index 0c50f63dac..0e05e50705 100644 --- a/examples/breakout/hello.roc +++ b/examples/gui/breakout/hello.roc @@ -12,6 +12,6 @@ update : Model, Event -> Model update = \model, _ -> model render : Model -> List Elem -render = \model -> [Text model.text] +render = \model -> [Text { text: model.text, top: 0, left: 0, size: 40, color: { r: 1, g: 1, b: 1, a: 1 } }] program = { init, update, render } diff --git a/examples/breakout/platform/.gitignore b/examples/gui/breakout/platform/.gitignore similarity index 100% rename from examples/breakout/platform/.gitignore rename to examples/gui/breakout/platform/.gitignore diff --git a/examples/breakout/platform/Action.roc b/examples/gui/breakout/platform/Action.roc similarity index 100% rename from examples/breakout/platform/Action.roc rename to examples/gui/breakout/platform/Action.roc diff --git a/examples/breakout/platform/Cargo.lock b/examples/gui/breakout/platform/Cargo.lock similarity index 99% rename from examples/breakout/platform/Cargo.lock rename to examples/gui/breakout/platform/Cargo.lock index 4d22e73d60..e2463d7baa 100644 --- a/examples/breakout/platform/Cargo.lock +++ b/examples/gui/breakout/platform/Cargo.lock @@ -585,12 +585,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "lazy_static", ] [[package]] @@ -2038,18 +2037,18 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "renderdoc-sys" diff --git a/examples/breakout/platform/Cargo.toml b/examples/gui/breakout/platform/Cargo.toml similarity index 97% rename from examples/breakout/platform/Cargo.toml rename to examples/gui/breakout/platform/Cargo.toml index 8dfaa8a0c5..5b612df452 100644 --- a/examples/breakout/platform/Cargo.toml +++ b/examples/gui/breakout/platform/Cargo.toml @@ -15,7 +15,7 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } +roc_std = { path = "../../../../crates/roc_std" } libc = "0.2" arrayvec = "0.7.2" page_size = "0.4.2" diff --git a/examples/breakout/platform/Elem.roc b/examples/gui/breakout/platform/Elem.roc similarity index 100% rename from examples/breakout/platform/Elem.roc rename to examples/gui/breakout/platform/Elem.roc diff --git a/examples/breakout/platform/Game.roc b/examples/gui/breakout/platform/Game.roc similarity index 70% rename from examples/breakout/platform/Game.roc rename to examples/gui/breakout/platform/Game.roc index 0ce338ee3d..26b966bf8d 100644 --- a/examples/breakout/platform/Game.roc +++ b/examples/gui/breakout/platform/Game.roc @@ -6,8 +6,8 @@ Rgba : { r : F32, g : F32, b : F32, a : F32 } Bounds : { height : F32, width : F32 } -Elem : [Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text Str] +Elem : [Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text { text : Str, color : Rgba, left : F32, top : F32, size : F32 }] -KeyCode : [Left, Right, Other] +KeyCode : [Left, Right, Other, Up, Down] Event : [Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128] diff --git a/examples/platform-switching/rust-platform/build.rs b/examples/gui/breakout/platform/build.rs similarity index 100% rename from examples/platform-switching/rust-platform/build.rs rename to examples/gui/breakout/platform/build.rs diff --git a/examples/platform-switching/rust-platform/host.c b/examples/gui/breakout/platform/host.c similarity index 100% rename from examples/platform-switching/rust-platform/host.c rename to examples/gui/breakout/platform/host.c diff --git a/examples/breakout/platform/main.roc b/examples/gui/breakout/platform/main.roc similarity index 100% rename from examples/breakout/platform/main.roc rename to examples/gui/breakout/platform/main.roc diff --git a/examples/breakout/platform/src/graphics/colors.rs b/examples/gui/breakout/platform/src/graphics/colors.rs similarity index 100% rename from examples/breakout/platform/src/graphics/colors.rs rename to examples/gui/breakout/platform/src/graphics/colors.rs diff --git a/examples/breakout/platform/src/graphics/lowlevel/buffer.rs b/examples/gui/breakout/platform/src/graphics/lowlevel/buffer.rs similarity index 100% rename from examples/breakout/platform/src/graphics/lowlevel/buffer.rs rename to examples/gui/breakout/platform/src/graphics/lowlevel/buffer.rs diff --git a/examples/breakout/platform/src/graphics/lowlevel/mod.rs b/examples/gui/breakout/platform/src/graphics/lowlevel/mod.rs similarity index 100% rename from examples/breakout/platform/src/graphics/lowlevel/mod.rs rename to examples/gui/breakout/platform/src/graphics/lowlevel/mod.rs diff --git a/examples/breakout/platform/src/graphics/lowlevel/ortho.rs b/examples/gui/breakout/platform/src/graphics/lowlevel/ortho.rs similarity index 100% rename from examples/breakout/platform/src/graphics/lowlevel/ortho.rs rename to examples/gui/breakout/platform/src/graphics/lowlevel/ortho.rs diff --git a/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs b/examples/gui/breakout/platform/src/graphics/lowlevel/pipelines.rs similarity index 100% rename from examples/breakout/platform/src/graphics/lowlevel/pipelines.rs rename to examples/gui/breakout/platform/src/graphics/lowlevel/pipelines.rs diff --git a/examples/breakout/platform/src/graphics/lowlevel/quad.rs b/examples/gui/breakout/platform/src/graphics/lowlevel/quad.rs similarity index 100% rename from examples/breakout/platform/src/graphics/lowlevel/quad.rs rename to examples/gui/breakout/platform/src/graphics/lowlevel/quad.rs diff --git a/examples/breakout/platform/src/graphics/lowlevel/vertex.rs b/examples/gui/breakout/platform/src/graphics/lowlevel/vertex.rs similarity index 100% rename from examples/breakout/platform/src/graphics/lowlevel/vertex.rs rename to examples/gui/breakout/platform/src/graphics/lowlevel/vertex.rs diff --git a/examples/breakout/platform/src/graphics/mod.rs b/examples/gui/breakout/platform/src/graphics/mod.rs similarity index 100% rename from examples/breakout/platform/src/graphics/mod.rs rename to examples/gui/breakout/platform/src/graphics/mod.rs diff --git a/examples/breakout/platform/src/graphics/primitives/mod.rs b/examples/gui/breakout/platform/src/graphics/primitives/mod.rs similarity index 100% rename from examples/breakout/platform/src/graphics/primitives/mod.rs rename to examples/gui/breakout/platform/src/graphics/primitives/mod.rs diff --git a/examples/breakout/platform/src/graphics/primitives/rect.rs b/examples/gui/breakout/platform/src/graphics/primitives/rect.rs similarity index 100% rename from examples/breakout/platform/src/graphics/primitives/rect.rs rename to examples/gui/breakout/platform/src/graphics/primitives/rect.rs diff --git a/examples/breakout/platform/src/graphics/primitives/text.rs b/examples/gui/breakout/platform/src/graphics/primitives/text.rs similarity index 98% rename from examples/breakout/platform/src/graphics/primitives/text.rs rename to examples/gui/breakout/platform/src/graphics/primitives/text.rs index 6aa2844878..45189fdc96 100644 --- a/examples/breakout/platform/src/graphics/primitives/text.rs +++ b/examples/gui/breakout/platform/src/graphics/primitives/text.rs @@ -127,7 +127,7 @@ pub fn build_glyph_brush( render_format: wgpu::TextureFormat, ) -> Result, InvalidFont> { let inconsolata = FontArc::try_from_slice(include_bytes!( - "../../../../../../crates/editor/Inconsolata-Regular.ttf" + "../../../../../../../crates/editor/Inconsolata-Regular.ttf" ))?; Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format)) diff --git a/examples/breakout/platform/src/graphics/shaders/quad.wgsl b/examples/gui/breakout/platform/src/graphics/shaders/quad.wgsl similarity index 100% rename from examples/breakout/platform/src/graphics/shaders/quad.wgsl rename to examples/gui/breakout/platform/src/graphics/shaders/quad.wgsl diff --git a/examples/breakout/platform/src/graphics/style.rs b/examples/gui/breakout/platform/src/graphics/style.rs similarity index 100% rename from examples/breakout/platform/src/graphics/style.rs rename to examples/gui/breakout/platform/src/graphics/style.rs diff --git a/examples/breakout/platform/src/gui.rs b/examples/gui/breakout/platform/src/gui.rs similarity index 98% rename from examples/breakout/platform/src/gui.rs rename to examples/gui/breakout/platform/src/gui.rs index c43604c638..0330398126 100644 --- a/examples/breakout/platform/src/gui.rs +++ b/examples/gui/breakout/platform/src/gui.rs @@ -455,7 +455,7 @@ fn to_drawable( wgpu_glyph::HorizontalAlign::Left }); - let section = owned_section_from_str(text.as_str(), bounds, layout); + let section = owned_section_from_str(text.text.as_str(),text.color, text.size, bounds, layout); // Calculate the bounds and offset by measuring glyphs let text_bounds; @@ -481,7 +481,7 @@ fn to_drawable( } let drawable = Drawable { - pos: (0.0, 0.0).into(), // TODO store the pos in Text and read it here + pos: (text.left, text.top).into(), bounds: text_bounds, content: DrawableContent::Text(section, offset), }; @@ -493,13 +493,11 @@ fn to_drawable( fn owned_section_from_str( string: &str, + color: Rgba, + size: f32, bounds: Bounds, layout: wgpu_glyph::Layout, ) -> OwnedSection { - // TODO don't hardcode any of this! - let color = Rgba::WHITE; - let size: f32 = 40.0; - OwnedSection { bounds: (bounds.width, bounds.height), layout, diff --git a/examples/breakout/platform/src/lib.rs b/examples/gui/breakout/platform/src/lib.rs similarity index 100% rename from examples/breakout/platform/src/lib.rs rename to examples/gui/breakout/platform/src/lib.rs diff --git a/examples/platform-switching/rust-platform/src/main.rs b/examples/gui/breakout/platform/src/main.rs similarity index 100% rename from examples/platform-switching/rust-platform/src/main.rs rename to examples/gui/breakout/platform/src/main.rs diff --git a/examples/breakout/platform/src/roc.rs b/examples/gui/breakout/platform/src/roc.rs similarity index 97% rename from examples/breakout/platform/src/roc.rs rename to examples/gui/breakout/platform/src/roc.rs index 50ab87b9cc..aecfd0427d 100644 --- a/examples/breakout/platform/src/roc.rs +++ b/examples/gui/breakout/platform/src/roc.rs @@ -142,9 +142,11 @@ impl RocEvent { #[allow(unused)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RocKeyCode { - Left = 0, + Down = 0, + Left, Other, Right, + Up, } impl From for RocKeyCode { @@ -154,6 +156,8 @@ impl From for RocKeyCode { match keycode { Left => RocKeyCode::Left, Right => RocKeyCode::Right, + Up => RocKeyCode::Up, + Down => RocKeyCode::Down, _ => RocKeyCode::Other, } } @@ -210,7 +214,7 @@ pub struct ElemId(*const RocElemEntry); #[repr(C)] pub union RocElemEntry { pub rect: ManuallyDrop, - pub text: ManuallyDrop, + pub text: ManuallyDrop, } #[repr(u8)] @@ -284,6 +288,16 @@ pub struct RocRect { pub width: f32, } +#[repr(C)] +#[derive(Debug, Clone)] +pub struct RocText { + pub text: RocStr, + pub color: Rgba, + pub left: f32, + pub size: f32, + pub top: f32, +} + impl Clone for RocElem { fn clone(&self) -> Self { unsafe { diff --git a/examples/gui/Hello.roc b/examples/gui/hello.roc similarity index 100% rename from examples/gui/Hello.roc rename to examples/gui/hello.roc diff --git a/examples/hello-world/.gitignore b/examples/hello-world/.gitignore deleted file mode 100644 index 7423c81faa..0000000000 --- a/examples/hello-world/.gitignore +++ /dev/null @@ -1 +0,0 @@ -helloWorld diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md deleted file mode 100644 index b736613cd5..0000000000 --- a/examples/hello-world/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Hello, World! - -To run, `cd` into this directory and run this in your terminal: - -```bash -roc run -``` - -This will run `main.roc` because, unless you explicitly give it a filename, `roc run` -defaults to running a file named `main.roc`. Other `roc` commands (like `roc build`, `roc test`, and so on) also default to `main.roc` unless you explicitly give them a filename. - -## About this example - -This uses a very simple platform which does nothing more than printing the string you give it. - -The line `main = "Hello, World!\n"` sets this string to be `"Hello, World!"` with a newline at the end, and the lines `packages { pf: "platform/main.roc" }` and `provides [main] to pf` specify that the `platform/` directory contains this app's platform. diff --git a/examples/hello-world/main.roc b/examples/hello-world/main.roc deleted file mode 100644 index 9aba749648..0000000000 --- a/examples/hello-world/main.roc +++ /dev/null @@ -1,6 +0,0 @@ -app "helloWorld" - packages { pf: "platform/main.roc" } - imports [] - provides [main] to pf - -main = "Hello, World!\n" diff --git a/examples/hello-world/platform/main.roc b/examples/hello-world/platform/main.roc deleted file mode 100644 index 175f7070d5..0000000000 --- a/examples/hello-world/platform/main.roc +++ /dev/null @@ -1,9 +0,0 @@ -platform "hello-world" - requires {} { main : Str } - exposes [] - packages {} - imports [] - provides [mainForHost] - -mainForHost : Str -mainForHost = main diff --git a/examples/helloWorld.roc b/examples/helloWorld.roc new file mode 100644 index 0000000000..08c5230d8d --- /dev/null +++ b/examples/helloWorld.roc @@ -0,0 +1,11 @@ +app "helloWorld" + packages { pf: "cli/cli-platform/main.roc" } + imports [pf.Stdout, pf.Program.{ Program }] + provides [main] to pf + +main = Program.noArgs mainTask + +mainTask = + Stdout.line "Hello, World!" + |> Program.exit 0 + diff --git a/examples/interactive/cli-platform/Arg.roc b/examples/interactive/cli-platform/Arg.roc deleted file mode 100644 index 668b94b9dc..0000000000 --- a/examples/interactive/cli-platform/Arg.roc +++ /dev/null @@ -1,818 +0,0 @@ -interface Arg - exposes [ - Parser, - NamedParser, - parse, - toHelp, - parseFormatted, - succeed, - bool, - str, - i64, - subCommand, - choice, - withParser, - program, - ] - imports [] - -## A parser for a command-line application. -## A [NamedParser] is usually built from a [Parser] using [program]. -NamedParser a := { - name : Str, - help : Str, - parser : Parser a, -} - -## Describes how to parse a slice of command-line arguments. -## [Parser]s can be composed in various ways, including via [withParser] and -## [subCommand]. -## Once you have a [Parser] that describes your application's entire parsing -## needs, consider transforming it into a [NamedParser]. -Parser a := [ - Succeed a, - Arg Config (List Str -> Result a [NotFound Str, WrongType { arg : Str, expected : Type }]), - # TODO: hiding the record behind an alias currently causes a panic - SubCommand - (List { - name : Str, - parser : Parser a, - }), - - # Constructed during transformations of the above variants - WithConfig (Parser a) Config, - Lazy ({} -> a), -] - -## Enumerates errors that can occur during parsing a list of command line arguments. -ParseError a : [ - ## The program name was not found as the first argument to be parsed. - ProgramNameNotProvided Str, - ## An argument is required, but it was not found. - MissingRequiredArg Str, - ## An argument was found, but it didn't have the expected [Type]. - WrongType - { - arg : Str, - expected : Type, - }, - ## A subcommand is required, but it was not found. - SubCommandNotFound - { - choices : List Str, - }, - ## A subcommand was found, but it was not the expected one. - IncorrectSubCommand - { - found : Str, - choices : List Str, - }, -]a - -## Expected type of an argument, in an argument list being parsed. -## Describes how a string argument should be interpreted as a certain type. -Type : [ - Str, - Bool, - I64, -] - -## Help metadata extracted from a [Parser]. -Help : [ - SubCommands (List { name : Str, help : Help }), - Config (List Config), -] - -Config : { - long : Str, - short : Str, - help : Str, - type : Type, -} - -## Generates help metadata from a [Parser]. -## -## This is useful if you would like to use this metadata to generate your own -## human-readable help or hint menus. -## -## A default help menu can be generated with [formatHelp]. -toHelp : Parser * -> Help -toHelp = \parser -> - toHelpHelper parser [] - -## A parser that immediately succeeds with its given input. -succeed : a -> Parser a -succeed = \val -> @Parser (Succeed val) - -# TODO: check overflows when this annotation is included -# toHelpHelper : Parser *, List Config -> Help -toHelpHelper = \@Parser parser, configs -> - when parser is - Succeed _ -> Config configs - Lazy _ -> Config configs - WithConfig innerParser config -> - toHelpHelper innerParser (List.append configs config) - - Arg config _ -> - List.append configs config - |> Config - - SubCommand commands -> - List.map - commands - (\{ name, parser: innerParser } -> { name, help: toHelpHelper innerParser [] }) - |> SubCommands - -findOneArg : Str, Str, List Str -> Result Str [NotFound]* -findOneArg = \long, short, args -> - argMatches = \arg -> - if arg == "--\(long)" then - Bool.true - else - Bool.not (Str.isEmpty short) && arg == "-\(short)" - - # TODO allow = as well, etc. - result = List.findFirstIndex args argMatches - - when result is - Ok index -> - # Return the next arg after the given one - List.get args (index + 1) - |> Result.mapErr \_ -> NotFound - - Err NotFound -> Err NotFound - -# andMap : Parser a, Parser (a -> b) -> Parser b -andMap = \@Parser parser, @Parser mapper -> - unwrapped = - when mapper is - Succeed fn -> - when parser is - Succeed a -> - Lazy \{} -> fn a - - Lazy thunk -> - Lazy \{} -> fn (thunk {}) - - WithConfig parser2 config -> - parser2 - |> andMap (@Parser mapper) - |> WithConfig config - - Arg config run -> - Arg config \args -> - run args - |> Result.map fn - - SubCommand cmds -> - mapSubParser = \{ name, parser: parser2 } -> - { name, parser: andMap parser2 (@Parser mapper) } - - List.map cmds mapSubParser - |> SubCommand - - Arg config run -> - when parser is - Succeed a -> - Arg config \args -> - when run args is - Ok fn -> Ok (fn a) - Err err -> Err err - - Lazy thunk -> - Arg config \args -> - when run args is - Ok fn -> Ok (fn (thunk {})) - Err err -> Err err - - WithConfig parser2 config2 -> - parser2 - |> andMap (@Parser mapper) - |> WithConfig config2 - - Arg config2 run2 -> - # Parse first the one and then the other. - combinedParser = Arg config2 \args -> - when run args is - Ok fn -> run2 args |> Result.map fn - Err err -> Err err - - # Store the extra config. - @Parser combinedParser - |> WithConfig config - - SubCommand cmds -> - # For each subcommand, first run the subcommand, then - # push the result through the arg parser. - mapSubParser = \{ name, parser: parser2 } -> - { name, parser: andMap parser2 (@Parser mapper) } - - List.map cmds mapSubParser - |> SubCommand - - Lazy thunk -> - fn = thunk {} - - when parser is - Succeed a -> - Lazy \{} -> fn a - - Lazy innerThunk -> - Lazy \{} -> fn (innerThunk {}) - - WithConfig parser2 config -> - parser2 - |> andMap (@Parser mapper) - |> WithConfig config - - Arg config run -> - Arg config \args -> - run args - |> Result.map fn - - SubCommand cmds -> - mapSubParser = \{ name, parser: parser2 } -> - { name, parser: andMap parser2 (@Parser mapper) } - - List.map cmds mapSubParser - |> SubCommand - - WithConfig mapper2 config -> - @Parser parser - |> andMap mapper2 - |> WithConfig config - - SubCommand cmds -> - mapSubParser = \{ name, parser: mapper2 } -> - { name, parser: andMap (@Parser parser) mapper2 } - - List.map cmds mapSubParser - |> SubCommand - - @Parser unwrapped - -## Marks a [Parser] as the entry point for parsing a command-line application, -## taking the program name and optionally a high-level help message for the -## application. -## -## The produced [NamedParser] can be used to parse arguments via [parse] or -## [parseFormatted]. -program = \parser, { name, help ? "" } -> - @NamedParser { name, help, parser } - -## Parses a list of command-line arguments with the given parser. The list of -## arguments is expected to contain the name of the program in the first -## position. -## -## If the arguments do not conform with what is expected by the parser, the -## first error seen will be returned. -# TODO panics in alias analysis when this annotation is included -# parse : NamedParser a, List Str -> Result a (ParseError*) -parse = \@NamedParser parser, args -> - # By convention the first string in the arg list is the program name. - if - List.isEmpty args - then - Err (ProgramNameNotProvided parser.name) - else - parseHelp parser.parser (List.split args 1).others - -parseHelp : Parser a, List Str -> Result a (ParseError *) -parseHelp = \@Parser parser, args -> - when parser is - Succeed val -> Ok val - Arg _ run -> - when run args is - Ok val -> Ok val - Err (NotFound long) -> Err (MissingRequiredArg long) - Err (WrongType { arg, expected }) -> Err (WrongType { arg, expected }) - - SubCommand cmds -> - when List.get args 0 is - Ok cmd -> - argsRest = (List.split args 1).others - state = - List.walkUntil - cmds - (Err {}) - \st, { name, parser: subParser } -> - if - cmd == name - then - Break (Ok (parseHelp subParser argsRest)) - else - Continue st - - when state is - Ok result -> result - Err {} -> Err (IncorrectSubCommand { found: cmd, choices: List.map cmds .name }) - - Err OutOfBounds -> Err (SubCommandNotFound { choices: List.map cmds .name }) - - Lazy thunk -> Ok (thunk {}) - WithConfig parser2 _config -> - parseHelp parser2 args - -## Creates a parser for a boolean flag argument. -## Flags of value "true" and "false" will be parsed as [Bool.true] and [Bool.false], respectively. -## All other values will result in a `WrongType` error. -bool : _ -> Parser Bool # TODO: panics if parameter annotation given -bool = \{ long, short ? "", help ? "" } -> - fn = \args -> - when findOneArg long short args is - Err NotFound -> Err (NotFound long) - Ok "true" -> Ok Bool.true - Ok "false" -> Ok Bool.false - Ok _ -> Err (WrongType { arg: long, expected: Bool }) - - @Parser (Arg { long, short, help, type: Bool } fn) - -## Creates a parser for a string flag argument. -str : _ -> Parser Str # TODO: panics if parameter annotation given -str = \{ long, short ? "", help ? "" } -> - fn = \args -> - when findOneArg long short args is - Err NotFound -> Err (NotFound long) - Ok foundArg -> Ok foundArg - - @Parser (Arg { long, short, help, type: Str } fn) - -## Creates a parser for a 64-bit signed integer ([I64]). -i64 : _ -> Parser I64 # TODO: panics if parameter annotation given -i64 = \{ long, short ? "", help ? "" } -> - fn = \args -> - when findOneArg long short args is - Err NotFound -> Err (NotFound long) - Ok foundArg -> - Str.toI64 foundArg - |> Result.mapErr \_ -> WrongType { arg: long, expected: I64 } - - @Parser (Arg { long, short, help, type: I64 } fn) - -## Wraps a given parser as a subcommand parser. -## -## When parsing arguments, the subcommand name will be expected to be parsed -## first, and then the wrapped parser will be applied to the rest of the -## arguments. -## -## To support multiple subcommands, use [choice]. -subCommand : Parser a, Str -> { name : Str, parser : Parser a } -subCommand = \parser, name -> { name, parser } - -## Creates a parser that matches over a list of subcommands. -## -## The given list of subcommands is expected to be non-empty, and unique in the -## subcommand name. These invariants are not enforced today, but may be in the -## future. -## -## During argument parsing, the list of subcommands will be tried in-order. Due -## to the described invariant, at most one given subcommand will match any -## argument list. -choice : List { name : Str, parser : Parser a } -> Parser a -choice = \subCommands -> @Parser (SubCommand subCommands) - -## Like [parse], runs a parser to completion on a list of arguments. -## -## If the parser fails, a formatted error and help message is returned. -# TODO: mono panics in the args example if the type annotation is included -# parseFormatted : NamedParser a, List Str -> Result a Str -parseFormatted = \@NamedParser parser, args -> - Result.mapErr - (parse (@NamedParser parser) args) - \e -> - Str.concat (Str.concat (formatHelp (@NamedParser parser)) "\n\n") (formatError e) - -indent : Nat -> Str -indent = \n -> Str.repeat " " n - -indentLevel : Nat -indentLevel = 4 - -mapNonEmptyStr = \s, f -> if Str.isEmpty s then s else f s - -# formatHelp : NamedParser a -> Str -formatHelp = \@NamedParser { name, help, parser } -> - fmtHelp = - mapNonEmptyStr help \helpStr -> "\n\(helpStr)" - - cmdHelp = toHelp parser - - fmtCmdHeading = - when cmdHelp is - SubCommands _ -> "COMMANDS:" - Config _ -> "OPTIONS:" - - fmtCmdHelp = formatCmdHelp indentLevel cmdHelp - - """ - \(name)\(fmtHelp) - - \(fmtCmdHeading) - \(fmtCmdHelp) - """ - -# formatCmdHelp : Nat, Help -> Str <- TODO: layout-gen panics when the following annotation is applied! -formatCmdHelp = \n, help -> - when help is - SubCommands cmds -> - Str.joinWith - (List.map cmds \subCmd -> formatSubCommand n subCmd) - "\n\n" - - Config configs -> - Str.joinWith (List.map configs \c -> formatConfig n c) "\n" - -formatSubCommand = \n, { name, help } -> - indented = indent n - - fmtHelp = formatCmdHelp (n + indentLevel) help - - "\(indented)\(name)\n\(fmtHelp)" - -formatConfig : Nat, Config -> Str -formatConfig = \n, { long, short, help, type } -> - indented = indent n - - formattedShort = - mapNonEmptyStr short \s -> ", -\(s)" - - formattedType = formatType type - - formattedHelp = - mapNonEmptyStr help \h -> " \(h)" - - "\(indented)--\(long)\(formattedShort)\(formattedHelp) (\(formattedType))" - -formatType : Type -> Str -formatType = \type -> - when type is - Bool -> "bool" - Str -> "string" - I64 -> "i64" - -quote = \s -> "\"\(s)\"" - -formatError : ParseError [] -> Str -formatError = \err -> - when err is - ProgramNameNotProvided programName -> - "The program name \"\(programName)\" was not probided as a first argument!" - - MissingRequiredArg arg -> - "Argument `--\(arg)` is required but was not provided!" - - WrongType { arg, expected } -> - formattedType = formatType expected - - "The argument `--\(arg)` expects a value of type \(formattedType)!" - - SubCommandNotFound { choices } -> - fmtChoices = - List.map choices quote - |> Str.joinWith ", " - - """ - A subcommand was expected, but not found! - The available subcommands are: - \t\(fmtChoices) - """ - - IncorrectSubCommand { found, choices } -> - fmtFound = quote found - - fmtChoices = - List.map choices quote - |> Str.joinWith ", " - - """ - The \(fmtFound) subcommand was found, but it's not expected in this context! - The available subcommands are: - \t\(fmtChoices) - """ - -## Applies one parser over another, mapping parser. -## -## `withParser mapper parser` produces a parser that will parse an argument list -## with `parser` first, then parse the remaining list with `mapper`, and feed -## the result of `parser` to `mapper`. -## -## This provides a way to chain the results of multiple parsers together. For -## example, to combine the results of two [str] arguments into a record, you -## could use -## -## ``` -## succeed (\host -> \port -> { host, port }) -## |> withParser (str { long: "host" }) -## |> withParser (str { long: "port" }) -## ``` -withParser = \arg1, arg2 -> andMap arg2 arg1 - -# bool undashed long argument is missing -expect - parser = bool { long: "foo" } - - parseHelp parser ["foo"] == Err (MissingRequiredArg "foo") - -# bool dashed long argument without value is missing -expect - parser = bool { long: "foo" } - - parseHelp parser ["--foo"] == Err (MissingRequiredArg "foo") - -# bool dashed long argument with value is determined true -expect - parser = bool { long: "foo" } - - parseHelp parser ["--foo", "true"] == Ok Bool.true - -# bool dashed long argument with value is determined false -expect - parser = bool { long: "foo" } - - parseHelp parser ["--foo", "false"] == Ok Bool.false - -# bool dashed long argument with value is determined wrong type -expect - parser = bool { long: "foo" } - - parseHelp parser ["--foo", "not-a-bool"] == Err (WrongType { arg: "foo", expected: Bool }) - -# bool dashed short argument with value is determined true -expect - parser = bool { long: "foo", short: "F" } - - parseHelp parser ["-F", "true"] == Ok Bool.true - -# bool dashed short argument with value is determined false -expect - parser = bool { long: "foo", short: "F" } - - parseHelp parser ["-F", "false"] == Ok Bool.false - -# bool dashed short argument with value is determined wrong type -expect - parser = bool { long: "foo", short: "F" } - - parseHelp parser ["-F", "not-a-bool"] == Err (WrongType { arg: "foo", expected: Bool }) - -# string dashed long argument without value is missing -expect - parser = str { long: "foo" } - - parseHelp parser ["--foo"] == Err (MissingRequiredArg "foo") - -# string dashed long argument with value is determined -expect - parser = str { long: "foo" } - - parseHelp parser ["--foo", "itsme"] == Ok "itsme" - -# string dashed short argument without value is missing -expect - parser = str { long: "foo", short: "F" } - - parseHelp parser ["-F"] == Err (MissingRequiredArg "foo") - -# string dashed short argument with value is determined -expect - parser = str { long: "foo", short: "F" } - - parseHelp parser ["-F", "itsme"] == Ok "itsme" - -# i64 dashed long argument without value is missing -expect - parser = i64 { long: "foo" } - - parseHelp parser ["--foo"] == Err (MissingRequiredArg "foo") - -# i64 dashed long argument with value is determined positive -expect - parser = i64 { long: "foo" } - - parseHelp parser ["--foo", "1234"] == Ok 1234 - -# i64 dashed long argument with value is determined negative -expect - parser = i64 { long: "foo" } - - parseHelp parser ["--foo", "-1234"] == Ok -1234 - -# i64 dashed short argument without value is missing -expect - parser = i64 { long: "foo", short: "F" } - - parseHelp parser ["-F"] == Err (MissingRequiredArg "foo") - -# i64 dashed short argument with value is determined -expect - parser = i64 { long: "foo", short: "F" } - - parseHelp parser ["-F", "1234"] == Ok 1234 - -# two string parsers complete cases -expect - parser = - succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)") - |> withParser (str { long: "foo" }) - |> withParser (str { long: "bar" }) - - cases = [ - ["--foo", "true", "--bar", "baz"], - ["--bar", "baz", "--foo", "true"], - ["--foo", "true", "--bar", "baz", "--other", "something"], - ] - - List.all cases \args -> parseHelp parser args == Ok "foo: true bar: baz" - -# one argument is missing out of multiple -expect - parser = - succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)") - |> withParser (str { long: "foo" }) - |> withParser (str { long: "bar" }) - - List.all - [ - parseHelp parser ["--foo", "zaz"] == Err (MissingRequiredArg "bar"), - parseHelp parser ["--bar", "zaz"] == Err (MissingRequiredArg "foo"), - ] - (\b -> b) - -# string and bool parsers build help -expect - parser = - succeed (\foo -> \bar -> \_bool -> "foo: \(foo) bar: \(bar)") - |> withParser (str { long: "foo", help: "the foo flag" }) - |> withParser (str { long: "bar", short: "B" }) - |> withParser (bool { long: "bool" }) - - toHelp parser - == Config [ - { long: "foo", short: "", help: "the foo flag", type: Str }, - { long: "bar", short: "B", help: "", type: Str }, - { long: "bool", short: "", help: "", type: Bool }, - ] - -# format argument is missing -expect - parser = bool { long: "foo" } - - when parseHelp parser ["foo"] is - Ok _ -> Bool.false - Err e -> - err = formatError e - - err == "Argument `--foo` is required but was not provided!" - -# format argument has wrong type -expect - parser = bool { long: "foo" } - - when parseHelp parser ["--foo", "12"] is - Ok _ -> Bool.false - Err e -> - err = formatError e - - err == "The argument `--foo` expects a value of type bool!" - -# format help menu with only args -expect - parser = - succeed (\_foo -> \_bar -> \_baz -> \_bool -> "") - |> withParser (str { long: "foo", help: "the foo flag" }) - |> withParser (str { long: "bar", short: "B" }) - |> withParser (str { long: "baz", short: "z", help: "the baz flag" }) - |> withParser (bool { long: "bool" }) - |> program { name: "test" } - - formatHelp parser - == - """ - test - - OPTIONS: - --foo the foo flag (string) - --bar, -B (string) - --baz, -z the baz flag (string) - --bool (bool) - """ - -# format help menu with subcommands -expect - parser = - choice [ - succeed (\user -> \pw -> "\(user)\(pw)") - |> withParser (str { long: "user" }) - |> withParser (str { long: "pw" }) - |> subCommand "login", - succeed (\file -> \url -> "\(file)\(url)") - |> withParser (str { long: "file" }) - |> withParser (str { long: "url" }) - |> subCommand "publish", - ] - |> program { name: "test" } - - formatHelp parser - == - """ - test - - COMMANDS: - login - --user (string) - --pw (string) - - publish - --file (string) - --url (string) - """ - -# format help menu with program help message -expect - parser = - choice [subCommand (succeed "") "login"] - |> program { name: "test", help: "a test cli app" } - - formatHelp parser - == - """ - test - a test cli app - - COMMANDS: - login - - """ - -# subcommand parser -expect - parser = - choice [ - succeed (\user -> \pw -> "logging in \(user) with \(pw)") - |> withParser (str { long: "user" }) - |> withParser (str { long: "pw" }) - |> subCommand "login", - succeed (\file -> \url -> "\(file)\(url)") - |> withParser (str { long: "file" }) - |> withParser (str { long: "url" }) - |> subCommand "publish", - ] - |> program { name: "test" } - - when parse parser ["test", "login", "--pw", "123", "--user", "abc"] is - Ok result -> result == "logging in abc with 123" - Err _ -> Bool.false - -# subcommand of subcommand parser -expect - parser = - choice [ - choice [ - succeed (\user -> \pw -> "logging in \(user) with \(pw)") - |> withParser (str { long: "user" }) - |> withParser (str { long: "pw" }) - |> subCommand "login", - ] - |> subCommand "auth", - ] - |> program { name: "test" } - - when parse parser ["test", "auth", "login", "--pw", "123", "--user", "abc"] is - Ok result -> result == "logging in abc with 123" - Err _ -> Bool.false - -# subcommand not provided -expect - parser = - choice [subCommand (succeed "") "auth", subCommand (succeed "") "publish"] - - when parseHelp parser [] is - Ok _ -> Bool.true - Err e -> - err = formatError e - - err - == - """ - A subcommand was expected, but not found! - The available subcommands are: - \t"auth", "publish" - """ - -# subcommand doesn't match choices -expect - parser = - choice [subCommand (succeed "") "auth", subCommand (succeed "") "publish"] - - when parseHelp parser ["logs"] is - Ok _ -> Bool.true - Err e -> - err = formatError e - - err - == - """ - The "logs" subcommand was found, but it's not expected in this context! - The available subcommands are: - \t"auth", "publish" - """ diff --git a/examples/interactive/cli-platform/Stderr.roc b/examples/interactive/cli-platform/Stderr.roc deleted file mode 100644 index dd10aad60d..0000000000 --- a/examples/interactive/cli-platform/Stderr.roc +++ /dev/null @@ -1,8 +0,0 @@ -interface Stderr - exposes [line] - imports [Effect, Task.{ Task }, InternalTask] - -line : Str -> Task {} * [Write [Stderr]*]* -line = \str -> - Effect.map (Effect.stderrLine str) (\_ -> Ok {}) - |> InternalTask.fromEffect diff --git a/examples/interactive/cli-platform/Stdout.roc b/examples/interactive/cli-platform/Stdout.roc deleted file mode 100644 index 154096d3b4..0000000000 --- a/examples/interactive/cli-platform/Stdout.roc +++ /dev/null @@ -1,8 +0,0 @@ -interface Stdout - exposes [line] - imports [Effect, Task.{ Task }, InternalTask] - -line : Str -> Task {} * [Write [Stdout]*]* -line = \str -> - Effect.map (Effect.stdoutLine str) (\_ -> Ok {}) - |> InternalTask.fromEffect diff --git a/examples/parser/Parser/CSV.roc b/examples/parser/Parser/CSV.roc index 48f7933974..e801d5390d 100644 --- a/examples/parser/Parser/CSV.roc +++ b/examples/parser/Parser/CSV.roc @@ -175,7 +175,7 @@ escapedCsvField = between escapedContents dquote dquote escapedContents = many ( oneOf [ - twodquotes |> map (\_ -> 34), # An escaped double quote + twodquotes |> map (\_ -> '"'), comma, cr, lf, @@ -187,10 +187,10 @@ twodquotes = Parser.Str.string "\"\"" nonescapedCsvField : Parser RawStr CSVField nonescapedCsvField = many textdata -comma = codeunit 44 # ',' -dquote = codeunit 34 # '"' +comma = codeunit ',' +dquote = codeunit '"' endOfLine = alt (ignore crlf) (ignore lf) -cr = codeunit 13 # '\r' -lf = codeunit 10 # '\n' +cr = codeunit '\r' +lf = codeunit '\n' crlf = Parser.Str.string "\r\n" textdata = codeunitSatisfies (\x -> (x >= 32 && x <= 33) || (x >= 35 && x <= 43) || (x >= 45 && x <= 126)) # Any printable char except " (34) and , (44) diff --git a/examples/parser/Parser/Str.roc b/examples/parser/Parser/Str.roc index e1f11c9d97..b01edbad6a 100644 --- a/examples/parser/Parser/Str.roc +++ b/examples/parser/Parser/Str.roc @@ -177,10 +177,9 @@ anyString = buildPrimitiveParser \fieldRawString -> digit : Parser RawStr U8 digit = digitParsers = - List.range 0 10 + List.range '0' ('9' + 1) |> List.map \digitNum -> digitNum - + 48 |> codeunit |> map (\_ -> digitNum) diff --git a/examples/platform-switching/c-platform/host.c b/examples/platform-switching/c-platform/host.c deleted file mode 100644 index 3f9a63c2a2..0000000000 --- a/examples/platform-switching/c-platform/host.c +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include -#include -#include -#include -#include - -void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); } - -void* roc_realloc(void* ptr, size_t new_size, size_t old_size, - unsigned int alignment) { - return realloc(ptr, new_size); -} - -void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); } - -void roc_panic(void* ptr, unsigned int alignment) { - char* msg = (char*)ptr; - fprintf(stderr, - "Application crashed with message\n\n %s\n\nShutting down\n", msg); - exit(0); -} - -void* roc_memcpy(void* dest, const void* src, size_t n) { - return memcpy(dest, src, n); -} - -void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); } - -struct RocStr { - char* bytes; - size_t len; - size_t capacity; -}; - -bool is_small_str(struct RocStr str) { return ((ssize_t)str.capacity) < 0; } - -// Determine the length of the string, taking into -// account the small string optimization -size_t roc_str_len(struct RocStr str) { - char* bytes = (char*)&str; - char last_byte = bytes[sizeof(str) - 1]; - char last_byte_xored = last_byte ^ 0b10000000; - size_t small_len = (size_t)(last_byte_xored); - size_t big_len = str.len; - - // Avoid branch misprediction costs by always - // determining both small_len and big_len, - // so this compiles to a cmov instruction. - if (is_small_str(str)) { - return small_len; - } else { - return big_len; - } -} - -extern void roc__mainForHost_1_exposed_generic(struct RocStr *string); - -int main() { - - struct RocStr str; - roc__mainForHost_1_exposed_generic(&str); - - // Determine str_len and the str_bytes pointer, - // taking into account the small string optimization. - size_t str_len = roc_str_len(str); - char* str_bytes; - - if (is_small_str(str)) { - str_bytes = (char*)&str; - } else { - str_bytes = str.bytes; - } - - // Write to stdout - if (write(1, str_bytes, str_len) >= 0) { - // Writing succeeded! - - // NOTE: the string is a static string, read from in the binary - // if you make it a heap-allocated string, it'll be leaked here - return 0; - } else { - printf("Error writing to stdout: %s\n", strerror(errno)); - - // NOTE: the string is a static string, read from in the binary - // if you make it a heap-allocated string, it'll be leaked here - return 1; - } -} diff --git a/examples/swiftui/.gitignore b/examples/swiftui/.gitignore new file mode 100644 index 0000000000..9463ca7329 --- /dev/null +++ b/examples/swiftui/.gitignore @@ -0,0 +1 @@ +swiftui diff --git a/getting_started/README.md b/getting_started/README.md index 5215badf27..f05c36a90e 100644 --- a/getting_started/README.md +++ b/getting_started/README.md @@ -6,15 +6,15 @@ play around with as long as you have a high tolerance for missing features and c The [tutorial](../TUTORIAL.md) is the best place to learn about how to use the language - it assumes no prior knowledge of Roc or similar languages. (If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/roc-lang/roc/blob/main/roc-for-elm-programmers.md) may be of interest.) -There's also a folder of [examples](https://github.com/roc-lang/roc/tree/main/examples) - the [CLI form example](https://github.com/roc-lang/roc/tree/main/examples/interactive/form.roc) in particular is a reasonable starting point to build on. +There's also a folder of [examples](https://github.com/roc-lang/roc/tree/main/examples) - the [CLI form example](https://github.com/roc-lang/roc/tree/main/examples/cli/form.roc) in particular is a reasonable starting point to build on. If you have a specific question, the [FAQ](../FAQ.md) might have an answer, although [Roc Zulip chat](https://roc.zulipchat.com) is overall the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects. ## Installation -- [Linux x86](linux_x86.md) +- [Linux x86_64](linux_x86_64.md) - [MacOS Apple Silicon](macos_apple_silicon.md) -- [MacOS x86](macos_x86.md) +- [MacOS x86_64](macos_x86_64.md) - [Windows](windows.md) - [Other](other.md) @@ -23,14 +23,14 @@ If you have a specific question, the [FAQ](../FAQ.md) might have an answer, alth You can run examples as follows: ```sh -cd examples/hello-world -roc run +cd examples +roc run helloWorld.roc ``` -Some examples like `examples/benchmarks/NQueens.roc` require input after running. +Some examples like `crates/cli_testing_examples/benchmarks/NQueens.roc` require input after running. For NQueens, input 10 in the terminal and press enter. -[examples/benchmarks](https://github.com/roc-lang/roc/tree/main/examples/benchmarks) contains larger examples. +[crates/cli_testing_examples/benchmarks](https://github.com/roc-lang/roc/tree/main/crates/cli_testing_examples/benchmarks) contains larger examples. **Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic. diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86_64.md similarity index 84% rename from getting_started/linux_x86.md rename to getting_started/linux_x86_64.md index e28ad62417..3b390d5b23 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86_64.md @@ -1,4 +1,4 @@ -# Roc installation guide for x86 Linux systems +# Roc installation guide for x86_64 Linux systems ## How to install Roc @@ -45,9 +45,9 @@ you need to install one or more of these platform language compilers, too. ```sh # Note: If you installed Rust in this terminal session, you'll need to open a new one first! - ./roc examples/platform-switching/rocLovesRust.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesRust.roc - ./roc examples/platform-switching/rocLovesZig.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesZig.roc - ./roc examples/platform-switching/rocLovesC.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesC.roc ``` diff --git a/getting_started/macos_apple_silicon.md b/getting_started/macos_apple_silicon.md index 2101c22da0..ed54cded01 100644 --- a/getting_started/macos_apple_silicon.md +++ b/getting_started/macos_apple_silicon.md @@ -48,9 +48,9 @@ you need to install one or more of these platform language compilers, too. ```sh # Note: If you installed rust in this terminal session, you'll need to open a new one first! - ./roc examples/platform-switching/rocLovesRust.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesRust.roc - ./roc examples/platform-switching/rocLovesZig.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesZig.roc - ./roc examples/platform-switching/rocLovesC.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesC.roc ``` diff --git a/getting_started/macos_x86.md b/getting_started/macos_x86_64.md similarity index 82% rename from getting_started/macos_x86.md rename to getting_started/macos_x86_64.md index 455086f32d..23aecdb905 100644 --- a/getting_started/macos_x86.md +++ b/getting_started/macos_x86_64.md @@ -1,4 +1,4 @@ -# Roc installation guide for x86 macOS systems +# Roc installation guide for x86_64 macOS systems ## How to install Roc @@ -42,9 +42,9 @@ you need to install one or more of these platform language compilers, too. ```sh # Note: If you installed rust in this terminal session, you'll need to open a new one first! - ./roc examples/platform-switching/rocLovesRust.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesRust.roc - ./roc examples/platform-switching/rocLovesZig.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesZig.roc - ./roc examples/platform-switching/rocLovesC.roc + ./roc crates/cli_testing_examples/platform-switching/rocLovesC.roc ``` diff --git a/getting_started/other.md b/getting_started/other.md index 70f01dd2e5..13c5c79db7 100644 --- a/getting_started/other.md +++ b/getting_started/other.md @@ -7,11 +7,11 @@ 1. Run examples: ```sh - cargo run examples/platform-switching/rocLovesRust.roc + cargo run crates/cli_testing_examples/platform-switching/rocLovesRust.roc # This requires installing the Zig compiler, too. - cargo run examples/platform-switching/rocLovesZig.roc + cargo run crates/cli_testing_examples/platform-switching/rocLovesZig.roc # This requires installing the `clang` C compiler, too. - cargo run examples/platform-switching/rocLovesC.roc + cargo run crates/cli_testing_examples/platform-switching/rocLovesC.roc ``` diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 681b87bf62..33b9b4dd1b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,8 +1,9 @@ [toolchain] channel = "1.61.0" # make sure to update the rust version in Earthfile as well +# channel = "nightly-2022-04-03" # nightly to be able to use unstable features profile = "default" components = [ # for usages of rust-analyzer or similar tools inside `nix develop` "rust-src" ] -targets = [ "x86_64-unknown-linux-gnu" ] \ No newline at end of file +targets = [ "x86_64-unknown-linux-gnu" ] diff --git a/www/build.sh b/www/build.sh index b292377034..6797db6467 100755 --- a/www/build.sh +++ b/www/build.sh @@ -45,7 +45,7 @@ export ROC_DOCS_URL_ROOT=/examples/cli # Until https://github.com/roc-lang/roc/issues/3280 is done, # manually exclude the Internal* modules and `main.roc`. -ls examples/interactive/cli-platform/*.roc | grep -v Internal | grep -v main.roc | grep -v Effect.roc | xargs cargo run --bin roc-docs +ls examples/cli/cli-platform/*.roc | grep -v Internal | grep -v main.roc | grep -v Effect.roc | xargs cargo run --bin roc-docs mkdir www/build/examples rm generated-docs/*.* # we already copied over the *.js and *.css files earlier, so just drop these. diff --git a/www/public/repl/repl.js b/www/public/repl/repl.js index 48a8b2c64f..91248858f7 100644 --- a/www/public/repl/repl.js +++ b/www/public/repl/repl.js @@ -42,7 +42,7 @@ const repl = { repl.elemSourceInput.addEventListener("change", onInputChange); repl.elemSourceInput.addEventListener("keyup", onInputKeyup); roc_repl_wasm.default("/repl/roc_repl_wasm_bg.wasm").then((instance) => { - repl.elemHistory.querySelector('#loading-message').remove(); + repl.elemHistory.querySelector("#loading-message").remove(); repl.elemSourceInput.disabled = false; repl.elemSourceInput.placeholder = "Type some Roc code and press Enter. (Use Shift+Enter for multi-line input)"; @@ -54,8 +54,7 @@ roc_repl_wasm.default("/repl/roc_repl_wasm_bg.wasm").then((instance) => { // ---------------------------------------------------------------------------- function onInputChange(event) { - const inputText = event.target.value; - if (!inputText) return; + const inputText = event.target.value.trim(); event.target.value = ""; @@ -122,13 +121,15 @@ async function processInputQueue() { repl.inputHistoryIndex = createHistoryEntry(inputText); repl.inputStash = ""; - let outputText; + let outputText = ""; let ok = true; - try { - outputText = await roc_repl_wasm.entrypoint_from_js(inputText); - } catch (e) { - outputText = `${e}`; - ok = false; + if (inputText) { + try { + outputText = await roc_repl_wasm.entrypoint_from_js(inputText); + } catch (e) { + outputText = `${e}`; + ok = false; + } } updateHistoryEntry(repl.inputHistoryIndex, ok, outputText); @@ -212,7 +213,7 @@ function updateHistoryEntry(index, ok, outputText) { outputElem.classList.add("output"); outputElem.classList.add(ok ? "output-ok" : "output-error"); - const historyItem = repl.elemHistory.childNodes[index]; + const historyItem = repl.elemHistory.children[index]; historyItem.appendChild(outputElem); repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight;