diff --git a/.github/workflows/basic_cli_build_release.yml b/.github/workflows/basic_cli_build_release.yml index 01c9266454..fa2176db1f 100644 --- a/.github/workflows/basic_cli_build_release.yml +++ b/.github/workflows/basic_cli_build_release.yml @@ -1,5 +1,5 @@ on: - #pull_request: +# pull_request: workflow_dispatch: # this cancels workflows currently in progress if you start a new one @@ -7,9 +7,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -# use .tar.gz for quick testing env: - ARCHIVE_FORMAT: .tar.br + # use .tar.gz for quick testing + ARCHIVE_FORMAT: .tar.gz + BASIC_CLI_BRANCH: main jobs: fetch-releases: @@ -18,6 +19,7 @@ jobs: - uses: actions/checkout@v3 - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz + - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz @@ -45,12 +47,37 @@ jobs: with: name: linux-x86_64-files path: | - basic-cli/src/metadata_linux-x86_64.rm - basic-cli/src/linux-x86_64.rh - basic-cli/src/linux-x86_64.o + basic-cli/src/metadata_linux-x64.rm + basic-cli/src/linux-x64.rh + basic-cli/src/linux-x64.o + + + build-linux-arm64-files: + runs-on: [self-hosted, Linux, ARM64] + needs: [fetch-releases] + steps: + - uses: actions/checkout@v3 + + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 + + - name: build basic-cli + env: + CARGO_BUILD_TARGET: aarch64-unknown-linux-musl + CC_aarch64_unknown_linux_musl: clang-16 + AR_aarch64_unknown_linux_musl: llvm-ar-16 + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld" + run: ./ci/build_basic_cli.sh linux_arm64 + + - name: Save .o file + uses: actions/upload-artifact@v3 + with: + name: linux-arm64-files + path: | + basic-cli/src/linux-arm64.o build-macos-x86_64-files: - runs-on: [macos-11] # I expect the generated files to work on macOS 12 + runs-on: [macos-11] # I expect the generated files to work on macOS 12 and up needs: [fetch-releases] steps: - uses: actions/checkout@v3 @@ -65,7 +92,7 @@ jobs: with: name: macos-x86_64-files path: | - basic-cli/src/macos-x86_64.o + basic-cli/src/macos-x64.o build-macos-apple-silicon-files: name: build apple silicon .o file @@ -87,7 +114,7 @@ jobs: basic-cli/src/macos-arm64.o create-release-archive: - needs: [build-linux-x86_64-files, build-macos-x86_64-files, build-macos-apple-silicon-files] + needs: [build-linux-x86_64-files, build-linux-arm64-files, build-macos-x86_64-files, build-macos-apple-silicon-files] name: create release archive runs-on: [ubuntu-20.04] steps: @@ -100,7 +127,7 @@ jobs: uses: actions/download-artifact@v3 - name: mv roc nightly and simplify name - run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz + run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz - name: decompress the tar run: tar -xzvf roc_nightly.tar.gz @@ -117,6 +144,8 @@ jobs: - run: cp linux-x86_64-files/* ./basic-cli/src + - run: cp linux-arm64-files/* ./basic-cli/src + - run: cp macos-x86_64-files/* ./basic-cli/src - run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc @@ -139,7 +168,7 @@ jobs: uses: actions/download-artifact@v3 - name: mv roc nightly and simplify name - run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz + run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz - name: decompress the tar run: tar -xzvf roc_nightly.tar.gz @@ -159,21 +188,31 @@ jobs: cd basic-cli-platform && ls | grep "tar" | xargs brotli -d ls | grep "tar$" | xargs tar -xf - - name: prep testing http-get.roc + - name: Install expect for tests if we dont have it yet + run: if ! dpkg -l | grep -qw expect; then sudo apt install -y expect; fi + + - name: Install ncat for tests if we dont have it yet + run: if ! dpkg -l | grep -qw ncat; then sudo apt install -y ncat; fi + + - name: prep testing run: | mv roc_nightly basic-cli-platform/. cd basic-cli-platform - mkdir examples - cd examples - curl -fOL https://raw.githubusercontent.com/roc-lang/basic-cli/main/examples/http-get.roc - sed -i 's/pf:\ \"[^"]*/pf:\ \"\.\.\/main.roc/g' http-get.roc - cd .. - curl -fOL https://raw.githubusercontent.com/roc-lang/basic-cli/main/ci/expect_scripts/http-get.exp - - - run: sudo apt install -y expect - - - name: execute test + mkdir src + find . -maxdepth 1 -type f -exec mv {} src/ \; + + mkdir temp-basic-cli + cd temp-basic-cli + git clone https://github.com/roc-lang/basic-cli.git + cd basic-cli + git checkout ${{ env.BASIC_CLI_BRANCH }} + cp -r examples ../.. + cp -r ci ../.. + cp -r LICENSE ../.. + # LICENSE is necessary for command test + + - name: run tests run: | cd basic-cli-platform - expect http-get.exp + ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ROC_BUILD_FLAGS=--prebuilt-platform ./ci/all_tests.sh diff --git a/.github/workflows/basic_cli_test_arm64.yml b/.github/workflows/basic_cli_test_arm64.yml index 7594ece28d..fc4ef3d476 100644 --- a/.github/workflows/basic_cli_test_arm64.yml +++ b/.github/workflows/basic_cli_test_arm64.yml @@ -36,7 +36,7 @@ jobs: - run: expect -v # Run all tests - - run: EXAMPLES_DIR=./examples/ ./ci/all_tests.sh + - run: ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ./ci/all_tests.sh ###### # Now test the latest basic-cli release, not the main branch @@ -51,4 +51,4 @@ jobs: - name: Run all tests with latest roc nightly and latest basic-cli release run: | sed -i 's/x86_64/arm64/g' ./ci/test_latest_release.sh - EXAMPLES_DIR=./latest-release-examples/ ./ci/test_latest_release.sh + ROC=./roc_nightly/roc EXAMPLES_DIR=./latest-release-examples/ ./ci/test_latest_release.sh diff --git a/.github/workflows/basic_webserver_build_release.yml b/.github/workflows/basic_webserver_build_release.yml new file mode 100644 index 0000000000..f1cad66170 --- /dev/null +++ b/.github/workflows/basic_webserver_build_release.yml @@ -0,0 +1,164 @@ +on: +# pull_request: + workflow_dispatch: + +# this cancels workflows currently in progress if you start a new one +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + # use .tar.gz for quick testing + ARCHIVE_FORMAT: .tar.br + BASIC_WEBSERVER_BRANCH: main + +jobs: + fetch-releases: + runs-on: [ubuntu-20.04] + steps: + - uses: actions/checkout@v3 + + - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz + - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz + - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz + - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz + + - name: Save roc_nightly archives + uses: actions/upload-artifact@v3 + with: + path: roc_nightly-* + + build-linux-x86_64-files: + runs-on: [ubuntu-20.04] + needs: [fetch-releases] + steps: + - uses: actions/checkout@v3 + + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 + + - name: build basic-webserver with surgical linker and also with legacy linker + env: + CARGO_BUILD_TARGET: x86_64-unknown-linux-musl + run: ./ci/build_basic_webserver.sh linux_x86_64 "--linker legacy" + + - name: Save .rh, .rm and .o file + uses: actions/upload-artifact@v3 + with: + name: linux-x86_64-files + path: | + basic-webserver/platform/metadata_linux-x64.rm + basic-webserver/platform/linux-x64.rh + basic-webserver/platform/linux-x64.o + + + build-linux-arm64-files: + runs-on: [self-hosted, Linux, ARM64] + needs: [fetch-releases] + steps: + - uses: actions/checkout@v3 + + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 + + - name: build basic-webserver + env: + CARGO_BUILD_TARGET: aarch64-unknown-linux-musl + CC_aarch64_unknown_linux_musl: clang-16 + AR_aarch64_unknown_linux_musl: llvm-ar-16 + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld" + run: ./ci/build_basic_webserver.sh linux_arm64 + + - name: Save .o file + uses: actions/upload-artifact@v3 + with: + name: linux-arm64-files + path: | + basic-webserver/platform/linux-arm64.o + + build-macos-x86_64-files: + runs-on: [macos-11] # I expect the generated files to work on macOS 12 and 13 + needs: [fetch-releases] + steps: + - uses: actions/checkout@v3 + + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 + + - run: ./ci/build_basic_webserver.sh macos_x86_64 + + - name: Save .o files + uses: actions/upload-artifact@v3 + with: + name: macos-x86_64-files + path: | + basic-webserver/platform/macos-x64.o + + build-macos-apple-silicon-files: + name: build apple silicon .o file + runs-on: [self-hosted, macOS, ARM64] + needs: [fetch-releases] + steps: + - uses: actions/checkout@v3 + + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 + + - run: ./ci/build_basic_webserver.sh macos_apple_silicon + + - name: Save macos-arm64.o file + uses: actions/upload-artifact@v3 + with: + name: macos-apple-silicon-files + path: | + basic-webserver/platform/macos-arm64.o + + create-release-archive: + needs: [build-linux-x86_64-files, build-linux-arm64-files, build-macos-x86_64-files, build-macos-apple-silicon-files] + name: create release archive + runs-on: [ubuntu-20.04] + steps: + - uses: actions/checkout@v3 + + - name: remove all folders except the ci folder + run: ls | grep -v ci | xargs rm -rf + + - name: Download the previously uploaded files + uses: actions/download-artifact@v3 + + - name: mv roc nightly and simplify name + run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz + + - name: decompress the tar + run: tar -xzvf roc_nightly.tar.gz + + - name: delete tar + run: rm roc_nightly.tar.gz + + - name: rename nightly folder + run: mv roc_nightly* roc_nightly + + - run: | + git clone https://github.com/roc-lang/basic-webserver.git + cd basic-webserver + git checkout ${{ env.BASIC_WEBSERVER_BRANCH }} + cd .. + + - run: cp macos-apple-silicon-files/* ./basic-webserver/platform + + - run: cp linux-x86_64-files/* ./basic-webserver/platform + + - run: cp linux-arm64-files/* ./basic-webserver/platform + + - run: cp macos-x86_64-files/* ./basic-webserver/platform + + - run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-webserver/platform/main.roc + + - run: echo "TAR_FILENAME=$(ls -d basic-webserver/platform/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV + + - name: Upload platform archive + uses: actions/upload-artifact@v3 + with: + name: basic-webserver-platform + path: | + ${{ env.TAR_FILENAME }} diff --git a/.github/workflows/nix_store_cleanup.yml b/.github/workflows/ci_cleanup.yml similarity index 66% rename from .github/workflows/nix_store_cleanup.yml rename to .github/workflows/ci_cleanup.yml index 53db710e82..65e774b6a8 100644 --- a/.github/workflows/nix_store_cleanup.yml +++ b/.github/workflows/ci_cleanup.yml @@ -5,44 +5,51 @@ on: name: Garbage collect nix store jobs: - clean-nix-store-big-ci: + clean-big-ci: runs-on: [self-hosted, i7-6700K] timeout-minutes: 120 steps: - name: Clean up nix store run: nix-store --gc - clean-nix-store-small-ci: + clean-small-ci: runs-on: [self-hosted, i5-4690K] timeout-minutes: 120 steps: - name: Clean up nix store run: nix-store --gc - clean-nix-store-mac-mini-arm64: + clean-mac-mini-arm64: runs-on: [self-hosted, macOS, ARM64] timeout-minutes: 120 steps: - name: Clean up nix store run: nix-store --gc - clean-nix-store-rpi-1: - runs-on: [self-hosted, pi-4-8GB-aarch64] + - name: Clean up nix shells + run: rm -rf /private/tmp/nix-shell.* + + clean-rpi-1: + runs-on: [self-hosted, Linux, ARM64] timeout-minutes: 120 steps: - name: Clean up nix store run: nix-store --gc - clean-nix-store-rpi-2: - runs-on: [self-hosted, rpiGreen] + clean-rpi-2: + runs-on: [self-hosted, Linux, ARM64] timeout-minutes: 120 steps: - name: Clean up nix store run: nix-store --gc - clean-nix-store-mac-mini-x86-64: + clean-mac-mini-x86-64: runs-on: [self-hosted, macOS, X64] timeout-minutes: 120 steps: - name: Clean up nix store run: nix-store --gc + + - name: Clean up temp roc binaries + run: find /private/var/folders/hq -type f -name "roc_app_binary" -exec rm {} \; || true + diff --git a/.github/workflows/devtools_test_linux_x86_64.yml b/.github/workflows/devtools_test_linux_x86_64.yml index 6c8d8b1e1c..bc4eb36b60 100644 --- a/.github/workflows/devtools_test_linux_x86_64.yml +++ b/.github/workflows/devtools_test_linux_x86_64.yml @@ -15,25 +15,25 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Only run all steps if flake.lock or flake.nix changed - id: checklock + - name: Only run all steps if a nix file changed run: | - if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep 'flake'; then - echo "A flake file was changed. Testing devtools nix files..." - echo "flake_changed=true" >> $GITHUB_ENV + git fetch origin ${{ github.base_ref }} + if git diff --name-only origin/${{ github.base_ref }} HEAD | grep 'nix'; then + echo "A nix file was changed. Testing devtools nix files..." + echo "nix_changed=true" >> $GITHUB_ENV else - echo "No flake file was changed. No need to run tests." - echo "flake_changed=false" >> $GITHUB_ENV + echo "A nix file was changed. No need to run tests." + echo "nix_changed=false" >> $GITHUB_ENV fi - uses: cachix/install-nix-action@v23 - if: env.flake_changed == 'true' + if: env.nix_changed == 'true' with: nix_path: nixpkgs=channel:nixos-unstable - name: test devtools/flake.nix - if: env.flake_changed == 'true' + if: env.nix_changed == 'true' id: devtools_test_step run: | sed -i "s|/home/username/gitrepos/roc|$(realpath .)|g" devtools/flake.nix diff --git a/.github/workflows/devtools_test_macos_apple_silicon.yml b/.github/workflows/devtools_test_macos_apple_silicon.yml index 68027ecb00..874e2c026f 100644 --- a/.github/workflows/devtools_test_macos_apple_silicon.yml +++ b/.github/workflows/devtools_test_macos_apple_silicon.yml @@ -15,19 +15,19 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Only run all steps if flake.lock or flake.nix changed - id: checklock + - name: Only run all steps if a nix file changed run: | - if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep 'flake'; then - echo "A flake file was changed. Testing devtools nix files..." - echo "flake_changed=true" >> $GITHUB_ENV + git fetch origin ${{ github.base_ref }} + if git diff --name-only origin/${{ github.base_ref }} HEAD | grep 'nix'; then + echo "A nix file was changed. Testing devtools nix files..." + echo "nix_changed=true" >> $GITHUB_ENV else - echo "No flake file was changed. No need to run tests." - echo "flake_changed=false" >> $GITHUB_ENV + echo "A nix file was changed. No need to run tests." + echo "nix_changed=false" >> $GITHUB_ENV fi - name: test devtools/flake.nix - if: env.flake_changed == 'true' + if: env.nix_changed == 'true' id: devtools_test_step run: | sed -i '' "s|/home/username/gitrepos/roc|$(realpath .)|g" devtools/flake.nix @@ -41,7 +41,7 @@ jobs: nix develop --show-trace - name: Print tip on fail - if: env.flake_changed == 'true' + if: steps.devtools_test_step.outcome == 'failure' run: | echo "The devtools test failed, this can likely be fixed by" echo "locally deleting devtools/flake.lock and following the" diff --git a/.gitignore b/.gitignore index 437842eb4c..fd1a81c005 100644 --- a/.gitignore +++ b/.gitignore @@ -61,9 +61,6 @@ roc_linux_x86_64.tar.gz # nix result -# tutorial -www/src/roc-tutorial - # Only keep Cargo.lock dependencies for the main compiler. # Examples and test only crates should be fine to be unlocked. # This remove unneccessary lock file versioning. @@ -77,7 +74,11 @@ www/src/roc-tutorial # checkmate checkmate_*.json +www/build/ +www/main +www/dist + # ignore the examples folder in the WIP website, this is copied from roc-lang/examples in when building the site -www/wip_new_website/content/examples +www/content/examples www/examples-main.zip www/examples-main \ No newline at end of file diff --git a/README.md b/README.md index 43415008e7..bd88afac66 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/), If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! -We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: +We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: * [James Birtles](https://github.com/jamesbirtles) * [Ivo Balbaert](https://github.com/Ivo-Balbaert) diff --git a/ci/build_basic_cli.sh b/ci/build_basic_cli.sh index 1a74dce3c7..832f6d316f 100755 --- a/ci/build_basic_cli.sh +++ b/ci/build_basic_cli.sh @@ -5,10 +5,20 @@ set -euxo pipefail git clone https://github.com/roc-lang/basic-cli.git -if [ "$(uname -m)" == "x86_64" ] && [ "$(uname -s)" == "Linux" ]; then - sudo apt-get install musl-tools +if [ "$(uname -s)" == "Linux" ]; then + + # check if musl-tools is installed + if ! dpkg -l | grep -q musl-tools; then + # install musl-tools with timeout for sudo problems with CI + timeout 300s sudo apt-get install -y musl-tools + fi + cd basic-cli/src # we cd to install the target for the right rust version - rustup target add x86_64-unknown-linux-musl + if [ "$(uname -m)" == "x86_64" ]; then + rustup target add x86_64-unknown-linux-musl + elif [ "$(uname -m)" == "aarch64" ]; then + rustup target add aarch64-unknown-linux-musl + fi cd ../.. fi diff --git a/ci/build_basic_webserver.sh b/ci/build_basic_webserver.sh new file mode 100755 index 0000000000..23460c9075 --- /dev/null +++ b/ci/build_basic_webserver.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -euxo pipefail + +git clone https://github.com/roc-lang/basic-webserver.git + +OS=$(uname -s) +ARCH=$(uname -m) + +if [ "$OS" == "Linux" ]; then + + # check if musl-tools is installed + if ! dpkg -l | grep -q musl-tools; then + # install musl-tools with timeout for sudo problems with CI + timeout 300s sudo apt-get install -y musl-tools + fi + + cd basic-webserver/platform # we cd to install the target for the right rust version + if [ "$ARCH" == "x86_64" ]; then + rustup target add x86_64-unknown-linux-musl + elif [ "$ARCH" == "aarch64" ]; then + rustup target add aarch64-unknown-linux-musl + fi + cd ../.. +fi + +# simplify tar name +mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "$1") ./roc_nightly.tar.gz + +# decompress the tar +tar -xzvf roc_nightly.tar.gz + +# delete tar +rm roc_nightly.tar.gz + +# simplify dir name +mv roc_nightly* roc_nightly + +cd roc_nightly + +# prevent https://github.com/roc-lang/basic-webserver/issues/9 +if [ "$OS" != "Linux" ] || [ "$ARCH" != "x86_64" ]; then + # build the basic-webserver platform + ./roc build ../basic-webserver/examples/echo.roc +fi + +# We need this extra variable so we can safely check if $2 is empty later +EXTRA_ARGS=${2:-} + +# In some rare cases it's nice to be able to use the legacy linker, so we produce the .o file to be able to do that +if [ -n "${EXTRA_ARGS}" ]; + then ./roc build $EXTRA_ARGS ../basic-webserver/examples/echo.roc +fi + +cd .. diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 3966eb7978..05cdda1c06 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -474,18 +474,6 @@ mod cli_run { ) } - #[test] - #[serial(cli_platform)] - #[cfg_attr(windows, ignore)] - fn hello_world_no_url() { - test_roc_app_slim( - "examples", - "helloWorldNoURL.roc", - "Hello, World!\n", - UseValgrind::Yes, - ) - } - #[cfg(windows)] const LINE_ENDING: &str = "\r\n"; #[cfg(not(windows))] @@ -687,12 +675,13 @@ mod cli_run { } #[test] + #[ignore = "currently broken in basic-cli platform"] #[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")] #[serial(cli_platform)] fn cli_args() { test_roc_app( "examples/cli", - "args.roc", + "argsBROKEN.roc", &[], &[ Arg::PlainText("log"), @@ -713,7 +702,7 @@ mod cli_run { #[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")] #[serial(cli_platform)] fn cli_args_check() { - let path = file_path_from_root("examples/cli", "args.roc"); + let path = file_path_from_root("examples/cli", "argsBROKEN.roc"); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); assert!(out.status.success()); } @@ -736,6 +725,51 @@ mod cli_run { assert!(out.status.success()); } + #[test] + #[cfg_attr(windows, ignore)] + #[serial(cli_platform)] + fn cli_countdown_check() { + let path = file_path_from_root("examples/cli", "countdown.roc"); + let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); + assert!(out.status.success()); + } + + #[test] + #[cfg_attr(windows, ignore)] + #[serial(cli_platform)] + fn cli_echo_check() { + let path = file_path_from_root("examples/cli", "echo.roc"); + let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); + assert!(out.status.success()); + } + + #[test] + #[cfg_attr(windows, ignore)] + #[serial(cli_platform)] + fn cli_file_check() { + let path = file_path_from_root("examples/cli", "fileBROKEN.roc"); + let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); + assert!(out.status.success()); + } + + #[test] + #[cfg_attr(windows, ignore)] + #[serial(cli_platform)] + fn cli_form_check() { + let path = file_path_from_root("examples/cli", "form.roc"); + let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); + assert!(out.status.success()); + } + + #[test] + #[cfg_attr(windows, ignore)] + #[serial(cli_platform)] + fn cli_http_get_check() { + let path = file_path_from_root("examples/cli", "http-get.roc"); + let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); + assert!(out.status.success()); + } + #[test] #[cfg_attr(windows, ignore)] fn interactive_effects() { @@ -839,7 +873,7 @@ mod cli_run { This roc file can print it's own source code. The source is: app "ingested-file" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [ pf.Stdout, "ingested-file.roc" as ownCode : Str, @@ -866,7 +900,7 @@ mod cli_run { &[], &[], &[], - "22424\n", + "30256\n", UseValgrind::No, TestCliCommands::Run, ) diff --git a/crates/compiler/builtins/roc/main.roc b/crates/compiler/builtins/roc/main.roc index a11f36ec53..8e73148b6b 100644 --- a/crates/compiler/builtins/roc/main.roc +++ b/crates/compiler/builtins/roc/main.roc @@ -1,3 +1,3 @@ package "builtins" - exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Set, Box, TotallyNotJson] + exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Box, TotallyNotJson] packages {} diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index a520d924a9..9b69c8497f 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -502,6 +502,7 @@ pub fn constrain_expr( let reason = Reason::FnArg { name: opt_symbol, arg_index: HumanIndex::zero_based(index), + called_via: *called_via, }; let expected_arg = constraints.push_expected_type(ForReason(reason, arg_type_index, region)); diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 32732ceb7d..cf516a53e8 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -2447,7 +2447,7 @@ mod test_reporting { @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `add` has an unexpected type: + This 2nd argument to + has an unexpected type: 4│ 0x4 + "foo" ^^^^^ @@ -2456,7 +2456,7 @@ mod test_reporting { Str - But `add` needs its 2nd argument to be: + But + needs its 2nd argument to be: Int * "### @@ -2472,7 +2472,7 @@ mod test_reporting { @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `add` has an unexpected type: + This 2nd argument to + has an unexpected type: 4│ 0x4 + 3.14 ^^^^ @@ -2481,7 +2481,7 @@ mod test_reporting { Frac * - But `add` needs its 2nd argument to be: + But + needs its 2nd argument to be: Int * @@ -2500,7 +2500,7 @@ mod test_reporting { @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `add` has an unexpected type: + This 2nd argument to + has an unexpected type: 4│ 42 + True ^^^^ @@ -2509,7 +2509,7 @@ mod test_reporting { [True] - But `add` needs its 2nd argument to be: + But + needs its 2nd argument to be: Num * "### @@ -3556,7 +3556,7 @@ mod test_reporting { ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `add` has an unexpected type: + This 2nd argument to + has an unexpected type: 14│ x + y + h + l + minlit + maxlit ^^^^^^ @@ -3565,7 +3565,7 @@ mod test_reporting { U128 - But `add` needs its 2nd argument to be: + But + needs its 2nd argument to be: I128 or Dec "### @@ -3843,7 +3843,7 @@ mod test_reporting { @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `add` has an unexpected type: + This 2nd argument to + has an unexpected type: 4│ \{ x, y ? True } -> x + y ^ @@ -3852,7 +3852,7 @@ mod test_reporting { [True] - But `add` needs its 2nd argument to be: + But + needs its 2nd argument to be: Num a "### @@ -6377,7 +6377,7 @@ In roc, functions are always written as a lambda, like{} @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `mul` has an unexpected type: + This 2nd argument to * has an unexpected type: 5│ mult = \a, b -> a * b ^ @@ -6386,7 +6386,7 @@ In roc, functions are always written as a lambda, like{} F64 - But `mul` needs its 2nd argument to be: + But * needs its 2nd argument to be: Num * @@ -6421,7 +6421,7 @@ In roc, functions are always written as a lambda, like{} @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `mul` has an unexpected type: + This 2nd argument to * has an unexpected type: 5│ mult = \a, b -> a * b ^ @@ -6430,7 +6430,7 @@ In roc, functions are always written as a lambda, like{} F64 - But `mul` needs its 2nd argument to be: + But * needs its 2nd argument to be: Num a @@ -9234,7 +9234,7 @@ In roc, functions are always written as a lambda, like{} @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `isEq` has an unexpected type: + This 2nd argument to == has an unexpected type: 9│ Job lst -> lst == "" ^^ @@ -9243,7 +9243,7 @@ In roc, functions are always written as a lambda, like{} Str - But `isEq` needs its 2nd argument to be: + But == needs its 2nd argument to be: List [Job ∞] as ∞ "### @@ -10013,7 +10013,7 @@ In roc, functions are always written as a lambda, like{} @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `isEq` has an unexpected type: + This 2nd argument to == has an unexpected type: 4│ 0x80000000000000000000000000000000 == -0x80000000000000000000000000000000 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -10022,7 +10022,7 @@ In roc, functions are always written as a lambda, like{} I128 - But `isEq` needs its 2nd argument to be: + But == needs its 2nd argument to be: U128 "### @@ -10038,7 +10038,7 @@ In roc, functions are always written as a lambda, like{} @r###" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This 2nd argument to `isEq` has an unexpected type: + This 2nd argument to == has an unexpected type: 4│ 170141183460469231731687303715884105728 == -170141183460469231731687303715884105728 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -10047,7 +10047,7 @@ In roc, functions are always written as a lambda, like{} I128 or Dec - But `isEq` needs its 2nd argument to be: + But == needs its 2nd argument to be: U128 "### diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 57386b4546..ea2af62253 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -3291,7 +3291,7 @@ fn finish( exposed_types_storage: ExposedTypesStorageSubs, resolved_implementations: ResolvedImplementations, dep_idents: IdentIdsByModule, - documentation: VecMap, + mut documentation: VecMap, abilities_store: AbilitiesStore, // #[cfg(debug_assertions)] checkmate: Option, @@ -3330,6 +3330,18 @@ fn finish( roc_checkmate::dump_checkmate!(checkmate); + let mut docs_by_module = Vec::with_capacity(state.exposed_modules.len()); + + for module_id in state.exposed_modules.iter() { + let docs = documentation.remove(module_id).unwrap_or_else(|| { + panic!("A module was exposed but didn't have an entry in `documentation` somehow: {module_id:?}"); + }); + + docs_by_module.push(docs); + } + + debug_assert_eq!(documentation.len(), 0); + LoadedModule { module_id: state.root_id, interns, @@ -3346,7 +3358,7 @@ fn finish( resolved_implementations, sources, timings: state.timings, - docs_by_module: documentation, + docs_by_module, abilities_store, } } diff --git a/crates/compiler/load_internal/src/module.rs b/crates/compiler/load_internal/src/module.rs index 323456fb5f..c106c4083c 100644 --- a/crates/compiler/load_internal/src/module.rs +++ b/crates/compiler/load_internal/src/module.rs @@ -43,7 +43,7 @@ pub struct LoadedModule { pub resolved_implementations: ResolvedImplementations, pub sources: MutMap)>, pub timings: MutMap, - pub docs_by_module: VecMap, + pub docs_by_module: Vec<(ModuleId, ModuleDocumentation)>, pub abilities_store: AbilitiesStore, pub typechecked: MutMap, } diff --git a/crates/compiler/module/src/called_via.rs b/crates/compiler/module/src/called_via.rs index ac72ca8d62..3cb7394be1 100644 --- a/crates/compiler/module/src/called_via.rs +++ b/crates/compiler/module/src/called_via.rs @@ -102,6 +102,15 @@ pub enum UnaryOp { Not, } +impl std::fmt::Display for UnaryOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UnaryOp::Negate => write!(f, "-"), + UnaryOp::Not => write!(f, "!"), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BinOp { // highest precedence diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 8531a26d39..88a01c496b 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4270,35 +4270,50 @@ pub fn with_hole<'a>( let arena = env.arena; match can_expr { - Int(_, _, int_str, int, _bound) => assign_num_literal_expr( - env, - layout_cache, - assigned, - variable, - &int_str, - IntOrFloatValue::Int(int), - hole, - ), + Int(_, _, int_str, int, _bound) => { + match assign_num_literal_expr( + env, + layout_cache, + assigned, + variable, + &int_str, + IntOrFloatValue::Int(int), + hole, + ) { + Ok(stmt) => stmt, + Err(_) => hole.clone(), + } + } - Float(_, _, float_str, float, _bound) => assign_num_literal_expr( - env, - layout_cache, - assigned, - variable, - &float_str, - IntOrFloatValue::Float(float), - hole, - ), + Float(_, _, float_str, float, _bound) => { + match assign_num_literal_expr( + env, + layout_cache, + assigned, + variable, + &float_str, + IntOrFloatValue::Float(float), + hole, + ) { + Ok(stmt) => stmt, + Err(_) => hole.clone(), + } + } - Num(_, num_str, num, _bound) => assign_num_literal_expr( - env, - layout_cache, - assigned, - variable, - &num_str, - IntOrFloatValue::Int(num), - hole, - ), + Num(_, num_str, num, _bound) => { + match assign_num_literal_expr( + env, + layout_cache, + assigned, + variable, + &num_str, + IntOrFloatValue::Int(num), + hole, + ) { + Ok(stmt) => stmt, + Err(_) => hole.clone(), + } + } Str(string) => Stmt::Let( assigned, @@ -9267,14 +9282,12 @@ fn assign_num_literal_expr<'a>( num_str: &str, num_value: IntOrFloatValue, hole: &'a Stmt<'a>, -) -> Stmt<'a> { - let layout = layout_cache - .from_var(env.arena, variable, env.subs) - .unwrap(); +) -> Result, RuntimeError> { + let layout = layout_cache.from_var(env.arena, variable, env.subs)?; let literal = make_num_literal(&layout_cache.interner, layout, num_str, num_value).to_expr_literal(); - Stmt::Let(assigned, Expr::Literal(literal), layout, hole) + Ok(Stmt::Let(assigned, Expr::Literal(literal), layout, hole)) } type ToLowLevelCallArguments<'a> = ( diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index 39b6349da3..e7fc675b25 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -3370,6 +3370,7 @@ pub enum Reason { FnArg { name: Option, arg_index: HumanIndex, + called_via: CalledVia, }, TypedArg { name: Option, diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index ff09138e4d..5fc3d5753a 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -110,13 +110,13 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { .replace("", &base_url()) .replace( "", - render_sidebar(loaded_module.docs_by_module.values()).as_str(), + render_sidebar(loaded_module.docs_by_module.iter().map(|(_, docs)| docs)).as_str(), ); let all_exposed_symbols = { let mut set = VecSet::default(); - for docs in loaded_module.docs_by_module.values() { + for (_, docs) in loaded_module.docs_by_module.iter() { set.insert_all(docs.exposed_symbols.iter().copied()); } @@ -146,7 +146,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { } // Write each package module's index.html file - for module_docs in loaded_module.docs_by_module.values() { + for (_, module_docs) in loaded_module.docs_by_module.iter() { let module_name = module_docs.name.as_str(); let module_dir = build_dir.join(module_name.replace('.', "/").as_str()); @@ -183,7 +183,7 @@ fn render_package_index(root_module: &LoadedModule) -> String { // The list items containing module links let mut module_list_buf = String::new(); - for module in root_module.docs_by_module.values() { + for (_, module) in root_module.docs_by_module.iter() { // The anchor tag containing the module link let mut link_buf = String::new(); @@ -200,7 +200,12 @@ fn render_package_index(root_module: &LoadedModule) -> String { // The HTML for the index page let mut index_buf = String::new(); - push_html(&mut index_buf, "h2", vec![], "Exposed Modules"); + push_html( + &mut index_buf, + "h2", + vec![("class", "module-name")], + "Exposed Modules", + ); push_html( &mut index_buf, "ul", @@ -222,7 +227,7 @@ fn render_module_documentation( push_html(&mut buf, "h2", vec![("class", "module-name")], { let mut link_buf = String::new(); - push_html(&mut link_buf, "a", vec![("href", "/#")], module_name); + push_html(&mut link_buf, "a", vec![("href", "/")], module_name); link_buf }); diff --git a/crates/docs/src/static/search.js b/crates/docs/src/static/search.js index 5d404a0ce0..3beea47a51 100644 --- a/crates/docs/src/static/search.js +++ b/crates/docs/src/static/search.js @@ -1,124 +1,147 @@ (() => { - let sidebar = document.getElementById("sidebar-nav"); - let searchBox = document.getElementById("module-search"); + let sidebar = document.getElementById("sidebar-nav"); + let searchBox = document.getElementById("module-search"); - function search() { - let text = searchBox.value.toLowerCase(); // Search is case-insensitive. + if (searchBox != null) { + function search() { + let text = searchBox.value.toLowerCase(); // Search is case-insensitive. - if (text === "") { - // Un-hide everything - sidebar.querySelectorAll(".sidebar-entry a").forEach((entry) => entry.classList.remove("hidden")); + if (text === "") { + // Un-hide everything + sidebar + .querySelectorAll(".sidebar-entry a") + .forEach((entry) => entry.classList.remove("hidden")); - // Re-hide all the sub-entries except for those of the current module - let currentModuleName = document.querySelector('.module-name').textContent; + // Re-hide all the sub-entries except for those of the current module + let currentModuleName = + document.querySelector(".module-name").textContent; - sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => { - let entryName = entry.querySelector('.sidebar-module-link').textContent; - if (currentModuleName === entryName) { - entry.firstChild.classList.add("active"); - return; - }; - entry.querySelectorAll(".sidebar-sub-entries a").forEach((subEntry) => subEntry.classList.add("hidden")); - }) - } else { - // First, show/hide all the sub-entries within each module (top-level functions etc.) - sidebar.querySelectorAll(".sidebar-sub-entries a").forEach((entry) => { - if (entry.textContent.toLowerCase().includes(text)) { - entry.classList.remove("hidden"); - } else { - entry.classList.add("hidden"); + sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => { + let entryName = entry.querySelector( + ".sidebar-module-link" + ).textContent; + if (currentModuleName === entryName) { + entry.firstChild.classList.add("active"); + return; + } + entry + .querySelectorAll(".sidebar-sub-entries a") + .forEach((subEntry) => + subEntry.classList.add("hidden") + ); + }); + } else { + // First, show/hide all the sub-entries within each module (top-level functions etc.) + sidebar + .querySelectorAll(".sidebar-sub-entries a") + .forEach((entry) => { + if (entry.textContent.toLowerCase().includes(text)) { + entry.classList.remove("hidden"); + } else { + entry.classList.add("hidden"); + } + }); + + // Then, show/hide modules based on whether they match, or any of their sub-entries matched + sidebar + .querySelectorAll(".sidebar-module-link") + .forEach((entry) => { + if ( + entry.textContent.toLowerCase().includes(text) || + entry.parentNode.querySelectorAll( + ".sidebar-sub-entries a:not(.hidden)" + ).length > 0 + ) { + entry.classList.remove("hidden"); + } else { + entry.classList.add("hidden"); + } + }); + } } - }); - // Then, show/hide modules based on whether they match, or any of their sub-entries matched - sidebar.querySelectorAll(".sidebar-module-link").forEach((entry) => { - if (entry.textContent.toLowerCase().includes(text) || entry.parentNode.querySelectorAll(".sidebar-sub-entries a:not(.hidden)").length > 0) { - entry.classList.remove("hidden"); - } else { - entry.classList.add("hidden"); - } - }); - } - } + searchBox.addEventListener("input", search); - searchBox.addEventListener("input", search); + search(); - search(); + // Capture '/' keypress for quick search + window.addEventListener("keyup", (e) => { + if (e.key === "s" && document.activeElement !== searchBox) { + e.preventDefault; + searchBox.focus(); + searchBox.value = ""; + } - // Capture '/' keypress for quick search - window.addEventListener("keyup", (e) => { - if (e.key === "s" && document.activeElement !== searchBox) { - e.preventDefault; - searchBox.focus(); - searchBox.value = ""; + if (e.key === "Escape" && document.activeElement === searchBox) { + e.preventDefault; + + // De-focus input box + searchBox.blur(); + + // Reset sidebar state + search(); + } + }); } - if (e.key === "Escape" && document.activeElement === searchBox) { - e.preventDefault; - - // De-focus input box - searchBox.blur(); - - // Reset sidebar state - search(); - - } - }); - - const isTouchSupported = () => { - try{ document.createEvent("TouchEvent"); return true; } - catch(e){ return false; } - } - - // Select all elements that are children of
 elements
-  const codeBlocks = document.querySelectorAll("pre > samp");
-
-  // Iterate over each code block
-  codeBlocks.forEach((codeBlock) => {
-    // Create a "Copy" button
-    const copyButton = document.createElement("button");
-    copyButton.classList.add("copy-button");
-    copyButton.textContent = "Copy";
-
-    // Add event listener to copy button
-    copyButton.addEventListener("click", () => {
-      const codeText = codeBlock.innerText;
-      navigator.clipboard.writeText(codeText);
-      copyButton.textContent = "Copied!";
-      copyButton.classList.add("copy-button-copied");
-      copyButton.addEventListener("mouseleave", () => {
-          copyButton.textContent = "Copy";
-          copyButton.classList.remove('copy-button-copied');
-      });
-    });
-
-    // Create a container for the copy button and append it to the document
-    const buttonContainer = document.createElement("div");
-    buttonContainer.classList.add("button-container");
-    buttonContainer.appendChild(copyButton);
-    codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
-
-    // Hide the button container by default
-    buttonContainer.style.display = "none";
-
-    if (isTouchSupported()) {
-      // Show the button container on click for touch support (e.g. mobile)
-      document.addEventListener("click", (event) => {
-        if (event.target.closest("pre > samp") !== codeBlock) {
-          buttonContainer.style.display = "none";
-        } else {
-          buttonContainer.style.display = "block";
+    const isTouchSupported = () => {
+        try {
+            document.createEvent("TouchEvent");
+            return true;
+        } catch (e) {
+            return false;
         }
-      });
-    } else {
-      // Show the button container on hover for non-touch support (e.g. desktop)
-      codeBlock.parentNode.addEventListener("mouseenter", () => {
-        buttonContainer.style.display = "block";
-      });
+    };
 
-      codeBlock.parentNode.addEventListener("mouseleave", () => {
+    // Select all  elements that are children of 
 elements
+    const codeBlocks = document.querySelectorAll("pre > samp");
+
+    // Iterate over each code block
+    codeBlocks.forEach((codeBlock) => {
+        // Create a "Copy" button
+        const copyButton = document.createElement("button");
+        copyButton.classList.add("copy-button");
+        copyButton.textContent = "Copy";
+
+        // Add event listener to copy button
+        copyButton.addEventListener("click", () => {
+            const codeText = codeBlock.innerText;
+            navigator.clipboard.writeText(codeText);
+            copyButton.textContent = "Copied!";
+            copyButton.classList.add("copy-button-copied");
+            copyButton.addEventListener("mouseleave", () => {
+                copyButton.textContent = "Copy";
+                copyButton.classList.remove("copy-button-copied");
+            });
+        });
+
+        // Create a container for the copy button and append it to the document
+        const buttonContainer = document.createElement("div");
+        buttonContainer.classList.add("button-container");
+        buttonContainer.appendChild(copyButton);
+        codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
+
+        // Hide the button container by default
         buttonContainer.style.display = "none";
-      });
-    }
-  });
+
+        if (isTouchSupported()) {
+            // Show the button container on click for touch support (e.g. mobile)
+            document.addEventListener("click", (event) => {
+                if (event.target.closest("pre > samp") !== codeBlock) {
+                    buttonContainer.style.display = "none";
+                } else {
+                    buttonContainer.style.display = "block";
+                }
+            });
+        } else {
+            // Show the button container on hover for non-touch support (e.g. desktop)
+            codeBlock.parentNode.addEventListener("mouseenter", () => {
+                buttonContainer.style.display = "block";
+            });
+
+            codeBlock.parentNode.addEventListener("mouseleave", () => {
+                buttonContainer.style.display = "none";
+            });
+        }
+    });
 })();
diff --git a/crates/docs/src/static/styles.css b/crates/docs/src/static/styles.css
index b2fc3fb812..3aafa6ca81 100644
--- a/crates/docs/src/static/styles.css
+++ b/crates/docs/src/static/styles.css
@@ -9,6 +9,7 @@
   --violet: #7c38f5;
   --violet-bg: #ece2fd;
   --magenta: #a20031;
+  --link-hover-color: #333;
 
   --link-color: var(--violet);
   --code-link-color: var(--violet);
@@ -53,7 +54,7 @@ table tr td {
 }
 
 .logo svg:hover {
-  fill: var(--green);
+  fill: var(--link-hover-color);
 }
 
 .pkg-full-name {
@@ -130,7 +131,7 @@ a:hover code {
 }
 
 .pkg-and-logo a:hover {
-  color: var(--green);
+  color: var(--link-hover-color);
   text-decoration: none;
 }
 
@@ -173,8 +174,9 @@ main {
   line-height: 1.85em;
   margin-top: 2px;
   padding: 48px;
-
-  min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
+  max-width: 740px;
+  /* necessary for text-overflow: ellipsis to work in descendants */
+  min-width: 0;
 }
 
 /* Module links on the package index page (/index.html) */
@@ -254,7 +256,8 @@ padding: 0px 16px;
   font-family: var(--font-sans);
   font-size: 24px;
   height: 100%;
-  min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
+  /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants, but we want this anyway. */
+  min-width: 1024px;
 }
 
 .top-header-triangle {
@@ -340,7 +343,7 @@ color: inherit;
 }
 
 .module-name a:hover {
-  color: var(--green);
+  color: var(--link-hover-color);
 }
 
 .sidebar-module-link {
@@ -519,6 +522,7 @@ pre>samp {
       --violet: #CAADFB;
       --violet-bg: #332944;
       --magenta: #f39bac;
+      --link-hover-color: #fff;
 
       --link-color: var(--violet);
       --code-link-color: var(--violet);
@@ -548,6 +552,8 @@ pre>samp {
   .top-header {
       justify-content: space-between;
       width: auto;
+      /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants. */
+      min-width: 0;
   }
 
   .pkg-full-name {
@@ -591,6 +597,7 @@ pre>samp {
       grid-row-end: above-footer;
       padding: 18px;
       font-size: 16px;
+      max-width: none;
   }
 
   #sidebar-nav {
@@ -739,6 +746,6 @@ code .dim {
 }
 
 .copy-button:hover {
-  border-color: var(--green);
-  color: var(--green);
+  border-color: var(--link-hover-color);
+  color: var(--link-hover-color);
 }
diff --git a/crates/repl_cli/src/lib.rs b/crates/repl_cli/src/lib.rs
index 5d70da11bb..965ce87906 100644
--- a/crates/repl_cli/src/lib.rs
+++ b/crates/repl_cli/src/lib.rs
@@ -20,7 +20,7 @@ use target_lexicon::Triple;
 use crate::cli_gen::eval_llvm;
 
 pub const WELCOME_MESSAGE: &str = concatcp!(
-    "\n  The rockin’ ",
+    "\n  The rockin' ",
     BLUE,
     "roc repl",
     END_COL,
diff --git a/crates/repl_test/src/tests.rs b/crates/repl_test/src/tests.rs
index 39a0d37038..f7816f0a30 100644
--- a/crates/repl_test/src/tests.rs
+++ b/crates/repl_test/src/tests.rs
@@ -658,17 +658,17 @@ fn too_few_args() {
 
 #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
 #[test]
-fn type_problem() {
+fn type_problem_function() {
     expect_failure(
-        "1 + \"\"",
+        "Num.add 1 \"not a num\"",
         indoc!(
             r#"
                 ── TYPE MISMATCH ───────────────────────────────────────────────────────────────
 
                 This 2nd argument to add has an unexpected type:
 
-                4│      1 + ""
-                            ^^
+                4│      Num.add 1 "not a num"
+                                  ^^^^^^^^^^^
 
                 The argument is a string of type:
 
@@ -682,6 +682,84 @@ fn type_problem() {
     );
 }
 
+#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
+#[test]
+fn type_problem_binary_operator() {
+    expect_failure(
+        "1 + \"\"",
+        indoc!(
+            r#"
+                ── TYPE MISMATCH ───────────────────────────────────────────────────────────────
+
+                This 2nd argument to + has an unexpected type:
+
+                4│      1 + ""
+                            ^^
+
+                The argument is a string of type:
+
+                    Str
+
+                But + needs its 2nd argument to be:
+
+                    Num *
+                "#
+        ),
+    );
+}
+
+#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
+#[test]
+fn type_problem_unary_operator() {
+    expect_failure(
+        "!\"not a bool\"",
+        indoc!(
+            r#"
+                ── TYPE MISMATCH ───────────────────────────────────────────────────────────────
+
+                This 1st argument to ! has an unexpected type:
+
+                4│      !"not a bool"
+                         ^^^^^^^^^^^^
+
+                The argument is a string of type:
+
+                    Str
+
+                But ! needs its 1st argument to be:
+
+                    Bool
+                "#
+        ),
+    );
+}
+
+#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
+#[test]
+fn type_problem_string_interpolation() {
+    expect_failure(
+        "\"This is not a string -> \\(1)\"",
+        indoc!(
+            r#"
+                ── TYPE MISMATCH ───────────────────────────────────────────────────────────────
+
+                This argument to this string interpolation has an unexpected type:
+
+                4│      "This is not a string -> \(1)"
+                                                   ^
+
+                The argument is a number of type:
+
+                    Num *
+
+                But this string interpolation needs its argument to be:
+
+                    Str
+                "#
+        ),
+    );
+}
+
 #[test]
 fn issue_2149_i8_ok() {
     expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [InvalidNumStr]");
diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs
index 4c754275d1..686840203e 100644
--- a/crates/reporting/src/error/parse.rs
+++ b/crates/reporting/src/error/parse.rs
@@ -3205,21 +3205,31 @@ fn to_header_report<'a>(
             let surroundings = Region::new(start, *pos);
             let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
 
-            let doc = alloc.stack([
-                alloc.reflow(r"I am expecting a header, but got stuck here:"),
-                alloc.region_with_subregion(lines.convert_region(surroundings), region),
-                alloc.concat([
-                    alloc.reflow("I am expecting a module keyword next, one of "),
-                    alloc.keyword("interface"),
-                    alloc.reflow(", "),
-                    alloc.keyword("app"),
-                    alloc.reflow(", "),
-                    alloc.keyword("package"),
-                    alloc.reflow(" or "),
-                    alloc.keyword("platform"),
-                    alloc.reflow("."),
-                ]),
-            ]);
+            let is_utf8 = alloc
+                .src_lines
+                .iter()
+                .all(|line| std::str::from_utf8(line.as_bytes()).is_ok());
+
+            let preamble = if is_utf8 {
+                vec![
+                    alloc.reflow(r"I am expecting a header, but got stuck here:"),
+                    alloc.region_with_subregion(lines.convert_region(surroundings), region),
+                ]
+            } else {
+                vec![alloc.reflow(r"I am expecting a header, but the file is not UTF-8 encoded.")]
+            };
+
+            let doc = alloc.stack(preamble.into_iter().chain([alloc.concat([
+                alloc.reflow("I am expecting a module keyword next, one of "),
+                alloc.keyword("interface"),
+                alloc.reflow(", "),
+                alloc.keyword("app"),
+                alloc.reflow(", "),
+                alloc.keyword("package"),
+                alloc.reflow(" or "),
+                alloc.keyword("platform"),
+                alloc.reflow("."),
+            ])]));
 
             Report {
                 filename,
diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs
index b3a7e4676d..e4b2ca711e 100644
--- a/crates/reporting/src/error/type.rs
+++ b/crates/reporting/src/error/type.rs
@@ -1273,12 +1273,24 @@ fn to_expr_report<'b>(
                     }
                 }
             },
-            Reason::FnArg { name, arg_index } => {
+            Reason::FnArg {
+                name,
+                arg_index,
+                called_via,
+            } => {
                 let ith = arg_index.ordinal();
 
-                let this_function = match name {
-                    None => alloc.text("this function"),
-                    Some(symbol) => alloc.symbol_unqualified(symbol),
+                let this_function = match (called_via, name) {
+                    (CalledVia::Space, Some(symbole)) => alloc.symbol_unqualified(symbole),
+                    (CalledVia::BinOp(op), _) => alloc.binop(op),
+                    (CalledVia::UnaryOp(op), _) => alloc.unop(op),
+                    (CalledVia::StringInterpolation, _) => alloc.text("this string interpolation"),
+                    _ => alloc.text("this function"),
+                };
+
+                let argument = match called_via {
+                    CalledVia::StringInterpolation => "argument".to_string(),
+                    _ => format!("{ith} argument"),
                 };
 
                 report_mismatch(
@@ -1292,7 +1304,7 @@ fn to_expr_report<'b>(
                     region,
                     Some(expr_region),
                     alloc.concat([
-                        alloc.string(format!("This {ith} argument to ")),
+                        alloc.string(format!("This {argument} to ")),
                         this_function.clone(),
                         alloc.text(" has an unexpected type:"),
                     ]),
@@ -1300,7 +1312,7 @@ fn to_expr_report<'b>(
                     alloc.concat([
                         alloc.text("But "),
                         this_function,
-                        alloc.string(format!(" needs its {ith} argument to be:")),
+                        alloc.string(format!(" needs its {argument} to be:")),
                     ]),
                     None,
                 )
diff --git a/crates/reporting/src/report.rs b/crates/reporting/src/report.rs
index 386c0d5100..cb86c5eac7 100644
--- a/crates/reporting/src/report.rs
+++ b/crates/reporting/src/report.rs
@@ -490,6 +490,13 @@ impl<'a> RocDocAllocator<'a> {
         self.text(content.to_string()).annotate(Annotation::BinOp)
     }
 
+    pub fn unop(
+        &'a self,
+        content: roc_module::called_via::UnaryOp,
+    ) -> DocBuilder<'a, Self, Annotation> {
+        self.text(content.to_string()).annotate(Annotation::UnaryOp)
+    }
+
     /// Turns off backticks/colors in a block
     pub fn type_block(
         &'a self,
@@ -843,6 +850,7 @@ pub enum Annotation {
     Structure,
     Symbol,
     BinOp,
+    UnaryOp,
     Error,
     GutterBar,
     LineNumber,
@@ -1027,6 +1035,9 @@ where
             BinOp => {
                 self.write_str(self.palette.alias)?;
             }
+            UnaryOp => {
+                self.write_str(self.palette.alias)?;
+            }
             Symbol => {
                 self.write_str(self.palette.variable)?;
             }
@@ -1075,9 +1086,9 @@ where
         match self.style_stack.pop() {
             None => {}
             Some(annotation) => match annotation {
-                Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
-                | Ellipsis | Typo | TypoSuggestion | ParserSuggestion | Structure | CodeBlock
-                | PlainText | LineNumber | Tip | Module | Header | Keyword => {
+                Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | UnaryOp | Error
+                | GutterBar | Ellipsis | Typo | TypoSuggestion | ParserSuggestion | Structure
+                | CodeBlock | PlainText | LineNumber | Tip | Module | Header | Keyword => {
                     self.write_str(self.palette.reset)?;
                 }
 
diff --git a/crates/roc_std/src/lib.rs b/crates/roc_std/src/lib.rs
index 8004d568bc..ec549e461a 100644
--- a/crates/roc_std/src/lib.rs
+++ b/crates/roc_std/src/lib.rs
@@ -101,6 +101,13 @@ where
     }
 }
 
+impl Eq for RocResult
+where
+    T: Eq,
+    E: Eq,
+{
+}
+
 impl PartialEq for RocResult
 where
     T: PartialEq,
@@ -111,6 +118,37 @@ where
     }
 }
 
+impl Ord for RocResult
+where
+    T: Ord,
+    E: Ord,
+{
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.as_result_of_refs().cmp(&other.as_result_of_refs())
+    }
+}
+
+impl PartialOrd for RocResult
+where
+    T: PartialOrd,
+    E: PartialOrd,
+{
+    fn partial_cmp(&self, other: &Self) -> Option {
+        self.as_result_of_refs()
+            .partial_cmp(&other.as_result_of_refs())
+    }
+}
+
+impl Hash for RocResult
+where
+    T: Hash,
+    E: Hash,
+{
+    fn hash(&self, state: &mut H) {
+        self.as_result_of_refs().hash(state)
+    }
+}
+
 impl Clone for RocResult
 where
     T: Clone,
diff --git a/default.nix b/default.nix
index 1d4a8bb48f..f05dbb5e43 100644
--- a/default.nix
+++ b/default.nix
@@ -1,111 +1,10 @@
-{ rev ? (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.nixpkgs.locked.rev
-, nixpkgsSource ? builtins.fetchTarball {
-    url = "https://github.com/nixos/nixpkgs/tarball/${rev}";
-    sha256 = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.nixpkgs.locked.narHash;
-  }
-, pkgs ? import nixpkgsSource { }
-,
-}:
-# we only use this file to release a nix package, use flake.nix for development
-let
-  desiredRustVersion = (builtins.fromTOML (builtins.readFile (./rust-toolchain.toml))).toolchain.channel;
-  actualRustVersion = pkgs.rustc;
-  rustVersionsMatch = pkgs.lib.strings.hasSuffix desiredRustVersion actualRustVersion;
-
-  # When updating the zig or llvm version, make sure they stay in sync.
-  # Also update in flake.nix (TODO: maybe we can use nix code to sync this)
-  zigPkg = pkgs.zig_0_11;
-  llvmPkgs = pkgs.llvmPackages_16;
-  llvmVersion = builtins.splitVersion llvmPkgs.release_version;
-  llvmMajorMinorStr = builtins.elemAt llvmVersion 0 + builtins.elemAt llvmVersion 1;
-  # nix does not store libs in /usr/lib or /lib
-  glibcPath =
-    if pkgs.stdenv.isLinux then "${pkgs.glibc.out}/lib" else "";
-  libGccSPath =
-    if pkgs.stdenv.isLinux then "${pkgs.stdenv.cc.cc.lib}/lib" else "";
-in
-
-  assert pkgs.lib.assertMsg rustVersionsMatch ''
-    The rust version changed in rust-toolchain.toml but the rev(commit) in nixpkgs.url in flake.nix was not updated.
-    1. clone the nixpkgs repo: `git clone --depth 60000 git@github.com:NixOS/nixpkgs.git`
-    2. `cd nixpkgs`
-    3. `git log --oneline | rg -A 1 "rustc: "`
-    4. Copy the short SHA from the line **after** the commit with the message of for example `rustc: 1.67.1 -> 1.68.0`
-    5. Find the long SHA by executing `git rev-parse `
-    6. Copy the long SHA
-    7. Paste it in place of the old SHA(rev) in flake.nix:inputs:nixpkgs.url
-    8. execute `nix flake lock --update-input rust-overlay`
-  '';
-
-  pkgs.rustPlatform.buildRustPackage {
-    pname = "roc";
-    version = "0.0.1";
-
-    src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
-
-    cargoLock = {
-      lockFile = ./Cargo.lock;
-      outputHashes = {
-        "criterion-0.3.5" = "sha256-+FibPQGiR45g28xCHcM0pMN+C+Q8gO8206Wb5fiTy+k=";
-        "inkwell-0.2.0" = "sha256-VhTapYGonoSQ4hnDoLl4AAgj0BppAhPNA+UPuAJSuAU=";
-        "plotters-0.3.1" = "sha256-noy/RSjoEPZZbOJTZw1yxGcX5S+2q/7mxnUrzDyxOFw=";
-        "rustyline-9.1.1" = "sha256-aqQqz6nSp+Qn44gm3jXmmQUO6/fYTx7iLph2tbA24Bs=";
-      };
-    };
-
-    shellHook = ''
-      export LLVM_SYS_${llvmMajorMinorStr}_PREFIX="${llvmPkgs.llvm.dev}"
-    '';
-
-    # required for zig
-    XDG_CACHE_HOME =
-      "xdg_cache"; # prevents zig AccessDenied error github.com/ziglang/zig/issues/6810
-    # want to see backtrace in case of failure
-    RUST_BACKTRACE = 1;
-
-    # skip running rust tests, problems:
-    # building of example platforms requires network: Could not resolve host
-    # zig AccessDenied error github.com/ziglang/zig/issues/6810
-    # Once instance has previously been poisoned ??
-    doCheck = false;
-
-    nativeBuildInputs = (with pkgs; [
-      cmake
-      git
-      pkg-config
-      python3
-      llvmPkgs.clang
-      llvmPkgs.llvm.dev
-      llvmPkgs.bintools-unwrapped # contains lld      
-      zigPkg
-    ]);
-
-    buildInputs = (with pkgs;
-      [
-        libffi
-        libxml2
-        ncurses
-        zlib
-        cargo
-        makeWrapper # necessary for postBuild wrapProgram
-      ] ++ lib.optionals pkgs.stdenv.isDarwin [
-        pkgs.darwin.apple_sdk.frameworks.AppKit
-        pkgs.darwin.apple_sdk.frameworks.CoreFoundation
-        pkgs.darwin.apple_sdk.frameworks.CoreServices
-        pkgs.darwin.apple_sdk.frameworks.Foundation
-        pkgs.darwin.apple_sdk.frameworks.Security
-      ]);
-
-    # cp: to copy str.zig,list.zig...
-    # wrapProgram pkgs.stdenv.cc: to make ld available for compiler/build/src/link.rs
-    postInstall =
-      if pkgs.stdenv.isLinux then ''
-        wrapProgram $out/bin/roc --set NIX_GLIBC_PATH ${glibcPath} --set NIX_LIBGCC_S_PATH ${libGccSPath} --prefix PATH : ${
-          pkgs.lib.makeBinPath [ pkgs.stdenv.cc ]
-        }
-      '' else ''
-        wrapProgram $out/bin/roc --prefix PATH : ${
-          pkgs.lib.makeBinPath [ pkgs.stdenv.cc ]
-        }
-      '';
-  }
+(import
+  (
+    let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
+    in fetchTarball {
+      url =
+        "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+      sha256 = lock.nodes.flake-compat.locked.narHash;
+    }
+  )
+  { src = ./.; }).defaultNix
diff --git a/devtools/flake.lock b/devtools/flake.lock
old mode 100755
new mode 100644
index c15d838e56..d90cc13aad
--- a/devtools/flake.lock
+++ b/devtools/flake.lock
@@ -1,21 +1,36 @@
 {
   "nodes": {
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1696426674,
+        "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
     "flake-utils": {
       "inputs": {
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1692799911,
-        "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
+        "lastModified": 1694529238,
+        "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
+        "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
         "type": "github"
       },
       "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
+        "id": "flake-utils",
+        "type": "indirect"
       }
     },
     "flake-utils_2": {
@@ -23,44 +38,11 @@
         "systems": "systems_2"
       },
       "locked": {
-        "lastModified": 1681202837,
-        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
+        "lastModified": 1694529238,
+        "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
-    "flake-utils_3": {
-      "locked": {
-        "lastModified": 1659877975,
-        "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
-    "flake-utils_4": {
-      "inputs": {
-        "systems": "systems_3"
-      },
-      "locked": {
-        "lastModified": 1681202837,
-        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
+        "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
         "type": "github"
       },
       "original": {
@@ -71,18 +53,21 @@
     },
     "nixgl": {
       "inputs": {
-        "flake-utils": "flake-utils_3",
+        "flake-utils": [
+          "roc",
+          "flake-utils"
+        ],
         "nixpkgs": [
           "roc",
           "nixpkgs"
         ]
       },
       "locked": {
-        "lastModified": 1676383589,
-        "narHash": "sha256-KCkWZXCjH+C4Kn7fUGSrEl5btk+sERHhZueSsvVbPWc=",
+        "lastModified": 1685908677,
+        "narHash": "sha256-E4zUPEUFyVWjVm45zICaHRpfGepfkE9Z2OECV9HXfA4=",
         "owner": "guibou",
         "repo": "nixGL",
-        "rev": "c917918ab9ebeee27b0dd657263d3f57ba6bb8ad",
+        "rev": "489d6b095ab9d289fe11af0219a9ff00fe87c7c5",
         "type": "github"
       },
       "original": {
@@ -93,35 +78,36 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1690279121,
-        "narHash": "sha256-XoPGhV1UJQPue6RiehAu7lQwKss3J1B/K0QtVOMD83A=",
+        "lastModified": 1693140250,
+        "narHash": "sha256-URyIDETtu1bbxcSl83xp7irEV04dPEgj7O3LjHcD1Sk=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "821c72743ceae44bdd09718d47cab98fd5fd90af",
+        "rev": "676fe5e01b9a41fa14aaa48d87685677664104b1",
         "type": "github"
       },
       "original": {
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "821c72743ceae44bdd09718d47cab98fd5fd90af",
+        "rev": "676fe5e01b9a41fa14aaa48d87685677664104b1",
         "type": "github"
       }
     },
     "roc": {
       "inputs": {
+        "flake-compat": "flake-compat",
         "flake-utils": "flake-utils_2",
         "nixgl": "nixgl",
         "nixpkgs": "nixpkgs",
         "rust-overlay": "rust-overlay"
       },
       "locked": {
-        "lastModified": 1694000770,
-        "narHash": "sha256-92bAbPmwXxD6rwaAViG5O9r91ZBh9bqaZhM3egPCjuw=",
-        "path": "/home/anton/gitrepos/roc",
+        "lastModified": 1700241573,
+        "narHash": "sha256-+hjY1FieVbF8jvRE3Cvo8GBWh1OlFrF+QDFF8OlWM/s=",
+        "path": "/home/username/gitrepos/JRMurr/roc",
         "type": "path"
       },
       "original": {
-        "path": "/home/anton/gitrepos/roc",
+        "path": "/home/username/gitrepos/JRMurr/roc",
         "type": "path"
       }
     },
@@ -133,18 +119,21 @@
     },
     "rust-overlay": {
       "inputs": {
-        "flake-utils": "flake-utils_4",
+        "flake-utils": [
+          "roc",
+          "flake-utils"
+        ],
         "nixpkgs": [
           "roc",
           "nixpkgs"
         ]
       },
       "locked": {
-        "lastModified": 1690252178,
-        "narHash": "sha256-9oEz822bvbHobfCUjJLDor2BqW3I5tycIauzDlzOALY=",
+        "lastModified": 1695694299,
+        "narHash": "sha256-0CucEiOZzOVHwmGDJKNXLj7aDYOqbRtqChp9nbGrh18=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "8d64353ca827002fb8459e44d49116c78d868eba",
+        "rev": "c89a55d2d91cf55234466934b25deeffa365188a",
         "type": "github"
       },
       "original": {
@@ -182,21 +171,6 @@
         "repo": "default",
         "type": "github"
       }
-    },
-    "systems_3": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
     }
   },
   "root": "root",
diff --git a/examples/cli/.gitignore b/examples/cli/.gitignore
index 6ec4e04020..95d47a0ea9 100644
--- a/examples/cli/.gitignore
+++ b/examples/cli/.gitignore
@@ -2,6 +2,7 @@ args
 countdown
 echo
 effects
+file
 form
 tui
 http-get
@@ -9,4 +10,4 @@ file-io
 env
 ingested-file
 ingested-file-bytes
-out.txt
\ No newline at end of file
+out.txt
diff --git a/examples/cli/args.roc b/examples/cli/argsBROKEN.roc
similarity index 89%
rename from examples/cli/args.roc
rename to examples/cli/argsBROKEN.roc
index 9f9215840f..e5dc6ac8eb 100644
--- a/examples/cli/args.roc
+++ b/examples/cli/argsBROKEN.roc
@@ -1,9 +1,9 @@
 app "args"
-    packages { pf: "cli-platform/main.roc" }
-    imports [pf.Stdout, pf.Arg, pf.Task.{ Task }, pf.Process]
+    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
+    imports [pf.Stdout, pf.Arg, pf.Task.{ Task }]
     provides [main] to pf
 
-main : Task {} []
+main : Task {} I32
 main =
     args <- Arg.list |> Task.await
     parser =
@@ -57,7 +57,7 @@ main =
 
         Err helpMenu ->
             {} <- Stdout.line helpMenu |> Task.await
-            Process.exit 1
+            Task.err 1
 
 runCmd = \cmd ->
     when cmd is
diff --git a/examples/cli/cli-platform/Arg.roc b/examples/cli/cli-platform/Arg.roc
deleted file mode 100644
index c74945dcf5..0000000000
--- a/examples/cli/cli-platform/Arg.roc
+++ /dev/null
@@ -1,1074 +0,0 @@
-interface Arg
-    exposes [
-        Parser,
-        NamedParser,
-        parse,
-        toHelp,
-        parseFormatted,
-        succeed,
-        boolOption,
-        strOption,
-        i64Option,
-        str,
-        subCommand,
-        choice,
-        withParser,
-        program,
-        list,
-    ]
-    imports [Effect, InternalTask, Task.{ Task }]
-
-## Gives a list of the program's command-line arguments.
-list : Task (List Str) *
-list =
-    Effect.args
-    |> Effect.map Ok
-    |> InternalTask.fromEffect
-
-## 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)
-
-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/cli/cli-platform/Cargo.toml b/examples/cli/cli-platform/Cargo.toml
deleted file mode 100644
index 7ac8e5ecf8..0000000000
--- a/examples/cli/cli-platform/Cargo.toml
+++ /dev/null
@@ -1,25 +0,0 @@
-[package]
-name = "host"
-authors = ["The Roc Contributors"]
-edition = "2021"
-license = "UPL-1.0"
-version = "0.0.1"
-
-links = "app"
-
-[lib]
-name = "host"
-path = "src/lib.rs"
-crate-type = ["staticlib", "rlib"]
-
-[[bin]]
-name = "host"
-path = "src/main.rs"
-
-[dependencies]
-backtrace = "0.3"
-libc = "0.2"
-reqwest = { version = "=0.11.20", default-features = false, features = ["blocking", "rustls-tls"] }
-roc_std = { path = "../../../crates/roc_std" }
-
-[workspace]
diff --git a/examples/cli/cli-platform/Dir.roc b/examples/cli/cli-platform/Dir.roc
deleted file mode 100644
index a58d091ad8..0000000000
--- a/examples/cli/cli-platform/Dir.roc
+++ /dev/null
@@ -1,25 +0,0 @@
-interface Dir
-    exposes [ReadErr, DeleteErr, DirEntry, deleteEmptyDir, deleteRecursive, list]
-    imports [Effect, Task.{ Task }, InternalTask, Path.{ Path }, InternalPath, InternalDir]
-
-ReadErr : InternalDir.ReadErr
-
-DeleteErr : InternalDir.DeleteErr
-
-DirEntry : InternalDir.DirEntry
-
-## Lists the files and directories inside the directory.
-list : Path -> Task (List Path) [DirReadErr Path ReadErr]
-list = \path ->
-    effect = Effect.map (Effect.dirList (InternalPath.toBytes path)) \result ->
-        when result is
-            Ok entries -> Ok (List.map entries InternalPath.fromOsBytes)
-            Err err -> Err (DirReadErr path err)
-
-    InternalTask.fromEffect effect
-
-## Deletes a directory if it's empty.
-deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]
-
-## Recursively deletes the directory as well as all files and directories inside it.
-deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]
diff --git a/examples/cli/cli-platform/Effect.roc b/examples/cli/cli-platform/Effect.roc
deleted file mode 100644
index 20d88e59c7..0000000000
--- a/examples/cli/cli-platform/Effect.roc
+++ /dev/null
@@ -1,55 +0,0 @@
-hosted Effect
-    exposes [
-        Effect,
-        after,
-        args,
-        map,
-        always,
-        forever,
-        loop,
-        dirList,
-        envDict,
-        envVar,
-        cwd,
-        setCwd,
-        exePath,
-        stdoutLine,
-        stdoutWrite,
-        stderrLine,
-        stderrWrite,
-        stdinLine,
-        sendRequest,
-        fileReadBytes,
-        fileDelete,
-        fileWriteUtf8,
-        fileWriteBytes,
-        processExit,
-    ]
-    imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir]
-    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)
-fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr)
-fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr)
-fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr)
-dirList : List U8 -> Effect (Result (List (List U8)) InternalDir.ReadErr)
-envDict : Effect (Dict Str Str)
-envVar : Str -> Effect (Result Str {})
-exePath : Effect (Result (List U8) {})
-setCwd : List U8 -> Effect (Result {} {})
-
-processExit : U8 -> Effect {}
-
-# If we encounter a Unicode error in any of the args, it will be replaced with
-# the Unicode replacement char where necessary.
-args : Effect (List Str)
-
-cwd : Effect (List U8)
-
-sendRequest : Box Request -> Effect Response
diff --git a/examples/cli/cli-platform/Env.roc b/examples/cli/cli-platform/Env.roc
deleted file mode 100644
index 50af317d8c..0000000000
--- a/examples/cli/cli-platform/Env.roc
+++ /dev/null
@@ -1,123 +0,0 @@
-interface Env
-    exposes [cwd, dict, var, decode, exePath, setCwd]
-    imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask, EnvDecoding]
-
-## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory)
-## from the environment. File operations on relative [Path]s are relative to this directory.
-cwd : Task Path [CwdUnavailable]
-cwd =
-    effect = Effect.map Effect.cwd \bytes ->
-        if List.isEmpty bytes then
-            Err CwdUnavailable
-        else
-            Ok (InternalPath.fromArbitraryBytes bytes)
-
-    InternalTask.fromEffect effect
-
-## Sets the [current working directory](https://en.wikipedia.org/wiki/Working_directory)
-## in the environment. After changing it, file operations on relative [Path]s will be relative
-## to this directory.
-setCwd : Path -> Task {} [InvalidCwd]
-setCwd = \path ->
-    Effect.setCwd (InternalPath.toBytes path)
-    |> Effect.map (\result -> Result.mapErr result \{} -> InvalidCwd)
-    |> InternalTask.fromEffect
-
-## Gets the path to the currently-running executable.
-exePath : Task Path [ExePathUnavailable]
-exePath =
-    effect =
-        Effect.map Effect.exePath \result ->
-            when result is
-                Ok bytes -> Ok (InternalPath.fromOsBytes bytes)
-                Err {} -> Err ExePathUnavailable
-
-    InternalTask.fromEffect effect
-
-## Reads the given environment variable.
-##
-## If the value is invalid Unicode, the invalid parts will be replaced with the
-## [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
-## (`�`).
-var : Str -> Task Str [VarNotFound]
-var = \name ->
-    Effect.envVar name
-    |> Effect.map (\result -> Result.mapErr result \{} -> VarNotFound)
-    |> InternalTask.fromEffect
-
-## Reads the given environment variable and attempts to decode it.
-##
-## The type being decoded into will be determined by type inference. For example,
-## if this ends up being used like a `Task U16 …` then the environment variable
-## will be decoded as a string representation of a `U16`.
-##
-##     getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]]
-##     getU16Var = \var -> Env.decode var
-##     # If the environment contains a variable NUM_THINGS=123, then calling
-##     # (getU16Var "NUM_THINGS") would return a task which succeeds with the U16 number 123.
-##     #
-##     # However, if the NUM_THINGS environment variable was set to 1234567, then
-##     # (getU16Var "NUM_THINGS") would fail because that number is too big to fit in a U16.
-##
-## Supported types:
-## - strings
-## - numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers
-## - comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas
-##
-## Trying to decode into any other types will always fail with a `DecodeErr`.
-decode : Str -> Task val [VarNotFound, DecodeErr DecodeError] where val implements Decoding
-decode = \name ->
-    Effect.envVar name
-    |> Effect.map
-        (
-            \result ->
-                result
-                |> Result.mapErr (\{} -> VarNotFound)
-                |> Result.try
-                    (\varStr ->
-                        Decode.fromBytes (Str.toUtf8 varStr) (EnvDecoding.format {})
-                        |> Result.mapErr (\_ -> DecodeErr TooShort)))
-    |> InternalTask.fromEffect
-
-## Reads all the process's environment variables into a [Dict].
-##
-## If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
-## (`�`) will be used in place of any parts of keys or values that are invalid Unicode.
-dict : Task (Dict Str Str) *
-dict =
-    Effect.envDict
-    |> Effect.map Ok
-    |> InternalTask.fromEffect
-
-# ## Walks over the process's environment variables as key-value arguments to the walking function.
-# ##
-# ##     Env.walk "Vars:\n" \state, key, value ->
-# ##         "- \(key): \(value)\n"
-# ##     # This might produce a string such as:
-# ##     #
-# ##     #     """
-# ##     #     Vars:
-# ##     #     - FIRST_VAR: first value
-# ##     #     - SECOND_VAR: second value
-# ##     #     - THIRD_VAR: third value
-# ##     #
-# ##     #     """
-# ##
-# ## If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
-# ## (`�`) will be used in place of any parts of keys or values that are invalid Unicode.
-# walk : state, (state, Str, Str -> state) -> Task state [NonUnicodeEnv state] [Read [Env]]
-# walk = \state, walker ->
-#     Effect.envWalk state walker
-#     |> InternalTask.fromEffect
-# TODO could potentially offer something like walkNonUnicode which takes (state, Result Str Str, Result Str Str) so it
-# tells you when there's invalid Unicode. This is both faster than (and would give you more accurate info than)
-# using regular `walk` and searching for the presence of the replacement character in the resulting
-# strings. However, it's unclear whether anyone would use it. What would the use case be? Reporting
-# an error that the provided command-line args weren't valid Unicode? Does that still happen these days?
-# TODO need to figure out clear rules for how to convert from camelCase to SCREAMING_SNAKE_CASE.
-# Note that all the env vars decoded in this way become effectively *required* vars, since if any
-# of them are missing, decoding will fail. For this reason, it might make sense to use this to
-# decode all the required vars only, and then decode the optional ones separately some other way.
-# Alternatively, it could make sense to have some sort of tag union convention here, e.g.
-# if decoding into a tag union of [Present val, Missing], then it knows what to do.
-# decodeAll : Task val [] [EnvDecodingFailed Str] [Env] where val implements Decoding
diff --git a/examples/cli/cli-platform/EnvDecoding.roc b/examples/cli/cli-platform/EnvDecoding.roc
deleted file mode 100644
index b817143975..0000000000
--- a/examples/cli/cli-platform/EnvDecoding.roc
+++ /dev/null
@@ -1,103 +0,0 @@
-interface EnvDecoding exposes [EnvFormat, format] imports []
-
-EnvFormat := {} implements [
-        DecoderFormatting {
-            u8: envU8,
-            u16: envU16,
-            u32: envU32,
-            u64: envU64,
-            u128: envU128,
-            i8: envI8,
-            i16: envI16,
-            i32: envI32,
-            i64: envI64,
-            i128: envI128,
-            f32: envF32,
-            f64: envF64,
-            dec: envDec,
-            bool: envBool,
-            string: envString,
-            list: envList,
-            record: envRecord,
-            tuple: envTuple,
-        },
-    ]
-
-format : {} -> EnvFormat
-format = \{} -> @EnvFormat {}
-
-decodeBytesToNum = \bytes, transformer ->
-    when Str.fromUtf8 bytes is
-        Ok s ->
-            when transformer s is
-                Ok n -> { result: Ok n, rest: [] }
-                Err _ -> { result: Err TooShort, rest: bytes }
-
-        Err _ -> { result: Err TooShort, rest: bytes }
-
-envU8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU8
-envU16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU16
-envU32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU32
-envU64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU64
-envU128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU128
-envI8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI8
-envI16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI16
-envI32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI32
-envI64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI64
-envI128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI128
-envF32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF32
-envF64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF64
-envDec = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toDec
-envBool = Decode.custom \bytes, @EnvFormat {} ->
-    when Str.fromUtf8 bytes is
-        Ok "true" -> { result: Ok Bool.true, rest: [] }
-        Ok "false" -> { result: Ok Bool.false, rest: [] }
-        _ -> { result: Err TooShort, rest: bytes }
-envString = Decode.custom \bytes, @EnvFormat {} ->
-    when Str.fromUtf8 bytes is
-        Ok s -> { result: Ok s, rest: [] }
-        Err _ -> { result: Err TooShort, rest: bytes }
-
-envList = \decodeElem -> Decode.custom \bytes, @EnvFormat {} ->
-        # Per our supported methods of decoding, this is either a list of strings or
-        # a list of numbers; in either case, the list of bytes must be Utf-8
-        # decodable. So just parse it as a list of strings and pass each chunk to
-        # the element decoder. By construction, our element decoders expect to parse
-        # a whole list of bytes anyway.
-        decodeElems = \allBytes, accum ->
-            { toParse, remainder } =
-                when List.splitFirst allBytes (Num.toU8 ',') is
-                    Ok { before, after } ->
-                        { toParse: before, remainder: Some after }
-
-                    Err NotFound ->
-                        { toParse: allBytes, remainder: None }
-
-            when Decode.decodeWith toParse decodeElem (@EnvFormat {}) is
-                { result, rest } ->
-                    when result is
-                        Ok val ->
-                            when remainder is
-                                Some restBytes -> decodeElems restBytes (List.append accum val)
-                                None -> Done (List.append accum val)
-
-                        Err e -> Errored e rest
-
-        when decodeElems bytes [] is
-            Errored e rest -> { result: Err e, rest }
-            Done vals ->
-                { result: Ok vals, rest: [] }
-
-# TODO: we must currently annotate the arrows here so that the lambda sets are
-# exercised, and the solver can find an ambient lambda set for the
-# specialization.
-envRecord : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_ -> _) -> Decoder _ _
-envRecord = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} ->
-        { result: Err TooShort, rest: bytes }
-
-# TODO: we must currently annotate the arrows here so that the lambda sets are
-# exercised, and the solver can find an ambient lambda set for the
-# specialization.
-envTuple : _, (_, _ -> [Next (Decoder _ _), TooLong]), (_ -> _) -> Decoder _ _
-envTuple = \_initialState, _stepElem, _finalizer -> Decode.custom \bytes, @EnvFormat {} ->
-        { result: Err TooShort, rest: bytes }
diff --git a/examples/cli/cli-platform/File.roc b/examples/cli/cli-platform/File.roc
deleted file mode 100644
index b5e367f889..0000000000
--- a/examples/cli/cli-platform/File.roc
+++ /dev/null
@@ -1,144 +0,0 @@
-interface File
-    exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete]
-    imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }]
-
-ReadErr : InternalFile.ReadErr
-
-WriteErr : InternalFile.WriteErr
-
-## Encodes a value using the given `EncodingFormat` and writes it to a file.
-##
-## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON)
-## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
-## to write some encodable data to a file as JSON, like so:
-##
-##     File.write
-##         (Path.fromStr "output.json")
-##         { some: "json stuff" }
-##         Json.toCompactUtf8
-##     # Writes the following to the file `output.json`:
-##     #
-##     # {"some":"json stuff"}
-##
-## If writing to the file fails, for example because of a file permissions issue,
-## the task fails with [WriteErr].
-##
-## This opens the file first and closes it after writing to it.
-##
-## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
-write : Path, val, fmt -> Task {} [FileWriteErr Path WriteErr] where val implements Encode.Encoding, fmt implements Encode.EncoderFormatting
-write = \path, val, fmt ->
-    bytes = Encode.toBytes val fmt
-
-    # TODO handle encoding errors here, once they exist
-    writeBytes path bytes
-
-## Writes bytes to a file.
-##
-##     # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
-##     File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
-##
-## This opens the file first and closes it after writing to it.
-##
-## To format data before writing it to a file, you can use [File.write] instead.
-writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]
-writeBytes = \path, bytes ->
-    toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes
-
-## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8).
-##
-##     # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
-##     File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
-##
-## This opens the file first and closes it after writing to it.
-##
-## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
-writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]
-writeUtf8 = \path, str ->
-    toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str
-
-## Deletes a file from the filesystem.
-##
-##     # Deletes the file named
-##     File.delete (Path.fromStr "myfile.dat") [1, 2, 3]
-##
-## Note that this does not securely erase the file's contents from disk; instead, the operating
-## system marks the space it was occupying as safe to write over in the future. Also, the operating
-## system may not immediately mark the space as free; for example, on Windows it will wait until
-## the last file handle to it is closed, and on UNIX, it will not remove it until the last
-## [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted.
-##
-## This performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile)
-## on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems.
-##
-## On Windows, this will fail when attempting to delete a readonly file; the file's
-## readonly permission must be disabled before it can be successfully deleted.
-delete : Path -> Task {} [FileWriteErr Path WriteErr]
-delete = \path ->
-    toWriteTask path \bytes -> Effect.fileDelete bytes
-
-## Reads all the bytes in a file.
-##
-##     # Read all the bytes in `myfile.txt`.
-##     File.readBytes (Path.fromStr "myfile.txt")
-##
-## This opens the file first and closes it after reading its contents.
-##
-## To read and decode data from a file, you can use `File.read` instead.
-readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]
-readBytes = \path ->
-    toReadTask path \bytes -> Effect.fileReadBytes bytes
-
-## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text.
-##
-##     # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`.
-##     File.readUtf8 (Path.fromStr "myfile.txt")
-##
-## This opens the file first and closes it after writing to it.
-## The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8.
-##
-## To read unformatted bytes from a file, you can use [File.readBytes] instead.
-readUtf8 :
-    Path
-    -> Task Str [FileReadErr Path ReadErr, FileReadUtf8Err Path _]
-readUtf8 = \path ->
-    effect = Effect.map (Effect.fileReadBytes (InternalPath.toBytes path)) \result ->
-        when result is
-            Ok bytes ->
-                Str.fromUtf8 bytes
-                |> Result.mapErr \err -> FileReadUtf8Err path err
-
-            Err readErr -> Err (FileReadErr path readErr)
-
-    InternalTask.fromEffect effect
-
-# read :
-#     Path,
-#     fmt
-#     -> Task
-#         Str
-#         [FileReadErr Path ReadErr, FileReadDecodeErr Path [Leftover (List U8)]Decode.DecodeError ]
-#         [Read [File]]
-#     where val implements Decode.Decoding, fmt implements Decode.DecoderFormatting
-# read = \path, fmt ->
-#     effect = Effect.map (Effect.fileReadBytes (InternalPath.toBytes path)) \result ->
-#         when result is
-#             Ok bytes ->
-#                 when Decode.fromBytes bytes fmt is
-#                     Ok val -> Ok val
-#                     Err decodingErr -> Err (FileReadDecodeErr decodingErr)
-#             Err readErr -> Err (FileReadErr readErr)
-#     InternalTask.fromEffect effect
-toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]
-toWriteTask = \path, toEffect ->
-    InternalPath.toBytes path
-    |> toEffect
-    |> InternalTask.fromEffect
-    |> Task.mapFail \err -> FileWriteErr path err
-
-toReadTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileReadErr Path err]
-toReadTask = \path, toEffect ->
-    InternalPath.toBytes path
-    |> toEffect
-    |> InternalTask.fromEffect
-    |> Task.mapFail \err -> FileReadErr path err
diff --git a/examples/cli/cli-platform/FileMetadata.roc b/examples/cli/cli-platform/FileMetadata.roc
deleted file mode 100644
index 4c071e6fa4..0000000000
--- a/examples/cli/cli-platform/FileMetadata.roc
+++ /dev/null
@@ -1,32 +0,0 @@
-interface FileMetadata
-    exposes [FileMetadata, bytes, type, isReadonly, mode]
-    imports []
-
-# Design note: this is an opaque type rather than a type alias so that
-# we can add new operating system info if new OS releases introduce them,
-# as a backwards-compatible change.
-FileMetadata := {
-    bytes : U64,
-    type : [File, Dir, Symlink],
-    isReadonly : Bool,
-    mode : [Unix U32, NonUnix],
-}
-
-bytes : FileMetadata -> U64
-bytes = \@FileMetadata info -> info.bytes
-
-isReadonly : FileMetadata -> Bool
-isReadonly = \@FileMetadata info -> info.isReadonly
-
-type : FileMetadata -> [File, Dir, Symlink]
-type = \@FileMetadata info -> info.type
-
-mode : FileMetadata -> [Unix U32, NonUnix]
-mode = \@FileMetadata info -> info.mode
-
-# TODO need to create a Time module and return something like Time.Utc here.
-# lastModified : FileMetadata -> Utc
-# TODO need to create a Time module and return something like Time.Utc here.
-# lastAccessed : FileMetadata -> Utc
-# TODO need to create a Time module and return something like Time.Utc here.
-# created : FileMetadata -> Utc
diff --git a/examples/cli/cli-platform/Http.roc b/examples/cli/cli-platform/Http.roc
deleted file mode 100644
index 8304277c44..0000000000
--- a/examples/cli/cli-platform/Http.roc
+++ /dev/null
@@ -1,111 +0,0 @@
-interface Http
-    exposes [
-        Request,
-        Method,
-        Header,
-        TimeoutConfig,
-        Body,
-        Response,
-        Metadata,
-        Error,
-        header,
-        emptyBody,
-        bytesBody,
-        stringBody,
-        handleStringResponse,
-        defaultRequest,
-        errorToString,
-        send,
-    ]
-    imports [Effect, InternalTask, Task.{ Task }, InternalHttp]
-
-Request : InternalHttp.Request
-Method : InternalHttp.Method
-Header : InternalHttp.Header
-TimeoutConfig : InternalHttp.TimeoutConfig
-Body : InternalHttp.Body
-Response : InternalHttp.Response
-Metadata : InternalHttp.Metadata
-Error : InternalHttp.Error
-
-defaultRequest : Request
-defaultRequest = {
-    method: Get,
-    headers: [],
-    url: "",
-    body: Http.emptyBody,
-    timeout: NoTimeout,
-}
-
-## An HTTP header for configuring requests. See a bunch of common headers
-## [here](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields).
-##
-header : Str, Str -> Header
-header =
-    Header
-
-emptyBody : Body
-emptyBody =
-    EmptyBody
-
-bytesBody : [MimeType Str], List U8 -> Body
-bytesBody =
-    Body
-
-stringBody : [MimeType Str], Str -> Body
-stringBody = \mimeType, str ->
-    Body mimeType (Str.toUtf8 str)
-
-# jsonBody : a -> Body where a implements Encoding
-# jsonBody = \val ->
-#     Body (MimeType "application/json") (Encode.toBytes val TotallyNotJson.format)
-#
-# multiPartBody : List Part -> Body
-# multiPartBody = \parts ->
-#     boundary = "7MA4YWxkTrZu0gW" # TODO: what's this exactly? a hash of all the part bodies?
-#     beforeName = Str.toUtf8 "-- \(boundary)\r\nContent-Disposition: form-data; name=\""
-#     afterName = Str.toUtf8 "\"\r\n"
-#     appendPart = \buffer, Part name partBytes ->
-#         buffer
-#         |> List.concat beforeName
-#         |> List.concat (Str.toUtf8 name)
-#         |> List.concat afterName
-#         |> List.concat partBytes
-#     bodyBytes = List.walk parts [] appendPart
-#     Body (MimeType "multipart/form-data;boundary=\"\(boundary)\"") bodyBytes
-# bytesPart : Str, List U8 -> Part
-# bytesPart =
-#     Part
-# stringPart : Str, Str -> Part
-# stringPart = \name, str ->
-#     Part name (Str.toUtf8 str)
-handleStringResponse : Response -> Result Str Error
-handleStringResponse = \response ->
-    when response is
-        BadRequest err -> Err (BadRequest err)
-        Timeout -> Err Timeout
-        NetworkError -> Err NetworkError
-        BadStatus metadata _ -> Err (BadStatus metadata.statusCode)
-        GoodStatus _ bodyBytes ->
-            Str.fromUtf8 bodyBytes
-            |> Result.mapErr
-                \BadUtf8 _ pos ->
-                    position = Num.toStr pos
-
-                    BadBody "Invalid UTF-8 at byte offset \(position)"
-
-errorToString : Error -> Str
-errorToString = \err ->
-    when err is
-        BadRequest e -> "Invalid Request: \(e)"
-        Timeout -> "Request timed out"
-        NetworkError -> "Network error"
-        BadStatus code -> Str.concat "Request failed with status " (Num.toStr code)
-        BadBody details -> Str.concat "Request failed. Invalid body. " details
-
-send : Request -> Task Str Error
-send = \req ->
-    # TODO: Fix our C ABI codegen so that we don't this Box.box heap allocation
-    Effect.sendRequest (Box.box req)
-    |> Effect.map handleStringResponse
-    |> InternalTask.fromEffect
diff --git a/examples/cli/cli-platform/InternalDir.roc b/examples/cli/cli-platform/InternalDir.roc
deleted file mode 100644
index 6163125e84..0000000000
--- a/examples/cli/cli-platform/InternalDir.roc
+++ /dev/null
@@ -1,41 +0,0 @@
-interface InternalDir
-    exposes [ReadErr, DeleteErr, DirEntry]
-    imports [FileMetadata.{ FileMetadata }, Path.{ Path }]
-
-DirEntry : {
-    path : Path,
-    type : [File, Dir, Symlink],
-    metadata : FileMetadata,
-}
-
-ReadErr : [
-    NotFound,
-    Interrupted,
-    InvalidFilename,
-    PermissionDenied,
-    TooManySymlinks, # aka FilesystemLoop
-    TooManyHardlinks,
-    TimedOut,
-    StaleNetworkFileHandle,
-    NotADirectory,
-    OutOfMemory,
-    Unsupported,
-    Unrecognized I32 Str,
-]
-
-DeleteErr : [
-    NotFound,
-    Interrupted,
-    InvalidFilename,
-    PermissionDenied,
-    TooManySymlinks, # aka FilesystemLoop
-    TooManyHardlinks,
-    TimedOut,
-    StaleNetworkFileHandle,
-    NotADirectory,
-    ReadOnlyFilesystem,
-    DirectoryNotEmpty,
-    OutOfMemory,
-    Unsupported,
-    Unrecognized I32 Str,
-]
diff --git a/examples/cli/cli-platform/InternalFile.roc b/examples/cli/cli-platform/InternalFile.roc
deleted file mode 100644
index f6d4476df8..0000000000
--- a/examples/cli/cli-platform/InternalFile.roc
+++ /dev/null
@@ -1,71 +0,0 @@
-interface InternalFile
-    exposes [ReadErr, WriteErr]
-    imports []
-
-ReadErr : [
-    NotFound,
-    Interrupted,
-    InvalidFilename,
-    PermissionDenied,
-    TooManySymlinks, # aka FilesystemLoop
-    TooManyHardlinks,
-    TimedOut,
-    StaleNetworkFileHandle,
-    OutOfMemory,
-    Unsupported,
-    Unrecognized I32 Str,
-]
-
-WriteErr : [
-    NotFound,
-    Interrupted,
-    InvalidFilename,
-    PermissionDenied,
-    TooManySymlinks, # aka FilesystemLoop
-    TooManyHardlinks,
-    TimedOut,
-    StaleNetworkFileHandle,
-    ReadOnlyFilesystem,
-    AlreadyExists, # can this happen here?
-    WasADirectory,
-    WriteZero, # TODO come up with a better name for this, or roll it into another error tag
-    StorageFull,
-    FilesystemQuotaExceeded, # can this be combined with StorageFull?
-    FileTooLarge,
-    ResourceBusy,
-    ExecutableFileBusy,
-    OutOfMemory,
-    Unsupported,
-    Unrecognized I32 Str,
-]
-
-# DirReadErr : [
-#     NotFound,
-#     Interrupted,
-#     InvalidFilename,
-#     PermissionDenied,
-#     TooManySymlinks, # aka FilesystemLoop
-#     TooManyHardlinks,
-#     TimedOut,
-#     StaleNetworkFileHandle,
-#     NotADirectory,
-#     OutOfMemory,
-#     Unsupported,
-#     Unrecognized I32 Str,
-# ]
-# RmDirError : [
-#     NotFound,
-#     Interrupted,
-#     InvalidFilename,
-#     PermissionDenied,
-#     TooManySymlinks, # aka FilesystemLoop
-#     TooManyHardlinks,
-#     TimedOut,
-#     StaleNetworkFileHandle,
-#     NotADirectory,
-#     ReadOnlyFilesystem,
-#     DirectoryNotEmpty,
-#     OutOfMemory,
-#     Unsupported,
-#     Unrecognized I32 Str,
-# ]
diff --git a/examples/cli/cli-platform/InternalHttp.roc b/examples/cli/cli-platform/InternalHttp.roc
deleted file mode 100644
index 0b8037a694..0000000000
--- a/examples/cli/cli-platform/InternalHttp.roc
+++ /dev/null
@@ -1,48 +0,0 @@
-interface InternalHttp
-    exposes [Request, Method, Header, TimeoutConfig, Part, Body, Response, Metadata, Error]
-    imports []
-
-Request : {
-    method : Method,
-    headers : List Header,
-    url : Str,
-    body : Body,
-    timeout : TimeoutConfig,
-}
-
-Method : [Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch]
-
-Header : [Header Str Str]
-
-# Name is distinguished from the Timeout tag used in Response and Error
-TimeoutConfig : [TimeoutMilliseconds U64, NoTimeout]
-
-Part : [Part Str (List U8)]
-
-Body : [
-    Body [MimeType Str] (List U8),
-    EmptyBody,
-]
-
-Response : [
-    BadRequest Str,
-    Timeout,
-    NetworkError,
-    BadStatus Metadata (List U8),
-    GoodStatus Metadata (List U8),
-]
-
-Metadata : {
-    url : Str,
-    statusCode : U16,
-    statusText : Str,
-    headers : List Header,
-}
-
-Error : [
-    BadRequest Str,
-    Timeout,
-    NetworkError,
-    BadStatus U16,
-    BadBody Str,
-]
diff --git a/examples/cli/cli-platform/InternalPath.roc b/examples/cli/cli-platform/InternalPath.roc
deleted file mode 100644
index 10ebe7f0e5..0000000000
--- a/examples/cli/cli-platform/InternalPath.roc
+++ /dev/null
@@ -1,73 +0,0 @@
-interface InternalPath
-    exposes [
-        UnwrappedPath,
-        InternalPath,
-        wrap,
-        unwrap,
-        toBytes,
-        fromArbitraryBytes,
-        fromOsBytes,
-    ]
-    imports []
-
-InternalPath := UnwrappedPath
-
-UnwrappedPath : [
-    # We store these separately for two reasons:
-    # 1. If I'm calling an OS API, passing a path I got from the OS is definitely safe.
-    #    However, passing a Path I got from a RocStr might be unsafe; it may contain \0
-    #    characters, which would result in the operation happening on a totally different
-    #    path. As such, we need to check for \0s and fail without calling the OS API if we
-    #    find one in the path.
-    # 2. If I'm converting the Path to a Str, doing that conversion on a Path that was
-    #    created from a RocStr needs no further processing. However, if it came from the OS,
-    #    then we need to know what charset to assume it had, in order to decode it properly.
-    # These come from the OS (e.g. when reading a directory, calling `canonicalize`,
-    # or reading an environment variable - which, incidentally, are nul-terminated),
-    # so we know they are both nul-terminated and do not contain interior nuls.
-    # As such, they can be passed directly to OS APIs.
-    #
-    # Note that the nul terminator byte is right after the end of the length (into the
-    # unused capacity), so this can both be compared directly to other `List U8`s that
-    # aren't nul-terminated, while also being able to be passed directly to OS APIs.
-    FromOperatingSystem (List U8),
-
-    # These come from userspace (e.g. Path.fromBytes), so they need to be checked for interior
-    # nuls and then nul-terminated before the host can pass them to OS APIs.
-    ArbitraryBytes (List U8),
-
-    # This was created as a RocStr, so it might have interior nul bytes but it's definitely UTF-8.
-    # That means we can `toStr` it trivially, but have to validate before sending it to OS
-    # APIs that expect a nul-terminated `char*`.
-    #
-    # Note that both UNIX and Windows APIs will accept UTF-8, because on Windows the host calls
-    # `_setmbcp(_MB_CP_UTF8);` to set the process's Code Page to UTF-8 before doing anything else.
-    # See https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page#-a-vs--w-apis
-    # and https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmbcp?view=msvc-170
-    # for more details on the UTF-8 Code Page in Windows.
-    FromStr Str,
-]
-
-wrap : UnwrappedPath -> InternalPath
-wrap = @InternalPath
-
-unwrap : InternalPath -> UnwrappedPath
-unwrap = \@InternalPath raw -> raw
-
-## TODO do this in the host, and iterate over the Str
-## bytes when possible instead of always converting to
-## a heap-allocated List.
-toBytes : InternalPath -> List U8
-toBytes = \@InternalPath path ->
-    when path is
-        FromOperatingSystem bytes -> bytes
-        ArbitraryBytes bytes -> bytes
-        FromStr str -> Str.toUtf8 str
-
-fromArbitraryBytes : List U8 -> InternalPath
-fromArbitraryBytes = \bytes ->
-    @InternalPath (ArbitraryBytes bytes)
-
-fromOsBytes : List U8 -> InternalPath
-fromOsBytes = \bytes ->
-    @InternalPath (FromOperatingSystem bytes)
diff --git a/examples/cli/cli-platform/InternalTask.roc b/examples/cli/cli-platform/InternalTask.roc
deleted file mode 100644
index 5fd5804003..0000000000
--- a/examples/cli/cli-platform/InternalTask.roc
+++ /dev/null
@@ -1,17 +0,0 @@
-interface InternalTask
-    exposes [Task, fromEffect, toEffect, succeed, fail]
-    imports [Effect.{ Effect }]
-
-Task ok err := Effect (Result ok err)
-
-succeed : ok -> Task ok *
-succeed = \ok -> @Task (Effect.always (Ok ok))
-
-fail : err -> Task * err
-fail = \err -> @Task (Effect.always (Err err))
-
-fromEffect : Effect (Result ok err) -> Task ok err
-fromEffect = \effect -> @Task effect
-
-toEffect : Task ok err -> Effect (Result ok err)
-toEffect = \@Task effect -> effect
diff --git a/examples/cli/cli-platform/Path.roc b/examples/cli/cli-platform/Path.roc
deleted file mode 100644
index e04ca42641..0000000000
--- a/examples/cli/cli-platform/Path.roc
+++ /dev/null
@@ -1,323 +0,0 @@
-interface Path
-    exposes [
-        Path,
-        PathComponent,
-        CanonicalizeErr,
-        WindowsRoot,
-        # toComponents,
-        # walkComponents,
-        display,
-        fromStr,
-        fromBytes,
-        withExtension,
-    ]
-    imports [InternalPath.{ InternalPath }]
-
-## You can canonicalize a [Path] using [Path.canonicalize].
-##
-## Comparing canonical paths is often more reliable than comparing raw ones.
-## For example, `Path.fromStr "foo/bar/../baz" == Path.fromStr "foo/baz"` will return `Bool.false`,
-## because those are different paths even though their canonical equivalents would be equal.
-##
-## Also note that canonicalization reads from the file system (in order to resolve symbolic
-## links, and to convert relative paths into absolute ones). This means that it is not only
-## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
-## may give different answers. An example of a way this could happen is if a symbolic link
-## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
-##
-## Similarly, remember that canonical paths are not guaranteed to refer to a valid file. They
-## might have referred to one when they were canonicalized, but that file may have moved or
-## been deleted since the canonical path was created. So you might [canonicalize] a [Path],
-## and then immediately use that [Path] to read a file from disk, and still get back an error
-## because something relevant changed on the filesystem between the two operations.
-##
-## Also note that different filesystems have different rules for syntactically valid paths.
-## Suppose you're on a machine with two disks, one formatted as ext4 and another as FAT32.
-## It's possible to list the contents of a directory on the ext4 disk, and get a [CanPath] which
-## is valid on that disk, but invalid on the other disk. One way this could happen is if the
-## directory on the ext4 disk has a filename containing a `:` in it. `:` is allowed in ext4
-## paths but is considered invalid in FAT32 paths.
-Path : InternalPath
-
-CanonicalizeErr a : [
-    PathCanonicalizeErr {},
-]a
-
-## Note that the path may not be valid depending on the filesystem where it is used.
-## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not
-## on FAT ones. So if you have multiple disks on the same machine, but they have
-## different filesystems, then this path could be valid on one but invalid on another!
-##
-## It's safest to assume paths are invalid (even syntactically) until given to an operation
-## which uses them to open a file. If that operation succeeds, then the path was valid
-## (at the time). Otherwise, error handling can happen for that operation rather than validating
-## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.).
-fromStr : Str -> Path
-fromStr = \str ->
-    FromStr str
-    |> InternalPath.wrap
-
-## Not all filesystems use Unicode paths. This function can be used to create a path which
-## is not valid Unicode (like a Roc [Str] is), but which is valid for a particular filesystem.
-##
-## Note that if the list contains any `0` bytes, sending this path to any file operations
-## (e.g. `File.read` or `WriteStream.openPath`) will fail.
-fromBytes : List U8 -> Path
-fromBytes = \bytes ->
-    ArbitraryBytes bytes
-    |> InternalPath.wrap
-
-## Note that canonicalization reads from the file system (in order to resolve symbolic
-## links, and to convert relative paths into absolute ones). This means that it is not only
-## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
-## may give different answers. An example of a way this could happen is if a symbolic link
-## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
-##
-## Returns an effect type of `[Metadata, Cwd]` because it can resolve symbolic links
-## and can access the current working directory by turning a relative path into an
-## absolute one (which can prepend the absolute path of the current working directory to
-## the relative path).
-# canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
-## Unfortunately, operating system paths do not include information about which charset
-## they were originally encoded with. It's most common (but not guaranteed) that they will
-## have been encoded with the same charset as the operating system's curent locale (which
-## typically does not change after it is set during installation of the OS), so
-## this should convert a [Path] to a valid string as long as the path was created
-## with the given `Charset`. (Use `Env.charset` to get the current system charset.)
-##
-## For a conversion to [Str] that is lossy but does not return a [Result], see
-## [display].
-# toInner : Path -> [Str Str, Bytes (List U8)]
-## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
-## and converts it to a string using `Str.display`.
-##
-## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens,
-## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
-## instead of returning an error. As such, it's rarely a good idea to use the [Str] returned
-## by this function for any purpose other than displaying it to a user.
-##
-## When you don't know for sure what a path's encoding is, UTF-8 is a popular guess because
-## it's the default on UNIX and also is the encoding used in Roc strings. This platform also
-## automatically runs applications under the [UTF-8 code page](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)
-## on Windows.
-##
-## Converting paths to strings can be an unreliable operation, because operating systems
-## don't record the paths' encodings. This means it's possible for the path to have been
-## encoded with a different character set than UTF-8 even if UTF-8 is the system default,
-## which means when [display] converts them to a string, the string may include gibberish.
-## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863)
-##
-## If you happen to know the `Charset` that was used to encode the path, you can use
-## `toStrUsingCharset` instead of [display].
-display : Path -> Str
-display = \path ->
-    when InternalPath.unwrap path is
-        FromStr str -> str
-        FromOperatingSystem bytes | ArbitraryBytes bytes ->
-            when Str.fromUtf8 bytes is
-                Ok str -> str
-                # TODO: this should use the builtin Str.display to display invalid UTF-8 chars in just the right spots, but that does not exist yet!
-                Err _ -> "�"
-
-# isEq : Path, Path -> Bool
-# isEq = \p1, p2 ->
-#     when InternalPath.unwrap p1 is
-#         FromOperatingSystem bytes1 | ArbitraryBytes bytes1 ->
-#             when InternalPath.unwrap p2 is
-#                 FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2
-#                 # We can't know the encoding that was originally used in the path, so we convert
-#                 # the string to bytes and see if those bytes are equal to the path's bytes.
-#                 #
-#                 # This may sound unreliable, but it's how all paths are compared; since the OS
-#                 # doesn't record which encoding was used to encode the path name, the only
-#                 # reasonable# definition for path equality is byte-for-byte equality.
-#                 FromStr str2 -> Str.isEqUtf8 str2 bytes1
-#         FromStr str1 ->
-#             when InternalPath.unwrap p2 is
-#                 FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2
-#                 FromStr str2 -> str1 == str2
-# compare : Path, Path -> [Lt, Eq, Gt]
-# compare = \p1, p2 ->
-#     when InternalPath.unwrap p1 is
-#         FromOperatingSystem bytes1 | ArbitraryBytes bytes1 ->
-#             when InternalPath.unwrap p2 is
-#                 FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2
-#                 FromStr str2 -> Str.compareUtf8 str2 bytes1 |> Ord.reverse
-#         FromStr str1 ->
-#             when InternalPath.unwrap p2 is
-#                 FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Str.compareUtf8 str1 bytes2
-#                 FromStr str2 -> Ord.compare str1 str2
-## ## Path Components
-PathComponent : [
-    ParentDir, # e.g. ".." on UNIX or Windows
-    CurrentDir, # e.g. "." on UNIX
-    Named Str, # e.g. "stuff" on UNIX
-    DirSep Str, # e.g. "/" on UNIX, "\" or "/" on Windows. Or, sometimes, "¥" on Windows - see
-    # https://docs.microsoft.com/en-us/windows/win32/intl/character-sets-used-in-file-names
-    #
-    # This is included as an option so if you're transforming part of a path,
-    # you can write back whatever separator was originally used.
-]
-
-## Note that a root of Slash (`/`) has different meanings on UNIX and on Windows.
-## * On UNIX, `/` at the beginning of the path refers to the filesystem root, and means the path is absolute.
-## * On Windows, `/` at the beginning of the path refers to the current disk drive, and means the path is relative.
-# PathRoot : [
-#     WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows
-#     Slash,
-#     None,
-# ]
-# TODO see https://doc.rust-lang.org/std/path/enum.Prefix.html
-WindowsRoot : []
-
-## Returns the root of the path.
-# root : Path -> PathRoot
-# components : Path -> { root : PathRoot, components : List PathComponent }
-## Walk over the path's [components].
-# walk :
-#     Path,
-#     # None means it's a relative path
-#     (PathRoot -> state),
-#     (state, PathComponent -> state)
-#     -> state
-## Returns the path without its last [`component`](#components).
-##
-## If the path was empty or contained only a [root](#PathRoot), returns the original path.
-# dropLast : Path -> Path
-# TODO see https://doc.rust-lang.org/std/path/struct.Path.html#method.join for
-# the definition of the term "adjoin" - should we use that term?
-# append : Path, Path -> Path
-# append = \prefix, suffix ->
-#     content =
-#         when InternalPath.unwrap prefix is
-#             FromOperatingSystem prefixBytes ->
-#                 when InternalPath.unwrap suffix is
-#                     FromOperatingSystem suffixBytes ->
-#                         # Neither prefix nor suffix had interior nuls, so the answer won't either
-#                         List.concat prefixBytes suffixBytes
-#                         |> FromOperatingSystem
-#                     ArbitraryBytes suffixBytes ->
-#                         List.concat prefixBytes suffixBytes
-#                         |> ArbitraryBytes
-#                     FromStr suffixStr ->
-#                         # Append suffixStr by writing it to the end of prefixBytes
-#                         Str.appendToUtf8 suffixStr prefixBytes (List.len prefixBytes)
-#                         |> ArbitraryBytes
-#             ArbitraryBytes prefixBytes ->
-#                 when InternalPath.unwrap suffix is
-#                     ArbitraryBytes suffixBytes | FromOperatingSystem suffixBytes ->
-#                         List.concat prefixBytes suffixBytes
-#                         |> ArbitraryBytes
-#                     FromStr suffixStr ->
-#                         # Append suffixStr by writing it to the end of prefixBytes
-#                         Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
-#                         |> ArbitraryBytes
-#             FromStr prefixStr ->
-#                 when InternalPath.unwrap suffix is
-#                     ArbitraryBytes suffixBytes | FromOperatingSystem suffixBytes ->
-#                         List.concat suffixBytes (Str.toUtf8 prefixStr)
-#                         |> ArbitraryBytes
-#                     FromStr suffixStr ->
-#                         Str.concat prefixStr suffixStr
-#                         |> FromStr
-#     InternalPath.wrap content
-# appendStr : Path, Str -> Path
-# appendStr = \prefix, suffixStr ->
-#     content =
-#         when InternalPath.unwrap prefix is
-#             FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
-#                 # Append suffixStr by writing it to the end of prefixBytes
-#                 Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
-#                 |> ArbitraryBytes
-#             FromStr prefixStr ->
-#                 Str.concat prefixStr suffixStr
-#                 |> FromStr
-#     InternalPath.wrap content
-## Returns `Bool.true` if the first path begins with the second.
-# startsWith : Path, Path -> Bool
-# startsWith = \path, prefix ->
-#     when InternalPath.unwrap path is
-#         FromOperatingSystem pathBytes | ArbitraryBytes pathBytes ->
-#             when InternalPath.unwrap prefix is
-#                 FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
-#                     List.startsWith pathBytes prefixBytes
-#                 FromStr prefixStr ->
-#                     strLen = Str.countUtf8Bytes prefixStr
-#                     if strLen == List.len pathBytes then
-#                         # Grab the first N bytes of the list, where N = byte length of string.
-#                         bytesPrefix = List.takeAt pathBytes 0 strLen
-#                         # Compare the two for equality.
-#                         Str.isEqUtf8 prefixStr bytesPrefix
-#                     else
-#                         Bool.false
-#         FromStr pathStr ->
-#             when InternalPath.unwrap prefix is
-#                 FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
-#                     Str.startsWithUtf8 pathStr prefixBytes
-#                 FromStr prefixStr ->
-#                     Str.startsWith pathStr prefixStr
-## Returns `Bool.true` if the first path ends with the second.
-# endsWith : Path, Path -> Bool
-# endsWith = \path, prefix ->
-#     when InternalPath.unwrap path is
-#         FromOperatingSystem pathBytes | ArbitraryBytes pathBytes ->
-#             when InternalPath.unwrap suffix is
-#                 FromOperatingSystem suffixBytes | ArbitraryBytes suffixBytes ->
-#                     List.endsWith pathBytes suffixBytes
-#                 FromStr suffixStr ->
-#                     strLen = Str.countUtf8Bytes suffixStr
-#                     if strLen == List.len pathBytes then
-#                         # Grab the last N bytes of the list, where N = byte length of string.
-#                         bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen
-#                         # Compare the two for equality.
-#                         Str.startsWithUtf8 suffixStr bytesSuffix
-#                     else
-#                         Bool.false
-#         FromStr pathStr ->
-#             when InternalPath.unwrap suffix is
-#                 FromOperatingSystem suffixBytes | ArbitraryBytes suffixBytes ->
-#                     Str.endsWithUtf8 pathStr suffixBytes
-#                 FromStr suffixStr ->
-#                     Str.endsWith pathStr suffixStr
-# TODO https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
-# TODO idea: what if it's File.openRead and File.openWrite? And then e.g. File.metadata,
-# File.isDir, etc.
-## If the last component of this path has no `.`, appends `.` followed by the given string.
-## Otherwise, replaces everything after the last `.` with the given string.
-##
-## Examples:
-##
-##     Path.fromStr "foo/bar/baz" |> Path.withExtension "txt" #    foo/bar/baz.txt
-##     Path.fromStr "foo/bar/baz." |> Path.withExtension "txt" #   foo/bar/baz.txt
-##     Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt" # foo/bar/baz.txt
-withExtension : Path, Str -> Path
-withExtension = \path, extension ->
-    when InternalPath.unwrap path is
-        FromOperatingSystem bytes | ArbitraryBytes bytes ->
-            beforeDot =
-                when List.splitLast bytes (Num.toU8 '.') is
-                    Ok { before } -> before
-                    Err NotFound -> bytes
-
-            beforeDot
-            |> List.reserve (1 + Str.countUtf8Bytes extension)
-            |> List.append (Num.toU8 '.')
-            |> List.concat (Str.toUtf8 extension)
-            |> ArbitraryBytes
-            |> InternalPath.wrap
-
-        FromStr str ->
-            beforeDot =
-                when Str.splitLast str "." is
-                    Ok { before } -> before
-                    Err NotFound -> str
-
-            beforeDot
-            |> Str.reserve (1 + Str.countUtf8Bytes extension)
-            |> Str.concat "."
-            |> Str.concat extension
-            |> FromStr
-            |> InternalPath.wrap
-
-# NOTE: no withExtensionBytes because it's too narrow. If you really need to get some
-# non-Unicode in there, do it with
diff --git a/examples/cli/cli-platform/Process.roc b/examples/cli/cli-platform/Process.roc
deleted file mode 100644
index b8d8a0551f..0000000000
--- a/examples/cli/cli-platform/Process.roc
+++ /dev/null
@@ -1,13 +0,0 @@
-interface Process
-    exposes [exit]
-    imports [Task.{ Task }, InternalTask, Effect]
-
-## Exit the process with
-##
-##     {} <- Stderr.line "Exiting right now!" |> Task.await
-##     Process.exit 1
-exit : U8 -> Task {} *
-exit = \code ->
-    Effect.processExit code
-    |> Effect.map \_ -> Ok {}
-    |> InternalTask.fromEffect
diff --git a/examples/cli/cli-platform/Stderr.roc b/examples/cli/cli-platform/Stderr.roc
deleted file mode 100644
index 557a0a21a9..0000000000
--- a/examples/cli/cli-platform/Stderr.roc
+++ /dev/null
@@ -1,15 +0,0 @@
-interface Stderr
-    exposes [line, write]
-    imports [Effect, Task.{ Task }, InternalTask]
-
-line : Str -> Task {} *
-line = \str ->
-    Effect.stderrLine str
-    |> Effect.map (\_ -> Ok {})
-    |> InternalTask.fromEffect
-
-write : Str -> Task {} *
-write = \str ->
-    Effect.stderrWrite str
-    |> Effect.map (\_ -> Ok {})
-    |> InternalTask.fromEffect
diff --git a/examples/cli/cli-platform/Stdin.roc b/examples/cli/cli-platform/Stdin.roc
deleted file mode 100644
index f3a6124f53..0000000000
--- a/examples/cli/cli-platform/Stdin.roc
+++ /dev/null
@@ -1,9 +0,0 @@
-interface Stdin
-    exposes [line]
-    imports [Effect, Task.{ Task }, InternalTask]
-
-line : Task Str *
-line =
-    Effect.stdinLine
-    |> Effect.map Ok
-    |> InternalTask.fromEffect
diff --git a/examples/cli/cli-platform/Stdout.roc b/examples/cli/cli-platform/Stdout.roc
deleted file mode 100644
index 0b651563ec..0000000000
--- a/examples/cli/cli-platform/Stdout.roc
+++ /dev/null
@@ -1,15 +0,0 @@
-interface Stdout
-    exposes [line, write]
-    imports [Effect, Task.{ Task }, InternalTask]
-
-line : Str -> Task {} *
-line = \str ->
-    Effect.stdoutLine str
-    |> Effect.map (\_ -> Ok {})
-    |> InternalTask.fromEffect
-
-write : Str -> Task {} *
-write = \str ->
-    Effect.stdoutWrite str
-    |> Effect.map (\_ -> Ok {})
-    |> InternalTask.fromEffect
diff --git a/examples/cli/cli-platform/Task.roc b/examples/cli/cli-platform/Task.roc
deleted file mode 100644
index e350559665..0000000000
--- a/examples/cli/cli-platform/Task.roc
+++ /dev/null
@@ -1,102 +0,0 @@
-interface Task
-    exposes [Task, succeed, fail, await, map, mapFail, onFail, attempt, forever, loop, fromResult]
-    imports [Effect, InternalTask]
-
-Task ok err : InternalTask.Task ok err
-
-forever : Task val err -> Task * err
-forever = \task ->
-    looper = \{} ->
-        task
-        |> InternalTask.toEffect
-        |> Effect.map
-            \res ->
-                when res is
-                    Ok _ -> Step {}
-                    Err e -> Done (Err e)
-
-    Effect.loop {} looper
-    |> InternalTask.fromEffect
-
-loop : state, (state -> Task [Step state, Done done] err) -> Task done err
-loop = \state, step ->
-    looper = \current ->
-        step current
-        |> InternalTask.toEffect
-        |> Effect.map
-            \res ->
-                when res is
-                    Ok (Step newState) -> Step newState
-                    Ok (Done result) -> Done (Ok result)
-                    Err e -> Done (Err e)
-
-    Effect.loop state looper
-    |> InternalTask.fromEffect
-
-succeed : ok -> Task ok *
-succeed = \ok -> InternalTask.succeed ok
-
-fail : err -> Task * err
-fail = \err -> InternalTask.fail err
-
-attempt : Task a b, (Result a b -> Task c d) -> Task c d
-attempt = \task, transform ->
-    effect = Effect.after
-        (InternalTask.toEffect task)
-        \result ->
-            when result is
-                Ok ok -> transform (Ok ok) |> InternalTask.toEffect
-                Err err -> transform (Err err) |> InternalTask.toEffect
-
-    InternalTask.fromEffect effect
-
-await : Task a err, (a -> Task b err) -> Task b err
-await = \task, transform ->
-    effect = Effect.after
-        (InternalTask.toEffect task)
-        \result ->
-            when result is
-                Ok a -> transform a |> InternalTask.toEffect
-                Err err -> Task.fail err |> InternalTask.toEffect
-
-    InternalTask.fromEffect effect
-
-onFail : Task ok a, (a -> Task ok b) -> Task ok b
-onFail = \task, transform ->
-    effect = Effect.after
-        (InternalTask.toEffect task)
-        \result ->
-            when result is
-                Ok a -> Task.succeed a |> InternalTask.toEffect
-                Err err -> transform err |> InternalTask.toEffect
-
-    InternalTask.fromEffect effect
-
-map : Task a err, (a -> b) -> Task b err
-map = \task, transform ->
-    effect = Effect.after
-        (InternalTask.toEffect task)
-        \result ->
-            when result is
-                Ok ok -> Task.succeed (transform ok) |> InternalTask.toEffect
-                Err err -> Task.fail err |> InternalTask.toEffect
-
-    InternalTask.fromEffect effect
-
-mapFail : Task ok a, (a -> b) -> Task ok b
-mapFail = \task, transform ->
-    effect = Effect.after
-        (InternalTask.toEffect task)
-        \result ->
-            when result is
-                Ok ok -> Task.succeed ok |> InternalTask.toEffect
-                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/cli/cli-platform/Url.roc b/examples/cli/cli-platform/Url.roc
deleted file mode 100644
index 7aea2602f1..0000000000
--- a/examples/cli/cli-platform/Url.roc
+++ /dev/null
@@ -1,431 +0,0 @@
-interface Url
-    exposes [
-        Url,
-        append,
-        fromStr,
-        toStr,
-        appendParam,
-        hasQuery,
-        hasFragment,
-        query,
-        fragment,
-        reserve,
-        withQuery,
-        withFragment,
-    ]
-    imports []
-
-## A [Uniform Resource Locator](https://en.wikipedia.org/wiki/URL).
-##
-## It could be an absolute address, such as `https://roc-lang.org/authors` or
-## a relative address, such as `/authors`. You can create one using [Url.fromStr].
-Url := Str
-
-## Reserve the given number of bytes as extra capacity. This can avoid reallocation
-## when calling multiple functions that increase the length of the URL.
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.reserve 50 # We're about to add 50 UTF-8 bytes to it
-##         |> Url.append "stuff"
-##         |> Url.appendParam "café" "du Monde"
-##         |> Url.appendParam "email" "hi@example.com"
-##     # https://example.com/stuff?caf%C3%A9=du%20Monde&email=hi%40example.com
-##
-## The [Str.countUtf8Bytes] function can be helpful in finding out how many bytes to reserve.
-##
-## There is no `Url.withCapacity` because it's better to reserve extra capacity
-## on a [Str] first, and then pass that string to [Url.fromStr]. This function will make use
-## of the extra capacity.
-reserve : Url, Nat -> Url
-reserve = \@Url str, cap ->
-    @Url (Str.reserve str cap)
-
-## Create a [Url] without validating or [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding)
-## anything.
-##
-##     Url.fromStr "https://example.com#stuff"
-##     # https://example.com#stuff
-##
-## URLs can be absolute, like `https://example.com`, or they can be relative, like `/blah`.
-##
-##     Url.fromStr "/this/is#relative"
-##     # /this/is#relative
-##
-## Since nothing is validated, this can return invalid URLs.
-##
-##     Url.fromStr "https://this is not a valid URL, not at all!"
-##     # https://this is not a valid URL, not at all!
-##
-## Naturally, passing invalid URLs to functions that need valid ones will tend to result in errors.
-fromStr : Str -> Url
-fromStr = \str -> @Url str
-
-## Return a [Str] representation of this URL.
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.append "two words"
-##         |> Url.toStr
-##     # "https://example.com/two%20words"
-toStr : Url -> Str
-toStr = \@Url str -> str
-
-## [Percent-encodes](https://en.wikipedia.org/wiki/Percent-encoding) a
-## [path component](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax)
-## and appends to the end of the URL's path.
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.append "some stuff"
-##     # https://example.com/some%20stuff
-##
-## This will be appended before any queries and fragments.
-##
-##     Url.fromStr "https://example.com?search=blah#fragment"
-##         |> Url.append "stuff"
-##     # https://example.com/stuff?search=blah#fragment
-##
-## If the given path string begins with `"/"` and the URL already ends with `"/"`, one
-## will be ignored. This avoids turning a single slash into a double slash.
-##
-##     Url.fromStr "https://example.com/things/"
-##         |> Url.append "/stuff/"
-##         |> Url.append "/more/etc/"
-##     # https://example.com/things/stuff/more/etc/"
-##
-## If either the given URL or the given string is empty, no `"/"` will be added.
-##
-##     Url.fromStr "https://example.com/things"
-##         |> Url.append ""
-##     # https://example.com/things
-append : Url, Str -> Url
-append = \@Url urlStr, suffixUnencoded ->
-    suffix = percentEncode suffixUnencoded
-
-    when Str.splitFirst urlStr "?" is
-        Ok { before, after } ->
-            bytes =
-                Str.countUtf8Bytes before
-                + 1 # for "/"
-                + Str.countUtf8Bytes suffix
-                + 1 # for "?"
-                + Str.countUtf8Bytes after
-
-            before
-            |> Str.reserve bytes
-            |> appendHelp suffix
-            |> Str.concat "?"
-            |> Str.concat after
-            |> @Url
-
-        Err NotFound ->
-            # There wasn't a query, but there might still be a fragment
-            when Str.splitFirst urlStr "#" is
-                Ok { before, after } ->
-                    bytes =
-                        Str.countUtf8Bytes before
-                        + 1 # for "/"
-                        + Str.countUtf8Bytes suffix
-                        + 1 # for "#"
-                        + Str.countUtf8Bytes after
-
-                    before
-                    |> Str.reserve bytes
-                    |> appendHelp suffix
-                    |> Str.concat "#"
-                    |> Str.concat after
-                    |> @Url
-
-                Err NotFound ->
-                    # No query and no fragment, so just append it
-                    @Url (appendHelp urlStr suffix)
-
-## Internal helper
-appendHelp : Str, Str -> Str
-appendHelp = \prefix, suffix ->
-    if Str.endsWith prefix "/" then
-        if Str.startsWith suffix "/" then
-            # Avoid a double-slash by appending only the part of the suffix after the "/"
-            when Str.splitFirst suffix "/" is
-                Ok { after } ->
-                    # TODO `expect before == ""`
-                    Str.concat prefix after
-
-                Err NotFound ->
-                    # This should never happen, because we already verified
-                    # that the suffix startsWith "/"
-                    # TODO `expect Bool.false` here with a comment
-                    Str.concat prefix suffix
-        else
-            # prefix ends with "/" but suffix doesn't start with one, so just append.
-            Str.concat prefix suffix
-    else if Str.startsWith suffix "/" then
-        # Suffix starts with "/" but prefix doesn't end with one, so just append them.
-        Str.concat prefix suffix
-    else if Str.isEmpty prefix then
-        # Prefix is empty; return suffix.
-        suffix
-    else if Str.isEmpty suffix then
-        # Suffix is empty; return prefix.
-        prefix
-    else
-        # Neither is empty, but neither has a "/", so add one in between.
-        prefix
-        |> Str.concat "/"
-        |> Str.concat suffix
-
-## Internal helper. This is intentionally unexposed so that you don't accidentally
-## double-encode things. If you really want to percent-encode an arbitrary string,
-## you can always do:
-##
-##     Url.fromStr ""
-##         |> Url.append myStrToEncode
-##         |> Url.toStr
-##
-## Note that it's not necessary to situationally encode spaces as `+` instead of `%20` -
-## it's apparently always safe to use `%20` (but not always safe to use `+`):
-## https://stackoverflow.com/questions/2678551/when-should-space-be-encoded-to-plus-or-20/47188851#47188851
-percentEncode : Str -> Str
-percentEncode = \input ->
-    # Optimistically assume we won't need any percent encoding, and can have
-    # the same capacity as the input string. If we're wrong, it will get doubled.
-    initialOutput = strWithCapacity (Str.countUtf8Bytes input)
-
-    # TODO use Str.walkUtf8 once it exists
-    Str.walkUtf8WithIndex input initialOutput \output, byte, _index ->
-        # Spec for percent-encoding: https://www.ietf.org/rfc/rfc3986.txt
-        if
-            (byte >= 97 && byte <= 122) # lowercase ASCII
-            || (byte >= 65 && byte <= 90) # uppercase ASCII
-            || (byte >= 48 && byte <= 57) # digit
-        then
-            # This is the most common case: an unreserved character,
-            # which needs no encoding in a path
-            Str.appendScalar output (Num.toU32 byte)
-            |> Result.withDefault "" # this will never fail
-        else
-            when byte is
-                46 # '.'
-                | 95 # '_'
-                | 126 # '~'
-                | 150 -> # '-'
-                    # These special characters can all be unescaped in paths
-                    Str.appendScalar output (Num.toU32 byte)
-                    |> Result.withDefault "" # this will never fail
-
-                _ ->
-                    # This needs encoding in a path
-                    suffix =
-                        Str.toUtf8 percentEncoded
-                        |> List.sublist { len: 3, start: 3 * Num.toNat byte }
-                        |> Str.fromUtf8
-                        |> Result.withDefault "" # This will never fail
-
-                    Str.concat output suffix
-
-## Adds a [Str] query parameter to the end of the [Url]. The key
-## and value both get [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding).
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.appendParam "email" "someone@example.com"
-##     # https://example.com?email=someone%40example.com
-##
-## This can be called multiple times on the same URL.
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.appendParam "café" "du Monde"
-##         |> Url.appendParam "email" "hi@example.com"
-##     # https://example.com?caf%C3%A9=du%20Monde&email=hi%40example.com
-appendParam : Url, Str, Str -> Url
-appendParam = \@Url urlStr, key, value ->
-    { withoutFragment, afterQuery } =
-        when Str.splitLast urlStr "#" is
-            Ok { before, after } ->
-                # The fragment is almost certainly going to be a small string,
-                # so this interpolation should happen on the stack.
-                { withoutFragment: before, afterQuery: "#\(after)" }
-
-            Err NotFound ->
-                { withoutFragment: urlStr, afterQuery: "" }
-
-    encodedKey = percentEncode key
-    encodedValue = percentEncode value
-
-    bytes =
-        Str.countUtf8Bytes withoutFragment
-        + 1 # for "?" or "&"
-        + Str.countUtf8Bytes encodedKey
-        + 1 # for "="
-        + Str.countUtf8Bytes encodedValue
-        + Str.countUtf8Bytes afterQuery
-
-    withoutFragment
-    |> Str.reserve bytes
-    |> Str.concat (if hasQuery (@Url withoutFragment) then "&" else "?")
-    |> Str.concat encodedKey
-    |> Str.concat "="
-    |> Str.concat encodedValue
-    |> Str.concat afterQuery
-    |> @Url
-
-## Replaces the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after
-## the `?`, if it has one, but before any `#` it might have.
-##
-##     Url.fromStr "https://example.com?key1=val1&key2=val2#stuff"
-##         |> Url.withQuery "newQuery=thisRightHere"
-##     # https://example.com?newQuery=thisRightHere#stuff
-##
-## Passing `""` removes the `?` (if there was one).
-##
-##     Url.fromStr "https://example.com?key1=val1&key2=val2#stuff"
-##         |> Url.withQuery ""
-##     # https://example.com#stuff
-withQuery : Url, Str -> Url
-withQuery = \@Url urlStr, queryStr ->
-    { withoutFragment, afterQuery } =
-        when Str.splitLast urlStr "#" is
-            Ok { before, after } ->
-                # The fragment is almost certainly going to be a small string,
-                # so this interpolation should happen on the stack.
-                { withoutFragment: before, afterQuery: "#\(after)" }
-
-            Err NotFound ->
-                { withoutFragment: urlStr, afterQuery: "" }
-
-    beforeQuery =
-        when Str.splitLast withoutFragment "?" is
-            Ok { before } -> before
-            Err NotFound -> withoutFragment
-
-    if Str.isEmpty queryStr then
-        @Url (Str.concat beforeQuery afterQuery)
-    else
-        bytes =
-            Str.countUtf8Bytes beforeQuery
-            + 1 # for "?"
-            + Str.countUtf8Bytes queryStr
-            + Str.countUtf8Bytes afterQuery
-
-        beforeQuery
-        |> Str.reserve bytes
-        |> Str.concat "?"
-        |> Str.concat queryStr
-        |> Str.concat afterQuery
-        |> @Url
-
-## Returns the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after
-## the `?`, if it has one, but before any `#` it might have.
-##
-##     Url.fromStr "https://example.com?key1=val1&key2=val2&key3=val3#stuff"
-##         |> Url.query
-##     # "key1=val1&key2=val2&key3=val3"
-##
-## Returns `""` if the URL has no query.
-##
-##     Url.fromStr "https://example.com#stuff"
-##         |> Url.query
-##     # ""
-query : Url -> Str
-query = \@Url urlStr ->
-    withoutFragment =
-        when Str.splitLast urlStr "#" is
-            Ok { before } -> before
-            Err NotFound -> urlStr
-
-    when Str.splitLast withoutFragment "?" is
-        Ok { after } -> after
-        Err NotFound -> ""
-
-## Returns `Bool.true` if the URL has a `?` in it.
-##
-##     Url.fromStr "https://example.com?key=value#stuff"
-##         |> Url.hasQuery
-##     # Bool.true
-##
-##     Url.fromStr "https://example.com#stuff"
-##         |> Url.hasQuery
-##     # Bool.false
-hasQuery : Url -> Bool
-hasQuery = \@Url urlStr ->
-    # TODO use Str.contains once it exists. It should have a "fast path"
-    # with SIMD iteration if the string is small enough to fit in a SIMD register.
-    Str.toUtf8 urlStr
-    |> List.contains (Num.toU8 '?')
-
-## Returns the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax)—the part after
-## the `#`, if it has one.
-##
-##     Url.fromStr "https://example.com#stuff"
-##         |> Url.fragment
-##     # "stuff"
-##
-## Returns `""` if the URL has no fragment.
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.fragment
-##     # ""
-fragment : Url -> Str
-fragment = \@Url urlStr ->
-    when Str.splitLast urlStr "#" is
-        Ok { after } -> after
-        Err NotFound -> ""
-
-## Replaces the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax).
-##
-##     Url.fromStr "https://example.com#stuff"
-##         |> Url.withFragment "things"
-##     # https://example.com#things
-##
-## If the URL didn't have a fragment, adds one.
-##
-##     Url.fromStr "https://example.com"
-##         |> Url.withFragment "things"
-##     # https://example.com#things
-##
-## Passing `""` removes the fragment.
-##
-##     Url.fromStr "https://example.com#stuff"
-##         |> Url.withFragment ""
-##     # https://example.com
-withFragment : Url, Str -> Url
-withFragment = \@Url urlStr, fragmentStr ->
-    when Str.splitLast urlStr "#" is
-        Ok { before } ->
-            if Str.isEmpty fragmentStr then
-                # If the given fragment is empty, remove the URL's fragment
-                @Url before
-            else
-                # Replace the URL's old fragment with this one, discarding `after`
-                @Url "\(before)#\(fragmentStr)"
-
-        Err NotFound ->
-            if Str.isEmpty fragmentStr then
-                # If the given fragment is empty, leave the URL as having no fragment
-                @Url urlStr
-            else
-                # The URL didn't have a fragment, so give it this one
-                @Url "\(urlStr)#\(fragmentStr)"
-
-## Returns `Bool.true` if the URL has a `#` in it.
-##
-##     Url.fromStr "https://example.com?key=value#stuff"
-##         |> Url.hasFragment
-##     # Bool.true
-##
-##     Url.fromStr "https://example.com?key=value"
-##         |> Url.hasFragment
-##     # Bool.false
-hasFragment : Url -> Bool
-hasFragment = \@Url urlStr ->
-    # TODO use Str.contains once it exists. It should have a "fast path"
-    # with SIMD iteration if the string is small enough to fit in a SIMD register.
-    Str.toUtf8 urlStr
-    |> List.contains (Num.toU8 '#')
-
-strWithCapacity : Nat -> Str
-strWithCapacity = \cap ->
-    Str.reserve "" cap
-
-# Adapted from the percent-encoding crate, © The rust-url developers, Apache2-licensed
-#
-# https://github.com/servo/rust-url/blob/e12d76a61add5bc09980599c738099feaacd1d0d/percent_encoding/src/lib.rs#L183
-percentEncoded : Str
-percentEncoded = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
diff --git a/examples/cli/cli-platform/build.rs b/examples/cli/cli-platform/build.rs
deleted file mode 100644
index 47763b34c3..0000000000
--- a/examples/cli/cli-platform/build.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-fn main() {
-    #[cfg(not(windows))]
-    println!("cargo:rustc-link-lib=dylib=app");
-
-    #[cfg(windows)]
-    println!("cargo:rustc-link-lib=dylib=libapp");
-
-    println!("cargo:rustc-link-search=.");
-}
diff --git a/examples/cli/cli-platform/host.c b/examples/cli/cli-platform/host.c
deleted file mode 100644
index 03247ebb14..0000000000
--- a/examples/cli/cli-platform/host.c
+++ /dev/null
@@ -1,3 +0,0 @@
-extern unsigned char rust_main();
-
-int main() { return (int)rust_main(); }
diff --git a/examples/cli/cli-platform/main.roc b/examples/cli/cli-platform/main.roc
deleted file mode 100644
index c8f90a9191..0000000000
--- a/examples/cli/cli-platform/main.roc
+++ /dev/null
@@ -1,9 +0,0 @@
-platform "cli"
-    requires {} { main : Task {} [] }
-    exposes []
-    packages {}
-    imports [Task.{ Task }]
-    provides [mainForHost]
-
-mainForHost : Task {} []
-mainForHost = main
diff --git a/examples/cli/cli-platform/src/file_glue.rs b/examples/cli/cli-platform/src/file_glue.rs
deleted file mode 100644
index dcef850715..0000000000
--- a/examples/cli/cli-platform/src/file_glue.rs
+++ /dev/null
@@ -1,2643 +0,0 @@
-// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
-
-#![allow(unused_unsafe)]
-#![allow(dead_code)]
-#![allow(unused_mut)]
-#![allow(non_snake_case)]
-#![allow(non_camel_case_types)]
-#![allow(non_upper_case_globals)]
-#![allow(clippy::undocumented_unsafe_blocks)]
-#![allow(clippy::redundant_static_lifetimes)]
-#![allow(clippy::unused_unit)]
-#![allow(clippy::missing_safety_doc)]
-#![allow(clippy::let_and_return)]
-#![allow(clippy::missing_safety_doc)]
-#![allow(clippy::redundant_static_lifetimes)]
-#![allow(clippy::needless_borrow)]
-#![allow(clippy::clone_on_copy)]
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_ReadErr {
-    Interrupted = 0,
-    InvalidFilename = 1,
-    NotFound = 2,
-    OutOfMemory = 3,
-    PermissionDenied = 4,
-    StaleNetworkFileHandle = 5,
-    TimedOut = 6,
-    TooManyHardlinks = 7,
-    TooManySymlinks = 8,
-    Unrecognized = 9,
-    Unsupported = 10,
-}
-
-impl core::fmt::Debug for discriminant_ReadErr {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::Interrupted => f.write_str("discriminant_ReadErr::Interrupted"),
-            Self::InvalidFilename => f.write_str("discriminant_ReadErr::InvalidFilename"),
-            Self::NotFound => f.write_str("discriminant_ReadErr::NotFound"),
-            Self::OutOfMemory => f.write_str("discriminant_ReadErr::OutOfMemory"),
-            Self::PermissionDenied => f.write_str("discriminant_ReadErr::PermissionDenied"),
-            Self::StaleNetworkFileHandle => f.write_str("discriminant_ReadErr::StaleNetworkFileHandle"),
-            Self::TimedOut => f.write_str("discriminant_ReadErr::TimedOut"),
-            Self::TooManyHardlinks => f.write_str("discriminant_ReadErr::TooManyHardlinks"),
-            Self::TooManySymlinks => f.write_str("discriminant_ReadErr::TooManySymlinks"),
-            Self::Unrecognized => f.write_str("discriminant_ReadErr::Unrecognized"),
-            Self::Unsupported => f.write_str("discriminant_ReadErr::Unsupported"),
-        }
-    }
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "wasm32",
-    target_arch = "x86"
-))]
-#[repr(C)]
-pub union ReadErr {
-    Unrecognized: core::mem::ManuallyDrop,
-    _sizer: [u8; 20],
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "wasm32",
-    target_arch = "x86"
-))]
-#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(C)]
-struct ReadErr_Unrecognized {
-    pub f0: i32,
-    pub f1: roc_std::RocStr,
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_WriteErr {
-    AlreadyExists = 0,
-    ExecutableFileBusy = 1,
-    FileTooLarge = 2,
-    FilesystemQuotaExceeded = 3,
-    Interrupted = 4,
-    InvalidFilename = 5,
-    NotFound = 6,
-    OutOfMemory = 7,
-    PermissionDenied = 8,
-    ReadOnlyFilesystem = 9,
-    ResourceBusy = 10,
-    StaleNetworkFileHandle = 11,
-    StorageFull = 12,
-    TimedOut = 13,
-    TooManyHardlinks = 14,
-    TooManySymlinks = 15,
-    Unrecognized = 16,
-    Unsupported = 17,
-    WasADirectory = 18,
-    WriteZero = 19,
-}
-
-impl core::fmt::Debug for discriminant_WriteErr {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::AlreadyExists => f.write_str("discriminant_WriteErr::AlreadyExists"),
-            Self::ExecutableFileBusy => f.write_str("discriminant_WriteErr::ExecutableFileBusy"),
-            Self::FileTooLarge => f.write_str("discriminant_WriteErr::FileTooLarge"),
-            Self::FilesystemQuotaExceeded => f.write_str("discriminant_WriteErr::FilesystemQuotaExceeded"),
-            Self::Interrupted => f.write_str("discriminant_WriteErr::Interrupted"),
-            Self::InvalidFilename => f.write_str("discriminant_WriteErr::InvalidFilename"),
-            Self::NotFound => f.write_str("discriminant_WriteErr::NotFound"),
-            Self::OutOfMemory => f.write_str("discriminant_WriteErr::OutOfMemory"),
-            Self::PermissionDenied => f.write_str("discriminant_WriteErr::PermissionDenied"),
-            Self::ReadOnlyFilesystem => f.write_str("discriminant_WriteErr::ReadOnlyFilesystem"),
-            Self::ResourceBusy => f.write_str("discriminant_WriteErr::ResourceBusy"),
-            Self::StaleNetworkFileHandle => f.write_str("discriminant_WriteErr::StaleNetworkFileHandle"),
-            Self::StorageFull => f.write_str("discriminant_WriteErr::StorageFull"),
-            Self::TimedOut => f.write_str("discriminant_WriteErr::TimedOut"),
-            Self::TooManyHardlinks => f.write_str("discriminant_WriteErr::TooManyHardlinks"),
-            Self::TooManySymlinks => f.write_str("discriminant_WriteErr::TooManySymlinks"),
-            Self::Unrecognized => f.write_str("discriminant_WriteErr::Unrecognized"),
-            Self::Unsupported => f.write_str("discriminant_WriteErr::Unsupported"),
-            Self::WasADirectory => f.write_str("discriminant_WriteErr::WasADirectory"),
-            Self::WriteZero => f.write_str("discriminant_WriteErr::WriteZero"),
-        }
-    }
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "wasm32",
-    target_arch = "x86"
-))]
-#[repr(C)]
-pub union WriteErr {
-    Unrecognized: core::mem::ManuallyDrop,
-    _sizer: [u8; 20],
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "wasm32",
-    target_arch = "x86"
-))]
-#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(C)]
-struct WriteErr_Unrecognized {
-    pub f0: i32,
-    pub f1: roc_std::RocStr,
-}
-
-#[cfg(any(
-    target_arch = "aarch64",
-    target_arch = "x86_64"
-))]
-#[repr(C)]
-pub union ReadErr {
-    Unrecognized: core::mem::ManuallyDrop,
-    _sizer: [u8; 40],
-}
-
-#[cfg(any(
-    target_arch = "aarch64",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(C)]
-struct ReadErr_Unrecognized {
-    pub f1: roc_std::RocStr,
-    pub f0: i32,
-}
-
-#[cfg(any(
-    target_arch = "aarch64",
-    target_arch = "x86_64"
-))]
-#[repr(C)]
-pub union WriteErr {
-    Unrecognized: core::mem::ManuallyDrop,
-    _sizer: [u8; 40],
-}
-
-#[cfg(any(
-    target_arch = "aarch64",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(C)]
-struct WriteErr_Unrecognized {
-    pub f1: roc_std::RocStr,
-    pub f0: i32,
-}
-
-impl ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// Returns which variant this tag union holds. Note that this never includes a payload!
-    pub fn discriminant(&self) -> discriminant_ReadErr {
-        unsafe {
-            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
-
-            core::mem::transmute::(*bytes.as_ptr().add(16))
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// Internal helper
-    fn set_discriminant(&mut self, discriminant: discriminant_ReadErr) {
-        let discriminant_ptr: *mut discriminant_ReadErr = (self as *mut ReadErr).cast();
-
-        unsafe {
-            *(discriminant_ptr.add(16)) = discriminant;
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named Interrupted, which has no payload.
-    pub const Interrupted: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::Interrupted as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the Interrupted tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_Interrupted(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the Interrupted tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_Interrupted(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named InvalidFilename, which has no payload.
-    pub const InvalidFilename: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::InvalidFilename as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the InvalidFilename tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_InvalidFilename(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the InvalidFilename tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_InvalidFilename(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named NotFound, which has no payload.
-    pub const NotFound: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::NotFound as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the NotFound tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_NotFound(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the NotFound tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_NotFound(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named OutOfMemory, which has no payload.
-    pub const OutOfMemory: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::OutOfMemory as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the OutOfMemory tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_OutOfMemory(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the OutOfMemory tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_OutOfMemory(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named PermissionDenied, which has no payload.
-    pub const PermissionDenied: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::PermissionDenied as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the PermissionDenied tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_PermissionDenied(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the PermissionDenied tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_PermissionDenied(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named StaleNetworkFileHandle, which has no payload.
-    pub const StaleNetworkFileHandle: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::StaleNetworkFileHandle as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the StaleNetworkFileHandle tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_StaleNetworkFileHandle(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the StaleNetworkFileHandle tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_StaleNetworkFileHandle(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named TimedOut, which has no payload.
-    pub const TimedOut: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::TimedOut as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the TimedOut tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_TimedOut(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the TimedOut tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_TimedOut(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named TooManyHardlinks, which has no payload.
-    pub const TooManyHardlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::TooManyHardlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the TooManyHardlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_TooManyHardlinks(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the TooManyHardlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_TooManyHardlinks(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named TooManySymlinks, which has no payload.
-    pub const TooManySymlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::TooManySymlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the TooManySymlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_TooManySymlinks(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the TooManySymlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_TooManySymlinks(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Construct a tag named `Unrecognized`, with the appropriate payload
-    pub fn Unrecognized(arg0: i32, arg1: roc_std::RocStr) -> Self {
-            let mut answer = Self {
-                Unrecognized: core::mem::ManuallyDrop::new(ReadErr_Unrecognized {
-                    f0: arg0,
-                    f1: arg1,
-                })
-            };
-
-            answer.set_discriminant(discriminant_ReadErr::Unrecognized);
-
-            answer
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Unsafely assume the given `ReadErr` has a `.discriminant()` of `Unrecognized` and convert it to `Unrecognized`'s payload.
-            /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
-            /// Panics in debug builds if the `.discriminant()` doesn't return `Unrecognized`.
-            pub unsafe fn into_Unrecognized(mut self) -> (i32, roc_std::RocStr) {
-                debug_assert_eq!(self.discriminant(), discriminant_ReadErr::Unrecognized);
-        let payload = {
-            let mut uninitialized = core::mem::MaybeUninit::uninit();
-            let swapped = unsafe {
-                core::mem::replace(
-                    &mut self.Unrecognized,
-                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
-                )
-            };
-
-            core::mem::forget(self);
-
-            core::mem::ManuallyDrop::into_inner(swapped)
-        };
-
-        (
-            payload.f0, 
-            payload.f1
-        )
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Unsafely assume the given `ReadErr` has a `.discriminant()` of `Unrecognized` and return its payload.
-            /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
-            /// Panics in debug builds if the `.discriminant()` doesn't return `Unrecognized`.
-            pub unsafe fn as_Unrecognized(&self) -> (&i32, &roc_std::RocStr) {
-                debug_assert_eq!(self.discriminant(), discriminant_ReadErr::Unrecognized);
-        let payload = &self.Unrecognized;
-
-        (
-            &payload.f0, 
-            &payload.f1
-        )
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named Unsupported, which has no payload.
-    pub const Unsupported: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_ReadErr::Unsupported as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the Unsupported tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_Unsupported(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the Unsupported tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_Unsupported(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// Returns which variant this tag union holds. Note that this never includes a payload!
-    pub fn discriminant(&self) -> discriminant_ReadErr {
-        unsafe {
-            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
-
-            core::mem::transmute::(*bytes.as_ptr().add(32))
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// Internal helper
-    fn set_discriminant(&mut self, discriminant: discriminant_ReadErr) {
-        let discriminant_ptr: *mut discriminant_ReadErr = (self as *mut ReadErr).cast();
-
-        unsafe {
-            *(discriminant_ptr.add(32)) = discriminant;
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named Interrupted, which has no payload.
-    pub const Interrupted: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::Interrupted as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named InvalidFilename, which has no payload.
-    pub const InvalidFilename: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::InvalidFilename as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named NotFound, which has no payload.
-    pub const NotFound: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::NotFound as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named OutOfMemory, which has no payload.
-    pub const OutOfMemory: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::OutOfMemory as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named PermissionDenied, which has no payload.
-    pub const PermissionDenied: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::PermissionDenied as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named StaleNetworkFileHandle, which has no payload.
-    pub const StaleNetworkFileHandle: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::StaleNetworkFileHandle as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named TimedOut, which has no payload.
-    pub const TimedOut: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::TimedOut as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named TooManyHardlinks, which has no payload.
-    pub const TooManyHardlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::TooManyHardlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named TooManySymlinks, which has no payload.
-    pub const TooManySymlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::TooManySymlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named Unsupported, which has no payload.
-    pub const Unsupported: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_ReadErr::Unsupported as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], ReadErr>(bytes)
-    };
-}
-
-impl Drop for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn drop(&mut self) {
-        // Drop the payloads
-                    match self.discriminant() {
-                discriminant_ReadErr::Interrupted => {}
-                discriminant_ReadErr::InvalidFilename => {}
-                discriminant_ReadErr::NotFound => {}
-                discriminant_ReadErr::OutOfMemory => {}
-                discriminant_ReadErr::PermissionDenied => {}
-                discriminant_ReadErr::StaleNetworkFileHandle => {}
-                discriminant_ReadErr::TimedOut => {}
-                discriminant_ReadErr::TooManyHardlinks => {}
-                discriminant_ReadErr::TooManySymlinks => {}
-                discriminant_ReadErr::Unrecognized => unsafe { core::mem::ManuallyDrop::drop(&mut self.Unrecognized) },
-                discriminant_ReadErr::Unsupported => {}
-            }
-
-    }
-}
-
-impl Eq for ReadErr {}
-
-impl PartialEq for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn eq(&self, other: &Self) -> bool {
-            if self.discriminant() != other.discriminant() {
-                return false;
-            }
-
-            unsafe {
-            match self.discriminant() {
-                discriminant_ReadErr::Interrupted => true,
-                discriminant_ReadErr::InvalidFilename => true,
-                discriminant_ReadErr::NotFound => true,
-                discriminant_ReadErr::OutOfMemory => true,
-                discriminant_ReadErr::PermissionDenied => true,
-                discriminant_ReadErr::StaleNetworkFileHandle => true,
-                discriminant_ReadErr::TimedOut => true,
-                discriminant_ReadErr::TooManyHardlinks => true,
-                discriminant_ReadErr::TooManySymlinks => true,
-                discriminant_ReadErr::Unrecognized => self.Unrecognized == other.Unrecognized,
-                discriminant_ReadErr::Unsupported => true,
-            }
-        }
-    }
-}
-
-impl PartialOrd for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn partial_cmp(&self, other: &Self) -> Option {
-        match self.discriminant().partial_cmp(&other.discriminant()) {
-            Some(core::cmp::Ordering::Equal) => {}
-            not_eq => return not_eq,
-        }
-
-        unsafe {
-            match self.discriminant() {
-                discriminant_ReadErr::Interrupted => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::InvalidFilename => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::NotFound => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::OutOfMemory => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::PermissionDenied => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::StaleNetworkFileHandle => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::TimedOut => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::TooManyHardlinks => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::TooManySymlinks => Some(core::cmp::Ordering::Equal),
-                discriminant_ReadErr::Unrecognized => self.Unrecognized.partial_cmp(&other.Unrecognized),
-                discriminant_ReadErr::Unsupported => Some(core::cmp::Ordering::Equal),
-            }
-        }
-    }
-}
-
-impl Ord for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
-            match self.discriminant().cmp(&other.discriminant()) {
-                core::cmp::Ordering::Equal => {}
-                not_eq => return not_eq,
-            }
-
-            unsafe {
-            match self.discriminant() {
-                discriminant_ReadErr::Interrupted => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::InvalidFilename => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::NotFound => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::OutOfMemory => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::PermissionDenied => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::StaleNetworkFileHandle => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::TimedOut => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::TooManyHardlinks => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::TooManySymlinks => core::cmp::Ordering::Equal,
-                discriminant_ReadErr::Unrecognized => self.Unrecognized.cmp(&other.Unrecognized),
-                discriminant_ReadErr::Unsupported => core::cmp::Ordering::Equal,
-            }
-        }
-    }
-}
-
-impl Clone for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn clone(&self) -> Self {
-        let mut answer = unsafe {
-            match self.discriminant() {
-                discriminant_ReadErr::Interrupted => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::InvalidFilename => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::NotFound => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::OutOfMemory => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::PermissionDenied => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::StaleNetworkFileHandle => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::TimedOut => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::TooManyHardlinks => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::TooManySymlinks => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_ReadErr::Unrecognized => Self {
-                    Unrecognized: self.Unrecognized.clone(),
-                },
-                discriminant_ReadErr::Unsupported => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    ReadErr,
-                >(core::mem::MaybeUninit::uninit()),
-            }
-
-        };
-
-        answer.set_discriminant(self.discriminant());
-
-        answer
-    }
-}
-
-impl core::hash::Hash for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn hash(&self, state: &mut H) {        match self.discriminant() {
-            discriminant_ReadErr::Interrupted => discriminant_ReadErr::Interrupted.hash(state),
-            discriminant_ReadErr::InvalidFilename => discriminant_ReadErr::InvalidFilename.hash(state),
-            discriminant_ReadErr::NotFound => discriminant_ReadErr::NotFound.hash(state),
-            discriminant_ReadErr::OutOfMemory => discriminant_ReadErr::OutOfMemory.hash(state),
-            discriminant_ReadErr::PermissionDenied => discriminant_ReadErr::PermissionDenied.hash(state),
-            discriminant_ReadErr::StaleNetworkFileHandle => discriminant_ReadErr::StaleNetworkFileHandle.hash(state),
-            discriminant_ReadErr::TimedOut => discriminant_ReadErr::TimedOut.hash(state),
-            discriminant_ReadErr::TooManyHardlinks => discriminant_ReadErr::TooManyHardlinks.hash(state),
-            discriminant_ReadErr::TooManySymlinks => discriminant_ReadErr::TooManySymlinks.hash(state),
-            discriminant_ReadErr::Unrecognized => unsafe {
-                    discriminant_ReadErr::Unrecognized.hash(state);
-                    self.Unrecognized.hash(state);
-                },
-            discriminant_ReadErr::Unsupported => discriminant_ReadErr::Unsupported.hash(state),
-        }
-    }
-}
-
-impl core::fmt::Debug for ReadErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        f.write_str("ReadErr::")?;
-
-        unsafe {
-            match self.discriminant() {
-                discriminant_ReadErr::Interrupted => f.write_str("Interrupted"),
-                discriminant_ReadErr::InvalidFilename => f.write_str("InvalidFilename"),
-                discriminant_ReadErr::NotFound => f.write_str("NotFound"),
-                discriminant_ReadErr::OutOfMemory => f.write_str("OutOfMemory"),
-                discriminant_ReadErr::PermissionDenied => f.write_str("PermissionDenied"),
-                discriminant_ReadErr::StaleNetworkFileHandle => f.write_str("StaleNetworkFileHandle"),
-                discriminant_ReadErr::TimedOut => f.write_str("TimedOut"),
-                discriminant_ReadErr::TooManyHardlinks => f.write_str("TooManyHardlinks"),
-                discriminant_ReadErr::TooManySymlinks => f.write_str("TooManySymlinks"),
-                discriminant_ReadErr::Unrecognized => f.debug_tuple("Unrecognized")
-        .field(&(&*self.Unrecognized).f0)
-.field(&(&*self.Unrecognized).f1)
-        .finish(),
-                discriminant_ReadErr::Unsupported => f.write_str("Unsupported"),
-            }
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        f.write_str("ReadErr::")?;
-
-        unsafe {
-            match self.discriminant() {
-                discriminant_ReadErr::Interrupted => f.write_str("Interrupted"),
-                discriminant_ReadErr::InvalidFilename => f.write_str("InvalidFilename"),
-                discriminant_ReadErr::NotFound => f.write_str("NotFound"),
-                discriminant_ReadErr::OutOfMemory => f.write_str("OutOfMemory"),
-                discriminant_ReadErr::PermissionDenied => f.write_str("PermissionDenied"),
-                discriminant_ReadErr::StaleNetworkFileHandle => f.write_str("StaleNetworkFileHandle"),
-                discriminant_ReadErr::TimedOut => f.write_str("TimedOut"),
-                discriminant_ReadErr::TooManyHardlinks => f.write_str("TooManyHardlinks"),
-                discriminant_ReadErr::TooManySymlinks => f.write_str("TooManySymlinks"),
-                discriminant_ReadErr::Unrecognized => f.debug_tuple("Unrecognized")
-        .field(&(&*self.Unrecognized).f1)
-.field(&(&*self.Unrecognized).f0)
-        .finish(),
-                discriminant_ReadErr::Unsupported => f.write_str("Unsupported"),
-            }
-        }
-    }
-}
-
-impl WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// Returns which variant this tag union holds. Note that this never includes a payload!
-    pub fn discriminant(&self) -> discriminant_WriteErr {
-        unsafe {
-            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
-
-            core::mem::transmute::(*bytes.as_ptr().add(16))
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// Internal helper
-    fn set_discriminant(&mut self, discriminant: discriminant_WriteErr) {
-        let discriminant_ptr: *mut discriminant_WriteErr = (self as *mut WriteErr).cast();
-
-        unsafe {
-            *(discriminant_ptr.add(16)) = discriminant;
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named AlreadyExists, which has no payload.
-    pub const AlreadyExists: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::AlreadyExists as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the AlreadyExists tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_AlreadyExists(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the AlreadyExists tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_AlreadyExists(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named ExecutableFileBusy, which has no payload.
-    pub const ExecutableFileBusy: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::ExecutableFileBusy as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the ExecutableFileBusy tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_ExecutableFileBusy(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the ExecutableFileBusy tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_ExecutableFileBusy(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named FileTooLarge, which has no payload.
-    pub const FileTooLarge: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::FileTooLarge as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the FileTooLarge tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_FileTooLarge(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the FileTooLarge tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_FileTooLarge(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named FilesystemQuotaExceeded, which has no payload.
-    pub const FilesystemQuotaExceeded: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::FilesystemQuotaExceeded as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the FilesystemQuotaExceeded tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_FilesystemQuotaExceeded(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the FilesystemQuotaExceeded tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_FilesystemQuotaExceeded(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named Interrupted, which has no payload.
-    pub const Interrupted: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::Interrupted as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the Interrupted tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_Interrupted(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the Interrupted tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_Interrupted(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named InvalidFilename, which has no payload.
-    pub const InvalidFilename: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::InvalidFilename as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the InvalidFilename tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_InvalidFilename(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the InvalidFilename tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_InvalidFilename(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named NotFound, which has no payload.
-    pub const NotFound: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::NotFound as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the NotFound tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_NotFound(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the NotFound tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_NotFound(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named OutOfMemory, which has no payload.
-    pub const OutOfMemory: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::OutOfMemory as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the OutOfMemory tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_OutOfMemory(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the OutOfMemory tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_OutOfMemory(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named PermissionDenied, which has no payload.
-    pub const PermissionDenied: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::PermissionDenied as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the PermissionDenied tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_PermissionDenied(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the PermissionDenied tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_PermissionDenied(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named ReadOnlyFilesystem, which has no payload.
-    pub const ReadOnlyFilesystem: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::ReadOnlyFilesystem as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the ReadOnlyFilesystem tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_ReadOnlyFilesystem(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the ReadOnlyFilesystem tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_ReadOnlyFilesystem(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named ResourceBusy, which has no payload.
-    pub const ResourceBusy: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::ResourceBusy as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the ResourceBusy tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_ResourceBusy(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the ResourceBusy tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_ResourceBusy(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named StaleNetworkFileHandle, which has no payload.
-    pub const StaleNetworkFileHandle: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::StaleNetworkFileHandle as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the StaleNetworkFileHandle tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_StaleNetworkFileHandle(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the StaleNetworkFileHandle tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_StaleNetworkFileHandle(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named StorageFull, which has no payload.
-    pub const StorageFull: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::StorageFull as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the StorageFull tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_StorageFull(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the StorageFull tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_StorageFull(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named TimedOut, which has no payload.
-    pub const TimedOut: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::TimedOut as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the TimedOut tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_TimedOut(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the TimedOut tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_TimedOut(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named TooManyHardlinks, which has no payload.
-    pub const TooManyHardlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::TooManyHardlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the TooManyHardlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_TooManyHardlinks(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the TooManyHardlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_TooManyHardlinks(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named TooManySymlinks, which has no payload.
-    pub const TooManySymlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::TooManySymlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the TooManySymlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_TooManySymlinks(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the TooManySymlinks tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_TooManySymlinks(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Construct a tag named `Unrecognized`, with the appropriate payload
-    pub fn Unrecognized(arg0: i32, arg1: roc_std::RocStr) -> Self {
-            let mut answer = Self {
-                Unrecognized: core::mem::ManuallyDrop::new(WriteErr_Unrecognized {
-                    f0: arg0,
-                    f1: arg1,
-                })
-            };
-
-            answer.set_discriminant(discriminant_WriteErr::Unrecognized);
-
-            answer
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Unsafely assume the given `WriteErr` has a `.discriminant()` of `Unrecognized` and convert it to `Unrecognized`'s payload.
-            /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
-            /// Panics in debug builds if the `.discriminant()` doesn't return `Unrecognized`.
-            pub unsafe fn into_Unrecognized(mut self) -> (i32, roc_std::RocStr) {
-                debug_assert_eq!(self.discriminant(), discriminant_WriteErr::Unrecognized);
-        let payload = {
-            let mut uninitialized = core::mem::MaybeUninit::uninit();
-            let swapped = unsafe {
-                core::mem::replace(
-                    &mut self.Unrecognized,
-                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
-                )
-            };
-
-            core::mem::forget(self);
-
-            core::mem::ManuallyDrop::into_inner(swapped)
-        };
-
-        (
-            payload.f0, 
-            payload.f1
-        )
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Unsafely assume the given `WriteErr` has a `.discriminant()` of `Unrecognized` and return its payload.
-            /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
-            /// Panics in debug builds if the `.discriminant()` doesn't return `Unrecognized`.
-            pub unsafe fn as_Unrecognized(&self) -> (&i32, &roc_std::RocStr) {
-                debug_assert_eq!(self.discriminant(), discriminant_WriteErr::Unrecognized);
-        let payload = &self.Unrecognized;
-
-        (
-            &payload.f0, 
-            &payload.f1
-        )
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named Unsupported, which has no payload.
-    pub const Unsupported: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::Unsupported as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the Unsupported tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_Unsupported(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the Unsupported tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_Unsupported(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named WasADirectory, which has no payload.
-    pub const WasADirectory: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::WasADirectory as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the WasADirectory tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_WasADirectory(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the WasADirectory tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_WasADirectory(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    /// A tag named WriteZero, which has no payload.
-    pub const WriteZero: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[16] = discriminant_WriteErr::WriteZero as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `into_` methods return a payload, but since the WriteZero tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn into_WriteZero(self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    /// Other `as` methods return a payload, but since the WriteZero tag
-    /// has no payload, this does nothing and is only here for completeness.
-    pub fn as_WriteZero(&self) {
-        ()
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// Returns which variant this tag union holds. Note that this never includes a payload!
-    pub fn discriminant(&self) -> discriminant_WriteErr {
-        unsafe {
-            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
-
-            core::mem::transmute::(*bytes.as_ptr().add(32))
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// Internal helper
-    fn set_discriminant(&mut self, discriminant: discriminant_WriteErr) {
-        let discriminant_ptr: *mut discriminant_WriteErr = (self as *mut WriteErr).cast();
-
-        unsafe {
-            *(discriminant_ptr.add(32)) = discriminant;
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named AlreadyExists, which has no payload.
-    pub const AlreadyExists: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::AlreadyExists as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named ExecutableFileBusy, which has no payload.
-    pub const ExecutableFileBusy: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::ExecutableFileBusy as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named FileTooLarge, which has no payload.
-    pub const FileTooLarge: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::FileTooLarge as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named FilesystemQuotaExceeded, which has no payload.
-    pub const FilesystemQuotaExceeded: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::FilesystemQuotaExceeded as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named Interrupted, which has no payload.
-    pub const Interrupted: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::Interrupted as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named InvalidFilename, which has no payload.
-    pub const InvalidFilename: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::InvalidFilename as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named NotFound, which has no payload.
-    pub const NotFound: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::NotFound as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named OutOfMemory, which has no payload.
-    pub const OutOfMemory: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::OutOfMemory as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named PermissionDenied, which has no payload.
-    pub const PermissionDenied: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::PermissionDenied as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named ReadOnlyFilesystem, which has no payload.
-    pub const ReadOnlyFilesystem: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::ReadOnlyFilesystem as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named ResourceBusy, which has no payload.
-    pub const ResourceBusy: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::ResourceBusy as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named StaleNetworkFileHandle, which has no payload.
-    pub const StaleNetworkFileHandle: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::StaleNetworkFileHandle as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named StorageFull, which has no payload.
-    pub const StorageFull: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::StorageFull as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named TimedOut, which has no payload.
-    pub const TimedOut: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::TimedOut as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named TooManyHardlinks, which has no payload.
-    pub const TooManyHardlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::TooManyHardlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named TooManySymlinks, which has no payload.
-    pub const TooManySymlinks: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::TooManySymlinks as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named Unsupported, which has no payload.
-    pub const Unsupported: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::Unsupported as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named WasADirectory, which has no payload.
-    pub const WasADirectory: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::WasADirectory as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    /// A tag named WriteZero, which has no payload.
-    pub const WriteZero: Self = unsafe {
-        let mut bytes = [0; core::mem::size_of::()];
-
-        bytes[32] = discriminant_WriteErr::WriteZero as u8;
-
-        core::mem::transmute::<[u8; core::mem::size_of::()], WriteErr>(bytes)
-    };
-}
-
-impl Drop for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn drop(&mut self) {
-        // Drop the payloads
-                    match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => {}
-                discriminant_WriteErr::ExecutableFileBusy => {}
-                discriminant_WriteErr::FileTooLarge => {}
-                discriminant_WriteErr::FilesystemQuotaExceeded => {}
-                discriminant_WriteErr::Interrupted => {}
-                discriminant_WriteErr::InvalidFilename => {}
-                discriminant_WriteErr::NotFound => {}
-                discriminant_WriteErr::OutOfMemory => {}
-                discriminant_WriteErr::PermissionDenied => {}
-                discriminant_WriteErr::ReadOnlyFilesystem => {}
-                discriminant_WriteErr::ResourceBusy => {}
-                discriminant_WriteErr::StaleNetworkFileHandle => {}
-                discriminant_WriteErr::StorageFull => {}
-                discriminant_WriteErr::TimedOut => {}
-                discriminant_WriteErr::TooManyHardlinks => {}
-                discriminant_WriteErr::TooManySymlinks => {}
-                discriminant_WriteErr::Unrecognized => unsafe { core::mem::ManuallyDrop::drop(&mut self.Unrecognized) },
-                discriminant_WriteErr::Unsupported => {}
-                discriminant_WriteErr::WasADirectory => {}
-                discriminant_WriteErr::WriteZero => {}
-            }
-
-    }
-}
-
-impl Eq for WriteErr {}
-
-impl PartialEq for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn eq(&self, other: &Self) -> bool {
-            if self.discriminant() != other.discriminant() {
-                return false;
-            }
-
-            unsafe {
-            match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => true,
-                discriminant_WriteErr::ExecutableFileBusy => true,
-                discriminant_WriteErr::FileTooLarge => true,
-                discriminant_WriteErr::FilesystemQuotaExceeded => true,
-                discriminant_WriteErr::Interrupted => true,
-                discriminant_WriteErr::InvalidFilename => true,
-                discriminant_WriteErr::NotFound => true,
-                discriminant_WriteErr::OutOfMemory => true,
-                discriminant_WriteErr::PermissionDenied => true,
-                discriminant_WriteErr::ReadOnlyFilesystem => true,
-                discriminant_WriteErr::ResourceBusy => true,
-                discriminant_WriteErr::StaleNetworkFileHandle => true,
-                discriminant_WriteErr::StorageFull => true,
-                discriminant_WriteErr::TimedOut => true,
-                discriminant_WriteErr::TooManyHardlinks => true,
-                discriminant_WriteErr::TooManySymlinks => true,
-                discriminant_WriteErr::Unrecognized => self.Unrecognized == other.Unrecognized,
-                discriminant_WriteErr::Unsupported => true,
-                discriminant_WriteErr::WasADirectory => true,
-                discriminant_WriteErr::WriteZero => true,
-            }
-        }
-    }
-}
-
-impl PartialOrd for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn partial_cmp(&self, other: &Self) -> Option {
-        match self.discriminant().partial_cmp(&other.discriminant()) {
-            Some(core::cmp::Ordering::Equal) => {}
-            not_eq => return not_eq,
-        }
-
-        unsafe {
-            match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::ExecutableFileBusy => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::FileTooLarge => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::FilesystemQuotaExceeded => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::Interrupted => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::InvalidFilename => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::NotFound => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::OutOfMemory => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::PermissionDenied => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::ReadOnlyFilesystem => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::ResourceBusy => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::StaleNetworkFileHandle => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::StorageFull => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::TimedOut => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::TooManyHardlinks => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::TooManySymlinks => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::Unrecognized => self.Unrecognized.partial_cmp(&other.Unrecognized),
-                discriminant_WriteErr::Unsupported => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::WasADirectory => Some(core::cmp::Ordering::Equal),
-                discriminant_WriteErr::WriteZero => Some(core::cmp::Ordering::Equal),
-            }
-        }
-    }
-}
-
-impl Ord for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
-            match self.discriminant().cmp(&other.discriminant()) {
-                core::cmp::Ordering::Equal => {}
-                not_eq => return not_eq,
-            }
-
-            unsafe {
-            match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::ExecutableFileBusy => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::FileTooLarge => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::FilesystemQuotaExceeded => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::Interrupted => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::InvalidFilename => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::NotFound => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::OutOfMemory => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::PermissionDenied => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::ReadOnlyFilesystem => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::ResourceBusy => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::StaleNetworkFileHandle => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::StorageFull => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::TimedOut => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::TooManyHardlinks => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::TooManySymlinks => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::Unrecognized => self.Unrecognized.cmp(&other.Unrecognized),
-                discriminant_WriteErr::Unsupported => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::WasADirectory => core::cmp::Ordering::Equal,
-                discriminant_WriteErr::WriteZero => core::cmp::Ordering::Equal,
-            }
-        }
-    }
-}
-
-impl Clone for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn clone(&self) -> Self {
-        let mut answer = unsafe {
-            match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::ExecutableFileBusy => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::FileTooLarge => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::FilesystemQuotaExceeded => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::Interrupted => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::InvalidFilename => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::NotFound => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::OutOfMemory => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::PermissionDenied => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::ReadOnlyFilesystem => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::ResourceBusy => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::StaleNetworkFileHandle => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::StorageFull => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::TimedOut => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::TooManyHardlinks => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::TooManySymlinks => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::Unrecognized => Self {
-                    Unrecognized: self.Unrecognized.clone(),
-                },
-                discriminant_WriteErr::Unsupported => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::WasADirectory => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-                discriminant_WriteErr::WriteZero => core::mem::transmute::<
-                    core::mem::MaybeUninit,
-                    WriteErr,
-                >(core::mem::MaybeUninit::uninit()),
-            }
-
-        };
-
-        answer.set_discriminant(self.discriminant());
-
-        answer
-    }
-}
-
-impl core::hash::Hash for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "aarch64",
-        target_arch = "wasm32",
-        target_arch = "x86",
-        target_arch = "x86_64"
-    ))]
-    fn hash(&self, state: &mut H) {        match self.discriminant() {
-            discriminant_WriteErr::AlreadyExists => discriminant_WriteErr::AlreadyExists.hash(state),
-            discriminant_WriteErr::ExecutableFileBusy => discriminant_WriteErr::ExecutableFileBusy.hash(state),
-            discriminant_WriteErr::FileTooLarge => discriminant_WriteErr::FileTooLarge.hash(state),
-            discriminant_WriteErr::FilesystemQuotaExceeded => discriminant_WriteErr::FilesystemQuotaExceeded.hash(state),
-            discriminant_WriteErr::Interrupted => discriminant_WriteErr::Interrupted.hash(state),
-            discriminant_WriteErr::InvalidFilename => discriminant_WriteErr::InvalidFilename.hash(state),
-            discriminant_WriteErr::NotFound => discriminant_WriteErr::NotFound.hash(state),
-            discriminant_WriteErr::OutOfMemory => discriminant_WriteErr::OutOfMemory.hash(state),
-            discriminant_WriteErr::PermissionDenied => discriminant_WriteErr::PermissionDenied.hash(state),
-            discriminant_WriteErr::ReadOnlyFilesystem => discriminant_WriteErr::ReadOnlyFilesystem.hash(state),
-            discriminant_WriteErr::ResourceBusy => discriminant_WriteErr::ResourceBusy.hash(state),
-            discriminant_WriteErr::StaleNetworkFileHandle => discriminant_WriteErr::StaleNetworkFileHandle.hash(state),
-            discriminant_WriteErr::StorageFull => discriminant_WriteErr::StorageFull.hash(state),
-            discriminant_WriteErr::TimedOut => discriminant_WriteErr::TimedOut.hash(state),
-            discriminant_WriteErr::TooManyHardlinks => discriminant_WriteErr::TooManyHardlinks.hash(state),
-            discriminant_WriteErr::TooManySymlinks => discriminant_WriteErr::TooManySymlinks.hash(state),
-            discriminant_WriteErr::Unrecognized => unsafe {
-                    discriminant_WriteErr::Unrecognized.hash(state);
-                    self.Unrecognized.hash(state);
-                },
-            discriminant_WriteErr::Unsupported => discriminant_WriteErr::Unsupported.hash(state),
-            discriminant_WriteErr::WasADirectory => discriminant_WriteErr::WasADirectory.hash(state),
-            discriminant_WriteErr::WriteZero => discriminant_WriteErr::WriteZero.hash(state),
-        }
-    }
-}
-
-impl core::fmt::Debug for WriteErr {
-    #[cfg(any(
-        target_arch = "arm",
-        target_arch = "wasm32",
-        target_arch = "x86"
-    ))]
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        f.write_str("WriteErr::")?;
-
-        unsafe {
-            match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => f.write_str("AlreadyExists"),
-                discriminant_WriteErr::ExecutableFileBusy => f.write_str("ExecutableFileBusy"),
-                discriminant_WriteErr::FileTooLarge => f.write_str("FileTooLarge"),
-                discriminant_WriteErr::FilesystemQuotaExceeded => f.write_str("FilesystemQuotaExceeded"),
-                discriminant_WriteErr::Interrupted => f.write_str("Interrupted"),
-                discriminant_WriteErr::InvalidFilename => f.write_str("InvalidFilename"),
-                discriminant_WriteErr::NotFound => f.write_str("NotFound"),
-                discriminant_WriteErr::OutOfMemory => f.write_str("OutOfMemory"),
-                discriminant_WriteErr::PermissionDenied => f.write_str("PermissionDenied"),
-                discriminant_WriteErr::ReadOnlyFilesystem => f.write_str("ReadOnlyFilesystem"),
-                discriminant_WriteErr::ResourceBusy => f.write_str("ResourceBusy"),
-                discriminant_WriteErr::StaleNetworkFileHandle => f.write_str("StaleNetworkFileHandle"),
-                discriminant_WriteErr::StorageFull => f.write_str("StorageFull"),
-                discriminant_WriteErr::TimedOut => f.write_str("TimedOut"),
-                discriminant_WriteErr::TooManyHardlinks => f.write_str("TooManyHardlinks"),
-                discriminant_WriteErr::TooManySymlinks => f.write_str("TooManySymlinks"),
-                discriminant_WriteErr::Unrecognized => f.debug_tuple("Unrecognized")
-        .field(&(&*self.Unrecognized).f0)
-.field(&(&*self.Unrecognized).f1)
-        .finish(),
-                discriminant_WriteErr::Unsupported => f.write_str("Unsupported"),
-                discriminant_WriteErr::WasADirectory => f.write_str("WasADirectory"),
-                discriminant_WriteErr::WriteZero => f.write_str("WriteZero"),
-            }
-        }
-    }
-
-    #[cfg(any(
-        target_arch = "aarch64",
-        target_arch = "x86_64"
-    ))]
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        f.write_str("WriteErr::")?;
-
-        unsafe {
-            match self.discriminant() {
-                discriminant_WriteErr::AlreadyExists => f.write_str("AlreadyExists"),
-                discriminant_WriteErr::ExecutableFileBusy => f.write_str("ExecutableFileBusy"),
-                discriminant_WriteErr::FileTooLarge => f.write_str("FileTooLarge"),
-                discriminant_WriteErr::FilesystemQuotaExceeded => f.write_str("FilesystemQuotaExceeded"),
-                discriminant_WriteErr::Interrupted => f.write_str("Interrupted"),
-                discriminant_WriteErr::InvalidFilename => f.write_str("InvalidFilename"),
-                discriminant_WriteErr::NotFound => f.write_str("NotFound"),
-                discriminant_WriteErr::OutOfMemory => f.write_str("OutOfMemory"),
-                discriminant_WriteErr::PermissionDenied => f.write_str("PermissionDenied"),
-                discriminant_WriteErr::ReadOnlyFilesystem => f.write_str("ReadOnlyFilesystem"),
-                discriminant_WriteErr::ResourceBusy => f.write_str("ResourceBusy"),
-                discriminant_WriteErr::StaleNetworkFileHandle => f.write_str("StaleNetworkFileHandle"),
-                discriminant_WriteErr::StorageFull => f.write_str("StorageFull"),
-                discriminant_WriteErr::TimedOut => f.write_str("TimedOut"),
-                discriminant_WriteErr::TooManyHardlinks => f.write_str("TooManyHardlinks"),
-                discriminant_WriteErr::TooManySymlinks => f.write_str("TooManySymlinks"),
-                discriminant_WriteErr::Unrecognized => f.debug_tuple("Unrecognized")
-        .field(&(&*self.Unrecognized).f1)
-.field(&(&*self.Unrecognized).f0)
-        .finish(),
-                discriminant_WriteErr::Unsupported => f.write_str("Unsupported"),
-                discriminant_WriteErr::WasADirectory => f.write_str("WasADirectory"),
-                discriminant_WriteErr::WriteZero => f.write_str("WriteZero"),
-            }
-        }
-    }
-}
diff --git a/examples/cli/cli-platform/src/glue.rs b/examples/cli/cli-platform/src/glue.rs
deleted file mode 100644
index 779b0bf874..0000000000
--- a/examples/cli/cli-platform/src/glue.rs
+++ /dev/null
@@ -1,4057 +0,0 @@
-// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
-
-#![allow(dead_code)]
-#![allow(unused_mut)]
-#![allow(non_snake_case)]
-#![allow(non_camel_case_types)]
-#![allow(non_upper_case_globals)]
-#![allow(clippy::undocumented_unsafe_blocks)]
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_Error {
-    BadBody = 0,
-    BadRequest = 1,
-    BadStatus = 2,
-    NetworkError = 3,
-    Timeout = 4,
-}
-
-impl core::fmt::Debug for discriminant_Error {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::BadBody => f.write_str("discriminant_Error::BadBody"),
-            Self::BadRequest => f.write_str("discriminant_Error::BadRequest"),
-            Self::BadStatus => f.write_str("discriminant_Error::BadStatus"),
-            Self::NetworkError => f.write_str("discriminant_Error::NetworkError"),
-            Self::Timeout => f.write_str("discriminant_Error::Timeout"),
-        }
-    }
-}
-
-#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
-#[repr(C)]
-pub union Error {
-    BadBody: core::mem::ManuallyDrop,
-    BadRequest: core::mem::ManuallyDrop,
-    BadStatus: u16,
-    _sizer: [u8; 16],
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_Header {
-    Header = 0,
-}
-
-impl core::fmt::Debug for discriminant_Header {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::Header => f.write_str("discriminant_Header::Header"),
-        }
-    }
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[repr(C)]
-pub union Header {
-    Header: core::mem::ManuallyDrop,
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_U4 {
-    MimeType = 0,
-}
-
-impl core::fmt::Debug for discriminant_U4 {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::MimeType => f.write_str("discriminant_U4::MimeType"),
-        }
-    }
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[repr(C)]
-pub union U4 {
-    MimeType: core::mem::ManuallyDrop,
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_Body {
-    Body = 0,
-    EmptyBody = 1,
-}
-
-impl core::fmt::Debug for discriminant_Body {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::Body => f.write_str("discriminant_Body::Body"),
-            Self::EmptyBody => f.write_str("discriminant_Body::EmptyBody"),
-        }
-    }
-}
-
-#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
-#[repr(C)]
-pub union Body {
-    Body: core::mem::ManuallyDrop,
-    _sizer: [u8; 28],
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_U2 {
-    MimeType = 0,
-}
-
-impl core::fmt::Debug for discriminant_U2 {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::MimeType => f.write_str("discriminant_U2::MimeType"),
-        }
-    }
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[repr(C)]
-pub union U2 {
-    MimeType: core::mem::ManuallyDrop,
-}
-
-#[cfg(any(
-    target_arch = "arm",
-    target_arch = "aarch64",
-    target_arch = "wasm32",
-    target_arch = "x86",
-    target_arch = "x86_64"
-))]
-#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(u8)]
-pub enum discriminant_Response {
-    BadRequest = 0,
-    BadStatus = 1,
-    GoodStatus = 2,
-    NetworkError = 3,
-    Timeout = 4,
-}
-
-impl core::fmt::Debug for discriminant_Response {
-    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        match self {
-            Self::BadRequest => f.write_str("discriminant_Response::BadRequest"),
-            Self::BadStatus => f.write_str("discriminant_Response::BadStatus"),
-            Self::GoodStatus => f.write_str("discriminant_Response::GoodStatus"),
-            Self::NetworkError => f.write_str("discriminant_Response::NetworkError"),
-            Self::Timeout => f.write_str("discriminant_Response::Timeout"),
-        }
-    }
-}
-
-#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
-#[repr(C)]
-pub union Response {
-    BadRequest: core::mem::ManuallyDrop,
-    BadStatus: core::mem::ManuallyDrop,
-    GoodStatus: core::mem::ManuallyDrop,
-    _sizer: [u8; 56],
-}
-
-#[cfg(any(target_arch = "arm", target_arch = "wasm32"))]
-#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
-#[repr(C)]
-pub struct Request {
-    pub timeout: TimeoutConfig,
-    pub body: Body,
-    pub headers: roc_std::RocList
, - pub url: roc_std::RocStr, - pub method: Method, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum discriminant_U7 { - MimeType = 0, -} - -impl core::fmt::Debug for discriminant_U7 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::MimeType => f.write_str("discriminant_U7::MimeType"), - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[repr(C)] -pub union U7 { - MimeType: core::mem::ManuallyDrop, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum discriminant_U6 { - MimeType = 0, -} - -impl core::fmt::Debug for discriminant_U6 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::MimeType => f.write_str("discriminant_U6::MimeType"), - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[repr(C)] -pub union U6 { - MimeType: core::mem::ManuallyDrop, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum discriminant_U5 { - MimeType = 0, -} - -impl core::fmt::Debug for discriminant_U5 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::MimeType => f.write_str("discriminant_U5::MimeType"), - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[repr(C)] -pub union U5 { - MimeType: core::mem::ManuallyDrop, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum discriminant_U3 { - MimeType = 0, -} - -impl core::fmt::Debug for discriminant_U3 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::MimeType => f.write_str("discriminant_U3::MimeType"), - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[repr(C)] -pub union U3 { - MimeType: core::mem::ManuallyDrop, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(C)] -struct Response_GoodStatus { - pub f0: Metadata, - pub f1: roc_std::RocList, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(C)] -struct Response_BadStatus { - pub f0: Metadata, - pub f1: roc_std::RocList, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(C)] -pub struct Metadata { - pub headers: roc_std::RocList
, - pub statusText: roc_std::RocStr, - pub url: roc_std::RocStr, - pub statusCode: u16, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum Method { - Connect = 0, - Delete = 1, - Get = 2, - Head = 3, - Options = 4, - Patch = 5, - Post = 6, - Put = 7, - Trace = 8, -} - -impl core::fmt::Debug for Method { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Connect => f.write_str("Method::Connect"), - Self::Delete => f.write_str("Method::Delete"), - Self::Get => f.write_str("Method::Get"), - Self::Head => f.write_str("Method::Head"), - Self::Options => f.write_str("Method::Options"), - Self::Patch => f.write_str("Method::Patch"), - Self::Post => f.write_str("Method::Post"), - Self::Put => f.write_str("Method::Put"), - Self::Trace => f.write_str("Method::Trace"), - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(C)] -struct Header_Header { - pub f0: roc_std::RocStr, - pub f1: roc_std::RocStr, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(C)] -struct Body_Body { - pub f0: U1, - pub f1: roc_std::RocList, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum discriminant_U1 { - MimeType = 0, -} - -impl core::fmt::Debug for discriminant_U1 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::MimeType => f.write_str("discriminant_U1::MimeType"), - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[repr(C)] -pub union U1 { - MimeType: core::mem::ManuallyDrop, -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" -))] -#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum discriminant_TimeoutConfig { - NoTimeout = 0, - TimeoutMilliseconds = 1, -} - -impl core::fmt::Debug for discriminant_TimeoutConfig { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::NoTimeout => f.write_str("discriminant_TimeoutConfig::NoTimeout"), - Self::TimeoutMilliseconds => { - f.write_str("discriminant_TimeoutConfig::TimeoutMilliseconds") - } - } - } -} - -#[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86_64" -))] -#[repr(C)] -pub union TimeoutConfig { - TimeoutMilliseconds: u64, - _sizer: [u8; 16], -} - -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -#[repr(C)] -pub union Error { - BadBody: core::mem::ManuallyDrop, - BadRequest: core::mem::ManuallyDrop, - BadStatus: u16, - _sizer: [u8; 32], -} - -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -#[repr(C)] -pub union Body { - Body: core::mem::ManuallyDrop, - _sizer: [u8; 56], -} - -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] -#[repr(C)] -pub union Response { - BadRequest: core::mem::ManuallyDrop, - BadStatus: core::mem::ManuallyDrop, - GoodStatus: core::mem::ManuallyDrop, - _sizer: [u8; 112], -} - -#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] -#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] -#[repr(C)] -pub struct Request { - pub body: Body, - pub headers: roc_std::RocList
, - pub timeout: TimeoutConfig, - pub url: roc_std::RocStr, - pub method: Method, -} - -#[cfg(target_arch = "x86")] -#[repr(C)] -pub union TimeoutConfig { - TimeoutMilliseconds: u64, - _sizer: [u8; 12], -} - -impl Error { - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Error { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(16)) - } - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Error) { - let discriminant_ptr: *mut discriminant_Error = (self as *mut Error).cast(); - - unsafe { - *(discriminant_ptr.add(16)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `BadBody`, with the appropriate payload - pub fn BadBody(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - BadBody: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_Error::BadBody); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Error` has a `.discriminant()` of `BadBody` and convert it to `BadBody`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadBody`. - pub unsafe fn into_BadBody(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_Error::BadBody); - - let payload = core::mem::ManuallyDrop::take(&mut self.BadBody); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Error` has a `.discriminant()` of `BadBody` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadBody`. - pub unsafe fn as_BadBody(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_Error::BadBody); - - let payload = &self.BadBody; - - &payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `BadRequest`, with the appropriate payload - pub fn BadRequest(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - BadRequest: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_Error::BadRequest); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Error` has a `.discriminant()` of `BadRequest` and convert it to `BadRequest`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadRequest`. - pub unsafe fn into_BadRequest(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_Error::BadRequest); - - let payload = core::mem::ManuallyDrop::take(&mut self.BadRequest); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Error` has a `.discriminant()` of `BadRequest` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadRequest`. - pub unsafe fn as_BadRequest(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_Error::BadRequest); - - let payload = &self.BadRequest; - - &payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `BadStatus`, with the appropriate payload - pub fn BadStatus(arg: u16) -> Self { - let mut answer = Self { BadStatus: arg }; - - answer.set_discriminant(discriminant_Error::BadStatus); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Error` has a `.discriminant()` of `BadStatus` and convert it to `BadStatus`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadStatus`. - pub unsafe fn into_BadStatus(self) -> u16 { - debug_assert_eq!(self.discriminant(), discriminant_Error::BadStatus); - - let payload = self.BadStatus; - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Error` has a `.discriminant()` of `BadStatus` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadStatus`. - pub unsafe fn as_BadStatus(&self) -> &u16 { - debug_assert_eq!(self.discriminant(), discriminant_Error::BadStatus); - - let payload = &self.BadStatus; - - &payload - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// A tag named NetworkError, which has no payload. - pub const NetworkError: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[16] = discriminant_Error::NetworkError as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Error>(bytes) - }; - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `into_` methods return a payload, but since the NetworkError tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_NetworkError(self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `as` methods return a payload, but since the NetworkError tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_NetworkError(&self) { - () - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// A tag named Timeout, which has no payload. - pub const Timeout: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[16] = discriminant_Error::Timeout as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Error>(bytes) - }; - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `into_` methods return a payload, but since the Timeout tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_Timeout(self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `as` methods return a payload, but since the Timeout tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Timeout(&self) { - () - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Error { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(24)) - } - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Error) { - let discriminant_ptr: *mut discriminant_Error = (self as *mut Error).cast(); - - unsafe { - *(discriminant_ptr.add(24)) = discriminant; - } - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// A tag named NetworkError, which has no payload. - pub const NetworkError: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[24] = discriminant_Error::NetworkError as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Error>(bytes) - }; - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// A tag named Timeout, which has no payload. - pub const Timeout: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[24] = discriminant_Error::Timeout as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Error>(bytes) - }; -} - -impl Drop for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_Error::BadBody => unsafe { - core::mem::ManuallyDrop::drop(&mut self.BadBody) - }, - discriminant_Error::BadRequest => unsafe { - core::mem::ManuallyDrop::drop(&mut self.BadRequest) - }, - discriminant_Error::BadStatus => {} - discriminant_Error::NetworkError => {} - discriminant_Error::Timeout => {} - } - } -} - -impl Eq for Error {} - -impl PartialEq for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_Error::BadBody => self.BadBody == other.BadBody, - discriminant_Error::BadRequest => self.BadRequest == other.BadRequest, - discriminant_Error::BadStatus => self.BadStatus == other.BadStatus, - discriminant_Error::NetworkError => true, - discriminant_Error::Timeout => true, - } - } - } -} - -impl PartialOrd for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Error::BadBody => self.BadBody.partial_cmp(&other.BadBody), - discriminant_Error::BadRequest => self.BadRequest.partial_cmp(&other.BadRequest), - discriminant_Error::BadStatus => self.BadStatus.partial_cmp(&other.BadStatus), - discriminant_Error::NetworkError => Some(core::cmp::Ordering::Equal), - discriminant_Error::Timeout => Some(core::cmp::Ordering::Equal), - } - } - } -} - -impl Ord for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Error::BadBody => self.BadBody.cmp(&other.BadBody), - discriminant_Error::BadRequest => self.BadRequest.cmp(&other.BadRequest), - discriminant_Error::BadStatus => self.BadStatus.cmp(&other.BadStatus), - discriminant_Error::NetworkError => core::cmp::Ordering::Equal, - discriminant_Error::Timeout => core::cmp::Ordering::Equal, - } - } - } -} - -impl Clone for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_Error::BadBody => Self { - BadBody: self.BadBody.clone(), - }, - discriminant_Error::BadRequest => Self { - BadRequest: self.BadRequest.clone(), - }, - discriminant_Error::BadStatus => Self { - BadStatus: self.BadStatus.clone(), - }, - discriminant_Error::NetworkError => { - core::mem::transmute::, Error>( - core::mem::MaybeUninit::uninit(), - ) - } - discriminant_Error::Timeout => core::mem::transmute::< - core::mem::MaybeUninit, - Error, - >(core::mem::MaybeUninit::uninit()), - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_Error::BadBody => unsafe { - discriminant_Error::BadBody.hash(state); - self.BadBody.hash(state); - }, - discriminant_Error::BadRequest => unsafe { - discriminant_Error::BadRequest.hash(state); - self.BadRequest.hash(state); - }, - discriminant_Error::BadStatus => unsafe { - discriminant_Error::BadStatus.hash(state); - self.BadStatus.hash(state); - }, - discriminant_Error::NetworkError => discriminant_Error::NetworkError.hash(state), - discriminant_Error::Timeout => discriminant_Error::Timeout.hash(state), - } - } -} - -impl core::fmt::Debug for Error { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Error::")?; - - unsafe { - match self.discriminant() { - discriminant_Error::BadBody => { - f.debug_tuple("BadBody").field(&*self.BadBody).finish() - } - discriminant_Error::BadRequest => f - .debug_tuple("BadRequest") - .field(&*self.BadRequest) - .finish(), - discriminant_Error::BadStatus => { - f.debug_tuple("BadStatus").field(&self.BadStatus).finish() - } - discriminant_Error::NetworkError => f.write_str("NetworkError"), - discriminant_Error::Timeout => f.write_str("Timeout"), - } - } - } -} - -impl Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Header { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Header) { - let discriminant_ptr: *mut discriminant_Header = (self as *mut Header).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `Header`, with the appropriate payload - pub fn Header(arg0: roc_std::RocStr, arg1: roc_std::RocStr) -> Self { - let mut answer = Self { - Header: core::mem::ManuallyDrop::new(Header_Header { f0: arg0, f1: arg1 }), - }; - - answer.set_discriminant(discriminant_Header::Header); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Header` has a `.discriminant()` of `Header` and convert it to `Header`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `Header`. - pub unsafe fn into_Header(mut self) -> (roc_std::RocStr, roc_std::RocStr) { - debug_assert_eq!(self.discriminant(), discriminant_Header::Header); - - let payload = core::mem::ManuallyDrop::take(&mut self.Header); - - (payload.f0, payload.f1) - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Header` has a `.discriminant()` of `Header` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `Header`. - pub unsafe fn as_Header(&self) -> (&roc_std::RocStr, &roc_std::RocStr) { - debug_assert_eq!(self.discriminant(), discriminant_Header::Header); - - let payload = &self.Header; - - (&payload.f0, &payload.f1) - } -} - -impl Drop for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_Header::Header => unsafe { - core::mem::ManuallyDrop::drop(&mut self.Header) - }, - } - } -} - -impl Eq for Header {} - -impl PartialEq for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_Header::Header => self.Header == other.Header, - } - } - } -} - -impl PartialOrd for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Header::Header => self.Header.partial_cmp(&other.Header), - } - } - } -} - -impl Ord for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Header::Header => self.Header.cmp(&other.Header), - } - } - } -} - -impl Clone for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_Header::Header => Self { - Header: self.Header.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_Header::Header => unsafe { - discriminant_Header::Header.hash(state); - self.Header.hash(state); - }, - } - } -} - -impl core::fmt::Debug for Header { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Header::")?; - - unsafe { - match self.discriminant() { - discriminant_Header::Header => f - .debug_tuple("Header") - .field(&(&*self.Header).f0) - .field(&(&*self.Header).f1) - .finish(), - } - } - } -} - -impl U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U4 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U4) { - let discriminant_ptr: *mut discriminant_U4 = (self as *mut U4).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U4::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U4` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U4::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U4` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U4::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U4::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U4 {} - -impl PartialEq for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U4::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U4::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U4::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U4::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U4::MimeType => unsafe { - discriminant_U4::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U4 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U4::")?; - - unsafe { - match self.discriminant() { - discriminant_U4::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl Body { - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Body { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(24)) - } - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Body) { - let discriminant_ptr: *mut discriminant_Body = (self as *mut Body).cast(); - - unsafe { - *(discriminant_ptr.add(24)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `Body`, with the appropriate payload - pub fn Body(arg0: U1, arg1: roc_std::RocList) -> Self { - let mut answer = Self { - Body: core::mem::ManuallyDrop::new(Body_Body { f0: arg0, f1: arg1 }), - }; - - answer.set_discriminant(discriminant_Body::Body); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Body` has a `.discriminant()` of `Body` and convert it to `Body`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `Body`. - pub unsafe fn into_Body(mut self) -> (U1, roc_std::RocList) { - debug_assert_eq!(self.discriminant(), discriminant_Body::Body); - - let payload = core::mem::ManuallyDrop::take(&mut self.Body); - - (payload.f0, payload.f1) - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Body` has a `.discriminant()` of `Body` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `Body`. - pub unsafe fn as_Body(&self) -> (&U1, &roc_std::RocList) { - debug_assert_eq!(self.discriminant(), discriminant_Body::Body); - - let payload = &self.Body; - - (&payload.f0, &payload.f1) - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// A tag named EmptyBody, which has no payload. - pub const EmptyBody: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[24] = discriminant_Body::EmptyBody as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Body>(bytes) - }; - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `into_` methods return a payload, but since the EmptyBody tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_EmptyBody(self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `as` methods return a payload, but since the EmptyBody tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_EmptyBody(&self) { - () - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Body { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(48)) - } - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Body) { - let discriminant_ptr: *mut discriminant_Body = (self as *mut Body).cast(); - - unsafe { - *(discriminant_ptr.add(48)) = discriminant; - } - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// A tag named EmptyBody, which has no payload. - pub const EmptyBody: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[48] = discriminant_Body::EmptyBody as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Body>(bytes) - }; -} - -impl Drop for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_Body::Body => unsafe { core::mem::ManuallyDrop::drop(&mut self.Body) }, - discriminant_Body::EmptyBody => {} - } - } -} - -impl Eq for Body {} - -impl PartialEq for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_Body::Body => self.Body == other.Body, - discriminant_Body::EmptyBody => true, - } - } - } -} - -impl PartialOrd for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Body::Body => self.Body.partial_cmp(&other.Body), - discriminant_Body::EmptyBody => Some(core::cmp::Ordering::Equal), - } - } - } -} - -impl Ord for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Body::Body => self.Body.cmp(&other.Body), - discriminant_Body::EmptyBody => core::cmp::Ordering::Equal, - } - } - } -} - -impl Clone for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_Body::Body => Self { - Body: self.Body.clone(), - }, - discriminant_Body::EmptyBody => core::mem::transmute::< - core::mem::MaybeUninit, - Body, - >(core::mem::MaybeUninit::uninit()), - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_Body::Body => unsafe { - discriminant_Body::Body.hash(state); - self.Body.hash(state); - }, - discriminant_Body::EmptyBody => discriminant_Body::EmptyBody.hash(state), - } - } -} - -impl core::fmt::Debug for Body { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Body::")?; - - unsafe { - match self.discriminant() { - discriminant_Body::Body => f - .debug_tuple("Body") - .field(&(&*self.Body).f0) - .field(&(&*self.Body).f1) - .finish(), - discriminant_Body::EmptyBody => f.write_str("EmptyBody"), - } - } - } -} - -impl U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U2 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U2) { - let discriminant_ptr: *mut discriminant_U2 = (self as *mut U2).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U2::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U2` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U2::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U2` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U2::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U2::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U2 {} - -impl PartialEq for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U2::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U2::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U2::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U2::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U2::MimeType => unsafe { - discriminant_U2::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U2 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U2::")?; - - unsafe { - match self.discriminant() { - discriminant_U2::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl Response { - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Response { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(56)) - } - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Response) { - let discriminant_ptr: *mut discriminant_Response = (self as *mut Response).cast(); - - unsafe { - *(discriminant_ptr.add(56)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `BadRequest`, with the appropriate payload - pub fn BadRequest(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - BadRequest: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_Response::BadRequest); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Response` has a `.discriminant()` of `BadRequest` and convert it to `BadRequest`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadRequest`. - pub unsafe fn into_BadRequest(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_Response::BadRequest); - - let payload = core::mem::ManuallyDrop::take(&mut self.BadRequest); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Response` has a `.discriminant()` of `BadRequest` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadRequest`. - pub unsafe fn as_BadRequest(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_Response::BadRequest); - - let payload = &self.BadRequest; - - &payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `BadStatus`, with the appropriate payload - pub fn BadStatus(arg0: Metadata, arg1: roc_std::RocList) -> Self { - let mut answer = Self { - BadStatus: core::mem::ManuallyDrop::new(Response_BadStatus { f0: arg0, f1: arg1 }), - }; - - answer.set_discriminant(discriminant_Response::BadStatus); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Response` has a `.discriminant()` of `BadStatus` and convert it to `BadStatus`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadStatus`. - pub unsafe fn into_BadStatus(mut self) -> (Metadata, roc_std::RocList) { - debug_assert_eq!(self.discriminant(), discriminant_Response::BadStatus); - - let payload = core::mem::ManuallyDrop::take(&mut self.BadStatus); - - (payload.f0, payload.f1) - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Response` has a `.discriminant()` of `BadStatus` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `BadStatus`. - pub unsafe fn as_BadStatus(&self) -> (&Metadata, &roc_std::RocList) { - debug_assert_eq!(self.discriminant(), discriminant_Response::BadStatus); - - let payload = &self.BadStatus; - - (&payload.f0, &payload.f1) - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `GoodStatus`, with the appropriate payload - pub fn GoodStatus(arg0: Metadata, arg1: roc_std::RocList) -> Self { - let mut answer = Self { - GoodStatus: core::mem::ManuallyDrop::new(Response_GoodStatus { f0: arg0, f1: arg1 }), - }; - - answer.set_discriminant(discriminant_Response::GoodStatus); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Response` has a `.discriminant()` of `GoodStatus` and convert it to `GoodStatus`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `GoodStatus`. - pub unsafe fn into_GoodStatus(mut self) -> (Metadata, roc_std::RocList) { - debug_assert_eq!(self.discriminant(), discriminant_Response::GoodStatus); - - let payload = core::mem::ManuallyDrop::take(&mut self.GoodStatus); - - (payload.f0, payload.f1) - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `Response` has a `.discriminant()` of `GoodStatus` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `GoodStatus`. - pub unsafe fn as_GoodStatus(&self) -> (&Metadata, &roc_std::RocList) { - debug_assert_eq!(self.discriminant(), discriminant_Response::GoodStatus); - - let payload = &self.GoodStatus; - - (&payload.f0, &payload.f1) - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// A tag named NetworkError, which has no payload. - pub const NetworkError: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[56] = discriminant_Response::NetworkError as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Response>(bytes) - }; - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `into_` methods return a payload, but since the NetworkError tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_NetworkError(self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `as` methods return a payload, but since the NetworkError tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_NetworkError(&self) { - () - } - - #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] - /// A tag named Timeout, which has no payload. - pub const Timeout: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[56] = discriminant_Response::Timeout as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Response>(bytes) - }; - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `into_` methods return a payload, but since the Timeout tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_Timeout(self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `as` methods return a payload, but since the Timeout tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Timeout(&self) { - () - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_Response { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(104)) - } - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_Response) { - let discriminant_ptr: *mut discriminant_Response = (self as *mut Response).cast(); - - unsafe { - *(discriminant_ptr.add(104)) = discriminant; - } - } - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// A tag named NetworkError, which has no payload. - pub const NetworkError: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[104] = discriminant_Response::NetworkError as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Response>(bytes) - }; - - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] - /// A tag named Timeout, which has no payload. - pub const Timeout: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[104] = discriminant_Response::Timeout as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], Response>(bytes) - }; -} - -impl Drop for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_Response::BadRequest => unsafe { - core::mem::ManuallyDrop::drop(&mut self.BadRequest) - }, - discriminant_Response::BadStatus => unsafe { - core::mem::ManuallyDrop::drop(&mut self.BadStatus) - }, - discriminant_Response::GoodStatus => unsafe { - core::mem::ManuallyDrop::drop(&mut self.GoodStatus) - }, - discriminant_Response::NetworkError => {} - discriminant_Response::Timeout => {} - } - } -} - -impl Eq for Response {} - -impl PartialEq for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_Response::BadRequest => self.BadRequest == other.BadRequest, - discriminant_Response::BadStatus => self.BadStatus == other.BadStatus, - discriminant_Response::GoodStatus => self.GoodStatus == other.GoodStatus, - discriminant_Response::NetworkError => true, - discriminant_Response::Timeout => true, - } - } - } -} - -impl PartialOrd for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Response::BadRequest => self.BadRequest.partial_cmp(&other.BadRequest), - discriminant_Response::BadStatus => self.BadStatus.partial_cmp(&other.BadStatus), - discriminant_Response::GoodStatus => self.GoodStatus.partial_cmp(&other.GoodStatus), - discriminant_Response::NetworkError => Some(core::cmp::Ordering::Equal), - discriminant_Response::Timeout => Some(core::cmp::Ordering::Equal), - } - } - } -} - -impl Ord for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_Response::BadRequest => self.BadRequest.cmp(&other.BadRequest), - discriminant_Response::BadStatus => self.BadStatus.cmp(&other.BadStatus), - discriminant_Response::GoodStatus => self.GoodStatus.cmp(&other.GoodStatus), - discriminant_Response::NetworkError => core::cmp::Ordering::Equal, - discriminant_Response::Timeout => core::cmp::Ordering::Equal, - } - } - } -} - -impl Clone for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_Response::BadRequest => Self { - BadRequest: self.BadRequest.clone(), - }, - discriminant_Response::BadStatus => Self { - BadStatus: self.BadStatus.clone(), - }, - discriminant_Response::GoodStatus => Self { - GoodStatus: self.GoodStatus.clone(), - }, - discriminant_Response::NetworkError => { - core::mem::transmute::, Response>( - core::mem::MaybeUninit::uninit(), - ) - } - discriminant_Response::Timeout => { - core::mem::transmute::, Response>( - core::mem::MaybeUninit::uninit(), - ) - } - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_Response::BadRequest => unsafe { - discriminant_Response::BadRequest.hash(state); - self.BadRequest.hash(state); - }, - discriminant_Response::BadStatus => unsafe { - discriminant_Response::BadStatus.hash(state); - self.BadStatus.hash(state); - }, - discriminant_Response::GoodStatus => unsafe { - discriminant_Response::GoodStatus.hash(state); - self.GoodStatus.hash(state); - }, - discriminant_Response::NetworkError => discriminant_Response::NetworkError.hash(state), - discriminant_Response::Timeout => discriminant_Response::Timeout.hash(state), - } - } -} - -impl core::fmt::Debug for Response { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Response::")?; - - unsafe { - match self.discriminant() { - discriminant_Response::BadRequest => f - .debug_tuple("BadRequest") - .field(&*self.BadRequest) - .finish(), - discriminant_Response::BadStatus => f - .debug_tuple("BadStatus") - .field(&(&*self.BadStatus).f0) - .field(&(&*self.BadStatus).f1) - .finish(), - discriminant_Response::GoodStatus => f - .debug_tuple("GoodStatus") - .field(&(&*self.GoodStatus).f0) - .field(&(&*self.GoodStatus).f1) - .finish(), - discriminant_Response::NetworkError => f.write_str("NetworkError"), - discriminant_Response::Timeout => f.write_str("Timeout"), - } - } - } -} - -impl U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U7 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U7) { - let discriminant_ptr: *mut discriminant_U7 = (self as *mut U7).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U7::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U7` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U7::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U7` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U7::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U7::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U7 {} - -impl PartialEq for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U7::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U7::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U7::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U7::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U7::MimeType => unsafe { - discriminant_U7::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U7 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U7::")?; - - unsafe { - match self.discriminant() { - discriminant_U7::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U6 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U6) { - let discriminant_ptr: *mut discriminant_U6 = (self as *mut U6).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U6::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U6` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U6::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U6` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U6::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U6::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U6 {} - -impl PartialEq for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U6::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U6::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U6::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U6::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U6::MimeType => unsafe { - discriminant_U6::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U6 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U6::")?; - - unsafe { - match self.discriminant() { - discriminant_U6::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U5 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U5) { - let discriminant_ptr: *mut discriminant_U5 = (self as *mut U5).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U5::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U5` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U5::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U5` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U5::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U5::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U5 {} - -impl PartialEq for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U5::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U5::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U5::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U5::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U5::MimeType => unsafe { - discriminant_U5::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U5 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U5::")?; - - unsafe { - match self.discriminant() { - discriminant_U5::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U3 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U3) { - let discriminant_ptr: *mut discriminant_U3 = (self as *mut U3).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U3::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U3` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U3::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U3` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U3::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U3::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U3 {} - -impl PartialEq for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U3::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U3::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U3::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U3::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U3::MimeType => unsafe { - discriminant_U3::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U3 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U3::")?; - - unsafe { - match self.discriminant() { - discriminant_U3::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_U1 { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(0)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_U1) { - let discriminant_ptr: *mut discriminant_U1 = (self as *mut U1).cast(); - - unsafe { - *(discriminant_ptr.add(0)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `MimeType`, with the appropriate payload - pub fn MimeType(arg: roc_std::RocStr) -> Self { - let mut answer = Self { - MimeType: core::mem::ManuallyDrop::new(arg), - }; - - answer.set_discriminant(discriminant_U1::MimeType); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U1` has a `.discriminant()` of `MimeType` and convert it to `MimeType`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn into_MimeType(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U1::MimeType); - - let payload = core::mem::ManuallyDrop::take(&mut self.MimeType); - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `U1` has a `.discriminant()` of `MimeType` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `MimeType`. - pub unsafe fn as_MimeType(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.discriminant(), discriminant_U1::MimeType); - - let payload = &self.MimeType; - - &payload - } -} - -impl Drop for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_U1::MimeType => unsafe { - core::mem::ManuallyDrop::drop(&mut self.MimeType) - }, - } - } -} - -impl Eq for U1 {} - -impl PartialEq for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_U1::MimeType => self.MimeType == other.MimeType, - } - } - } -} - -impl PartialOrd for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U1::MimeType => self.MimeType.partial_cmp(&other.MimeType), - } - } - } -} - -impl Ord for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_U1::MimeType => self.MimeType.cmp(&other.MimeType), - } - } - } -} - -impl Clone for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_U1::MimeType => Self { - MimeType: self.MimeType.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_U1::MimeType => unsafe { - discriminant_U1::MimeType.hash(state); - self.MimeType.hash(state); - }, - } - } -} - -impl core::fmt::Debug for U1 { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("U1::")?; - - unsafe { - match self.discriminant() { - discriminant_U1::MimeType => { - f.debug_tuple("MimeType").field(&*self.MimeType).finish() - } - } - } - } -} - -impl TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Returns which variant this tag union holds. Note that this never includes a payload! - pub fn discriminant(&self) -> discriminant_TimeoutConfig { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(8)) - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Internal helper - fn set_discriminant(&mut self, discriminant: discriminant_TimeoutConfig) { - let discriminant_ptr: *mut discriminant_TimeoutConfig = (self as *mut TimeoutConfig).cast(); - - unsafe { - *(discriminant_ptr.add(8)) = discriminant; - } - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// A tag named NoTimeout, which has no payload. - pub const NoTimeout: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[8] = discriminant_TimeoutConfig::NoTimeout as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], TimeoutConfig>(bytes) - }; - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `into_` methods return a payload, but since the NoTimeout tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_NoTimeout(self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Other `as` methods return a payload, but since the NoTimeout tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_NoTimeout(&self) { - () - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Construct a tag named `TimeoutMilliseconds`, with the appropriate payload - pub fn TimeoutMilliseconds(arg: u64) -> Self { - let mut answer = Self { - TimeoutMilliseconds: arg, - }; - - answer.set_discriminant(discriminant_TimeoutConfig::TimeoutMilliseconds); - - answer - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `TimeoutConfig` has a `.discriminant()` of `TimeoutMilliseconds` and convert it to `TimeoutMilliseconds`'s payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `TimeoutMilliseconds`. - pub unsafe fn into_TimeoutMilliseconds(self) -> u64 { - debug_assert_eq!( - self.discriminant(), - discriminant_TimeoutConfig::TimeoutMilliseconds - ); - - let payload = self.TimeoutMilliseconds; - - payload - } - - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - /// Unsafely assume the given `TimeoutConfig` has a `.discriminant()` of `TimeoutMilliseconds` and return its payload. - /// (Always examine `.discriminant()` first to make sure this is the correct variant!) - /// Panics in debug builds if the `.discriminant()` doesn't return `TimeoutMilliseconds`. - pub unsafe fn as_TimeoutMilliseconds(&self) -> &u64 { - debug_assert_eq!( - self.discriminant(), - discriminant_TimeoutConfig::TimeoutMilliseconds - ); - - let payload = &self.TimeoutMilliseconds; - - &payload - } -} - -impl Drop for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn drop(&mut self) { - // Drop the payloads - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => {} - discriminant_TimeoutConfig::TimeoutMilliseconds => {} - } - } -} - -impl Eq for TimeoutConfig {} - -impl PartialEq for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn eq(&self, other: &Self) -> bool { - if self.discriminant() != other.discriminant() { - return false; - } - - unsafe { - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => true, - discriminant_TimeoutConfig::TimeoutMilliseconds => { - self.TimeoutMilliseconds == other.TimeoutMilliseconds - } - } - } - } -} - -impl PartialOrd for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn partial_cmp(&self, other: &Self) -> Option { - match self.discriminant().partial_cmp(&other.discriminant()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => Some(core::cmp::Ordering::Equal), - discriminant_TimeoutConfig::TimeoutMilliseconds => self - .TimeoutMilliseconds - .partial_cmp(&other.TimeoutMilliseconds), - } - } - } -} - -impl Ord for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.discriminant().cmp(&other.discriminant()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => core::cmp::Ordering::Equal, - discriminant_TimeoutConfig::TimeoutMilliseconds => { - self.TimeoutMilliseconds.cmp(&other.TimeoutMilliseconds) - } - } - } - } -} - -// impl Copy for TimeoutConfig {} - -impl Clone for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => { - core::mem::transmute::, TimeoutConfig>( - core::mem::MaybeUninit::uninit(), - ) - } - discriminant_TimeoutConfig::TimeoutMilliseconds => Self { - TimeoutMilliseconds: self.TimeoutMilliseconds.clone(), - }, - } - }; - - answer.set_discriminant(self.discriminant()); - - answer - } -} - -impl core::hash::Hash for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn hash(&self, state: &mut H) { - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => { - discriminant_TimeoutConfig::NoTimeout.hash(state) - } - discriminant_TimeoutConfig::TimeoutMilliseconds => unsafe { - discriminant_TimeoutConfig::TimeoutMilliseconds.hash(state); - self.TimeoutMilliseconds.hash(state); - }, - } - } -} - -impl core::fmt::Debug for TimeoutConfig { - #[cfg(any( - target_arch = "arm", - target_arch = "aarch64", - target_arch = "wasm32", - target_arch = "x86", - target_arch = "x86_64" - ))] - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("TimeoutConfig::")?; - - unsafe { - match self.discriminant() { - discriminant_TimeoutConfig::NoTimeout => f.write_str("NoTimeout"), - discriminant_TimeoutConfig::TimeoutMilliseconds => f - .debug_tuple("TimeoutMilliseconds") - .field(&self.TimeoutMilliseconds) - .finish(), - } - } - } -} diff --git a/examples/cli/cli-platform/src/lib.rs b/examples/cli/cli-platform/src/lib.rs deleted file mode 100644 index df313bb110..0000000000 --- a/examples/cli/cli-platform/src/lib.rs +++ /dev/null @@ -1,564 +0,0 @@ -#![allow(non_snake_case)] - -mod file_glue; -mod glue; - -use core::alloc::Layout; -use core::ffi::c_void; -use core::mem::MaybeUninit; -use glue::Metadata; -use roc_std::{RocDict, RocList, RocResult, RocStr}; -use std::borrow::{Borrow, Cow}; -use std::ffi::OsStr; -use std::fs::File; -use std::io::Write; -use std::path::Path; -use std::time::Duration; - -use file_glue::ReadErr; -use file_glue::WriteErr; - -extern "C" { - #[link_name = "roc__mainForHost_1_exposed_generic"] - fn roc_main(output: *mut u8); - - #[link_name = "roc__mainForHost_1_exposed_size"] - fn roc_main_size() -> i64; - - #[link_name = "roc__mainForHost_0_caller"] - fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); - - #[allow(dead_code)] - #[link_name = "roc__mainForHost_0_size"] - fn size_Fx() -> i64; - - #[link_name = "roc__mainForHost_0_result_size"] - fn size_Fx_result() -> i64; -} - -#[no_mangle] -pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { - libc::malloc(size) -} - -#[no_mangle] -pub unsafe extern "C" fn roc_realloc( - c_ptr: *mut c_void, - new_size: usize, - _old_size: usize, - _alignment: u32, -) -> *mut c_void { - libc::realloc(c_ptr, new_size) -} - -#[no_mangle] -pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { - libc::free(c_ptr) -} - -#[no_mangle] -pub unsafe extern "C" fn roc_panic(msg: &RocStr, tag_id: u32) { - match tag_id { - 0 => { - eprintln!("Roc crashed with:\n\n\t{}\n", msg.as_str()); - - print_backtrace(); - std::process::exit(1); - } - 1 => { - eprintln!("The program crashed with:\n\n\t{}\n", msg.as_str()); - - print_backtrace(); - std::process::exit(1); - } - _ => todo!(), - } -} - -#[cfg(unix)] -#[no_mangle] -pub unsafe extern "C" fn roc_getppid() -> libc::pid_t { - libc::getppid() -} - -#[cfg(unix)] -#[no_mangle] -pub unsafe extern "C" fn roc_mmap( - addr: *mut libc::c_void, - len: libc::size_t, - prot: libc::c_int, - flags: libc::c_int, - fd: libc::c_int, - offset: libc::off_t, -) -> *mut libc::c_void { - libc::mmap(addr, len, prot, flags, fd, offset) -} - -#[cfg(unix)] -#[no_mangle] -pub unsafe extern "C" fn roc_shm_open( - name: *const libc::c_char, - oflag: libc::c_int, - mode: libc::mode_t, -) -> libc::c_int { - libc::shm_open(name, oflag, mode as libc::c_uint) -} - -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_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { - libc::memset(dst, c, n) -} - -#[no_mangle] -pub extern "C" fn rust_main() { - let size = unsafe { roc_main_size() } as usize; - let layout = Layout::array::(size).unwrap(); - - unsafe { - // TODO allocate on the stack if it's under a certain size - let buffer = std::alloc::alloc(layout); - - roc_main(buffer); - - call_the_closure(buffer); - - std::alloc::dealloc(buffer, layout); - } -} - -unsafe fn call_the_closure(closure_data_ptr: *const u8) -> u8 { - let size = size_Fx_result() as usize; - let layout = Layout::array::(size).unwrap(); - let buffer = std::alloc::alloc(layout) as *mut u8; - - call_Fx( - // This flags pointer will never get dereferenced - MaybeUninit::uninit().as_ptr(), - closure_data_ptr as *const u8, - buffer as *mut u8, - ); - - std::alloc::dealloc(buffer, layout); - - // TODO return the u8 exit code returned by the Fx closure - 0 -} - -#[no_mangle] -pub extern "C" fn roc_fx_envDict() -> RocDict { - // TODO: can we be more efficient about reusing the String's memory for RocStr? - std::env::vars_os() - .map(|(key, val)| { - ( - RocStr::from(key.to_string_lossy().borrow()), - RocStr::from(val.to_string_lossy().borrow()), - ) - }) - .collect() -} - -#[no_mangle] -pub extern "C" fn roc_fx_args() -> RocList { - // TODO: can we be more efficient about reusing the String's memory for RocStr? - std::env::args_os() - .map(|os_str| RocStr::from(os_str.to_string_lossy().borrow())) - .collect() -} - -#[no_mangle] -pub extern "C" fn roc_fx_envVar(roc_str: &RocStr) -> RocResult { - // TODO: can we be more efficient about reusing the String's memory for RocStr? - match std::env::var_os(roc_str.as_str()) { - Some(os_str) => RocResult::ok(RocStr::from(os_str.to_string_lossy().borrow())), - None => RocResult::err(()), - } -} - -#[no_mangle] -pub extern "C" fn roc_fx_setCwd(roc_path: &RocList) -> RocResult<(), ()> { - match std::env::set_current_dir(path_from_roc_path(roc_path)) { - Ok(()) => RocResult::ok(()), - Err(_) => RocResult::err(()), - } -} - -#[no_mangle] -pub extern "C" fn roc_fx_processExit(exit_code: u8) { - std::process::exit(exit_code as i32); -} - -#[no_mangle] -pub extern "C" fn roc_fx_exePath(_roc_str: &RocStr) -> RocResult, ()> { - match std::env::current_exe() { - Ok(path_buf) => RocResult::ok(os_str_to_roc_path(path_buf.as_path().as_os_str())), - Err(_) => RocResult::err(()), - } -} - -#[no_mangle] -pub extern "C" fn roc_fx_stdinLine() -> RocStr { - use std::io::BufRead; - - let stdin = std::io::stdin(); - let line1 = stdin.lock().lines().next().unwrap().unwrap(); - - RocStr::from(line1.as_str()) -} - -#[no_mangle] -pub extern "C" fn roc_fx_stdoutLine(line: &RocStr) { - let string = line.as_str(); - println!("{}", string); -} - -#[no_mangle] -pub extern "C" fn roc_fx_stdoutWrite(text: &RocStr) { - let string = text.as_str(); - print!("{}", string); - std::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); - std::io::stderr().flush().unwrap(); -} - -// #[no_mangle] -// pub extern "C" fn roc_fx_fileWriteUtf8( -// roc_path: &RocList, -// roc_string: &RocStr, -// // ) -> RocResult<(), WriteErr> { -// ) -> (u8, u8) { -// let _ = write_slice(roc_path, roc_string.as_str().as_bytes()); - -// (255, 255) -// } - -// #[no_mangle] -// pub extern "C" fn roc_fx_fileWriteUtf8(roc_path: &RocList, roc_string: &RocStr) -> Fail { -// write_slice2(roc_path, roc_string.as_str().as_bytes()) -// } -#[no_mangle] -pub extern "C" fn roc_fx_fileWriteUtf8( - roc_path: &RocList, - roc_str: &RocStr, -) -> RocResult<(), WriteErr> { - write_slice(roc_path, roc_str.as_str().as_bytes()) -} - -#[no_mangle] -pub extern "C" fn roc_fx_fileWriteBytes( - roc_path: &RocList, - roc_bytes: &RocList, -) -> RocResult<(), WriteErr> { - write_slice(roc_path, roc_bytes.as_slice()) -} - -fn write_slice(roc_path: &RocList, bytes: &[u8]) -> RocResult<(), WriteErr> { - match File::create(path_from_roc_path(roc_path)) { - Ok(mut file) => match file.write_all(bytes) { - Ok(()) => RocResult::ok(()), - Err(_) => { - todo!("Report a file write error"); - } - }, - Err(_) => { - todo!("Report a file open error"); - } - } -} - -#[cfg(target_family = "unix")] -fn path_from_roc_path(bytes: &RocList) -> Cow<'_, Path> { - use std::os::unix::ffi::OsStrExt; - let os_str = OsStr::from_bytes(bytes.as_slice()); - Cow::Borrowed(Path::new(os_str)) -} - -#[cfg(target_family = "windows")] -fn path_from_roc_path(bytes: &RocList) -> Cow<'_, Path> { - use std::os::windows::ffi::OsStringExt; - - let bytes = bytes.as_slice(); - assert_eq!(bytes.len() % 2, 0); - let characters: &[u16] = - unsafe { std::slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len() / 2) }; - - let os_string = std::ffi::OsString::from_wide(characters); - - Cow::Owned(std::path::PathBuf::from(os_string)) -} - -#[no_mangle] -pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList) -> RocResult, ReadErr> { - use std::io::Read; - - let mut bytes = Vec::new(); - - match File::open(path_from_roc_path(roc_path)) { - Ok(mut file) => match file.read_to_end(&mut bytes) { - Ok(_bytes_read) => RocResult::ok(RocList::from(bytes.as_slice())), - Err(_) => { - todo!("Report a file write error"); - } - }, - Err(_) => { - todo!("Report a file open error"); - } - } -} - -#[no_mangle] -pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList) -> RocResult<(), ReadErr> { - match std::fs::remove_file(path_from_roc_path(roc_path)) { - Ok(()) => RocResult::ok(()), - Err(_) => { - todo!("Report a file write error"); - } - } -} - -#[no_mangle] -pub extern "C" fn roc_fx_cwd() -> RocList { - // TODO instead, call getcwd on UNIX and GetCurrentDirectory on Windows - match std::env::current_dir() { - Ok(path_buf) => os_str_to_roc_path(path_buf.into_os_string().as_os_str()), - Err(_) => { - // Default to empty path - RocList::empty() - } - } -} - -#[no_mangle] -pub extern "C" fn roc_fx_dirList( - // TODO: this RocResult should use Dir.WriteErr - but right now it's File.WriteErr - // because glue doesn't have Dir.WriteErr yet. - roc_path: &RocList, -) -> RocResult>, WriteErr> { - println!("Dir.list..."); - match std::fs::read_dir(path_from_roc_path(roc_path)) { - Ok(dir_entries) => RocResult::ok( - dir_entries - .map(|opt_dir_entry| match opt_dir_entry { - Ok(entry) => os_str_to_roc_path(entry.path().into_os_string().as_os_str()), - Err(_) => { - todo!("handle dir_entry path didn't resolve") - } - }) - .collect::>>(), - ), - Err(_) => { - todo!("handle Dir.list error"); - } - } -} - -#[cfg(target_family = "unix")] -fn os_str_to_roc_path(os_str: &OsStr) -> RocList { - use std::os::unix::ffi::OsStrExt; - - RocList::from(os_str.as_bytes()) -} - -#[cfg(target_family = "windows")] -fn os_str_to_roc_path(os_str: &OsStr) -> RocList { - use std::os::windows::ffi::OsStrExt; - - let bytes: Vec<_> = os_str.encode_wide().flat_map(|c| c.to_be_bytes()).collect(); - - RocList::from(bytes.as_slice()) -} - -#[no_mangle] -pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response { - let mut builder = reqwest::blocking::ClientBuilder::new(); - - if roc_request.timeout.discriminant() == glue::discriminant_TimeoutConfig::TimeoutMilliseconds { - let ms: &u64 = unsafe { roc_request.timeout.as_TimeoutMilliseconds() }; - builder = builder.timeout(Duration::from_millis(*ms)); - } - - let client = match builder.build() { - Ok(c) => c, - Err(_) => { - return glue::Response::NetworkError; // TLS backend cannot be initialized - } - }; - - let method = match roc_request.method { - glue::Method::Connect => reqwest::Method::CONNECT, - glue::Method::Delete => reqwest::Method::DELETE, - glue::Method::Get => reqwest::Method::GET, - glue::Method::Head => reqwest::Method::HEAD, - glue::Method::Options => reqwest::Method::OPTIONS, - glue::Method::Patch => reqwest::Method::PATCH, - glue::Method::Post => reqwest::Method::POST, - glue::Method::Put => reqwest::Method::PUT, - glue::Method::Trace => reqwest::Method::TRACE, - }; - - let url = roc_request.url.as_str(); - - let mut req_builder = client.request(method, url); - for header in roc_request.headers.iter() { - let (name, value) = unsafe { header.as_Header() }; - req_builder = req_builder.header(name.as_str(), value.as_str()); - } - if roc_request.body.discriminant() == glue::discriminant_Body::Body { - let (mime_type_tag, body_byte_list) = unsafe { roc_request.body.as_Body() }; - let mime_type_str: &RocStr = unsafe { mime_type_tag.as_MimeType() }; - - req_builder = req_builder.header("Content-Type", mime_type_str.as_str()); - req_builder = req_builder.body(body_byte_list.as_slice().to_vec()); - } - - let request = match req_builder.build() { - Ok(req) => req, - Err(err) => { - return glue::Response::BadRequest(RocStr::from(err.to_string().as_str())); - } - }; - - match client.execute(request) { - Ok(response) => { - let status = response.status(); - let status_str = status.canonical_reason().unwrap_or_else(|| status.as_str()); - - let headers_iter = response.headers().iter().map(|(name, value)| { - glue::Header::Header( - RocStr::from(name.as_str()), - RocStr::from(value.to_str().unwrap_or_default()), - ) - }); - - let metadata = Metadata { - headers: RocList::from_iter(headers_iter), - statusText: RocStr::from(status_str), - url: RocStr::from(url), - statusCode: status.as_u16(), - }; - - let bytes = response.bytes().unwrap_or_default(); - let body: RocList = RocList::from_iter(bytes.into_iter()); - - if status.is_success() { - glue::Response::GoodStatus(metadata, body) - } else { - glue::Response::BadStatus(metadata, body) - } - } - Err(err) => { - if err.is_timeout() { - glue::Response::Timeout - } else if err.is_request() { - glue::Response::BadRequest(RocStr::from(err.to_string().as_str())) - } else { - glue::Response::NetworkError - } - } - } -} diff --git a/examples/cli/cli-platform/src/main.rs b/examples/cli/cli-platform/src/main.rs deleted file mode 100644 index 57692d3619..0000000000 --- a/examples/cli/cli-platform/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - host::rust_main(); -} diff --git a/examples/cli/countdown.roc b/examples/cli/countdown.roc index 710ee83486..f96624b40e 100644 --- a/examples/cli/countdown.roc +++ b/examples/cli/countdown.roc @@ -1,18 +1,18 @@ app "countdown" - packages { pf: "cli-platform/main.roc" } - imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop, succeed }] + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } + imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop }] provides [main] to pf main = - _ <- await (Stdout.line "\nLet's count down from 10 together - all you have to do is press .") + _ <- await (Stdout.line "\nLet's count down from 3 together - all you have to do is press .") _ <- await Stdin.line - loop 10 tick + loop 3 tick tick = \n -> if n == 0 then _ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂") - succeed (Done {}) + Task.ok (Done {}) else _ <- await (n |> Num.toStr |> \s -> "\(s)..." |> Stdout.line) _ <- await Stdin.line - succeed (Step (n - 1)) + Task.ok (Step (n - 1)) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index e380d095bf..5e744a9bac 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -1,17 +1,21 @@ app "echo" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [pf.Stdin, pf.Stdout, pf.Task.{ Task }] provides [main] to pf -main : Task {} [] +main : Task {} I32 main = _ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂") - Task.loop {} \_ -> Task.map tick Step -tick : Task.Task {} [] -tick = + Task.loop {} tick + +tick : {} -> Task [Step {}, Done {}] * +tick = \{} -> shout <- Task.await Stdin.line - Stdout.line (echo shout) + + when shout is + Input s -> Stdout.line (echo s) |> Task.map Step + End -> Stdout.line (echo "Received end of input (EOF).") |> Task.map Done echo : Str -> Str echo = \shout -> diff --git a/examples/cli/env.roc b/examples/cli/env.roc index 981c7f49c8..73e9061e66 100644 --- a/examples/cli/env.roc +++ b/examples/cli/env.roc @@ -1,9 +1,9 @@ app "env" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [pf.Stdout, pf.Stderr, pf.Env, pf.Task.{ Task }] provides [main] to pf -main : Task {} [] +main : Task {} I32 main = task = Env.decode "EDITOR" diff --git a/examples/cli/file.roc b/examples/cli/fileBROKEN.roc similarity index 88% rename from examples/cli/file.roc rename to examples/cli/fileBROKEN.roc index e7803d4a6b..172ef73d9f 100644 --- a/examples/cli/file.roc +++ b/examples/cli/fileBROKEN.roc @@ -1,7 +1,6 @@ app "file-io" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [ - pf.Process, pf.Stdout, pf.Stderr, pf.Task.{ Task }, @@ -12,7 +11,7 @@ app "file-io" ] provides [main] to pf -main : Task {} [] +main : Task {} I32 main = path = Path.fromStr "out.txt" task = @@ -42,4 +41,4 @@ main = _ -> "Uh oh, there was an error!" {} <- Stderr.line msg |> Task.await - Process.exit 1 + Task.err 1 diff --git a/examples/cli/form.roc b/examples/cli/form.roc index 9cd6e34817..48603a9136 100644 --- a/examples/cli/form.roc +++ b/examples/cli/form.roc @@ -1,12 +1,20 @@ app "form" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [pf.Stdin, pf.Stdout, pf.Task.{ await, Task }] provides [main] to pf -main : Task {} [] +main : Task {} I32 main = _ <- await (Stdout.line "What's your first name?") firstName <- await Stdin.line + _ <- await (Stdout.line "What's your last name?") lastName <- await Stdin.line - Stdout.line "Hi, \(firstName) \(lastName)! 👋" + + Stdout.line "Hi, \(unwrap firstName) \(unwrap lastName)! 👋" + +unwrap : [Input Str, End] -> Str +unwrap = \input -> + when input is + Input line -> line + End -> "Received end of input (EOF)." diff --git a/examples/cli/http-get.roc b/examples/cli/http-get.roc index 0333aba08f..0030c14cf1 100644 --- a/examples/cli/http-get.roc +++ b/examples/cli/http-get.roc @@ -1,24 +1,31 @@ app "http-get" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [pf.Http, pf.Task.{ Task }, pf.Stdin, pf.Stdout] provides [main] to pf -main : Task {} [] +main : Task {} I32 main = - _ <- Task.await (Stdout.line "Please enter a URL to fetch") + _ <- Task.await (Stdout.line "Enter a URL to fetch. It must contain a scheme like \"http://\" or \"https://\".") - url <- Task.await Stdin.line + input <- Task.await Stdin.line - request = { - method: Get, - headers: [], - url, - body: Http.emptyBody, - timeout: NoTimeout, - } + when input is + End -> + Stdout.line "I received end-of-input (EOF) instead of a URL." - output <- Http.send request - |> Task.onFail (\err -> err |> Http.errorToString |> Task.succeed) - |> Task.await + Input url -> + request = { + method: Get, + headers: [], + url, + body: Http.emptyBody, + timeout: NoTimeout, + } - Stdout.line output + output <- Http.send request + |> Task.onErr \err -> err + |> Http.errorToString + |> Task.ok + |> Task.await + + Stdout.line output diff --git a/examples/cli/ingested-file-bytes.roc b/examples/cli/ingested-file-bytes.roc index 6db12f4fe4..49898c84e1 100644 --- a/examples/cli/ingested-file-bytes.roc +++ b/examples/cli/ingested-file-bytes.roc @@ -1,5 +1,5 @@ app "ingested-file-bytes" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [ pf.Stdout, "ingested-file.roc" as ownCode : _, # A type hole can also be used here. diff --git a/examples/cli/ingested-file.roc b/examples/cli/ingested-file.roc index 4a342285a4..58e1cb2ca8 100644 --- a/examples/cli/ingested-file.roc +++ b/examples/cli/ingested-file.roc @@ -1,5 +1,5 @@ app "ingested-file" - packages { pf: "cli-platform/main.roc" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [ pf.Stdout, "ingested-file.roc" as ownCode : Str, diff --git a/examples/helloWorld.roc b/examples/helloWorld.roc index fe6bddd76e..50e600dd4e 100644 --- a/examples/helloWorld.roc +++ b/examples/helloWorld.roc @@ -1,5 +1,5 @@ app "helloWorld" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [pf.Stdout] provides [main] to pf diff --git a/examples/helloWorldNoURL.roc b/examples/helloWorldNoURL.roc deleted file mode 100644 index 7eef5d17a1..0000000000 --- a/examples/helloWorldNoURL.roc +++ /dev/null @@ -1,7 +0,0 @@ -app "helloWorld" - packages { pf: "cli/cli-platform/main.roc" } - imports [pf.Stdout] - provides [main] to pf - -main = - Stdout.line "Hello, World!" diff --git a/examples/inspect-logging.roc b/examples/inspect-logging.roc index fd284def02..147ad05e26 100644 --- a/examples/inspect-logging.roc +++ b/examples/inspect-logging.roc @@ -2,7 +2,7 @@ # Shows how Roc values can be logged # app "inspect-logging" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" } imports [ pf.Stdout, LogFormatter, diff --git a/examples/parser/examples/letter-counts.roc b/examples/parser/examples/letter-counts.roc index b7a58e7ab5..23a28cfb27 100644 --- a/examples/parser/examples/letter-counts.roc +++ b/examples/parser/examples/letter-counts.roc @@ -1,6 +1,6 @@ app "example" packages { - cli: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br", + cli: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br", parser: "../package/main.roc", } imports [ diff --git a/examples/static-site-gen/platform/Html/Attributes.roc b/examples/static-site-gen/platform/Html/Attributes.roc index 54381e0886..5a0ea68cd3 100644 --- a/examples/static-site-gen/platform/Html/Attributes.roc +++ b/examples/static-site-gen/platform/Html/Attributes.roc @@ -11,6 +11,7 @@ interface Html.Attributes alt, ariaLabel, ariaLabelledBy, + ariaHidden, async, autocapitalize, autocomplete, @@ -154,6 +155,7 @@ allow = attribute "allow" alt = attribute "alt" ariaLabel = attribute "aria-label" ariaLabelledBy = attribute "aria-labelledby" +ariaHidden = attribute "aria-label" async = attribute "async" autocapitalize = attribute "autocapitalize" autocomplete = attribute "autocomplete" diff --git a/flake.lock b/flake.lock index 37ed04c9a6..e0c185ee01 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,21 @@ { "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -59,6 +75,7 @@ }, "root": { "inputs": { + "flake-compat": "flake-compat", "flake-utils": "flake-utils", "nixgl": "nixgl", "nixpkgs": "nixpkgs", diff --git a/flake.nix b/flake.nix index 2338598af7..cfb5b92855 100644 --- a/flake.nix +++ b/flake.nix @@ -18,9 +18,15 @@ inputs.nixpkgs.follows = "nixpkgs"; inputs.flake-utils.follows = "flake-utils"; }; + + # for non flake backwards compatibility + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; }; - outputs = { self, nixpkgs, rust-overlay, flake-utils, nixgl }: + outputs = { self, nixpkgs, rust-overlay, flake-utils, nixgl, ... }@inputs: let supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux" ]; in flake-utils.lib.eachSystem supportedSystems (system: let @@ -28,17 +34,11 @@ ++ (if system == "x86_64-linux" then [ nixgl.overlay ] else [ ]); pkgs = import nixpkgs { inherit system overlays; }; - # When updating the zig or llvm version, make sure they stay in sync. - # Also update in default.nix (TODO: maybe we can use nix code to sync this) - zigPkg = pkgs.zig_0_11; - llvmPkgs = pkgs.llvmPackages_16; - llvmVersion = builtins.splitVersion llvmPkgs.release_version; - llvmMajorMinorStr = builtins.elemAt llvmVersion 0 + builtins.elemAt llvmVersion 1; + rocBuild = import ./nix { inherit pkgs; }; - # get current working directory - cwd = builtins.toString ./.; - rust = - pkgs.rust-bin.fromRustupToolchainFile "${cwd}/rust-toolchain.toml"; + compile-deps = rocBuild.compile-deps; + inherit (compile-deps) zigPkg llvmPkgs llvmVersion + llvmMajorMinorStr glibcPath libGccSPath darwinInputs; # DevInputs are not necessary to build roc as a user linuxDevInputs = with pkgs; @@ -55,24 +55,14 @@ xorg.libxcb ]; - darwinInputs = with pkgs; - lib.optionals stdenv.isDarwin - (with pkgs.darwin.apple_sdk.frameworks; [ - AppKit - CoreFoundation - CoreServices - Foundation - Security - ]); - # DevInputs are not necessary to build roc as a user darwinDevInputs = with pkgs; lib.optionals stdenv.isDarwin - (with pkgs.darwin.apple_sdk.frameworks; [ - CoreVideo # for examples/gui - Metal # for examples/gui - curl # for wasm-bindgen-cli libcurl (see ./ci/www-repl.sh) - ]); + (with pkgs.darwin.apple_sdk.frameworks; [ + CoreVideo # for examples/gui + Metal # for examples/gui + curl # for wasm-bindgen-cli libcurl (see ./ci/www-repl.sh) + ]); # For debugging LLVM IR debugir = pkgs.stdenv.mkDerivation { @@ -111,7 +101,7 @@ zlib # faster builds - see https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker llvmPkgs.lld - rust + rocBuild.rust-shell ]); sharedDevInputs = (with pkgs; [ @@ -134,14 +124,15 @@ alias fmtc='cargo fmt --all -- --check' ''; - in { + in + { devShell = pkgs.mkShell { - buildInputs = sharedInputs ++ sharedDevInputs ++ darwinInputs ++ darwinDevInputs++ linuxDevInputs + buildInputs = sharedInputs ++ sharedDevInputs ++ darwinInputs ++ darwinDevInputs ++ linuxDevInputs ++ (if system == "x86_64-linux" then - [ pkgs.nixgl.nixVulkanIntel ] - else - [ ]); + [ pkgs.nixgl.nixVulkanIntel ] + else + [ ]); # nix does not store libs in /usr/lib or /lib # for libgcc_s.so.1 @@ -153,11 +144,11 @@ LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath - ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] - ++ linuxDevInputs); + ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] + ++ linuxDevInputs); NIXPKGS_ALLOW_UNFREE = 1; # to run the GUI examples with NVIDIA's closed source drivers - + shellHook = '' export LLVM_SYS_${llvmMajorMinorStr}_PREFIX="${llvmPkgs.llvm.dev}" ${aliases} @@ -167,6 +158,14 @@ formatter = pkgs.nixpkgs-fmt; # You can build this package (the roc CLI) with the `nix build` command. - packages.default = import ./. { inherit pkgs; }; + packages = { + default = rocBuild.roc-cli; + + # all rust crates in workspace.members of Cargo.toml + full = rocBuild.roc-full; + # only the CLI crate = executable provided in nightly releases + cli = rocBuild.roc-cli; + lang-server = rocBuild.roc-lang-server; + }; }); } diff --git a/nix/builder.nix b/nix/builder.nix new file mode 100644 index 0000000000..4038a1bfbe --- /dev/null +++ b/nix/builder.nix @@ -0,0 +1,87 @@ +{ pkgs, lib, rustPlatform, compile-deps, subPackage ? null }: +let + inherit (compile-deps) zigPkg llvmPkgs llvmVersion llvmMajorMinorStr glibcPath libGccSPath; + + subPackagePath = if subPackage != null then "crates/${subPackage}" else null; +in +rustPlatform.buildRustPackage { + pname = "roc" + lib.optionalString (subPackage != null) "_${subPackage}"; + version = "0.0.1"; + + buildAndTestSubdir = subPackagePath; + + src = pkgs.nix-gitignore.gitignoreSource [ ] ../.; + + cargoLock = { + lockFile = ../Cargo.lock; + outputHashes = { + "criterion-0.3.5" = "sha256-+FibPQGiR45g28xCHcM0pMN+C+Q8gO8206Wb5fiTy+k="; + "inkwell-0.2.0" = "sha256-VhTapYGonoSQ4hnDoLl4AAgj0BppAhPNA+UPuAJSuAU="; + "plotters-0.3.1" = "sha256-noy/RSjoEPZZbOJTZw1yxGcX5S+2q/7mxnUrzDyxOFw="; + "rustyline-9.1.1" = "sha256-aqQqz6nSp+Qn44gm3jXmmQUO6/fYTx7iLph2tbA24Bs="; + }; + }; + + shellHook = '' + export LLVM_SYS_${llvmMajorMinorStr}_PREFIX="${llvmPkgs.llvm.dev}" + ''; + + # required for zig + XDG_CACHE_HOME = + "xdg_cache"; # prevents zig AccessDenied error github.com/ziglang/zig/issues/6810 + # want to see backtrace in case of failure + RUST_BACKTRACE = 1; + + # skip running rust tests, problems: + # building of example platforms requires network: Could not resolve host + # zig AccessDenied error github.com/ziglang/zig/issues/6810 + # Once instance has previously been poisoned ?? + doCheck = false; + + nativeBuildInputs = (with pkgs; [ + cmake + git + pkg-config + python3 + llvmPkgs.clang + llvmPkgs.llvm.dev + llvmPkgs.bintools-unwrapped # contains lld + zigPkg + ]); + + buildInputs = (with pkgs; + [ + libffi + libxml2 + ncurses + zlib + cargo + makeWrapper # necessary for postBuild wrapProgram + ] ++ lib.optionals pkgs.stdenv.isDarwin + (with pkgs.darwin.apple_sdk.frameworks; + [ + AppKit + CoreFoundation + CoreServices + Foundation + Security + ])); + + # cp: to copy str.zig,list.zig... + # wrapProgram pkgs.stdenv.cc: to make ld available for compiler/build/src/link.rs + postInstall = + let + binPath = + lib.makeBinPath [ pkgs.stdenv.cc ]; + linuxArgs = lib.optionalString pkgs.stdenv.isLinux + "--set NIX_GLIBC_PATH ${glibcPath} --set NIX_LIBGCC_S_PATH ${libGccSPath}"; + rocPath = "$out/bin/roc"; + wrapRoc = "wrapProgram ${rocPath} ${linuxArgs} --prefix PATH : ${binPath}"; + in + # need to check if roc bin exists since it might not if subPackage is set + '' + if test -f ${rocPath}; then + ${wrapRoc} + fi + ''; +} diff --git a/nix/compile-deps.nix b/nix/compile-deps.nix new file mode 100644 index 0000000000..59bf6b2086 --- /dev/null +++ b/nix/compile-deps.nix @@ -0,0 +1,25 @@ +{ pkgs }: +let + zigPkg = pkgs.zig_0_11; + llvmPkgs = pkgs.llvmPackages_16; + llvmVersion = builtins.splitVersion llvmPkgs.release_version; + llvmMajorMinorStr = builtins.elemAt llvmVersion 0 + builtins.elemAt llvmVersion 1; + # nix does not store libs in /usr/lib or /lib + glibcPath = + if pkgs.stdenv.isLinux then "${pkgs.glibc.out}/lib" else ""; + libGccSPath = + if pkgs.stdenv.isLinux then "${pkgs.stdenv.cc.cc.lib}/lib" else ""; +in +{ + inherit zigPkg llvmPkgs llvmVersion llvmMajorMinorStr glibcPath libGccSPath; + + darwinInputs = with pkgs; + lib.optionals stdenv.isDarwin + (with pkgs.darwin.apple_sdk.frameworks; [ + AppKit + CoreFoundation + CoreServices + Foundation + Security + ]); +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000000..d62664a142 --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,27 @@ +{ pkgs }: +let + rustVersion = (pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml); + rustPlatform = pkgs.makeRustPlatform { + cargo = rustVersion; + rustc = rustVersion; + }; + + # this will allow our callPackage to reference our own packages defined below + # mainly helps with passing compile-deps and rustPlatform to builder automatically + callPackage = pkgs.lib.callPackageWith (pkgs // packages); + + packages = { + inherit rustPlatform; + compile-deps = callPackage ./compile-deps.nix { }; + rust-shell = + (rustVersion.override { extensions = [ "rust-src" "rust-analyzer" ]; }); + + # all rust crates in workspace.members of Cargo.toml + roc-full = callPackage ./builder.nix { }; + roc-lang-server = callPackage ./builder.nix { subPackage = "lang_srv"; }; + # only the CLI crate = executable provided in nightly releases + roc-cli = callPackage ./builder.nix { subPackage = "cli"; }; + }; + +in +packages diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8cd4a6e17d..48dc4f568a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -9,7 +9,6 @@ # - update nightly-OLD_DATE in .github/workflows/windows_tests.yml # - update nightly-OLD_DATE in .github/workflows/windows_release_build.yml # - update nightly-OLD_DATE in crates/compiler/build/src/link.rs -# - Follow instructions in default.nix: `assert pkgs.lib.assertMsg rustVersionsMatch` ... channel = "1.71.1" # check ^^^ when changing this # diff --git a/www/.gitignore b/www/.gitignore deleted file mode 100644 index 486776780d..0000000000 --- a/www/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build -roc_repl_wasm.js -roc_repl_wasm_bg.wasm diff --git a/www/wip_new_website/InteractiveExample.roc b/www/InteractiveExample.roc similarity index 67% rename from www/wip_new_website/InteractiveExample.roc rename to www/InteractiveExample.roc index 33687444e0..752a7b697f 100644 --- a/www/wip_new_website/InteractiveExample.roc +++ b/www/InteractiveExample.roc @@ -18,23 +18,6 @@ Token : [ view : Html.Node view = output = - # # Select anything here to see an explanation. - # main = - # Path.fromStr "url.txt" - # |> storeEmail - # |> Task.onErr handleErr - # - # storeEmail = \filename -> - # url <- File.readUtf8 filename |> Task.await - # { name, email } <- Http.get url Json.codec |> Task.await - # - # File.writeUtf8 (Path.fromStr "\(name).txt") email - # - # handleUrl = \err -> - # when err is - # HttpErr url _ -> Stderr.line "Error fetching URL \(url)" - # FileReadErr path _ -> Stderr.line "Error reading \(Path.display path)" - # FileWriteErr path _ -> Stderr.line "Error writing \(Path.display path)" sectionsToStr [ Desc [Comment "Click anything here to see an explanation.Tap anything here to\n# see an explanation."] "

Comments in Roc begin with a # and go to the end of the line.

", Newline, @@ -42,34 +25,33 @@ view = Indent, Desc [Ident "Path.fromStr", Str "\"url.txt\""] "

This converts the string \"url.txt\" into a Path by passing it to Path.fromStr.

Function arguments are separated with whitespace. Parentheses are only needed in nested function calls.

", Newline, - Desc [Kw "|>", Ident "storeEmail"] "

The pipe operator (|>) is syntax sugar for passing the previous value to the next function in the “pipeline.”

This line takes the value that Path.fromStr \"url.txt\" returns and passes it to storeEmail.

The next |> continues the pipeline.

", + Desc [Kw "|>", Ident "storeEmail"] "

The pipe operator (|>) is syntax sugar for passing the previous value to the next function in the \"pipeline.\"

This line takes the value that Path.fromStr \"url.txt\" returns and passes it to storeEmail.

The next |> continues the pipeline.

", Newline, - Desc [Kw "|>", Ident "Task.onErr", Ident "handleErr"] "

If the task returned by the previous step in the pipeline fails, pass its error to handleErr.

The pipeline essentially does this:

val1 = Path.fromStr \"url.txt\"\nval2 = storeEmail val1\n\nTask.onErr val2 handleErr

It creates a Path from a string, stores an email based on that path, and then does error handling.

", + Desc [Kw "|>", Ident "Task.onErr", Ident "handleErr"] "

If the task returned by the previous step in the pipeline fails, pass its error to handleErr.

The pipeline essentially does this:

a = Path.fromStr \"url.txt\"\nb = storeEmail a\n\nTask.onErr b handleErr

It creates a Path from a string, stores an email based on that path, and then does error handling.

", Outdent, Newline, - Desc [Ident "storeEmail", Kw "=", Lambda ["filename"]] "

This defines a function named storeEmail.

In Roc, functions are ordinary values, so we assign names to them using = like with any other value.

The \\arg1, arg2 -> syntax begins a function, and the part after -> is the function's body.

", + Desc [Ident "storeEmail", Kw "=", Lambda ["path"]] "

This defines a function named storeEmail. It takes one argument, named path.

In Roc, functions are ordinary values, so we assign names to them using = like with any other value.

The \\arg1, arg2 -> syntax begins a function, and the part after -> is the function's body.

", Indent, - Desc [Ident "url", Kw "<-", Ident "File.readUtf8", Ident "filename", Kw "|>", Ident "Task.await"] "

This reads the contents of the file (as a UTF-8 string) into url.

The <- does backpassing, which is syntax sugar for defining a function. This line desugars to:

Task.await\n    (File.readUtf8 filename)\n    \\url ->

The lines after this one form the body of the \\url -> callback, which runs if the file read succeeds.

", + Desc [Ident "url", Kw "<-", Ident "File.readUtf8", Ident "path", Kw "|>", Ident "Task.await"] "

This reads the contents of the file (as a UTF-8 string) into url.

The <- does backpassing, which is syntax sugar for defining a function. This line desugars to:

Task.await\n    (File.readUtf8 path)\n    \\url ->

The lines after this one form the body of the \\url -> callback, which runs if the file read succeeds.

", Newline, - Desc [Ident "user", Kw "<-", Ident "Http.get", Ident "url", Ident "Json.codec", Kw "|>", Ident "Task.await"] "

This fetches the contents of the URL and decodes them as JSON.

If the shape of the JSON isn’t compatible with the type of user (based on type inference), this will give a decoding error immediately.

As with all the other lines ending in |> Task.await, if there’s an error, nothing else in storeEmail will be run, and handleErr will end up handling the error.

", + Desc [Ident "user", Kw "<-", Ident "Http.get", Ident "url", Ident "Json.codec", Kw "|>", Ident "Task.await"] "

This fetches the contents of the URL and decodes them as JSON.

If the shape of the JSON isn't compatible with the type of user (based on type inference), this will give a decoding error immediately.

As with all the other lines ending in |> Task.await, if there's an error, nothing else in storeEmail will be run, and handleErr will end up handling the error.

", Newline, - Desc [Ident "dest", Kw "=", StrInterpolation "\"" "user.name" ".txt\""] "

The \\(user.name) in this string literal will be replaced with the value in name. This is string interpolation.

Note that this line doesn't end with |> Task.await. Earlier lines needed that because they were I/O tasks, but this is a plain old definition, so there's no task to await.

", + Desc [Ident "dest", Kw "=", Ident "Path.fromStr", StrInterpolation "\"" "user.name" ".txt\""] "

The \\(user.name) in this string literal will be replaced with the value in name. This is string interpolation.

Note that this line doesn't end with |> Task.await. Earlier lines needed that because they were I/O tasks, but this is a plain old definition, so there's no task to await.

", Newline, - Desc [Literal "_"] "

In Roc, if you don’t want to bother naming something, you can always choose the name _.

You can name as many things as you like _, but you can never reference anything named _.

So it’s only useful for when you don’t want to choose a name.

", - Desc [Kw "<-", Ident "File.writeUtf8", ParensAround [Ident "Path.fromStr dest"], Ident "user.email", Kw "|>", Ident "Task.await"] "

This writes the user.email string to the file encoded as UTF-8.

The parentheses here show where the nested call to Path.fromStr begins and ends.

", + Desc [Literal "_", Kw "<-", Ident "File.writeUtf8", Ident "dest", Ident "user.email", Kw "|>", Ident "Task.await"] "

This writes user.email to the file, encoded as UTF-8.

We won't be using the output of writeUtf8, so we name it _. The special name _ is for when you don't want to use something.

You can name as many things as you like _, but you can never reference anything named _. So it's only useful for when you don't want to choose a name.

", Newline, - Desc [Ident "Stdout.line", StrInterpolation "\"Wrote email to " "dest" "\""] "

This prints what we did to stdout.

Note that this line doesn't end with |> Task.await. That’s because, although Stdout.line returns a task, we don’t need to await it because nothing happens after it.

", + Desc [Ident "Stdout.line", StrInterpolation "\"Wrote email to " "Path.display dest" "\""] "

This prints what we did to stdout.

Note that this line doesn't end with |> Task.await. That's because, although Stdout.line returns a task, we don't need to await it because nothing happens after it.

", Outdent, Newline, Desc [Ident "handleErr", Kw "=", Lambda ["err"]] "

Like storeEmail, handleErr is also a function.

Although type annotations are optional everywhere in Roc—because the language has 100% type inference—you could add type annotations to main, storeEmail, and handleErr if you wanted to.

", Indent, Desc [Kw "when", Ident "err", Kw "is"] "

This will run one of the following lines depending on what value the err argument has.

Each line does a pattern match on the shape of the error to decide whether to run, or to move on and try the next line's pattern.

Roc will do compile-time exhaustiveness checking and tell you if you forgot to handle any error cases here that could have occurred, based on the tasks that were run in storeEmail.

", Indent, - Desc [Literal "HttpErr", Ident "url", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error fetching URL " "url" "\""] "

This line will run if the Http.get request from earlier encountered an HTTP error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the HttpErr. If we wanted to print more detail about what the error was, we’d name that something other than _ and actually use it.

", + Desc [Literal "HttpErr", Ident "url", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error fetching URL " "url" "\""] "

This line will run if the Http.get request from earlier encountered an HTTP error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the HttpErr. If we wanted to print more detail about what the error was, we'd name that something other than _ and actually use it.

", Newline, - Desc [Literal "FileReadErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error reading from " "Path.display path" "\""] "

This line will run if the File.readUtf8 from earlier encountered a file I/O error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the FileReadErr. If we wanted to print more detail about what the error was, we’d name that something other than _ and actually use it.

", + Desc [Literal "FileReadErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error reading from " "Path.display path" "\""] "

This line will run if the File.readUtf8 from earlier encountered a file I/O error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the FileReadErr. If we wanted to print more detail about what the error was, we'd name that something other than _ and actually use it.

", Newline, - Desc [Literal "FileWriteErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error writing to " "Path.display path" "\""] "

This line will run if the File.writeUtf8 from earlier encountered a file I/O error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the FileWriteErr. If we wanted to print more detail about what the error was, we’d name that something other than _ and actually use it.

", + Desc [Literal "FileWriteErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error writing to " "Path.display path" "\""] "

This line will run if the File.writeUtf8 from earlier encountered a file I/O error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the FileWriteErr. If we wanted to print more detail about what the error was, we'd name that something other than _ and actually use it.

", ] div [role "presentation"] [ @@ -77,9 +59,9 @@ view = samp [] [text output], ], p [] [ - text "To get started learning the language, try the ", + text "To get started with the language, try the ", a [href "/tutorial"] [text "tutorial"], - text " next!", + text "!", ], p [id "final-tutorial-link"] [ a [class "btn-small", href "/tutorial"] [text "Start Tutorial"] diff --git a/www/README.md b/www/README.md index 3ffdf87a21..016e87cb75 100644 --- a/www/README.md +++ b/www/README.md @@ -1,12 +1,37 @@ # www.roc-lang.org +## Prerequisites + +- Linux or MacOS operating system, Windows users can use linux through WSL. +- Install [git](https://chat.openai.com/share/71fb3ae6-80d7-478c-8a27-a36aaa5ba921) +- Install [nix](https://nixos.org/download.html) + +## Building the website from scratch + +```bash +git clone https://github.com/roc-lang/roc.git +cd roc +nix develop +./www/build.sh +# make the roc command available +export PATH="$(pwd)/target/release/:$PATH" +bash build-dev-local.sh +``` + +Open http://0.0.0.0:8080 in your browser. + +## After you've made a change locally + +In the terminal where the web server is running: +1. kill the server with Ctrl+C +2. re-run the build script +3. refresh the page in your browser + To view the website after you've made a change, execute: ```bash -./www/build.sh -cd www/build -simple-http-server --nocache --index # already installed if you're using `nix develop`, otherwise use `cargo install simple-http-server` +bash build-dev-local.sh ``` -Open http://0.0.0.0:8000 in your browser. +Open http://0.0.0.0:8080 in your browser. diff --git a/www/wip_new_website/build-dev-local.sh b/www/build-dev-local.sh similarity index 78% rename from www/wip_new_website/build-dev-local.sh rename to www/build-dev-local.sh index 0fdf551425..37c62eb167 100755 --- a/www/wip_new_website/build-dev-local.sh +++ b/www/build-dev-local.sh @@ -11,9 +11,8 @@ DIR="$(dirname "$0")" cd "$DIR" || exit rm -rf dist/ -cp -r ../build dist/ -mkdir -p dist/wip -roc run main.roc -- content/ dist/wip/ -cp -r static/* dist/wip/ +cp -r build dist/ +cp -r public/* dist/ +roc run main.roc -- content/ dist/ npx http-server dist/ -p 8080 -c-1 --cors diff --git a/www/build.sh b/www/build.sh index cf0ae4c911..78a2244682 100755 --- a/www/build.sh +++ b/www/build.sh @@ -17,16 +17,16 @@ cd $SCRIPT_RELATIVE_DIR rm -rf build/ cp -r public/ build/ -mkdir build/wip # for WIP site # download the latest code for the examples echo 'Downloading latest examples...' -curl -fLJO https://github.com/roc-lang/examples/archive/refs/heads/main.zip -unzip examples-main.zip -cp -R examples-main/examples/ wip_new_website/content/examples/ +curl -fL -o examples-main.zip https://github.com/roc-lang/examples/archive/refs/heads/main.zip +rm -rf examples-main/ +unzip -o examples-main.zip +cp -R examples-main/examples/ content/examples/ -# relace links in wip_new_website/content/examples/index.md to work on the WIP site -sed -i 's|](/|](/wip/examples/|g' wip_new_website/content/examples/index.md +# relace links in content/examples/index.md to work on the WIP site +perl -pi -e 's|\]\(/|\]\(/examples/|g' content/examples/index.md # clean up examples artifacts rm -rf examples-main examples-main.zip @@ -48,11 +48,10 @@ curl -fLJO https://github.com/roc-lang/roc/archive/www.tar.gz # Download the latest pre-built Web REPL as a zip file. (Build takes longer than Netlify's timeout.) REPL_TARFILE="roc_repl_wasm.tar.gz" curl -fLJO https://github.com/roc-lang/roc/releases/download/nightly/$REPL_TARFILE +mkdir repl tar -xzf $REPL_TARFILE -C repl -tar -xzf $REPL_TARFILE -C wip # note we also need this for WIP repl rm $REPL_TARFILE ls -lh repl -ls -lh wip popd @@ -77,9 +76,6 @@ rm -rf roc_nightly roc_releases.json # we use `! [ -v GITHUB_TOKEN_READ_ONLY ];` to check if we're on a netlify server if ! [ -v GITHUB_TOKEN_READ_ONLY ]; then - echo 'Building tutorial.html from tutorial.md...' - mkdir www/build/tutorial - cargo build --release --bin roc roc=target/release/roc @@ -96,19 +92,12 @@ else mv roc_nightly* roc_nightly roc='./roc_nightly/roc' - - echo 'Building tutorial.html from tutorial.md...' - mkdir www/build/tutorial fi $roc version -$roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/input/ www/build/tutorial/ -mv www/build/tutorial/tutorial.html www/build/tutorial/index.html -# For WIP site -echo 'Building WIP site...' -$roc run www/wip_new_website/main.roc -- www/wip_new_website/content/ www/build/wip/ -cp -r www/wip_new_website/static/* www/build/wip/ +echo 'Building site markdown content' +$roc run www/main.roc -- www/content/ www/build/ # cleanup rm -rf roc_nightly roc_releases.json @@ -153,5 +142,3 @@ if [ -v GITHUB_TOKEN_READ_ONLY ]; then rm -rf $BASIC_CLI_DIR/generated-docs done <<< "$VERSION_NUMBERS" fi - -popd diff --git a/www/wip_new_website/content/bdfn.md b/www/content/bdfn.md similarity index 94% rename from www/wip_new_website/content/bdfn.md rename to www/content/bdfn.md index 1681e382fd..0a0e4dc86b 100644 --- a/www/wip_new_website/content/bdfn.md +++ b/www/content/bdfn.md @@ -10,4 +10,4 @@ Something that needs figuring out between now and then is how to keep the langua (This would be easy to solve if zero happened to be the optimal number of new features to add over time, but unfortunately the programming landscape will change around the language, so remaining static is unlikely to be best. As such, the goal becomes finding a system which keeps the number of new features low without artificially forcing it to zero.) -I don't have any plans for when the transition away from BDFN will happen, other than that I certainly imagine it being well after a 1.0 release of the language. +I don't have any plans for when the transition away from BDFN will happen, other than that I expect it to be well after a 1.0 release of the language! diff --git a/www/wip_new_website/content/community.md b/www/content/community.md similarity index 58% rename from www/wip_new_website/content/community.md rename to www/content/community.md index ac8e71a7b4..70a68b1843 100644 --- a/www/wip_new_website/content/community.md +++ b/www/content/community.md @@ -3,30 +3,36 @@ [Roc Zulip Chat](https://roc.zulipchat.com/) is the most active community gathering place. We love it when new people stop by and introduce themselves in [`#introductions`](https://roc.zulipchat.com/#narrow/stream/387892-introductions) so others can welcome you to the community! -The Roc Online Meetup meets about once per month. The organizer posts a [when2meet](https://when2meet.com) for the next month's meetup, so we can find the best time that works for everyone -who's interested in attending. This happens in [`#gatherings` on Zulip](https://roc.zulipchat.com/#narrow/stream/303057-gatherings). The organizer will then make a post on Zulip with a finalized time and a link to where people can join. They usually last between 1-2 hours, and consist of some informal presentations followed by casual conversation. They're fun! +The Roc Online Meetup meets about once per month. The organizer posts a [when2meet](https://when2meet.com) for the next month's meetup in [`#gatherings` on Zulip](https://roc.zulipchat.com/#narrow/stream/303057-gatherings), so we can find the best time that works for everyone +who's interested in attending. The organizer then posts a finalized time and a link to where people can join. They usually last between 1-2 hours, and consist of some casual show-and-tell presentations followed by conversation. They're fun! We have not had a Roc conference yet, although there have been a few Roc talks given at conferences, and a few times when Roc community members have met up in person. -## Code of Conduct +### [Code of Conduct](#code-of-conduct) {#code-of-conduct} The Roc project enforces [a code of conduct](https://github.com/roc-lang/roc/blob/main/CODE_OF_CONDUCT.md). Please read and follow it! -## Contributing +### [Contributing](#contributing) {#contributing} All the source code to the Roc project is on GitHub in the [roc-lang](https://github.com/roc-lang) organization. The compiler and CLI are at [roc-lang/roc](https://github.com/roc-lang/roc), and there's a tag for [Good First Issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). The project has [many contributors](https://github.com/roc-lang/roc/graphs/contributors), and we like to invest in helping new contributors get started. If you'd like to become a new contributor (even if you don't know what you'd like that contribution to be yet), just make a post in [the "new contributors" topic](https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/new.20contributors) and say hello! -## Design ideas, feature requests, proposals, etc. +### [Ideas, proposals, and feature requests](#ideas) {#ideas} -Roc doesn't have a formal process for managing design proposals. At the current size of the project, having a formal process (e.g. a [RFC](https://en.wikipedia.org/wiki/Change_request) system) would be more heavyweight than it's worth. Fow now, the guiding principle is that as a community we should all be friendly, supportive, and openly share and discuss ideas without the expectation that they will necessarily be accepted or not. We follow a [BDFN](/bdfn) leadership model today, although this is planned to change someday. +Roc doesn't have a formal process for managing design proposals. + +At the current size of the project, having a formal process (like a [RFC](https://en.wikipedia.org/wiki/Change_request) system) would be more heavyweight than it's worth. Fow now, the guiding principle is that as a community we should all be friendly, supportive, and openly share and discuss ideas without the expectation that they will necessarily be accepted or not. We follow a [BDFN](/bdfn) leadership model today, although this is planned to change someday. There are three loose stages that a design proposal can go through in Roc: idea, proposal, and implementation. These are guidelines, not strict requirements; their goal is to prevent the outcome where someone does a lot of implementation work only to have their contribution never make it into the code base because it's determined that we wanted to go in a different design direction. Confirming ahead of time that the design direction is desired can prevent implementing something that ends up not being used. -In the idea stage, people are encouraged to describe their idea and explore the problem, potential solutions, and trade-offs. It's a good idea to share the idea in [`#ideas` on Zulip](https://roc.zulipchat.com/#narrow/stream/304641-ideas). There's no prerequisite for sharing an idea (it's only an idea, after all!) and likewise there's also no obligation for any contributor to necessarily act on it. +In the idea stage, people are encouraged to describe their idea and explore the problem, potential solutions, and tradeoffs. It's a good idea to share the idea in [`#ideas` on Zulip](https://roc.zulipchat.com/#narrow/stream/304641-ideas). There's no prerequisite for sharing an idea (it's only an idea, after all!) and likewise there's also no obligation for any contributor to necessarily act on it. -If the idea seems promising and worth developing further (as confirmed by a Roc contributor with expertise in the relevant area, doesn't have to be the [BDFN](/bdfn)), usually the next step is to get more specific with a written proposal that details all the necessary information about what the change would involve. A written proposal isn't always necessary (e.g. it may be deemed a simple and uncontroversial enough change that we're comfortable proceeding straight to implementation), but since writing proposals can be time-consuming, it's definitely a good idea to get confirmation at the idea stage from an experienced contributor before taking the time to write one up. +If the idea seems promising and worth developing further (as confirmed by a Roc contributor with expertise in the relevant area—not necessarily the [BDFN](/bdfn)), usually the next step is to get more specific with a written proposal that details all the necessary information about what the change would involve. + +A written proposal isn't always necessary (for example, it may be deemed a simple and uncontroversial enough change that we're comfortable proceeding straight to implementation), but since writing proposals can be time-consuming, it's definitely a good idea to get confirmation at the idea stage from an experienced contributor before taking the time to write one up. There's no guarantee that a proposal will be accepted, and even if it is, there's no guarantee that something won't come up during implementation that changes the tradeoffs and means it doesn't end up making it into the language after all. But if it is accepted (again, doesn't have to be by the [BDFN](/bdfn) - although the BDFN does have final say if there's a disagreement at some point), that means a [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) should be reviewed, and the general understanding is that the PR will be accepted unless some problem is discovered during implementation that wasn't anticipated at the proposal stage. + +This is the process we're using for now, but of course it may change in the future! diff --git a/www/content/docs.md b/www/content/docs.md new file mode 100644 index 0000000000..f026e2234e --- /dev/null +++ b/www/content/docs.md @@ -0,0 +1,14 @@ +# Documentation + +- [builtins](/builtins) - docs for modules built into the language—`Str`, `Num`, etc. +- [basic-webserver](https://roc-lang.github.io/basic-webserver/) - a platform for making Web servers ([source code](https://github.com/roc-lang/basic-webserver)) +- [basic-cli](/packages/basic-cli) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli)) + +In the future, a language reference will be on this page too. + +## [Guides](#guides) {#guides} + +- [FAQ (Frequently Asked Questions)](https://github.com/roc-lang/roc/blob/main/FAQ.md) +- [Platforms & Applications](/platforms) +- [Roc for Elm Programmers](https://github.com/roc-lang/roc/blob/main/roc-for-elm-programmers.md) +- [Tutorial](/tutorial) diff --git a/www/content/donate.md b/www/content/donate.md new file mode 100644 index 0000000000..7350a79eed --- /dev/null +++ b/www/content/donate.md @@ -0,0 +1,13 @@ +# Donate + +We are a small group trying to do big things, and we are grateful for every donation! Right now we are trying to raise $4,000 USD/month in donations to fund one longtime Roc contributor to continue his work on Roc full-time. + +You can donate to Roc's development using: +- [GitHub Sponsors](https://github.com/sponsors/roc-lang) +- [Liberapay](https://liberapay.com/roc_lang) + +All donations go through the [Roc Programming Language Foundation](https://foundation.roc-lang.org/), a registered US 501(c)(3) nonprofit organization, which means these donations are tax-exempt in the US. + +It also means that the foundation's tax filings are a [matter of public record](https://en.wikipedia.org/wiki/501(c)(3)_organization#Transparency), so you have real transparency into how your donations are advancing Roc! + +If you would like your organization to become an official sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383). We'd love to talk to you! diff --git a/www/content/fast.md b/www/content/fast.md new file mode 100644 index 0000000000..b92ba3ad3d --- /dev/null +++ b/www/content/fast.md @@ -0,0 +1,84 @@ +# Fast + +Roc code is designed to build fast and run fast...but what does "fast" mean here? And how close is Roc's current implementation to realizing that goal? + +## [Fast programs](#fast-programs) {#fast-programs} + +What "fast" means in embedded systems is different from what it means in games, which in turn is different from what it means on the Web. To better understand Roc's performance capabilities, let's look at the upper bound of how fast optimized Roc programs are capable of running, and the lower bound of what types of languages Roc should generally outperform. + +### [Limiting factors: memory management and async I/O](#limiting-factors) {#limiting-factors} + +Roc is a [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) language with [automatic memory management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reference_counting). Automatic memory management has some unavoidable runtime overhead, and memory safety based on static analysis rules out certain performance optimizations—which is why [unsafe Rust](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) can outperform safe Rust. This gives Roc a lower performance ceiling than languages which support memory unsafety and manual memory management, such as C, C++, Zig, and Rust. + +Another part of Roc's design is that all I/O operations are done using a lightweight state machine so that they can be asynchronous. This has potential performance benefits compared to synchronous I/O, but it also has some unavoidable overhead. + +### [Generally faster than dynamic or gradual languages](#faster-than) {#faster-than} + +As a general rule, Roc programs should have almost strictly less runtime overhead than equivalent programs written in languages with dynamic types and automatic memory management. This doesn't mean all Roc programs will outperform all programs in these languages, but it does mean Roc should have a higher ceiling on what performance is achievable. + +This is because dynamic typing (and gradual typing) requires tracking types at runtime, which has overhead. Roc tracks types only at compile time, and tends to have [minimal (often zero) runtime overhead](https://vimeo.com/653510682) for language constructs compared to the top performers in industry. For example, Roc's generics, records, functions, numbers, and tag unions have no more runtime overhead than they would in their Rust or C++ equivalents. + +When [benchmarking compiled Roc programs](https://www.youtube.com/watch?v=vzfy4EKwG_Y), the goal is to have them normally outperform the fastest mainstream garbage-collected languages (for example, Go, C#, Java, and JavaScript), but it's a non-goal to outperform languages that support memory unsafety or manual memory management. There will always be some individual benchmarks where mainstream garbage-collected languages outperform Roc, but the goal is for these to be uncommon rather than the norm. + +### [Domain-specific memory management](#domain-specific-memory-management) {#domain-specific-memory-management} + +Roc's ["platforms and applications" design](/platforms) means its automatic memory management can take advantage of domain-specific properties to improve performance. + +For example, if you build an application on the [`basic-cli` platform](https://github.com/roc-lang/basic-cli) compared to the [`basic-webserver` platform](https://github.com/roc-lang/basic-webserver), each of those platforms may use a different memory management strategy under the hood that's tailored to their respective use cases. Your application's performance can benefit from this, even though building on either of those platforms feels like using ordinary automatic memory management. + +This is because Roc [platforms](/platforms) get to determine how memory gets allocated and deallocated in applications built on them. ([`basic-cli`](https://github.com/roc-lang/basic-cli) and [`basic-webserver`](https://github.com/roc-lang/basic-webserver) are examples of platforms, but anyone can build their own platform.) Here are some examples of how platforms can use this to improve application performance: + +- A platform for noninteractive command-line scripts can skip deallocations altogether, since any allocated memory will be cheaply reclaimed by the operating system anyway once the script exits. (This strategy is domain-specific; it would not work well for a long-running, interactive program!) +- A platform for Web servers can put all allocations for each request into a particular [region of memory](https://en.wikipedia.org/wiki/Region-based_memory_management) (this is known as "arena allocation" or "bump allocation") and then deallocate the entire region in one cheap operation after the response has been sent. This would essentially drop memory reclamation times to zero. (This strategy relies on Web servers' request/response architecture, and wouldn't make sense in other use cases. [`nea`](https://github.com/tweedegolf/nea) is a platform in early development that is working towards implementing this.) +- A platform for applications that have very long-lived state could implement [meshing compaction](https://youtu.be/c1UBJbfR-H0?si=D9Gp0cdpjZ_Is5v8) to decrease memory fragmentation. (Compaction would probably be a net negative for performance in the previous two examples.) + +[This talk](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) has more information about platforms and applications, including demos and examples of other benefits they unlock besides performance. + +### [Current progress](#current-progress) {#current-progress} + +Roc's "platforms and applications" design already works, including the domain-specific memory management. Most of Roc's data structures are already close to their theoretical limit in terms of performance, at least without changing their behavior or introducing memory unsafety. [This talk](https://vimeo.com/653510682) explains how they're implemented under the hood. + +That said, [the current implementation](https://ayazhafiz.com/articles/23/a-lambda-calculus-with-coroutines-and-heapless-closures) of [defunctionalization](https://blog.sigplan.org/2019/12/30/defunctionalization-everybody-does-it-nobody-talks-about-it/) (based on [this paper](https://dl.acm.org/doi/pdf/10.1145/3591260))—which unlocks stack-allocated closures, among other optimizations—has [significant known gaps](https://github.com/roc-lang/roc/issues/5969), and has a ways to go before it works across the board. (If you're interested in getting involved in that implementation, [we'd love to hear from you](https://roc.zulipchat.com)!) + +Current optimizations that are completely implemented (give or take the occasional bug) include [LLVM](https://llvm.org/), [Morphic](https://www.youtube.com/watch?v=F3z39M0gdJU&t=3547s), [Perceus](https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf), and tail recursion optimization (including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)). Promising examples of potential future optimizations include closure-aware [inlining](https://en.wikipedia.org/wiki/Inline_expansion), [automatic deforestation](https://www.cs.tufts.edu/~nr/cs257/archive/duncan-coutts/stream-fusion.pdf), and full [compile-time evaluation](https://en.wikipedia.org/wiki/Constant_folding) of top-level declarations. + +We're also interested in improving the performance of the Morphic alias analysis pass itself; if contributing to that project (or any other optimization project) interests you, please [let us know in the `#contributing` channel](https://roc.zulipchat.com/#narrow/stream/316715-contributing)! + +## Fast Feedback Loops + +One of Roc's goals is to provide fast feedback loops by making builds normally feel "instant" except on truly enormous projects. + +It's a concrete goal to have them almost always complete in under 1 second on the median computer being used to write Roc (assuming that system is not bogged down with other programs using up its resources), and ideally under the threshold at which humans typically find latency perceptible (around 100 milliseconds). In the future, hot code loading can make the feedback loop even faster, by letting you see changes without having to restart your program. + +Note that although having fast "clean" builds (without the benefit of caching) is a goal, the "normally feels instant" goal refers to builds where caching was involved. After all, the main downside of build latency is that it comes up over and over in a feedback loop; a fast initial "clean" build is valuable too, but it comes up rarely by comparison. + +### Current Progress + +`roc check` checks your code for errors (such as invalid syntax, naming errors, and type mismatches) and reports problems it finds. On typical development laptops, this usually takes well under 1 second for small projects (for very small projects, it can be around 10 milliseconds on some popular machines). To date, the largest known Roc projects have lines of code numbering in the low thousands, so there's no data yet on `roc check` times for larger projects. + +`roc build` does everything `roc check` does, but it additionally builds a runnable binary of your program. You may notice that `roc build` takes much longer to complete! This is because +of two projects that are underway but not completed yet: +- *Development backend* refers to generating machine code directly instead of asking [LLVM](https://llvm.org/) to generate it. LLVM is great at generating optimized machine code, but it takes a long time to generate it—even if you turn off all the optimizations (and `roc` only has LLVM perform optimizations when the `--optimize` flag is set). The dev backend is currently implemented for WebAssembly, which you can see in the [Web REPL](https://www.roc-lang.org/repl), and in `roc repl` except on Windows. Work is underway to implement it for `roc build` and `roc run`, as well as macOS, Windows, and the ARM versions of all of these. +- *Surgical linking* refers to a fast way of combining the platform and application into one binary. Today, this works on x64 Linux, x64 Windows, and WebAssembly. `roc build` on macOS is noticeably slower because it falls back on non-surgical linking. + +Here's a table summarizing the current progress: + +Target | Dev backend | Surgical linking | +------------|-------------|-------------------| +WebAssembly | yes | yes | +macOS ARM | repl only | | +macOS x64 | repl only | | +Linux ARM | repl only | | +Linux x64 | repl only | yes | +Windows x64 | | yes | +Windows ARM | | | + +Once we have full coverage, `roc build` (and `roc run` and `roc test`, which also perform builds) should take only a bit longer than `roc check`. + +The next major performance improvement will be caching. Currently, `roc` always builds everything from scratch. Most of the time, it could benefit from caching some of the work it had done in a previous build, but today it doesn't do that. There's a design for the caching system, but essentially none of the implementation has started yet. Hot code loading will be the next major improvement after caching, but it requires full dev backend coverage, and does not have a concrete design yet. + +## Friendly + +In addition to being fast, Roc also aims to be a friendly programming language. + +[What does _friendly_ mean here?](/friendly) diff --git a/www/content/friendly.md b/www/content/friendly.md new file mode 100644 index 0000000000..38b7431e8d --- /dev/null +++ b/www/content/friendly.md @@ -0,0 +1,100 @@ +# Friendly + +Besides having a [friendly community](/community), Roc also prioritizes being a user-friendly language. This impacts the syntax, semantics, and tools Roc ships with. + +## [Syntax and Formatter](#syntax) {#syntax} + +Roc's syntax isn't trivial, but there also isn't much of it to learn. It's designed to be uncluttered and unambiguous. A goal is that you can normally look at a piece of code and quickly get an accurate mental model of what it means, without having to think through several layers of indirection. Here are some examples: + +- `user.email` always accesses the `email` field of a record named `user`. (Roc has no inheritance, subclassing, or proxying.) +- `Email.isValid` always refers to something named `isValid` exported by a module named `Email`. (Module names are always capitalized, and variables/constants never are.) Modules are always defined statically and can't be modified at runtime; there's no [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) to consider either. +- `x = doSomething y z` always declares a new constant `x` (Roc has [no mutable variables, reassignment, or shadowing](/functional)) to be whatever the `doSomething` function returns when passed the arguments `y` and `z`. (Function calls in Roc don't need parentheses or commas.) +- `"Name: \(Str.trim name)"` uses *string interpolation* syntax: a backslash inside a string literal, followed by an expression in parentheses. This code is the same as combining the string `"Name: "` with the string returned by the function call `Str.trim name`.

Because Roc's string interpolation syntax begins with a backslash (just like other backlash-escapes such as `\n` and `\"`), you can always tell which parts of a string involve special handling: the parts that begin with backslashes. Everything else works as normal. + +Roc also ships with a source code formatter that helps you maintain a consistent style with little effort. The `roc format` command neatly formats your source code according to a common style, and it's designed with the time-saving feature of having no configuration options. This feature saves teams all the time they would otherwise spend debating which stylistic tweaks to settle on! + +## [Helpful compiler](#helpful-compiler) {#helpful-compiler} + +Roc's compiler is designed to help you out. It does complete type inference across all your code, and the type system is [sound](https://en.wikipedia.org/wiki/Type_safety). This means you'll never get a runtime type mismatch if everything type-checked (including null exceptions; Roc doesn't have the [billion-dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)), and you also don't have to write any type annotations—the compiler can infer all the types in your program. + +If there's a problem at compile time, the compiler is designed to report it in a helpful way. Here's an example: + +
── TYPE MISMATCH ─────── /home/my-roc-project/main.roc ─
+
+Something is off with the then branch of this if:
+
+4│      someInt : I64
+5│      someInt =
+6│          if someDecimal > 0 then
+7│              someDecimal + 1
+                ^^^^^^^^^^^^^^^
+
+This branch is a fraction of type:
+
+    Dec
+
+But the type annotation on `someInt` says it should be:
+
+    I64
+
+Tip: You can convert between integers and fractions
+using functions like `Num.toFrac` and `Num.round`.
+ +If you like, you can run a program that has compile-time errors like this. (If the program reaches the error at runtime, it will crash.) + +This lets you do things like trying out code that's only partially finished, or running tests for one part of your code base while other parts have compile errors. (Note that this feature is only partially completed, and often errors out; it has a ways to go before it works for all compile errors!) + +## [Serialization inference](#serialization-inference) {#serialization-inference} + +When dealing with [serialized data](https://en.wikipedia.org/wiki/Serialization), an important question is how and when that data will be decoded from a binary format (such as network packets or bytes on disk) into your program's data structures in memory. + +A technique used in some popular languages today is to decode without validation. For example, some languages parse [JSON](https://www.json.org) using a function whose return type is unchecked at compile time (commonly called an `any` type). This technique has a low up-front cost, because it does not require specifying the expected shape of the JSON data. + +Unfortunately, if there's any mismatch between the way that returned value ends up being used and the runtime shape of the JSON, it can result in errors that are time-consuming to debug because they are distant from (and may appear unrelated to) the JSON decoding where the problem originated. Since Roc has a [sound type system](https://en.wikipedia.org/wiki/Type_safety), it does not have an `any` type, and cannot support this technique. + +Another technique is to validate the serialized data against a schema specified at compile time, and give an error during decoding if the data doesn't match this schema. Serialization formats like [protocol buffers](https://protobuf.dev/) require this approach, but some languages encourage (or require) doing it for _all_ serialized data formats, which prevents decoding errors from propagating throughout the program and causing distant errors. Roc supports and encourages using this technique. + +In addition to this, Roc also supports serialization _inference_. It has some characteristics of both other approaches: +- Like the first technique, it does not require specifying a schema up front. +- Like the second technique, it reports any errors immediately during decoding rather than letting the problems propagate through the program. + +This technique works by using Roc's type inference to infer the expected shape of serialized data based on how it's used in your program. Here's an example, using [`Decode.fromBytes`](https://www.roc-lang.org/builtins/Decode#fromBytes) to decode some JSON: + +
when Decode.fromBytes data Json.codec is
+    Ok decoded -> # (use the decoded data here)
+    Err err -> # handle the decoding failure
+ +In this example, whether the `Ok` or `Err` branch gets taken at runtime is determined by the way the `decoded` value is used in the source code. + +For example, if `decoded` is used like a record with a `username` field and an `email` field, both of which are strings, then this will fail at runtime if the JSON doesn't have fields with those names and those types. No type annotations are needed for this; it relies entirely on Roc's type inference, which by design can correctly infer types for your entire program even without annotations. + +Serialization inference has a low up-front cost in the same way that the decode-without-validating technique does, but it doesn't have the downside of decoding failures propagating throughout your program to cause distant errors at runtime. (It also works for encoding; there is an [Encode.toBytes](https://www.roc-lang.org/builtins/Encode#toBytes) function which encodes similarly to how [`Decode.fromBytes`](https://www.roc-lang.org/builtins/Decode#fromBytes) decodes.) + +Explicitly writing out a schema has its own benefits that can balance out the extra up-front time investment, but having both techniques available means you can choose whatever will work best for you in a given scenario. + +## [Testing](#testing) {#testing} + +The `roc test` command runs a Roc program's tests. Each test is declared with the `expect` keyword, and can be as short as one line. For example, this is a complete test: + +```roc +## One plus one should equal two. +expect 1 + 1 == 2 +``` + +If the test fails, `roc test` will show you the source code of the `expect`, along with the values of any named variables inside it, so you don't have to separately check what they were. + +If you write a documentation comment right before it (like `## One plus one should equal two` here), it will appear in the test output, so you can use that to add some descriptive context to the test if you want to. + +## [Inline expectations](#inline-expect) {#inline-expect} + +You can also use `expect` in the middle of functions. This lets you verify assumptions that can't reasonably be encoded in types, but which can be checked at runtime. Similarly to [assertions](https://en.wikipedia.org/wiki/Assertion_(software_development)) in other languages, these will run not only during normal program execution, but also during your tests—and they will fail the test if any of them fails. + +Unlike assertions (and unlike the `crash` keyword), failed `expect`s do not halt the program; instead, the failure will be reported and the program will continue. This means all `expect`s can be safely removed during `--optimize` builds without affecting program behavior—and so `--optimize` does remove them. This means you can add inline `expect`s without having to weigh each one's helpfulness against the performance cost of its runtime check, because they won't have any runtime cost after `--optimize` removes them. + +In the future, there are plans to add built-in support for [benchmarking](https://en.wikipedia.org/wiki/Benchmark_(computing)), [generative tests](https://en.wikipedia.org/wiki/Software_testing#Property_testing), [snapshot tests](https://en.wikipedia.org/wiki/Software_testing#Output_comparison_testing), simulated I/O (so you don't have to actually run the real I/O operations, but also don't have to change your code to accommodate the tests), and "reproduction replays"—tests generated from a recording of what actually happened during a particular run of your program, which deterministically simulate all the I/O that happened. + +## Functional + +Besides being designed to be [fast](/fast) and friendly, Roc is also a functional programming language. + +[What does _functional_ mean here?](/functional) diff --git a/www/content/functional.md b/www/content/functional.md new file mode 100644 index 0000000000..28b2581865 --- /dev/null +++ b/www/content/functional.md @@ -0,0 +1,143 @@ +# Functional + +Roc is designed to have a small number of simple language primitives. This goal leads Roc to be a single-paradigm [functional](https://en.wikipedia.org/wiki/Functional_programming) language, while its [performance goals](/fast) lead to some design choices that are uncommon in functional languages. + +## [Opportunistic mutation](#opportunistic-mutation) {#opportunistic-mutation} + +All Roc values are semantically immutable, but may be opportunistically mutated behind the scenes when it would improve performance (without affecting the program's behavior). For example: + +```roc +colors +|> Set.insert "Purple" +|> Set.insert "Orange" +|> Set.insert "Blue" +``` + +The [`Set.insert`](https://www.roc-lang.org/builtins/Set#insert) function takes a `Set` and returns a `Set` with the given value inserted. It might seem like these three `Set.insert` calls would result in the creation of three brand-new sets, but Roc's *opportunistic mutation* optimizations mean this will be much more efficient. + +Opportunistic mutation works by detecting when a semantically immutable value can be safely mutated in-place without changing the behavior of the program. If `colors` is *unique* here—that is, nothing else is currently referencing it—then `Set.insert` will mutate it and then return it. Cloning it first would have no benefit, because nothing in the program could possibly tell the difference! + +If `colors` is _not_ unique, however, then the first call to `Set.insert` will not mutate it. Instead, it will clone `colors`, insert `"Purple"` into the clone, and then return that. At that point, since the clone will be unique (nothing else is referencing it, since it was just created), the subsequent `Set.insert` calls will all mutate in-place. + +Roc has ways of detecting uniqueness at compile time, so this optimization will often have no runtime cost, but in some cases it instead uses automatic reference counting to tell when something that was previously shared has become unique over the course of the running program. + +## [Everything is immutable (semantically)](#everything-is-immutable) {#everything-is-immutable} + +Since mutation is only ever done when it wouldn't change the behavior of the program, all Roc values are semantically immutable—even though they can still benefit from the performance of in-place mutation. + +In many languages, this is reversed; everything is mutable by default, and it's up to the programmer to "defensively" clone to avoid undesirable modification. Roc's approach means that cloning happens automatically, which can be less error-prone than defensive cloning (which might be forgotten), but which—to be fair—can also increase unintentional cloning. It's a different default with different tradeoffs. + +A reliability benefit of semantic immutability everywhere is that it rules out [data races](https://en.wikipedia.org/wiki/Race_condition#Data_race). These concurrency bugs can be difficult to reproduce and time-consuming to debug, and they are only possible through direct mutation. Roc's semantic immutability rules out this category of bugs. + +A performance benefit of having no direct mutation is that it lets Roc rule out [reference cycles](https://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles). Languages which support direct mutation can have reference cycles, and detecting these cycles automatically at runtime has a cost. (Failing to detect them can result in memory leaks in languages that use reference counting.) Roc's automatic reference counting neither pays for runtime cycle collection nor memory leaks from cycles, because the language's lack of direct mutation primitives lets it rule out reference cycles at language design time. + +An ergonomics benefit of having no direct mutation primitives is that functions in Roc tend to be chainable by default. For example, consider the `Set.insert` function. In many languages, this function would be written to accept a `Set` to mutate, and then return nothing. In contrast, in Roc it will necessarily be written to return a (potentially) new `Set`, even if in-place mutation will end up happening anyway if it's unique. + +This makes Roc functions naturally amenable to pipelining, as we saw in the earlier example: + +```roc +colors +|> Set.insert "Purple" +|> Set.insert "Orange" +|> Set.insert "Blue" +``` + +To be fair, direct mutation primitives have benefits too. Some algorithms are more concise or otherwise easier to read when written with direct mutation, and direct mutation can make the performance characteristics of some operations clearer. + +As such, Roc's opportunistic mutation design means that data races and reference cycles can be ruled out, and that functions will tend to be more amenable for chaining, but also that some algorithms will be harder to express, and that performance optimization will likely tend to involve more profiling. These tradeoffs fit well with the language's overall design goals. + +## [No reassignment or shadowing](#no-reassignment) {#no-reassignment} + +In some languages, the following is allowed. + +
x = 1
+x = 2
+ +In Roc, this will give a compile-time error. Once a name has been assigned to a value, nothing in the same scope can assign it again. (This includes [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing), which is disallowed.) + +This can make Roc code easier to read, because the answer to the question "might this have a different value later on in the scope?" is always "no." + +That said, this can also make Roc code take longer to write, due to needing to come up with unique names to avoid shadowing—although pipelining (as shown in the previous section) reduces how often intermediate values need names. + +### [Avoiding regressions](#avoiding-regressions) {#avoiding-regressions} + +A benefit of this design is that it makes Roc code easier to rearrange without causing regressions. Consider this code: + +
func = \arg ->
+    greeting = "Hello"
+    welcome = \name -> "\(greeting), \(name)!"
+
+    # …
+
+    message = welcome "friend"
+
+    # …
+ +Suppose I decide to extract the `welcome` function to the top level, so I can reuse it elsewhere: + +
func = \arg ->
+    # …
+
+    message = welcome "Hello" "friend"
+
+    # …
+
+welcome = \prefix, name -> "\(prefix), \(name)!"
+ +Even without knowing the rest of `func`, we can be confident this change will not alter the code's behavior. + +In contrast, suppose Roc allowed reassignment. Then it's possible something in the `# …` parts of the code could have modified `greeting` before it was used in the `message =` declaration. For example: + +
func = \arg ->
+    greeting = "Hello"
+    welcome = \name -> "\(greeting), \(name)!"
+
+    # …
+
+    if someCondition then
+        greeting = "Hi"
+        # …
+    else
+        # …
+
+    # …
+    message = welcome "friend"
+    # …
+ +If we didn't read the whole function and notice that `greeting` was sometimes (but not always) reassigned from `"Hello"` to `"Hi"`, we might not have known that changing it to `message = welcome "Hello" "friend"` would cause a regression due to having the greeting always be `"Hello"`. + +Even if Roc disallowed reassignment but allowed shadowing, a similar regression could happen if the `welcome` function were shadowed between when it was defined here and when `message` later called it in the same scope. Because Roc allows neither shadowing nor reassignment, these regressions can't happen, and rearranging code can be done with more confidence. + +In fairness, reassignment has benefits too. For example, using it with [early-exit control flow operations](https://en.wikipedia.org/wiki/Control_flow#Early_exit_from_loops) such as a `break` keyword can be a nice way to represent certain types of logic without incurring extra runtime overhead. + +Roc does not have early-exits or loop syntax; looping is done either with convenience functions like [`List.walkUntil`](https://www.roc-lang.org/builtins/List#walkUntil) or with recursion (Roc implements [tail-call optimization](https://en.wikipedia.org/wiki/Tail_call), including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)), but early-exit operators can potentially make some code easier to follow (and potentially even slightly more efficient) when used in scenarios where breaking out of nested loops with a single instruction is desirable. + +## [Managed effects over side effects](#managed-effects) {#managed-effects} + +Many languages support first-class [asynchronous](https://en.wikipedia.org/wiki/Asynchronous_I/O) effects, which can improve a system's throughput (usually at the cost of some latency) especially in the presence of long-running I/O operations like network requests. + +Asynchronous effects are commonly represented by a value such as a [Promise or Future](https://en.wikipedia.org/wiki/Futures_and_promises) (Roc calls these Tasks), which represent an effect to be performed. Tasks can be composed together, potentially while customizing concurrency properties and supporting I/O interruptions like cancellation and timeouts. + +Most languages also have a separate system for synchronous effects, namely [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). Having two different ways to perform every I/O operation—one synchronous and one asynchronous—can lead to a lot of duplication across a language's ecosystem. + +Instead of having [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), Roc functions exclusively use *managed effects* in which they return descriptions of effects to run, in the form of Tasks. Tasks can be composed and chained together, until they are ultimately handed off (usually via a `main` function or something similar) to an effect runner outside the program, which actually performs the effects the tasks describe. + +Having only (potentially asynchronous) *managed effects* and no (synchronous) *side effects* both simplifies the language's ecosystem and makes certain guarantees possible. For example, the combination of managed effects and semantically immutable values means all Roc functions are [pure](https://en.wikipedia.org/wiki/Pure_function)—that is, they have no side effects and always return the same answer when called with the same arguments. + +## [Pure functions](#pure-functions) {#pure-functions} + +Pure functions have some valuable properties, such as [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency) and being trivial to [memoize](https://en.wikipedia.org/wiki/Memoization). They also have testing benefits; for example, all Roc tests which either use simulated effects (or which do not involve Tasks at all) can never flake. They either consistently pass or consistently fail. Because of this, their results can be cached, so `roc test` can skip re-running them unless their source code (including dependencies) changed. (This caching has not yet been implemented, but is planned.) + +Roc does support [tracing](https://en.wikipedia.org/wiki/Tracing_(software)) via the `dbg` keyword, an essential [debugging](https://en.wikipedia.org/wiki/Debugging) tool which is unusual among side effects in that—similarly to opportunistic mutation—using it should not affect the behavior of the program. As such, it typically does not impact the guarantees of pure functions in practice. + +Pure functions are notably amenable to compiler optimizations, and Roc already takes advantage of them to implement [function-level dead code elimination](https://elm-lang.org/news/small-assets-without-the-headache). Here are some other examples of optimizations that will benefit from this in the future; these are planned, but not yet implemented: + +- [Loop fusion](https://en.wikipedia.org/wiki/Loop_fission_and_fusion), which can do things like combining consecutive `List.map` calls (potentially intermingled with other operations that traverse the list) into one pass over the list. +- [Compile-time evaluation](https://en.wikipedia.org/wiki/Compile-time_function_execution), which basically takes [constant folding](https://en.wikipedia.org/wiki/Constant_folding) to its natural limit: anything that can be evaluated at compile time is evaluated then. This saves work at runtime, and is easy to opt out of: if you want evaluation to happen at runtime, you can instead wrap the logic in a function and call it as needed. +- [Hoisting](https://en.wikipedia.org/wiki/Loop-invariant_code_motion), which moves certain operations outside loops to prevent them from being re-evaluated unnecessarily on each step of the loop. It's always safe to hoist calls to pure functions, and in some cases they can be hoisted all the way to the top level, at which point they become eligible for compile-time evaluation. + +There are other optimizations (some of which have yet to be considered) that pure functions enable; this is just a sample! + +## Get started + +If this design sounds interesting to you, you can give Roc a try by heading over to the [tutorial](/tutorial)! diff --git a/www/wip_new_website/content/index.md b/www/content/index.md similarity index 72% rename from www/wip_new_website/content/index.md rename to www/content/index.md index 57004d183a..2a4911ead9 100644 --- a/www/wip_new_website/content/index.md +++ b/www/content/index.md @@ -1,50 +1,35 @@ +
@@ -69,50 +54,63 @@
- + - +
## [Examples](#examples) {#examples} -Roc is a very young language (it doesn’t even have a numbered release yet, just nightly builds!) but it can already be used for several things if you’re up for being an early adopter—with all the bugs and missing features which come with that territory. +Roc is a young language. It doesn't even have a numbered release yet, just nightly builds! + +However, it can already be used for several things if you're up for being an early adopter—
+with all the bugs and missing features which come with that territory. Here are some examples of how it can be used today. -### Other Examples - -There are a variety of [other examples](/wip/examples) you can check out, if you’d like to get a feel for some different ways people write Roc code. +### [Other Examples](#other-examples) {#other-examples} +You can find more use cases and examples on the [examples page](/examples)! ## [Code Sample with Explanations](#code-sample) {#code-sample} -Here’s a code sample that shows a few different aspects of Roc: +Here's a code sample that shows a few different aspects of Roc: * File I/O and HTTP requests * Pattern matching for error handling * JSON deserialization via type inference * Common syntax sugar: string interpolation, pipelines, and backpassing -The [tutorial](/tutorial) introduces these gradually and in more depth, but this gives you a brief overview. +The [tutorial](/tutorial) introduces these gradually and in more depth, but this gives a brief overview. @@ -140,9 +138,9 @@ We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/),

-If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! +If you would like your organization to become an official sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! -We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: +We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
  • Aaron White
  • @@ -158,10 +156,10 @@ We'd also like to express our gratitude to each and every one of our fantastic [
  • Zeljko Nesic
-Thank you all so much for helping Roc progress! 💜 +Thank you all for your contributions! Roc would not be what it is without your generosity. 💜 We are currently trying to raise $4,000 USD/month in donations to fund one longtime Roc contributor to continue his work on Roc full-time. We are a small group trying to do big things, and every donation helps! You can donate using: - [GitHub Sponsors](https://github.com/sponsors/roc-lang) - [Liberapay](https://liberapay.com/roc_lang) -All donations go through the [Roc Programming Language Foundation](https://foundation.roc-lang.org/), a registered [US 503(c)(3) nonprofit organization](https://en.wikipedia.org/wiki/501(c)(3)_organization), which means these donations are tax-exempt in the US. +All donations go through the [Roc Programming Language Foundation](https://foundation.roc-lang.org/), a registered US 501(c)(3) nonprofit organization, which means these donations are tax-exempt in the US. diff --git a/www/content/install.md b/www/content/install.md new file mode 100644 index 0000000000..ddaa322cdf --- /dev/null +++ b/www/content/install.md @@ -0,0 +1,32 @@ +# Install + +Roc is a very young language with many incomplete features and known bugs. It doesn't even have a numbered release yet, but it does have [nightly builds](https://github.com/roc-lang/roc/releases) that you can download if you'd like to try it out without [building from source](https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md)! + +There are currently a few known OS-specific issues: +* **macOS:** There are no known compatibility issues, but the compiler doesn't run as fast as it does on Linux or Windows, because we don't (yet) do our own linking like we do on those targets. (Linking works similarly on Linux and Windows, but the way macOS does it is both different and significantly more complicated.) +* **Windows:** There are some known Windows-specific compiler bugs, and probably some other unknown ones because more people have tried out Roc on Mac and Linux than on Windows. +* **Linux:** The nightlies are built with glibc, so they aren't usable on distros that don't use (dynamically linked) glibc, like Alpine or NixOS. In the future we plan to build Linux releases with [musl libc](https://wiki.musl-libc.org/) to address this, but this requires [building LLVM from source with musl](https://wiki.musl-libc.org/building-llvm.html). +* **Other operating systems:** Roc has not been built on any other operating systems. [Building from source](https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md) on another OS might work, but you might very well be the first person ever to try it! + +### [Getting Started](#getting-started) {#getting-started} + +Here are some Getting Started guides for different operating systems: + + +- [Linux x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/linux_x86_64.md) +- [MacOS Apple Silicon](https://github.com/roc-lang/roc/blob/main/getting_started/macos_apple_silicon.md) +- [MacOS x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/macos_x86_64.md) +- [Windows](https://github.com/roc-lang/roc/blob/main/getting_started/windows.md) +- [Other Systems](https://github.com/roc-lang/roc/blob/main/getting_started/other.md) + +### [Editor Extensions](#editor-extensions) {#editor-extensions} + +There is currently a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=IvanDemchenko.roc-lang-unofficial) for Roc which includes instructions in its README for how to enable a Roc language server. + +Currently that language server has to be built from source; it would be a fantastic contribution if anyone could get it incorporated it into the extension directly. If you'd like to help with this, just make a post in [the "new contributors" topic on Zulip](https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/new.20contributors) and say hello! + +### [Tutorial](#tutorial) {#tutorial} + +Once you've installed roc, check out the [tutorial](/tutorial) to learn how to Roc! + +Start Tutorial diff --git a/www/content/platforms.md b/www/content/platforms.md new file mode 100644 index 0000000000..9ef3d85f39 --- /dev/null +++ b/www/content/platforms.md @@ -0,0 +1,114 @@ +# Platforms + +Something that sets Roc apart from other programming languages is its *platforms and applications* architecture. + +## [Applications](#applications) {#applications} + +Here is a Roc application that prints `"Hello, World!"` to the command line: + +```roc +app "hello" + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } + imports [pf.Stdout] + provides [main] to pf + +main = + Stdout.line "Hello, World!" +``` + +Every Roc application, including this one, is built on a _platform_. This application happens to be built on a platform called [basic-cli](https://github.com/roc-lang/basic-cli), which is a platform for building command-line interfaces. + +## [Domain-specific functionality](#domain-specific) {#domain-specific} + +Roc platforms provide domain-specific functionality that multiple applications can use as a foundation to build on, much like game engines and Web frameworks do. + +Also like many game engines and Web frameworks, Roc platforms have a high-level Roc API which presents a nice interface to a lower-level implementation (written in a different language), which provides the foundational primitives that platform needs to operate—such as a C++ 3D rendering system in a game engine, or a Rust HTTP networking system in a Web framework. + +Here are some example Roc platforms, and functionality they might provide: + +* A Roc game engine platform might provide functionality for rendering and sound. +* A Roc Web server platform (like [basic-webserver](https://github.com/roc-lang/basic-webserver)) probably would not provide functionality for rendering and sound, but it might provide functionality for responding to incoming HTTP requests—which a game engine platform likely would not. +* A Roc native [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface) platform might provide functionality for defining native operating system UI elements, whereas a game engine platform might focus more on rendering with [shaders](https://en.wikipedia.org/wiki/Shader), and a Web server platform would not have GUI functionality at all. + +These are broad domains, but platforms can be much more specific than this. For example, anyone could make a platform for writing [Vim](https://en.wikipedia.org/wiki/Vim_(text_editor)) plugins, or [Postgres](https://en.wikipedia.org/wiki/PostgreSQL) extensions, or robots ([which has already happened](https://roc.zulipchat.com/#narrow/stream/304902-show-and-tell/topic/Roc.20on.20a.20microcontroller/near/286678630)), or even [implementing servo logic for a clock that physically turns panels to simulate an LCD](https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Roc.20Clock/near/327939600). You really can get as specific as you like! + +Platforms can also be designed to have a single, specific application run on them. For example, you can make a platform that is essentially "your entire existing code base in another language," and then use Roc as an embedded language within that code base. For example, [Vendr](https://www.vendr.com/careers) is using this strategy to call Roc functions from their [Node.js](https://nodejs.org/en) backend using [roc-esbuild](https://github.com/vendrinc/roc-esbuild), as a way to incrementally transition code from Node to Roc. + +## [Platform scope](#scope) {#scope} + +Roc platforms have a broader scope of responsibility than game engines or Web frameworks. In addition to providing a nice domain-specific interface, platforms are also responsible for: +* Tailoring memory management to that domain (more on this later) +* Providing all I/O primitives + +In most languages, I/O primitives come with the standard library. In Roc, the [standard library](https://www.roc-lang.org/builtins/) contains only data structures; an application gets all of its I/O primitives from its platform. For example, in the "Hello, World" application above, the `Stdout.line` function comes from the `basic-cli` platform itself, not from Roc's standard library. + +This design has a few benefits. + +### [Ecosystem benefits](#ecosystem) {#ecosystem} + +Some I/O operations make sense in some use cases but not others. + +For example, suppose I'm building an application on a platform for command-line interfaces, and I use a third-party package which sometimes blocks the program while it waits for [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This might be fine for my command-line application, but it would probably be a very bad fit if I'm using a webserver. Similarly, a package which does some occasional file I/O for caching might work fine on either of those platforms, but might break in surprising ways when used in a platform that's designed to run in a browser on WebAssembly—since browsers don't offer arbitrary file I/O access! + +Because Roc's I/O primitives come from platforms, these mismatches can be prevented at build time. The browser-based platform would not expose file I/O primitives, the webserver wouldn't expose a way to block on reading from standard input, and so on. (Note that there's a design in the works for allowing packages which perform I/O to work across multiple platforms—but only platforms which support the I/O primitives it requires—but this design has not yet been implemented.) + +### [Security benefits](#security) {#security} + +Since platforms have exclusive control over all I/O primitives, one of the things they can do is create domain-specific security guarantees around them. For example, a platform for writing text editor plugins might want to display a prompt to the end user before performing any file I/O operations outside the directory that's currently open in the editor. + +[This talk](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) shows an example of taking this idea a step further with a "safe scripting" platform for writing command-line scripts. The idea is that you could download a script from the Internet and run it on this platform without worrying that the script would do bad things to your computer, because the platform would (much like a Web browswer) show you specific prompts before allowing the script to do potentially harmful I/O, such as filesystem operations. + +These security guarantees can be relied on because platforms have *exclusive* control over all I/O primitives, including how they are implemented. There are no escape hatches that a malicious program could use to get around these, either; for example, Roc programs that want to call functions in other languages must do so using primitives provided by the platform, which the platform can disallow (or sandbox with end-user prompts) in the same way. + +### [Performance benefits](#performance) {#performance} + +Many I/O operations can benefit from being run concurrently. Since platforms are in charge of how those I/O operations are implemented, they can also determine how they are scheduled. This means that both applications and packages can describe which operations they want to run concurrently, and then the platform can optimize the scheduling of these operations using its domain-specific knowledge. + +For example, a command-line platform might schedule concurrent operations across all available cores (or some lower number specified by a command-line argument). In contrast, a Web server platform might try to balance available cores across multiple request handlers—to prevent undesirable scenarios like one handler getting all the cores (meaning none of the others can progress). + +Note that although platform-implemented scheduling of concurrent operations is theoretically possible today, there are currently some missing pieces to make it practical for platform authors to implement. Implementing those missing pieces is already in progress, but is not yet complete. + +## [How platforms are implemented](#implementation) {#implementation} + +To understand how platforms can tailor automatic memory management to their particular domain, it's helpful to understand how platforms are implemented. + +### [The Host and the Roc API](#host-and-roc-api) {#host-and-roc-api} + +Each platform consists of two parts: +* **The Roc API** is the part that application authors see. For example, `Stdout.line` is part of basic-cli's Roc API. +* **The Host** is the under-the-hood implementation written in a language other than Roc. For example, basic-cli's host is written in Rust. It has a Rust function which implements the behavior of the `Stdout.line` operation, and all the other I/O operations it supports. + +This design means that application authors don't necessarily need to know (or care) about the non-Roc language being used to implement the platform's host. That can be a behind-the-scenes implementation detail that only the platform's author(s) are concerned with. Application authors only interact with the public-facing Roc API. + +### [Memory management](#memory) {#memory} + +Host authors implement not only the platform's I/O primitives, but also functions for memory allocation and deallocation. In C terms, the host provides [`malloc` and `free`](https://en.wikipedia.org/wiki/C_dynamic_memory_allocation) implementations which the compiled Roc application will automatically call whenever it needs to allocate or deallocate memory. + +[The same talk mentioned earlier](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) in the context of [security benefits](#security) demonstrates some benefits of this, such as being able to get accurate diagnostics on how much memory the Roc part (or even specific Roc parts) of a running program are using. + +The bigger benefit is tailoring memory management itself based on the domain. For example, [nea](https://github.com/tweedegolf/nea/) is a work-in-progress Web server which performs [arena allocation](https://en.wikipedia.org/wiki/Region-based_memory_management) on each request handler. In Roc terms, this means the host's implementation of `malloc` can allocate into the current handler's arena, and `free` can be a no-op. Instead, the arena can be reset when the response has been sent. + +In this design, heap allocations in a Web server running on `nea` are about as cheap as stack allocations, and deallocations are essentially free. This is much better for the server's throughput, latency, and predictability than (for example) having to pay for periodic garbage collection! + +### [Program start](#program-start) {#program-start} + +When a compiled Roc program runs, it's actually the host—not the Roc application—which starts running first. In C terms, the host implements `main()`, and then at some point it calls a function exposed by the compiled Roc application. + +Knowing this, a useful mental model for how Roc platforms and applications interact at the implementation level is: the Roc application compiles down to a C library which the platform can choose to call (or not). + +This is essentially what's happening behind the scenes when you run `roc build`. Specifically: +1. The Roc compiler builds the Roc application into a binary [object file](https://en.wikipedia.org/wiki/Object_file) +2. Since that application specified its platform, the compiler then looks up the platform's host implementation (which the platform will have provided as an already-compiled binary) +3. Now that it has a binary for the Roc application and a binary for the host, it links them together into one combined binary in which the host portion calls the application portion as many times as it likes. + +This process works for small platforms and large applications (for example, a very large Web server application) as well as for large platforms and small applications (for example, a very large C++ game which serves as a platform for a small amount of Roc application code that the game uses for scripting). + +## [Summary](#summary) {#summary} + +Every Roc application has exactly one platform. That platform provides all the I/O primitives that the application can use; Roc's standard library provides no I/O operations, and the only way for a Roc application to execute functions in other languages is if the platform offers a way to do that. + +This I/O design has [security benefits](#security), [ecosystem benefits](#ecosystem), and [performance benefits](#performance). The [domain-specific memory management](#memory) platforms can implement can offer additional benefits as well. + +Applications only interact with the *Roc API* portion of a platform, but there is also a *host* portion (written in a different language) that works behind the scenes. The host determines how the program starts, how memory is allocated and deallocated, and how I/O primitives are implemented. + +Anyone can implement their own platform. There isn't yet an official guide about how to do this, so the best way to get help if you'd like to create a platform is to [say hi in the `#beginners` channel](https://roc.zulipchat.com/#narrow/stream/231634-beginners) on [Roc Zulip!](https://roc.zulipchat.com) diff --git a/www/content/repl/index.md b/www/content/repl/index.md new file mode 100644 index 0000000000..dafb673594 --- /dev/null +++ b/www/content/repl/index.md @@ -0,0 +1,15 @@ + +## The rockin' Roc REPL + + + + diff --git a/www/generate_tutorial/src/input/tutorial.md b/www/content/tutorial.md similarity index 96% rename from www/generate_tutorial/src/input/tutorial.md rename to www/content/tutorial.md index fd1f7b41fc..c97094b3c3 100644 --- a/www/generate_tutorial/src/input/tutorial.md +++ b/www/content/tutorial.md @@ -1,14 +1,56 @@ - + + +
+
+

Tutorial

+

Welcome to Roc!

+

This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and more!

+
+
+

Installation

+

Roc doesn’t have a numbered release or an installer yet, but you can follow the install instructions for your OS here . If you get stuck, friendly people will be happy to help if you open a topic in #beginners on Roc Zulip Chat and ask for assistance!

+
-## [Strings and Numbers](#strings-and-numbers) {#strings-and-numbers} +## [REPL](#repl) {#repl} -Let's start by getting acquainted with Roc's [_Read-Eval-Print-Loop_](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), or **REPL** for short. Run this in a terminal: +Let's start by getting acquainted with Roc's [_Read-Eval-Print-Loop_](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), or **REPL** for short. -roc repl +You can use the online REPL at [roc-lang.org/repl](https://www.roc-lang.org/repl). -If Roc is [installed](#installation), you should see this: +Or you can run this in a terminal: roc repl, and if Roc is [installed](/install), you should see this: -
The rockin’ roc repl
+
+
+  The rockin’ roc repl
+────────────────────────
+
+Enter an expression, or :help, or :q to quit.
+
+
So far, so good! @@ -28,16 +70,12 @@ Congratulations! You've just written your first Roc code. When you entered the _expression_ `"Hello, World!"`, the REPL printed it back out. It also printed `: Str`, because `Str` is that expression's type. We'll talk about types later; for now, let's ignore the `:` and whatever comes after it whenever we see them. -Let's try that out. Put this into the repl and press Enter: +You can assign specific names to expressions. Try entering these lines: -
val1
- -You should see the same `"Hello, World!"` line as before. - -You can also assign specific names to expressions. Try entering these lines: - -
greeting = "Hi"
-
audience = "World"
+``` +greeting = "Hi" +audience = "World" +``` From now until you exit the REPL, you can refer to either `greeting` or `audience` by those names! We'll use these later on in the tutorial. @@ -49,7 +87,7 @@ Now let's try using an _operator_, specifically the `+` operator. Enter this: You should see this output: -
2 : Num *                # val2
+
2 : Num * 
According to the REPL, one plus one equals two. Sounds right! @@ -530,8 +568,6 @@ stoplightStr = This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not in the way we'd decide to if the compiler had drawn our attention to it! -## [Exhaustiveness](#exhaustiveness) {#exhaustiveness} - We can make this `when` _exhaustive_ (that is, covering all possibilities) without using `_ ->` by using `|` to specify multiple matching conditions for the same branch: ```roc @@ -1960,6 +1996,12 @@ For this reason, any time you see a function that only runs a `when` on its only \[This part of the tutorial has not been written yet. Coming soon!\] +### [Reserved Keywords](#reserved-keywords) {#reserved-keywords} + +These are all the reserved keywords in Roc. You can't choose any of these as names, except as record field names. + +`if`, `then`, `else`, `when`, `as`, `is`, `dbg`, `expect`, `expect-fx`, `crash`, `interface`, `app`, `package`, `platform`, `hosted`, `exposes`, `imports`, `with`, `generates`, `packages`, `requires`, `provides`, `to` + ### [Operator Desugaring Table](#operator-desugaring-table) {#operator-desugaring-table} Here are various Roc expressions involving operators, and what they desugar to. @@ -1982,8 +2024,6 @@ Here are various Roc expressions involving operators, and what they desugar to. | a \|> b | `b a` | | a b c \|> f x y | `f (a b c) x y` | - ### [Language Keywords](#language-keywords) {#language-keywords} -These are all of the language keywords supported by Roc; - -`if`,`then`,`else`,`when`,`as`,`is`,`dbg`,`expect`,`expect-fx`,`crash`,`interface`,`app`,`package`,`platform`,`hosted`,`exposes`,`imports`,`with`,`generates`,`packages`,`requires`,`provides`,`to` +
+ diff --git a/www/generate_tutorial/.gitignore b/www/generate_tutorial/.gitignore deleted file mode 100644 index 6981c2e5ec..0000000000 --- a/www/generate_tutorial/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -tutorial -src/output/tutorial.html \ No newline at end of file diff --git a/www/generate_tutorial/src/tutorial.roc b/www/generate_tutorial/src/tutorial.roc deleted file mode 100644 index fda98a3400..0000000000 --- a/www/generate_tutorial/src/tutorial.roc +++ /dev/null @@ -1,116 +0,0 @@ -app "roc-tutorial" - packages { pf: "../../../examples/static-site-gen/platform/main.roc" } - imports [ - pf.Html.{ html, head, body, header, footer, script, div, main, p, section, h1, h2, label, ol, input, text, nav, a, li, link, meta }, - pf.Html.Attributes.{ content, name, for, id, type, href, rel, lang, title, charset, src }, - ] - provides [transformFileContent] to pf - -transformFileContent : Str, Str -> Str -transformFileContent = \_, htmlContent -> - Html.render (view htmlContent) - -view : Str -> Html.Node -view = \htmlContent -> - html [lang "en"] [ - head [] [ - meta [charset "utf-8"], - Html.title [] [text "Roc Tutorial"], - meta [name "description", content "Learn how to use the Roc programming language."], - meta [name "viewport", content "width=device-width"], - link [rel "stylesheet", href "/site.css"], - link [rel "icon", href "/favicon.svg"], - ], - body [] [ - viewNavbar, - main [] [ - viewTutorialStart, - text htmlContent, - ], - footer [] [ - text "Made by people who like to make nice things.", - ], - script [src "/site.js"] [], - ], - ] - -viewNavbar : Html.Node -viewNavbar = - header [id "top-bar"] [ - nav [] [ - a [id "nav-home-link", href "/", title "The Roc Programming Language"] [text "roc"], - div [id "header-links"] [ - a [href "/tutorial"] [text "tutorial"], - a [href "https://github.com/roc-lang/roc/tree/main/getting_started"] [text "install"], - a [href "/repl"] [text "repl"], - a [href "/builtins"] [text "docs"], - ], - ], - ] - -viewTutorialStart : Html.Node -viewTutorialStart = - div [id "tutorial-start"] [ - input [id "tutorial-toc-toggle", name "tutorial-toc-toggle", type "checkbox"] [], - nav [id "tutorial-toc", ariaLabel "Table of Contents"] [ - label [id "close-tutorial-toc", for "tutorial-toc-toggle"] [text "close"], - # TODO fix search: input [id "toc-search", type "text", placeholder "Search"] [], - ol [] tocLinks, - ], - tutorialIntro, - ] - -tocLinks = - { tag, value } <- List.map [ - { tag: "#installation", value: "Installation" }, - { tag: "#strings-and-numbers", value: "Strings and Numbers" }, - { tag: "#building-an-application", value: "Building an Application" }, - { tag: "#defining-functions", value: "Defining Functions" }, - { tag: "#if-then-else", value: "if-then-else" }, - { tag: "#debugging", value: "Debugging" }, - { tag: "#records", value: "Records" }, - { tag: "#tags", value: "Tags & Pattern Matching" }, - { tag: "#booleans", value: "Booleans" }, - { tag: "#lists", value: "Lists" }, - { tag: "#types", value: "Types" }, - { tag: "#numeric-types", value: "Numeric Types" }, - { tag: "#crashing", value: "Crashing" }, - { tag: "#tests-and-expectations", value: "Tests and Expectations" }, - { tag: "#modules", value: "Modules" }, - { tag: "#tasks", value: "Tasks" }, - { tag: "#abilities", value: "Abilities" }, - { tag: "#appendix-advanced-concepts", value: "Advanced Concepts" }, - { tag: "#operator-desugaring-table", value: "Operator Desugaring Table" }, - ] - - li [] [ - a [href tag] [text value], - ] - -tutorialIntro = - div [id "tutorial-intro"] [ - section [] [ - h1 [] [ - text "Tutorial", - label [id "tutorial-toc-toggle-label", for "tutorial-toc-toggle"] [text "contents"], - ], - p [] [text "Welcome to Roc!"], - p [] [text "This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and more!"], - ], - section [] [ - h2 [id "installation"] [ - a [href "#installation"] [text "Installation"], - ], - p [] [ - text "Roc doesn’t have a numbered release or an installer yet, but you can follow the install instructions for your OS", - a [href "https://github.com/roc-lang/roc/tree/main/getting_started#installation"] [text " here "], - text ". If you get stuck, friendly people will be happy to help if you open a topic in", - a [href "https://roc.zulipchat.com/#narrow/stream/231634-beginners"] [text " #beginners "], - text "on", - a [href "https://roc.zulipchat.com/"] [text " Roc Zulip Chat "], - text "and ask for assistance!", - ], - ], - ] - -ariaLabel = Html.attribute "aria-label" diff --git a/www/wip_new_website/main.roc b/www/main.roc similarity index 62% rename from www/wip_new_website/main.roc rename to www/main.roc index 016f28573e..2872a74732 100644 --- a/www/wip_new_website/main.roc +++ b/www/main.roc @@ -1,21 +1,20 @@ app "roc-website" - packages { pf: "../../examples/static-site-gen/platform/main.roc" } + packages { pf: "../examples/static-site-gen/platform/main.roc" } imports [ - pf.Html.{ Node, html, head, body, header, footer, div, main, text, nav, a, link, meta, script }, - pf.Html.Attributes.{ attribute, content, name, id, href, rel, lang, class, title, charset, color, ariaLabel, type, role }, + pf.Html.{ Node, html, head, body, header, footer, div, span, main, text, nav, a, link, meta, script }, + pf.Html.Attributes.{ attribute, content, name, id, href, rel, lang, class, title, charset, color, ariaLabel, ariaHidden, type }, InteractiveExample, ] provides [transformFileContent] to pf pageData = Dict.empty {} - |> Dict.insert "community.html" { title: "Community", description: "The Roc community" } - |> Dict.insert "design_goals.html" { title: "Design Goals", description: "Roc's design goals" } - |> Dict.insert "docs.html" { title: "Documentation", description: "Learn the Roc programming language" } - |> Dict.insert "index.html" { title: "Roc", description: "The Roc programming language" } - |> Dict.insert "install.html" { title: "Install", description: "Getting started with the Roc programming language" } - |> Dict.insert "donate.html" { title: "Donate", description: "Sponsor Roc" } - |> Dict.insert "tutorial.html" { title: "Tutorial", description: "The Roc tutorial" } + |> Dict.insert "community.html" { title: "Roc Community", description: "Connect with the Roc programming language community" } + |> Dict.insert "docs.html" { title: "Roc Docs", description: "Documentation for the Roc programming language, including builtins" } + |> Dict.insert "index.html" { title: "The Roc Programming Language", description: "A fast, friendly, functional language" } + |> Dict.insert "install.html" { title: "Install Roc", description: "Install the Roc programming language" } + |> Dict.insert "donate.html" { title: "Donate to Roc", description: "Support the Roc programming language by donating or sponsoring" } + |> Dict.insert "tutorial.html" { title: "Roc Tutorial", description: "Learn the Roc programming language" } getPage : Str -> { title : Str, description : Str } getPage = \current -> @@ -56,6 +55,22 @@ view = \page, htmlContent -> else [text htmlContent] + bodyAttrs = + when page is + "index.html" -> [id "homepage-main"] + "tutorial.html" -> [id "tutorial-main", class "article-layout"] + _ -> + if Str.startsWith page "examples/" && page != "examples/index.html" then + # Individual examples should render wider than articles. + # Otherwise the width is unreasonably low for the code blocks, + # and those pages don't tend to have big paragraphs anyway. + # Keep the article width on examples/index.html though, + # because otherwise when you're clicking through the top nav links, + # /examples has a surprisingly different width from the other links. + [id "example-main"] + else + [class "article-layout"] + html [lang "en", class "no-js"] [ head [] [ meta [charset "utf-8"], @@ -67,9 +82,9 @@ view = \page, htmlContent -> # The homepage doesn't actually use latin-ext preloadWoff2 "/fonts/lato-v23-latin/lato-v23-latin-regular.woff2", preloadWoff2 "/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2", + preloadWoff2 "/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2", link [rel "prefetch", href "/repl/roc_repl_wasm.js"], - link [rel "stylesheet", href "/wip/site.css"], - link [rel "stylesheet", href "/wip/repl.css"], + link [rel "stylesheet", href "/site.css"], # Safari ignores rel="icon" and only respects rel="mask-icon". It will render the SVG with # fill="#000" unless this `color` attribute here is hardcoded (not a CSS `var()`) to override it. link [rel "mask-icon", href "/favicon.svg", color "#7d59dd"], @@ -81,7 +96,7 @@ view = \page, htmlContent -> # Otherwise, this will work locally and then fail in production! script [] [text "document.documentElement.className = document.documentElement.className.replace('no-js', '');"], ], - body [] [ + body bodyAttrs [ viewNavbar page, main [] mainBody, footer [] [ @@ -97,21 +112,20 @@ viewNavbar : Str -> Html.Node viewNavbar = \page -> isHomepage = page == "index.html" - homeLink = - if isHomepage then - div [role "presentation"] [] # This is a spacer for the nav bar - else - a [id "nav-home-link", href "/wip/", title "The Roc Programming Language"] [rocLogo] + homeLinkAttrs = + [id "nav-home-link", href "/", title "The Roc Programming Language Homepage"] + |> List.concat (if isHomepage then [ariaHidden "true"] else []) header [id "top-bar"] [ nav [ariaLabel "primary"] [ - homeLink, + a homeLinkAttrs [rocLogo, span [class "home-link-text"] [text "Roc"]], div [id "top-bar-links"] [ - a [href "/wip/tutorial"] [text "tutorial"], - a [href "/wip/install"] [text "install"], - a [href "/wip/community"] [text "community"], - a [href "/wip/docs"] [text "docs"], - a [href "/wip/donate"] [text "donate"], + a [href "/tutorial"] [text "tutorial"], + a [href "/install"] [text "install"], + a [href "/examples"] [text "examples"], + a [href "/community"] [text "community"], + a [href "/docs"] [text "docs"], + a [href "/donate"] [text "donate"], ], ], ] diff --git a/www/wip_new_website/optimize.sh b/www/optimize.sh similarity index 83% rename from www/wip_new_website/optimize.sh rename to www/optimize.sh index a61500a351..1fae5b8981 100755 --- a/www/wip_new_website/optimize.sh +++ b/www/optimize.sh @@ -43,11 +43,11 @@ done popd # Minify the CSS file, and let esbuild add a content hash to the file name -npm exec --yes esbuild -- --minify dist/wip/site.css --outdir=dist/wip/ --entry-names='[name]-[hash]' +npm exec --yes esbuild -- --minify dist/site.css --outdir=dist/ --entry-names='[name]-[hash]' # Remove unused original file -rm dist/wip/site.css +rm dist/site.css # Find the new filename -css_with_hash=$(basename dist/wip/site-*.css) +css_with_hash=$(basename dist/site-*.css) # Replace all occurances in the html -sed -i "s:/wip/site.css:/wip/${css_with_hash}:g" dist/wip/*.html +sed -i "s:/wip/site.css:/wip/${css_with_hash}:g" dist/*.html diff --git a/www/public/homepage.css b/www/public/homepage.css deleted file mode 100644 index c251974bc0..0000000000 --- a/www/public/homepage.css +++ /dev/null @@ -1,15 +0,0 @@ -html { - font-family: sans-serif; - line-height: 145%; -} - -li { - margin-bottom: 0.5rem; -} - -@media only screen and (min-device-width: 900px) { - body { - width: 900px; - margin: 24px auto; - } -} \ No newline at end of file diff --git a/www/public/index.html b/www/public/index.html deleted file mode 100644 index a47315b204..0000000000 --- a/www/public/index.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - The Roc Programming Language - - - - - - - -

The Roc Programming Language

-

Roc's goal is to be a fast, friendly, functional language. It's very much a work in progress; - below, you can see the current progress towards this goal. This website is intentionally unstyled - as a way to emphasize the language's current level of incompleteness. The website will become - more polished after the language itself becomes more polished!

- -

Roc compiles to machine code or to WebAssembly. Eventually - you'll be able to use Roc to build high-quality servers, command-line applications, graphical - native desktop user interfaces, among other classes of applications. Today, only command-line interfaces have - support beyond the - proof-of-concept stage; the other use cases will mature over time.

- -

Like Lua, Roc's automatic memory management doesn't require - a virtual machine, and it's possible to call Roc functions directly from any language that can - call C functions. This makes - Roc additionally useful as a language for implementing plugins, and gives you a way to - incrementally transition a legacy code base from another language to Roc.

- -

So far, the Roc compiler has progressed past the "proof of concept" stage, but there are - currently lots of known bugs and unimplemented features, and the documentation for both the - language and the standard library is incomplete. The overall ecosystem is in its infancy, and - the compiler is neither battle-tested nor fuzz-tested yet, so we don't recommend relying on Roc - for critical projects until its development is further along.

- -

With all that context in mind, if you'd like to try it out or to get involved with contributing, - the source code repository has - nightly builds you can download, - and a tutorial. -

- -

If you'd like to learn more about Roc, you can continue reading here, or check out one of these videos:

- - -

A Fast Language

- -

Goal

- -

We want Roc to run faster than any non-systems language (like C, C++, Rust, or Zig) - that sees mainstream use in industry. The goal is that nobody should find themselves - thinking "I should rewrite my Roc program in [some mainstream garbage-collected language] - because that will make it run significantly faster." -

- -

When benchmarking Roc code against similarly-optimized programs written in - Go, - Swift, Java, - C#, or - JavaScript, - we generally aim for Roc to outperform all of those languages. Outperforming systems - languages like Rust, Zig, C, D, and C++ is a non-goal, as is outperforming research languages - that see little or no use in industry. (Realistically, there will always be certain specific - benchmarks where some popular non-systems-level languages outperform Roc, but the goal is to - usually be at the front of that pack.) -

- -

Current progress

- -

Progress towards this performance goal is already quite far along.

- -

Roc already uses unboxed data structures and unboxed closures, monomorphizes polymorphic code, - and uses LLVM as a compiler backend. These optimizations, especially unboxed closures and - monomorphization, can be found in several systems-level languages (like C++ and Rust), but not - in any mainstream garbage-collected languages. Roc closures in particular have the distinction - of being as ergonomic as the closures found in garbage-collected languages - (where they are typically boxed), but have the performance of systems language closures - (which are typically unboxed, but have more complicated types). - -

Because of these optimizations, in many cases Roc code already - compiles to the same machine instructions that the equivalent code written in one of these - systems languages would. Something we do regularly is to compare the LLVM instructions generated - by Roc's compiler and by these systems languages' compilers, to check whether we're generating - equivalent instructions.

- -

That said, there are also cases where Roc has strictly more runtime overhead than languages - like C, C++, Zig, and Rust do. The most costly is automatic memory management, which Roc - implements using automatic reference counting. Static reference count optimizations like - elision and reuse (thanks to Morphic and - Perceus) - improve things, but significant runtime overhead remains. -

- -

Eliminating this overhead altogether would require sacrificing other design goals - (e.g. it would require introducing memory-unsafe operations, or compile-time lifetime errors), - and there isn't much overhead left to remove outside of automatic memory management. For example, - smaller sources of overhead include mandatory array bounds checks, disallowing cyclic references - (which rules out a certain niche of efficient graph data structures), and automatic opportunistic - in-place mutation instead of direct mutation. Even if all of these sources of overhead were - completely eliminated, it seems unlikely that typical Roc programs would see a particularly big - performance boost.

- -

Overall, we expect Roc's performance in the use cases mentioned above (servers, CLIs, GUIs, etc.) - to be about the same as the equivalent C++ code would be, if all that C++ code - (including its dependencies) were written in a restricted subset of C++ which always did array - bounds checks and used shared pointers for all heap allocations. - The Roc code might even run somewhat faster, because its reference counts are non-atomic by default, - and can be statically optimized away in some cases—but then again, Roc also has a bit of overhead - to perform opportunistic in-place mutation instead of direct mutation.

- -

To be clear, we don't expect this because we've benchmarked a bunch of programs written in Roc - and in this restricted C++ subset, and found that the numbers were about the same (although if - you know C++ well enough and want to do such experiments, we'd happy to help and would be - interested to see the results!) but rather because Roc's compiler and - clang should both be generating essentially the same - LLVM instructions when the C++ is restricted to that subset. -

- -

Of course, unrestricted C++ code can certainly run faster than unrestricted Roc code. - The same is true when comparing other such minimal-overhead systems languages to Roc, including - Rust, Zig, C, and D. The point of the comparison is to give you a general idea of what Roc - compiles to, since it is quite different from the VMs and JITted bytecode interpreters found in - today's most popular garbage-collected languages!

- -

The talk Outperforming Imperative with Pure Functional Languages - discusses some early results from Roc's optimizations, and - Roc at Handmade Seattle gets into - low-level details of how Roc's compiler generates programs similarly to how clang does. -

- -

A Friendly Language

- -

Goals

- -

Roc aims to be a user-friendly language with a friendly community of users.

- -

A programming language can be much more than a tool for writing software, it can also be a way - for people to come together through shared experiences, to teach and to learn from one another, - and to make new friends.

- -

No community is perfect, but a community where people show kindness to each another by default - can be a true joy to participate in. That all starts with friendliness, especially towards - beginners, and including towards people who prefer other programming languages. - After all, languages are tools people use to create software, and there's no need for us - to create artificial divisions between ourselves based on the tools we use!

- -

On a technical level, Roc aims to ship a toolset where user-friendliness is a major priority. - This includes everything from helpful error messages (aiming to meet the bar set by - Elm) to quality-of-life improvements inspired by dynamic - languages (always being able to run your program even if there are compile errors, automatic - serialization and deserialization using schemas determined by type inference, reliable hot - code loading that's always enabled and requires no configuration to set up, etc.) to accessibility - features in the included editor. -

- -

Roc also aims to ship a single binary that includes not only a compiler, but also a - REPL, - package manager, test runner, debugger, static analyzer, code formatter, and a full-featured - editor, all of which are designed to work seamlessly together. -

- -

Current Progress

- -

Work has not yet started on the package manager, static analyzer, debugger, or hot code loading - system, and although work has started on the editor, it's not yet far enough along to be usable - for practical purposes. The standard library is perhaps 80 percent complete in terms of - functionality, but a lot of operations do not yet have documentation.

- -

The REPL fully supports entering arbitrary expressions, and will evaluate them and print the - results. It remembers recent expressions entered in the current session (if you press the up arrow), - but it can't yet execute effects. You can try out the REPL in a browser at - roc-lang.org/repl - it uses a WebAssembly build of Roc's - compiler, and compiles the code you write to WebAssembly on the fly, which it then executes in - the browser to display the answer. -

- -

The compiler works well enough on a basic level to build things with it, but some error messages - could use significant improvement, and it has a lot of known bugs and missing features. You can - currently use it on macOS (either Intel or Apple Silicon), Linux (only x86-64 machines at the moment), - and Windows (only recently supported; debugging and testing features don't work on it yet, and - there are likely bugs we haven't encountered yet due to lack of battle testing). Support for other - operating systems has not yet been discussed.

- -

The compiler doesn't yet support incremental compilation or hot code loading, and build times vary - based on what machine you're building for.

- -

For example, suppose you run `roc check`, which reports errors it finds (type mismatches, naming - errors, and so on) but doesn't actually build an executable, on a code base that's under a thousand - lines of code. On an M1 MacBook Pro, this typically takes about 10 milliseconds.

- -

In contrast, if you do `roc build` (or `roc run`) on that same machine, it will take closer to 500 - milliseconds instead. Almost all that extra time is spent waiting for LLVM to generate (unoptimized) - machine code, and then for the system linker to assemble an executable from it.

- -

Fortunately, we can eliminate almost all of those extra 490 millisconds of build time by using - Roc's (work in progress) development backend instead of LLVM. This compiles directly from Roc's - internal representation to machine code, like most compilers did before LLVM. (LLVM can optimize - code into running very fast, but even when it performs no optimization at all, LLVM itself takes a lot - longer to run than generating unoptimized machine code directly.)

- -

The LLVM backend is currently the most feature-complete, followed closely by the WebAssembly backend - (which the online REPL uses exclusively, instead of LLVM). The x86 and ARM backends still have a - ways to go, but improving them can be done by anyone with the patience to read some documentation; - we have issues split up for them, and are happy to help new contributors get up and running!

- -

Builds on Linux and Windows also use Roc's surgical linker instead of the system linker, which - runs so fast that linking essentially disappears from the performance profile altogether. The - surgical linker currently only works on Linux and Windows, and it currently supports building - executables but not (yet) dynamic libraries, which is relevant if you're using Roc to create - plugins or want to call Roc functions from existing code bases in other languages. Work has started - on macOS surgical linking, but it isn't usable yet. If you're interested in working on that, - please get in touch on Roc Zulip!

- -

The test runner currently has first-class support for running standard non-effectful tests. - It does not yet have first-class support for effectful tests, property-based tests, snapshot tests, - or "simulation tests" (where effects are replaced by hardcoded values during the test - similar to - "mocking" in other languages), although these are all planned for the future.

- -

The code formatter is nearly feature-complete, although occasionally it will report an error - - usually due to a comment being placed somewhere it doesn't yet know how to handle. Unlike most of - the rest of the compiler, the formatter is one place where the number of known bugs is so small - that fuzzing would be very helpful as a way to surface bugs we don't yet know about. (If you're - interested in working on setting up fuzzing for the formatter, please let us know in - the #contributing channel - on Zulip! Separately, we're also very interested in fuzzing the compiler, even though we already - have a sizable list of known bugs there.)

- -

On the community side, so far the community is a friendly bunch, and we want to keep it that way - as it grows! We hope to do that by encouraging a culture of kindness and helping one another out, - especially by being welcoming towards beginners.

- -

If you'd like to join in, the best place to do that is in our Zulip chat. Feel free to drop by the - introductions - topic - and introduce yourself! -

- -

A Functional Language

- -

Goals

- -

Roc aims to be a purely functional programming language. This means all Roc functions are - pure functions, and all effects are - managed effects - instead of side effects. -

- -

A major motivating reason for this is to facilitate tooling. For example, in the future the goal - is that Roc's test runner won't bother re-running tests whose outcomes could not possibly have - changed (because they were pure functions whose inputs did not change). Tests that contain only - pure functions can be trivially run in parallel, and they will never flake. - Additionally, having the guarantee that the application contains only pure functions can also make - certain debugging tools more reliable, such as time travel and retroactive tracing. -

- -

Roc also takes a novel approach to managed effects. In most programming languages, the standard - library contains both data structures and I/O primitives (e.g. for using the file system or the - network), and then you might decide to use a framework - on top of that standard library.

- -

In Roc, every application is built on a platform. A platform is like a framework except - that it also provides I/O primitives and behind-the-scenes memory management. (Roc's standard - library only contains data structures.) In practice, this means that using Roc feels similar to - using any other programming language where you've chosen to use a framework, except that the - documentation for your I/O primitives comes from the framework instead of the standard library.

- -

This might sound like a minor distinction, but it turns out there are a lot of surprising benefits - to organizing things this way, which would be impossible to achieve without having platforms as a - first-class language concept. The Edges of Cutting-Edge Languages - goes into more detail about some of these benefits. -

- -

Current Progress

- -

Today, platforms as a concept already exist, and there are a few different ones implemented. - You can find them in the examples/ - directory in the source code repository. The platform for building command-line interfaces is the - most fully featured; the others are mostly in the proof-of-concept stage. -

- -

Roc's built-in tooling is not yet far enough along to take advantage of pure functions. For - example, there is a built-in test runner, but it does not yet run tests in parallel or skip - running tests whose outcomes could not possibly have changed. -

- -

Roc is already a purely functional programming language, though, so all of these benefits - are ready to be unlocked as the tooling implementations progress! -

- -

The Roc Programming Language Foundation

- -

We've created a nonprofit to support Roc, you can learn more about it here.

- - - - - diff --git a/www/public/repl/index.html b/www/public/repl/index.html deleted file mode 100644 index a26ddb85cf..0000000000 --- a/www/public/repl/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - REPL - - - - - - - - - -
-

The rockin’ Roc REPL

- -
-
Loading REPL WebAssembly module…please wait!
-
- - -
- - - - diff --git a/www/public/repl/repl.css b/www/public/repl/repl.css deleted file mode 100644 index 6ffe10237b..0000000000 --- a/www/public/repl/repl.css +++ /dev/null @@ -1,173 +0,0 @@ -main { - display: flex; - flex-direction: column; -} - -h1 { - font-family: "Lato"; - font-size: 48px; - line-height: 48px; - padding: 48px 0; - margin: 0; - color: var(--header-link-color); -} - -#source-input-wrapper { - position: relative; - width: 100%; - box-sizing: border-box; -} - -#source-input-wrapper::before { - content: "» "; - position: absolute; - left: 15px; - top: 18px; - line-height: 16px; - height: 16px; - z-index: 2; - font-family: var(--font-mono); - color: var(--cyan); - /* Let clicks pass through to the textarea */ - pointer-events: none; -} - -#source-input { - width: 100%; - font-family: var(--font-mono); - color: var(--code-color); - background-color: var(--code-bg); - display: inline-block; - height: 76px; - padding: 16px; - padding-left: 36px; - border: 1px solid transparent; - margin: 0; - margin-bottom: 2em; - box-sizing: border-box; -} - -#source-input:focus { - border: 1px solid var(--cyan); - box-sizing: border-box; - outline: none; -} - -li { - margin: 8px; -} - -.history { - padding: 1em; - padding-bottom: 0; -} - -#help-text, -#history-text { - white-space: pre-wrap; - -} - -#history-text { - margin-top: 16px; -} - -#loading-message { - text-align: center; - /* approximately match height after loading and printing help message */ - height: 96px; -} - -.history-item { - margin-bottom: 24px; -} - -.history-item .input { - margin: 0; - margin-bottom: 8px; -} - -.history-item .output { - margin: 0; -} - -.panic { - color: red; -} - -.input-line-prefix { - color: var(--cyan); -} - -.color-red { - color: red; -} - -.color-green { - color: var(--green); -} - -.color-yellow { - color: var(--orange); -} - -.color-blue { - color: var(--cyan); -} - -.color-magenta { - color: var(--magenta); -} - -.color-cyan { - color: var(--cyan); -} - -.color-white { - /* Really this isn't white so much as "default text color." For the repl, this should be black - in a light color scheme, and only white in dark mode. The name could be better! */ - color: black; -} - -@media (prefers-color-scheme: dark) { - .color-white { - color: white; - } -} - -.bold { - font-weight: bold; -} - -.underline { - text-decoration: underline; -} - - -/* Mobile-friendly screen width */ -@media only screen and (max-width: 767px) { - h1 { - font-size: 24px !important; - margin: 0; - padding: 16px 0; - text-align: center; - } - - #repl { - margin: 0; - padding: 0; - min-height: calc(100vh - var(--top-bar-height)); - } - - code.history { - flex-grow: 1; - } - - #source-input { - margin: 0 - } - - #loading-message { - margin: 0; - } -} diff --git a/www/public/repl/repl.js b/www/public/repl/repl.js deleted file mode 100644 index cc19037b46..0000000000 --- a/www/public/repl/repl.js +++ /dev/null @@ -1,307 +0,0 @@ -// The only way we can provide values to wasm_bindgen's generated code is to set globals -window.js_create_app = js_create_app; -window.js_run_app = js_run_app; -window.js_get_result_and_memory = js_get_result_and_memory; - -// The only place we use console.error is in wasm_bindgen, where it gets a single string argument. -console.error = function displayErrorInHistoryPanel(string) { - const html = `
${string}
`; - updateHistoryEntry(repl.inputHistoryIndex, false, html); -}; - -import * as roc_repl_wasm from "./roc_repl_wasm.js"; - -// ---------------------------------------------------------------------------- -// REPL state -// ---------------------------------------------------------------------------- - -const repl = { - elemHistory: document.getElementById("history-text"), - elemSourceInput: document.getElementById("source-input"), - - inputQueue: [], - inputHistory: [], - inputHistoryIndex: 0, - inputStash: "", // stash the user input while we're toggling through history with up/down arrows - - textDecoder: new TextDecoder(), - textEncoder: new TextEncoder(), - - compiler: null, - app: null, - - // Temporary storage for the address of the result of running the user's code. - // Used while control flow returns to Rust to allocate space to copy the app's memory buffer. - result_addr: 0, -}; - -// Initialise -repl.elemSourceInput.value = ""; // Some browsers remember the input across refreshes -resetSourceInputHeight(); -repl.elemSourceInput.addEventListener("input", resetSourceInputHeight); -repl.elemSourceInput.addEventListener("keydown", onInputKeydown); -repl.elemSourceInput.addEventListener("keyup", onInputKeyup); -roc_repl_wasm.default("/repl/roc_repl_wasm_bg.wasm").then(async (instance) => { - repl.elemHistory.querySelector("#loading-message").remove(); - repl.elemSourceInput.disabled = false; - repl.elemSourceInput.placeholder = "Type some Roc code and press Enter."; - repl.elemSourceInput.focus(); - repl.compiler = instance; - - // Get help text from the compiler, and display it at top of the history panel - try { - const helpText = await roc_repl_wasm.entrypoint_from_js(":help"); - const helpElem = document.getElementById("help-text"); - helpElem.innerHTML = helpText.trim(); - } catch (e) { - // Print error for Roc devs. Don't use console.error, we overrode that above to display on the page! - console.warn(e); - } -}); - -// ---------------------------------------------------------------------------- -// Handle inputs -// ---------------------------------------------------------------------------- - -function resetSourceInputHeight() { - repl.elemSourceInput.style.height = repl.elemSourceInput.scrollHeight + 2 + "px"; // +2 for the border -} - -function onInputKeydown(event) { - const ENTER = 13; - - const { keyCode } = event; - - if (keyCode === ENTER) { - if (!event.shiftKey && !event.ctrlKey && !event.altKey) { - // Don't advance the caret to the next line - event.preventDefault(); - - const inputText = repl.elemSourceInput.value.trim(); - - repl.elemSourceInput.value = ""; - repl.elemSourceInput.style.height = ""; - - repl.inputQueue.push(inputText); - if (repl.inputQueue.length === 1) { - processInputQueue(); - } - } - } -} - -function onInputKeyup(event) { - const UP = 38; - const DOWN = 40; - - const { keyCode } = event; - - const el = repl.elemSourceInput; - - switch (keyCode) { - case UP: - if (repl.inputHistory.length === 0) { - return; - } - if (repl.inputHistoryIndex == repl.inputHistory.length - 1) { - repl.inputStash = el.value; - } - setInput(repl.inputHistory[repl.inputHistoryIndex]); - - if (repl.inputHistoryIndex > 0) { - repl.inputHistoryIndex--; - } - break; - - case DOWN: - if (repl.inputHistory.length === 0) { - return; - } - if (repl.inputHistoryIndex === repl.inputHistory.length - 1) { - setInput(repl.inputStash); - } else { - repl.inputHistoryIndex++; - setInput(repl.inputHistory[repl.inputHistoryIndex]); - } - break; - - default: - break; - } -} - -function setInput(value) { - const el = repl.elemSourceInput; - el.value = value; - el.selectionStart = value.length; - el.selectionEnd = value.length; -} - -// Use a queue just in case we somehow get inputs very fast -// We want the REPL to only process one at a time, since we're using some global state. -// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? -async function processInputQueue() { - while (repl.inputQueue.length) { - const inputText = repl.inputQueue[0]; - repl.inputHistoryIndex = createHistoryEntry(inputText); - repl.inputStash = ""; - - let outputText = ""; - let ok = true; - if (inputText) { - try { - outputText = await roc_repl_wasm.entrypoint_from_js(inputText); - } catch (e) { - outputText = `${e}`; - ok = false; - } - } - - updateHistoryEntry(repl.inputHistoryIndex, ok, outputText); - repl.inputQueue.shift(); - } -} - -// ---------------------------------------------------------------------------- -// Callbacks to JS from Rust -// ---------------------------------------------------------------------------- - -var ROC_PANIC_INFO = null; - -function send_panic_msg_to_js(rocstr_ptr, panic_tag) { - const { memory } = repl.app.exports; - - const rocStrBytes = new Int8Array(memory.buffer, rocstr_ptr, 12); - const finalByte = rocStrBytes[11] - - let stringBytes = ""; - if (finalByte < 0) { - // small string - - // bitwise ops on negative JS numbers are weird. This clears the bit that we - // use to indicate a small string. In rust it's `finalByte as u8 ^ 0b1000_0000` - const length = finalByte + 128; - stringBytes = new Uint8Array(memory.buffer, rocstr_ptr, length); - } else { - // big string - const rocStrWords = new Uint32Array(memory.buffer, rocstr_ptr, 3); - const [ptr, len, _cap] = rocStrWords; - - const SEAMLESS_SLICE_BIT = 1 << 31; - const length = len & (~SEAMLESS_SLICE_BIT); - - stringBytes = new Uint8Array(memory.buffer, ptr, length); - } - - const decodedString = repl.textDecoder.decode(stringBytes); - - ROC_PANIC_INFO = { - msg: decodedString, - panic_tag: panic_tag, - }; -} - -// Load Wasm code into the browser's virtual machine, so we can run it later. -// This operation is async, so we call it before entering any code shared -// with the command-line REPL, which is sync. -async function js_create_app(wasm_module_bytes) { - const { instance } = await WebAssembly.instantiate(wasm_module_bytes, { - env: { - send_panic_msg_to_js: send_panic_msg_to_js, - } - }); - - // Keep the instance alive so we can run it later from shared REPL code - repl.app = instance; -} - -// Call the `main` function of the user app, via the `wrapper` function. -function js_run_app() { - const { wrapper, memory } = repl.app.exports; - - // Run the user code, and remember the result address - // We'll pass it to Rust in the next callback - try { - repl.result_addr = wrapper(); - } catch (e) { - // an exception could be that roc_panic was invoked, - // or some other crash (likely a compiler bug) - if (ROC_PANIC_INFO === null) { - throw e; - } else { - // when roc_panic set an error message, display it - const { msg, panic_tag } = ROC_PANIC_INFO; - ROC_PANIC_INFO = null; - - console.error(format_roc_panic_message(msg, panic_tag)); - } - } - - // Tell Rust how much space to reserve for its copy of the app's memory buffer. - // We couldn't know that size until we actually ran the app. - return memory.buffer.byteLength; -} - -function format_roc_panic_message(msg, panic_tag) { - switch (panic_tag) { - case 0: { - return `Roc failed with message: "${msg}"`; - } - case 1: { - return `User crash with message: "${msg}"`; - } - default: { - return `Got an invalid panic tag: "${panic_tag}"`; - } - } -} - -// After Rust has allocated space for the app's memory buffer, -// we copy it, and return the result address too -function js_get_result_and_memory(buffer_alloc_addr) { - const appMemory = new Uint8Array(repl.app.exports.memory.buffer); - const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); - compilerMemory.set(appMemory, buffer_alloc_addr); - return repl.result_addr; -} - -// ---------------------------------------------------------------------------- -// Rendering -// ---------------------------------------------------------------------------- - -function createHistoryEntry(inputText) { - const historyIndex = repl.inputHistory.length; - repl.inputHistory.push(inputText); - - const firstLinePrefix = '» '; - const otherLinePrefix = '
'; - const inputLines = inputText.split("\n"); - if (inputLines[inputLines.length - 1] === "") { - inputLines.pop(); - } - const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix); - - const inputElem = document.createElement("div"); - inputElem.innerHTML = inputWithPrefixes; - inputElem.classList.add("input"); - - const historyItem = document.createElement("div"); - historyItem.appendChild(inputElem); - historyItem.classList.add("history-item"); - - repl.elemHistory.appendChild(historyItem); - - return historyIndex; -} - -function updateHistoryEntry(index, ok, outputText) { - const outputElem = document.createElement("div"); - outputElem.innerHTML = outputText; - outputElem.classList.add("output", ok ? "output-ok" : "output-error"); - - const historyItem = repl.elemHistory.children[index]; - historyItem.appendChild(outputElem); - - // Scroll the page to the bottom so you can see the most recent output. - window.scrollTo(0, document.body.scrollHeight); -} diff --git a/www/public/site.css b/www/public/site.css index ae10354c4e..29767dc031 100644 --- a/www/public/site.css +++ b/www/public/site.css @@ -1,42 +1,50 @@ :root { /* WCAG AAA Compliant colors */ --code-bg: #f4f8f9; + --gray-bg: #f4f8f9; --gray: #717171; - --orange: #BF5000; - --green: #0B8400; - --cyan: #067C94; + --orange: #bf5000; + --green: #0b8400; + --light-cyan: #8af4e6; + --dark-cyan: #4eefd9; --blue: #05006d; - --magenta: #a20031; + --violet: #7c38f5; + --violet-bg: #ece2fd; + --magenta: #ff32cf; - --body-max-width: 900px; - --text-color: #121212; - --top-bar-bg: #222; - --top-bar-fg: #eee; - --top-bar-logo-hover: #8055E4; - --top-bar-height: 50px; - --header-link-color: #107F79; + --primary-1: #9b6bf2; + --primary-2: #7c38f5; + --highlight: #1bd6bd; + --code-color: white; + --link-color: var(--primary-2); + --code-link-color: var(--primary-2); + --text-color: #000; + --heading-color: #333; + --text-hover-color: var(--primary-2); + --body-bg-color: #ffffff; + --border-color: #717171; + --faded-color: #4c4c4c; + --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, + sans-serif; + --font-mono: "Source Code Pro", SFMono-Regular, Consolas, "Liberation Mono", + Menlo, Courier, monospace; + --top-header-height: 67px; + --sidebar-width: 280px; + + --font-size-normal: 18px; + --body-max-width: 1024px; + --dark-code-bg: #202746; + + /* Tutorial */ + --header-link-color: #1bbcb3; --header-link-hover: #222; - --link-color: #7546e2; - --h1-color: #8055E4; - --repl-prompt: #0064ff; - --body-bg: #fff; - - --code-color: #303030; - --toc-background: var(--code-bg); - --toc-border: var(--gray); - --toc-search-bg: #fcfcfc; - --toc-search-border: var(--gray); - --font-mono: "Source Code Pro", monospace; + --h1-color: #8055e4; } html { line-height: 1.5rem; - background: var(--body-bg); + background: var(--body-bg-color); color: var(--text-color); -} - -html, -#tutorial-toc-toggle-label { font-family: "Lato", sans-serif; } @@ -44,14 +52,132 @@ html, body { margin: 0; padding: 0; + width: 100%; height: 100%; + box-sizing: border-box; + overflow-x: hidden; /* This shouldn't be necessary, but without it, mobile has a right gutter. */ +} + +p { + margin-top: 0; +} + +p, +li { + max-width: 720px; } footer { - max-width: 1024px; + width: 100%; + color: var(--text-color); + text-align: center; + font-size: var(--font-size-normal); + padding: 20px; + box-sizing: border-box; + margin-top: 24px; +} + +#footer { + max-width: var(--body-max-width); margin: 0 auto; - font-size: 14px; - padding: 16px; +} + +hr { + color: var(--primary-1); + margin-bottom: 1rem; +} + +.svg-text { + fill: #000; +} + +.logo-dark { + fill: #612bde; +} + +.logo-light { + fill: #8257e5; +} + +.btn-small { + white-space: nowrap; + background: #7c38f5; + border: 4px solid #9b6bf2; + color: #fff !important; + cursor: pointer; + text-decoration: none !important; + padding: 12px; +} + +.btn-small .roc-logo { + fill: #fff; + position: relative; + left: -4px; + top: 11px; +} + +.btn-small:hover { + background: #9b6bf2; + border-color: #7c38f5; +} + +#individual-sponsors { + list-style-type: none; + padding: 24px 40px; + max-width: 720px; +} + +#individual-sponsors li { + display: inline; + white-space: nowrap; + margin: 0.2rem; +} + +#individual-sponsors li::after { + content: ","; + white-space: pre; /* Preserve the space after the comma */ +} + +#individual-sponsors li:last-child::after { + content: ""; /* No comma after the last one */ +} + +#sponsor-logos { + padding: 24px 36px; + padding-bottom: 36px; + min-width: 308px; /* Widest logo plus padding - Firefox on Android needs this */ + max-width: none !important; +} + +#sponsor-logos svg { + height: 54px; + margin-right: 72px; +} + +#sponsor-logos .logo-rwx { + position: relative; + top: 6px; +} + +#sponsor-logos .logo-tweede-golf { + position: relative; + top: 14px; + height: 4.5rem; +} + +#sponsor-logos + p { + margin-bottom: 3em; +} + +/* Used for e.g. displaying the instruction "Click" on desktop and "Touch" on mobile. + * When we think we're on mobile (based on max-width), we can switch the instruction. +*/ +.desktop { + display: inline; +} + +.mobile { + display: none; } section p:last-child { @@ -71,64 +197,145 @@ a:hover { text-decoration: underline; } +a:hover code { + text-decoration: inherit; +} + li { margin-bottom: 0.5rem; } +h1, +h2, +h3, +h4 { + font-weight: bold; +} + +h1 { + font-size: 5rem; +} + +h2 { + display: inline-block; + font-size: 2.5rem; + line-height: 5rem; + border-bottom: 4px solid var(--dark-cyan); + padding: 0; + margin: 0; + margin-bottom: 2rem; + color: var(--heading-color); +} + +.article-layout main, +.article-layout pre { + max-width: 720px; +} + +.article-layout p, +.article-layout li, +.article-layout pre { + font-size: 20px; +} + +#homepage-main h2 { + margin-top: 60px; /* On the homepage, these need to be spaced out more. */ +} + +#homepage-main #nav-home-link { + visibility: hidden; +} + +h2 a, +h3 a { + color: var(--heading-color); +} + +h2:hover a { + color: var(--link-color); + text-decoration: none; +} + +h3 { + font-size: 1.5rem; +} + +#top-bar, +#top-bar nav { + background-color: var(--gray-bg); +} + #top-bar { - background-color: var(--top-bar-bg); + box-sizing: border-box; width: 100%; - height: var(--top-bar-height); } #top-bar nav { - max-width: 1024px; + max-width: var(--body-max-width); margin: 0 auto; display: flex; justify-content: space-between; + padding-right: 9px; } #nav-home-link { - display: flex; + display: inline-block; color: var(--top-bar-fg); font-size: 1.8rem; - padding: 10px; + padding: 4px; } -#top-bar-links { - display: flex; +#tutorial-toc-toggle:checked + #tutorial-toc { + display: block; +} + +.home-link-text { + padding: 8px; + font-size: 24px; + position: relative; + top: -0.6rem; +} + +.home-examples-title { + margin-bottom: 4px; } #top-bar-links a, #top-bar-links label { box-sizing: border-box; color: var(--top-bar-fg); - font-size: 1.1rem; - display: block; - height: 100%; + display: inline-block; padding: 12px 16px; margin: 0 2px; } main { max-width: var(--body-max-width); - margin: 36px auto; - padding: 0 12px; + margin: auto; + padding: 12px; + box-sizing: border-box; +} + +.welcome-to-roc { + white-space: nowrap; + overflow-x: hidden; + padding-right: 60px; + margin-bottom: 12px; } code, samp { font-family: var(--font-mono); - color: var(--code-color); - background-color: var(--code-bg); + color: var(--text-color); + background-color: var(--gray-bg); display: inline-block; + padding: 5px; } p code, td code, li code, -th code, -samp { +th code { padding: 0 8px; } @@ -148,25 +355,87 @@ a:visited code { pre { position: relative; margin-bottom: 16px; - padding: 8px 16px; + padding: 0 0.35rem; box-sizing: border-box; - background-color: var(--code-bg); + background-color: var(--gray-bg); overflow-x: hidden; word-wrap: normal; - font-size: 1.2rem; + font-size: var(--font-size-normal); line-height: 1.76em; white-space: pre; + background-color: var(--dark-code-bg); } -pre>samp { +pre > samp, +pre > code { overflow-x: auto; display: block; + background-color: var(--dark-code-bg); + color: var(--code-color); } -.repl-prompt:before { - /* Add this using CSS so it isn't selectable, which would be annoying when trying to copy/paste! */ - color: var(--repl-prompt); - content: "» "; +/* The repl won't work at all if you have JS disabled. */ +.no-js #try-roc { + display: none !important; +} + +#homepage-repl-container { + display: flex; + flex-direction: row-reverse; +} + +#homepage-repl-container #repl-description { + padding: 0 30px; + margin-top: 2px; + flex: 1; +} + +#homepage-repl-container #repl-description a { + color: inherit; + text-decoration: underline; +} + +#homepage-repl-container #repl-description a:hover { + color: var(--primary-1); +} + +#homepage-repl-container #repl { + flex: 1; + border: 2px solid #444; + font-size: var(--font-size-normal); + min-height: 0; /* On /repl on mobile, this expands to fill the screen, which we don't want */ + margin-right: 6px; + max-width: 50%; +} + +#homepage-repl-container #repl, +#homepage-repl-container #repl code, +#homepage-repl-container #source-input { + color: white; + background-color: var(--dark-code-bg); +} + +#homepage-repl-container #source-input { + margin-bottom: 0; + margin-top: 6px; + font-size: var(--font-size-normal); + height: 57px; +} + +#homepage-repl-container p { + position: relative; /* Needed for the repl arrow's position: absolute */ +} + +#homepage-repl-container #repl-arrow { + cursor: default; + font-weight: bold; + font-size: 48px; + position: absolute; + top: -9px; + left: -79px; + text-shadow: 1px 1px 1px #444; + z-index: 3; + fill: var(--primary-1); } .repl-err { @@ -179,6 +448,7 @@ table { border-collapse: collapse; overflow-x: auto; border: 2px solid #f0f0f0; + margin-bottom: 1rem; } thead { @@ -206,40 +476,785 @@ td:last-child { border-right: none; } -#integer-types { - width: 100%; +p, +aside, +li { + font-size: var(--font-size-normal); + line-height: 1.85rem; } -#integer-types th:first-of-type, -#integer-types td:first-of-type { - text-align: right; +/* Homepage */ +#homepage-intro-outer { + margin: 60px auto; + text-align: center; } -#integer-types td:first-of-type code { - background-color: inherit; -} - -#integer-types th:last-of-type, -#integer-types td:last-of-type { +#homepage-intro-box { + position: relative; + display: inline-block; text-align: left; } -/* Tutorial Specific */ - -#tutorial-start { - display: flex; - flex-direction: row-reverse; +#homepage-h1 { + color: #222; + text-shadow: none; + font-family: inherit; + font-size: 64px; + padding: 0; + padding-top: 60px; + position: relative; + left: -5px; } -#tutorial-intro { +#homepage-logo { + height: 176px; + width: auto; + position: absolute; + left: -200px; + top: -100px; +} + +#first-code-sample { + margin-top: 60px; + line-height: 1.85em; + color: #fcf9fd; +} + +#first-code-sample .kw, +#first-code-sample .punctuation, +.interactive-example .kw, +.interactive-example .punctuation { + color: #9c7cea; +} + +#first-code-sample, +#first-code-sample .code-snippet { + background-color: var(--dark-code-bg); +} + +#homepage-tagline { + font-size: 20px; +} + +.nowrap { + white-space: nowrap; +} + +/* Mobile-friendly screen width */ +@media only screen and (max-width: 1023px) { + :root { + --font-size-normal: 16px; + --body-max-width: none; + } + + #tutorial-main main, + #tutorial-toc-toggle-label, + #close-tutorial-toc { + display: block !important; + } + + #tutorial-toc { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow-y: auto; + margin: 0 !important; + padding-right: 120px; + border: 0; + } + + #homepage-logo { + /* The bird runs off the screen unless we shrink it */ + height: 80px; + width: auto; + position: absolute; + left: 227px; + top: -28px; + } + + #homepage-main #nav-home-link { + display: none; + } + + #sponsor-logos { + padding: 4px; + } + + .home-examples-column { + padding-right: 0 !important; + border-right: none !important; + } + + /* Used for e.g. displaying the instruction "Click" on desktop and "Touch" on mobile. */ + .desktop { + display: none; + } + + .mobile { + display: inline; + } + + h2 { + margin-top: 48px; + padding: 12px 0; + } + + .home-link-text { + display: none; /* Don't show "Roc" in the header next to the logo, to save space */ + } + + h1 code, + h2 code, + h3 code, + h4 code, + h5 code { + font-size: inherit; + } + + code { + white-space: normal; + } + + /* Homepage */ + + #homepage-intro-box { + margin: 30px auto; + } + + #homepage-tagline { + margin-top: 0; + } + + #homepage-h1 { + /* TODO remove !important from repl.css (increasing specificity instead), and then this one too. */ + font-size: 48px !important; + padding: 0; + margin: 0; + text-align: left; + } + + #first-code-sample { + margin: 64px auto; + margin-bottom: 0; + } + + #homepage-tagline { + font-size: 16px; + } + + .home-goals-container, + .home-examples-container { + /* It's unclear why this !important is necessary, since its specificity + should theoretically be higher than what it's overriding. In practice, + at least in Chrome, removing the !important breaks this. */ + display: grid !important; + grid-template-columns: 1fr; + } + + h1, + h2, + h3, + h4, + h5, + h6, + p, + code { + word-break: break-word !important; + } + + h1, + h2, + h3, + h4, + h5 { + line-height: 1.2em !important; + font-size: 2rem !important; + width: auto; + } + + #top-bar-links { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr 1fr; /* Three equal-width columns */ + grid-template-rows: auto auto; /* Two rows */ + } + + /* Left-align the first link in each row, right-align the last one, and center the middle one. */ + #top-bar-links > :nth-child(3n + 1) { + text-align: left; + } + + #top-bar-links > :nth-child(3n + 2) { + text-align: center; + } + + #top-bar-links > :nth-child(3n + 3) { + text-align: right; + } + + #top-bar-links a, + #top-bar-links label { + font-size: 1.2rem; + padding: 12px 0.5rem; + margin: 0; + } + + #homepage-repl-container #repl { + max-width: none; + } +} + +/* iPhone SE and similar */ +@media only screen and (max-width: 320px) { + #homepage-logo { + /* The bird runs off the screen unless we shrink it */ + left: 188px; + top: -30px; + } + + :root { + --font-size-normal: 14px; + --body-max-width: 320px; + } +} + +@font-face { + font-family: "Permanent Marker"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2") + format("woff2"), + url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff") + format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2") + format("woff2"), + url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff") + format("woff"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff2") + format("woff2"), + url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff") format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2") + format("woff2"), + url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff") + format("woff"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2") + format("woff2"), + url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff") + format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} + +@media (prefers-color-scheme: dark) { + :root { + /* WCAG AAA Compliant colors */ + --code-bg: #202746; + --gray-bg: #202746; + --gray: #b6b6b6; + --orange: #fd6e08; + --green: #8ecc88; + --cyan: #12c9be; + --blue: #b1afdf; + --violet-bg: #332944; + --magenta: #f39bac; + + --primary-1: #9c7cea; + --primary-2: #1bd6bd; + --text-color: #ccc; + --body-bg-color: #151517; + --border-color: var(--gray); + --code-color: #eeeeee; + --logo-solid: #8f8f8f; + --faded-color: #bbbbbb; + --gray: #6e6e6e; + --heading-color: #eee; + + /* Tutorial */ + --header-link-color: #9c7cea; + --header-link-hover: #ddd; + --h1-color: #1bc6bd; + } + + .logo-dark { + fill: #6c3bdc; + } + + .logo-light { + fill: #8a66de; + } + + .svg-text { + fill: #fff; + } + + #homepage-h1 { + color: #fcf9fd; + } + + h3 { + color: #fff; + } + + h1, + h2, + h3, + h4, + h5 { + text-shadow: none; + } + + html { + scrollbar-color: #444444 #2f2f2f; + } + + table, + tr, + th, + td { + border-color: var(--gray); + } + + #first-code-sample, + #homepage-repl-container #repl { + border: 1px solid #ddd; + } + + .home-goals-content:hover { + background-color: #481870 !important; + } +} + +/* Comments `#` and Documentation comments `##` */ +samp .comment, +code .comment { + color: #ccc; +} + +/* Number, String, Tag literals */ +samp .storage.type, +code .storage.type, +samp .string, +code .string, +samp .string.begin, +code .string.begin, +samp .string.end, +code .string.end, +samp .constant, +code .constant, +samp .literal, +code .literal { + color: var(--dark-cyan); +} + +/* Keywords and punctuation */ +samp .keyword, +code .keyword, +samp .punctuation.section, +code .punctuation.section, +samp .punctuation.separator, +code .punctuation.separator, +samp .punctuation.terminator, +code .punctuation.terminator, +samp .kw, +code .kw { + color: var(--primary-1); +} + +/* Operators */ +samp .op, +code .op, +samp .keyword.operator, +code .keyword.operator, +samp .colon, +code .colon { + color: var(--primary-1); +} + +/* Delimieters */ +samp .delimeter, +code .delimeter { + color: var(--primary-1); +} + +/* Variables modules and field names */ +samp .function, +code .function, +samp .meta.group, +code .meta.group, +samp .meta.block, +code .meta.block, +samp .lowerident, +code .lowerident { + color: white; +} + +samp .error, +code .error { + color: hsl(0, 96%, 67%); +} + +/* Types, Tags, and Modules */ +samp .type, +code .type, +samp .meta.path, +code .meta.path, +samp .upperident, +code .upperident { + color: var(--dark-cyan); +} + +samp .dim, +code .dim { + opacity: 0.55; +} + +.button-container { + position: absolute; + top: 0; + right: 0; +} + +.copy-button { + background: var(--dark-code-bg); + border: 1px solid var(--dark-cyan); + color: var(--dark-cyan); + display: inline-block; + cursor: pointer; + margin: 8px; +} + +.copy-button:hover { + border-color: var(--code-color); + color: var(--code-color); +} + +.roc-logo { + width: 40px; + height: 40px; + margin: 0 auto; + fill: var(--primary-1); + text-shadow: 1px 1px 1px #010101; + position: relative; + top: -2px; +} + +/* HOME GOALS */ + +.home-goals-container { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 45px; + width: 100%; +} + +.home-goals-column { + display: flex; + flex-direction: column; flex: 1; + width: 100%; +} + +.home-goals-content { + flex: 1; + display: flex; + flex-direction: column; + padding: 20px; + border: 4px solid var(--light-cyan); + color: inherit; + cursor: pointer; +} + +.home-goals-content:hover { + text-decoration: none; + cursor: pointer; + background-color: var(--light-cyan); +} + +.home-goals-learn-more { + text-decoration: underline; +} + +.home-examples-container { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 24px; + width: 100%; +} + +.home-examples-column { + display: flex; + flex-direction: column; + flex: 1; + width: 100%; +} + +.home-examples-column:not(:last-of-type) { + padding-right: 24px; + border-right: 2px solid var(--primary-1); +} + +/* Wider screens */ +@media only screen and (min-width: 768px) { + .home-goals-column { + margin-bottom: 0; + } + + .home-goals-column:last-child { + margin-right: 0; + } +} + +.home-goals-learn-more { + margin-top: auto; + white-space: nowrap; +} + +.home-goals-title { + padding: 0; + font-weight: bold; + margin-bottom: 10px; + font-size: 32px; + border-bottom: none; + font-family: inherit; + text-transform: lowercase; + padding-bottom: 42px; + padding-top: 2px; + font-style: italic; + letter-spacing: 1px; + word-spacing: 3px; + margin: 0; + color: var(--text-color); +} + +.home-goals-description { + line-height: 1.5; + margin-bottom: 2em; +} + +/* Interactive example on homepage */ + +.interactive-example { + font-size: var(--font-size-normal); +} + +.interactive-example, +.interactive-example samp { + background-color: #202746; + color: white; +} + +.interactive-example samp { + position: relative; + display: block; + width: 100%; + height: 580px; + padding-right: 300px; + cursor: default; +} + +.interactive-example label:hover, +.interactive-radio:checked + label { + background-color: #000; + cursor: pointer; +} + +.interactive-desc { + display: none; + position: absolute; + top: 0; + right: 300px; + width: 300px; + background-color: #ede6ff; + border: 1px solid black; + color: black; + padding: 0 16px; + padding-top: 12px; + margin-top: 9px; + cursor: text; + white-space: normal; + font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial; +} + +.interactive-desc a { + color: #7c38f5; +} + +.interactive-radio { + display: none; +} + +.interactive-radio:checked + label + .interactive-desc { + display: block; +} + +.close-desc { + display: none; + position: absolute; + height: 40px; + width: 40px; + font-size: 24px; + top: -12px; + right: -12px; + color: #fff; + background: #9b6bf2; + border: 2px solid #7c38f5; + border-radius: 100%; + z-index: 4; +} + +.close-desc:hover { + color: #222; + background: var(--light-cyan); + border-color: var(--light-cyan); +} + +/* Tutorial */ + +#tutorial-main main { + display: flex; + flex-direction: row-reverse; + max-width: 1024px; +} + +#tutorial-main h1, +#tutorial-main h2, +#tutorial-main h3, +#tutorial-main h4, +#tutorial-main h5 { + font-family: "Permanent Marker"; + line-height: 1rem; + margin-top: 1.75rem; + margin-bottom: 0; + border: none; +} + +#tutorial-main h1 a, +#tutorial-main h2 a, +#tutorial-main h3 a, +#tutorial-main h4 a, +#tutorial-main h5 a { + color: var(--header-link-color); +} + +#tutorial-main h1 a:hover, +#tutorial-main h2 a:hover, +#tutorial-main h3 a:hover, +#tutorial-main h4 a:hover, +#tutorial-main h5 a:hover { + text-decoration: none; + color: var(--header-link-hover); +} + +#tutorial-main h1 { + font-size: 7rem; + line-height: 7rem; + color: var(--h1-color); + margin-top: 24px; + margin-bottom: 1.75rem; + text-shadow: 1px 1px 1px #010101; +} + +#tutorial-main h2 { + font-size: 4rem; + line-height: 4rem; + text-shadow: 1px 1px 1px #010101; + padding: 0.8rem 0; + margin-top: 2.5rem; + width: 60rem; /* Without this, "Building an application" wraps and looks awkward */ +} + +#tutorial-main h3 { + font-size: 3rem; + line-height: 3rem; + text-shadow: 1px 1px 1px #010101; + margin-bottom: 0.5rem; +} + +#tutorial-main h4 { + font-size: 2rem; + text-shadow: 1px 1px 1px #010101; +} + +#tutorial-body, +#tutorial-body pre { + max-width: 646px; } #tutorial-toc { - margin-top: 18px; + position: relative; + background-color: var(--gray-bg); + flex: 0 0 auto; /* Take up as much space as it needs */ + margin-top: 30px; background: var(--code-bg); padding: 12px 24px; margin-left: 64px; + align-self: flex-start; /* Aligns to the start, not stretching in height */ + z-index: 2; +} + +#tutorial-toc > ul { + display: flex; + flex-wrap: wrap; + list-style-type: none; + padding: 16px 16px; + margin: 0px; +} + +#tutorial-toc > ul > li { + flex: 1 1 50%; /* Adjust the percentage to control how many items per row */ + margin-bottom: 0; /* Reset the margin as they are now side by side */ + white-space: nowrap; + overflow: hidden; /* Ensures content doesn't overflow its container */ + text-overflow: ellipsis; /* Adds an ellipsis if the content overflows */ } #tutorial-toc code { @@ -284,433 +1299,210 @@ td:last-child { /* This may be overridden on mobile-friendly screen widths */ } +#tutorial-toc-toggle:hover, +#tutorial-toc-toggle-label:hover, +#close-tutorial-toc:hover { + text-decoration: underline; + cursor: pointer; +} + #tutorial-toc-toggle, #tutorial-toc-toggle-label { - font-size: 1.1rem; + font-size: 1.2rem; float: right; + padding: 0 1rem; + font-family: Lato; + font-weight: normal; } #close-tutorial-toc { position: absolute; - top: 20px; - right: 8px; - font-size: 18px; - padding: 12px 24px; -} - -p, -aside, -li, -footer { + top: 1rem; + right: 1rem; font-size: 1.2rem; - line-height: 1.85rem; + padding: 12px 24px; + font-weight: bold; } -/* Mobile-friendly screen width */ -@media only screen and (max-device-width: 480px) and (orientation: portrait) { - - p, - aside, - li, - footer, - code, - samp, - .code-snippet { - font-size: 16px; - } - - h1 code, - h2 code, - h3 code, - h4 code, - h5 code { - font-size: inherit; - } - - code { - white-space: normal; - } - - #tutorial-toc-toggle-label, - #close-tutorial-toc { - display: block; - } - - #tutorial-toc-toggle:checked+#tutorial-toc { - display: block; - } - - #tutorial-toc { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow-y: auto; - margin: 0; - padding-right: 120px; - border: 0; - } - - h1, - h2, - h3, - h4, - h5, - h6, - p, - code { - word-break: break-word !important; - } - - h1, - h2, - h3, - h4, - h5 { - line-height: 1.2em !important; - font-size: 2rem !important; - } - - #top-bar-links a, - #top-bar-links label { - padding: 12px 8px; - margin: 0; +/* for larger screens */ +@media only screen and (min-width: 768px) { + #tutorial-toc > ul > li { + flex: 1 1 33%; /* Adjust the percentage to control how many items per row */ } } -/* Used on on the different-names page. */ +/* REPL */ -th, -td { - text-align: left; - padding-right: 24px; +#repl { + position: relative; + display: flex; + flex-direction: column; + font-size: 18px; } -#different-names-body a, -#different-names-body li { - font-family: monospace; - font-size: 16px; +#homepage-repl-container #repl-prompt, #homepage-repl-container .input-line-prefix { + top: 1.25rem; + color: var(--light-cyan); } -#different-names-body li { - display: inline; +.input-line-prefix, #repl-prompt { + color: var(--cyan); + color: var(--primary-2); } -#different-names-body li:not(:last-of-type)::after { - /* This is injected via CSS for accessibility, so to a screen reader it's a normal
  • */ - content: ","; +#repl-prompt { + position: relative; + left: 16px; + top: 0.95rem; + font-size: 1.25rem; + height: 0; + z-index: 2; + font-family: var(--font-mono); + /* Let clicks pass through to the textarea */ + pointer-events: none; + user-select: none; } -#different-names-body ul { - padding: 0; - list-style-type: none; +#homepage-repl-container #source-input { + color: var(--code-color); } -h1, -h2, -h3, -h4, -h5 { - font-family: "Permanent Marker"; - line-height: 1rem; - margin-top: 1.75rem; - margin-bottom: 0; -} - -#tutorial-toc-toggle-label, -#close-tutorial-toc { - color: var(--header-link-color); -} - -#tutorial-toc-toggle-label:hover, -#close-tutorial-toc:hover { - color: var(--header-link-hover); - cursor: pointer; -} - -h1 a, -h2 a, -h3 a, -h4 a, -h5 a { - color: var(--header-link-color); -} - -h1 a:hover, -h2 a:hover, -h3 a:hover, -h4 a:hover, -h5 a:hover { - text-decoration: none; - color: var(--header-link-hover); -} - -h1 code, -h2 code, -h3 code, -h4 code, -h5 code { +#source-input { + width: 100%; + font-family: var(--font-mono); + background-color: var(--code-bg); + display: inline-block; + height: 78px; + padding: 16px; + padding-left: 36px; + border: 1px solid transparent; + margin: 0; + margin-bottom: 2em; + box-sizing: border-box; + font-size: 18px; + resize: none; color: inherit; - background-color: inherit; - padding: 0; +} + +#source-input:focus { + outline: 2px solid var(--primary-1); + box-sizing: border-box; +} + +.history { + padding: 1em; + padding-bottom: 0; + flex: 1; +} + +#help-text, +#history-text { + white-space: pre-wrap; +} + +#history-text { + margin-top: 16px; + min-height: 26px; +} + +#loading-message { + text-align: center; + /* approximately match height after loading and printing help message */ + height: 96px; +} + +.history-item { + margin-bottom: 24px; + overflow-x: hidden; +} + +.history-item .input { + margin: 0; + margin-bottom: 8px; +} + +.history-item .output { margin: 0; } -h1 { - font-size: 7rem; - line-height: 7rem; - color: var(--h1-color); - margin-top: 0; - margin-bottom: 1.75rem; - text-shadow: 1px 1px 1px #010101; +.panic { + color: #ff6666; } -h2 { - font-size: 4rem; - line-height: 4rem; - text-shadow: 1px 1px 1px #010101; +.color-red { + color: #ff6666; } -h3 { - font-size: 3rem; - line-height: 3rem; - text-shadow: 1px 1px 1px #010101; -} - -h4 { - font-size: 2rem; - text-shadow: 1px 1px 1px #010101; -} - -@font-face { - font-family: "Permanent Marker"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2") format("woff2"), - url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff") format("woff"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - -/* latin-ext */ -@font-face { - font-family: "Merriweather"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff2") format("woff2"), - url("/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff") format("woff"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, - U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -/* latin */ -@font-face { - font-family: "Merriweather"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff2") format("woff2"), - url("/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff") format("woff"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - -/* latin-ext */ -@font-face { - font-family: "Lato"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2") format("woff2"), - url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff") format("woff"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, - U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -/* latin */ -@font-face { - font-family: "Lato"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff2") format("woff2"), - url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff") format("woff"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - -/* latin-ext */ -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2") format("woff2"), - url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff") format("woff"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, - U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -/* latin */ -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2") format("woff2"), - url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff") format("woff"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - -@media (prefers-color-scheme: dark) { - :root { - /* WCAG AAA Compliant colors */ - --code-bg: #202746; - --gray: #b6b6b6; - --orange: #fd6e08; - --green: #8ecc88; - --cyan: #12c9be; - --blue: #b1afdf; - --magenta: #f39bac; - - --text-color: #cdcdcd; - --top-bar-bg: #2a2a2a; - --header-link-color: #9C7CEA; - --header-link-hover: #ddd; - --h1-color: #1bc6bd; - --link-color: #1bc6bd; - --repl-prompt: #1bc6bd; - --body-bg: #0e0e0f; - --code-snippet-border: #444; - --code-color: #cdcdcd; - --toc-background: var(--code-bg); - --toc-border: var(--code-snippet-border); - --toc-search-bg: #333; - --toc-search-border: var(--gray); - } - - h1, - h2, - h3, - h4, - h5 { - text-shadow: none; - } - - html { - scrollbar-color: #444444 #2f2f2f; - } - - table, - tr, - th, - td { - border-color: var(--gray); - } -} - -/* Comments `#` and Documentation comments `##` */ -samp .comment, -code .comment { +.color-green { color: var(--green); } -/* Number, String, Tag literals */ -samp .storage.type, -code .storage.type, -samp .string, -code .string, -samp .string.begin, -code .string.begin, -samp .string.end, -code .string.end, -samp .constant, -code .constant, -samp .literal, -code .literal { - color: var(--cyan); -} - -/* Keywords and punctuation */ -samp .keyword, -code .keyword, -samp .punctuation.section, -code .punctuation.section, -samp .punctuation.separator, -code .punctuation.separator, -samp .punctuation.terminator, -code .punctuation.terminator, -samp .kw, -code .kw { - color: var(--magenta); -} - -/* Operators */ -samp .op, -code .op, -samp .keyword.operator, -code .keyword.operator { +.color-yellow { color: var(--orange); } -/* Delimieters */ -samp .delimeter, -code .delimeter { - color: var(--gray); +.color-blue { + color: var(--cyan); } -/* Variables modules and field names */ -samp .function, -code .function, -samp .meta.group, -code .meta.group, -samp .meta.block, -code .meta.block, -samp .lowerident, -code .lowerident { - color: var(--blue); +.color-magenta { + color: var(--primary-1); } -/* Types, Tags, and Modules */ -samp .type, -code .type, -samp .meta.path, -code .meta.path, -samp .upperident, -code .upperident { - color: var(--green); +.color-cyan { + color: var(--cyan); } -samp .dim, -code .dim { - opacity: 0.55; +.color-white { + /* Really this isn't white so much as "default text color." For the repl, this should be black + in a light color scheme, and only white in dark mode. The name could be better! */ + color: black; } -.button-container { - position: absolute; - top: 0; - right: 0; +@media (prefers-color-scheme: dark) { + .color-white { + color: white; + } } -.copy-button { - background: var(--code-bg); - border: 1px solid var(--magenta); - color: var(--magenta); - display: inline-block; - cursor: pointer; - margin: 8px; +.bold { + font-weight: bold; } -.copy-button:hover { - border-color: var(--green); - color: var(--green); +.underline { + text-decoration: underline; +} + +/* Mobile-friendly screen width */ +@media only screen and (max-width: 767px) { + #repl { + margin: 0; + padding: 0; + min-height: calc(100vh - var(--top-bar-height)); + } + + code.history { + flex-grow: 1; + } + + #source-input { + margin: 0; + } + + #loading-message { + margin: 0; + } + + #homepage-repl-container { + flex-direction: column; + } + + #homepage-repl-container #repl-description { + padding: 0; + margin-bottom: 1.5em; + } + + #repl-arrow { + display: none; + } } diff --git a/www/public/site.js b/www/public/site.js index 89da8ce7b5..ebdd90065a 100644 --- a/www/public/site.js +++ b/www/public/site.js @@ -1,72 +1,516 @@ -const tutorialTocToggle = document.querySelector("#tutorial-toc-toggle"); +const isOnMobile = window.innerWidth <= 1024; -document.querySelectorAll("#tutorial-toc li a").forEach((elem) => { - // Clicking any of the ToC links closes the ToC - elem.addEventListener("click", (event) => { - tutorialTocToggle.checked = false; - }) -}); +// The only way we can provide values to wasm_bindgen's generated code is to set globals +window.js_create_app = js_create_app; +window.js_run_app = js_run_app; +window.js_get_result_and_memory = js_get_result_and_memory; -document.addEventListener("keydown", (event) => { - // Escape closes the ToC - if (event.key == "Escape") { - tutorialTocToggle.checked = false; +// The only place we use console.error is in wasm_bindgen, where it gets a single string argument. +console.error = function displayErrorInHistoryPanel(string) { + const html = `
    ${string}
    `; + updateHistoryEntry(repl.inputHistoryIndex, false, html); +}; + +import * as roc_repl_wasm from "./repl/roc_repl_wasm.js"; + +const isHomepage = document.getElementById("homepage-repl-container") != null; + +const tutorialButtonSvg = ``; + +// ---------------------------------------------------------------------------- +// REPL state +// ---------------------------------------------------------------------------- + +const repl = { + elemHistory: document.getElementById("history-text"), + elemSourceInput: document.getElementById("source-input"), + description: document.getElementById("repl-description"), + + inputQueue: [], + inputHistory: [], + inputHistoryIndex: 0, + inputStash: "", // stash the user input while we're toggling through history with up/down arrows + + // Current progress through the repl tutorial + tutorialStep: 0, + tutorialSteps: [ + { + match: (input) => input.replace(/ /g, "") === "0.1+0.2", + show: '

    Was this the answer you expected? (If so, try this in other programming languages and see what their answers are.)

    Roc has a decimal type as well as floating-point for when performance is more important than decimal precision.

    Next, enter name = "(put your name here)"

    ', + }, + { + match: (input) => input.replace(/ /g, "").match(/^name="/i), + show: '

    This created a new definitionname is now defined to be equal to the string you entered.

    Try using this definition by entering "Hi, \\(name)!"

    ', + }, + { + match: (input) => input.match(/^["][^\\]+\\\(name\)/i), + show: `

    Nicely done! This is an example of string interpolation, which replaces part of a string with whatever you put inside the parentheses after a \\.

    Now that you’ve written a few expressions, you can either continue exploring in this REPL, or move on to the tutorial to learn how to make full programs.

    Welcome to Roc! ${tutorialButtonSvg} Start Tutorial

    `, + }, + ], + + textDecoder: new TextDecoder(), + textEncoder: new TextEncoder(), + + compiler: null, + app: null, + + // Temporary storage for the address of the result of running the user's code. + // Used while control flow returns to Rust to allocate space to copy the app's memory buffer. + result_addr: 0, +}; + +// Initialise +repl.elemSourceInput.value = ""; // Some browsers remember the input across refreshes +resetSourceInputHeight(); +repl.elemSourceInput.addEventListener("input", resetSourceInputHeight); +repl.elemSourceInput.addEventListener("keydown", onInputKeydown); +repl.elemSourceInput.addEventListener("keyup", onInputKeyup); +roc_repl_wasm.default("/repl/roc_repl_wasm_bg.wasm").then(async (instance) => { + const loadingMessage = repl.elemHistory.querySelector("#loading-message"); + + if (loadingMessage != null) { + loadingMessage.remove(); + } + + repl.elemSourceInput.placeholder = "Enter some Roc code here."; + repl.compiler = instance; + + // Get help text from the compiler, and display it at top of the history panel + try { + const helpText = await roc_repl_wasm.entrypoint_from_js(":help"); + const helpElem = document.getElementById("help-text"); + + if (helpElem != null) { + helpElem.innerHTML = helpText.trim(); } + } catch (e) { + // Print error for Roc devs. Don't use console.error, we overrode that above to display on the page! + console.warn(e); + } }); -const isTouchSupported = () => { - try{ document.createEvent("TouchEvent"); return true; } - catch(e){ return false; } +// Focus the repl input the first time it scrolls into view +// (but not on mobile, because that would pop up the whole keyboard abruptly) +if (!isOnMobile) { + // Function to be called when the input enters the viewport + function handleIntersect(entries, observer) { + entries.forEach((entry) => { + // Check if the input is intersecting + if (entry.isIntersecting) { + // Apply focus to it, then unobserve it because we only want to do this once. + entry.target.focus(); + observer.unobserve(entry.target); + } + }); + } + + // Set up the Intersection Observer + let observer = new IntersectionObserver(handleIntersect, { + // Use the whole viewport for the intersection + root: null, + // Trigger the callback when the input is fully visible + threshold: 1.0, + }); + + observer.observe(repl.elemSourceInput); } -// Select all elements that are children of
     elements
    -const codeBlocks = document.querySelectorAll("pre > samp");
    +// ----------------------------------------------------------------------------
    +// Handle inputs
    +// ----------------------------------------------------------------------------
     
    -// Iterate over each code block
    -codeBlocks.forEach((codeBlock) => {
    -  // Create a "Copy" button
    -  const copyButton = document.createElement("button");
    -  copyButton.classList.add("copy-button");
    -  copyButton.textContent = "Copy";
    +function resetSourceInputHeight() {
    +  repl.elemSourceInput.style.height =
    +    repl.elemSourceInput.scrollHeight + 2 + "px"; // +2 for the border
    +}
     
    -  // Add event listener to copy button
    -  copyButton.addEventListener("click", () => {
    -    const codeText = codeBlock.innerText;
    -    navigator.clipboard.writeText(codeText);
    -    copyButton.textContent = "Copied!";
    -    copyButton.classList.add("copy-button-copied");
    -    copyButton.addEventListener("mouseleave", () => {
    -        copyButton.textContent = "Copy";
    -        copyButton.classList.remove('copy-button-copied');
    +function onInputKeydown(event) {
    +  const ENTER = 13;
    +
    +  const { keyCode } = event;
    +
    +  if (keyCode === ENTER) {
    +    if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
    +      // Don't advance the caret to the next line
    +      event.preventDefault();
    +
    +      const inputText = repl.elemSourceInput.value.trim();
    +
    +      repl.elemSourceInput.value = "";
    +      repl.elemSourceInput.style.height = "";
    +
    +      repl.inputQueue.push(inputText);
    +      if (repl.inputQueue.length === 1) {
    +        processInputQueue();
    +      }
    +
    +      // Hide the arrow on the homepage that prompts you to enter something
    +      const replArrow = document.getElementById("repl-arrow");
    +
    +      if (replArrow != null) {
    +        replArrow.style.display = "none";
    +      }
    +    }
    +  }
    +}
    +
    +function onInputKeyup(event) {
    +  const UP = 38;
    +  const DOWN = 40;
    +
    +  const { keyCode } = event;
    +
    +  const el = repl.elemSourceInput;
    +
    +  switch (keyCode) {
    +    case UP:
    +      if (repl.inputHistory.length === 0) {
    +        return;
    +      }
    +      if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
    +        repl.inputStash = el.value;
    +      }
    +      setInput(repl.inputHistory[repl.inputHistoryIndex]);
    +
    +      if (repl.inputHistoryIndex > 0) {
    +        repl.inputHistoryIndex--;
    +      }
    +      break;
    +
    +    case DOWN:
    +      if (repl.inputHistory.length === 0) {
    +        return;
    +      }
    +      if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
    +        setInput(repl.inputStash);
    +      } else {
    +        repl.inputHistoryIndex++;
    +        setInput(repl.inputHistory[repl.inputHistoryIndex]);
    +      }
    +      break;
    +
    +    default:
    +      break;
    +  }
    +}
    +
    +function setInput(value) {
    +  const el = repl.elemSourceInput;
    +  el.value = value;
    +  el.selectionStart = value.length;
    +  el.selectionEnd = value.length;
    +}
    +
    +function showNextReplTutorialEntry(inputText) {
    +  const nextStep = repl.tutorialSteps[repl.tutorialStep];
    +
    +  if (repl.description != null && typeof nextStep === "object" && nextStep.match(inputText)) {
    +    repl.description.innerHTML =
    +      repl.description.innerHTML + "
    " + nextStep.show; + + repl.tutorialStep = repl.tutorialStep + 1; + } +} + +// Use a queue just in case we somehow get inputs very fast +// We want the REPL to only process one at a time, since we're using some global state. +// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? +async function processInputQueue() { + while (repl.inputQueue.length) { + const inputText = repl.inputQueue[0]; + + if (inputText) { + repl.inputHistoryIndex = createHistoryEntry(inputText); + repl.inputStash = ""; + + let outputText = ""; + let ok = true; + try { + outputText = await roc_repl_wasm.entrypoint_from_js(inputText); + } catch (e) { + outputText = `${e}`; + ok = false; + } + + updateHistoryEntry(repl.inputHistoryIndex, ok, outputText); + showNextReplTutorialEntry(inputText); + } + + repl.inputQueue.shift(); + } +} + +// ---------------------------------------------------------------------------- +// Callbacks to JS from Rust +// ---------------------------------------------------------------------------- + +var ROC_PANIC_INFO = null; + +function send_panic_msg_to_js(rocstr_ptr, panic_tag) { + const { memory } = repl.app.exports; + + const rocStrBytes = new Int8Array(memory.buffer, rocstr_ptr, 12); + const finalByte = rocStrBytes[11]; + + let stringBytes = ""; + if (finalByte < 0) { + // small string + + // bitwise ops on negative JS numbers are weird. This clears the bit that we + // use to indicate a small string. In rust it's `finalByte as u8 ^ 0b1000_0000` + const length = finalByte + 128; + stringBytes = new Uint8Array(memory.buffer, rocstr_ptr, length); + } else { + // big string + const rocStrWords = new Uint32Array(memory.buffer, rocstr_ptr, 3); + const [ptr, len, _cap] = rocStrWords; + + const SEAMLESS_SLICE_BIT = 1 << 31; + const length = len & ~SEAMLESS_SLICE_BIT; + + stringBytes = new Uint8Array(memory.buffer, ptr, length); + } + + const decodedString = repl.textDecoder.decode(stringBytes); + + ROC_PANIC_INFO = { + msg: decodedString, + panic_tag: panic_tag, + }; +} + +// Load Wasm code into the browser's virtual machine, so we can run it later. +// This operation is async, so we call it before entering any code shared +// with the command-line REPL, which is sync. +async function js_create_app(wasm_module_bytes) { + const { instance } = await WebAssembly.instantiate(wasm_module_bytes, { + env: { + send_panic_msg_to_js: send_panic_msg_to_js, + }, + }); + + // Keep the instance alive so we can run it later from shared REPL code + repl.app = instance; +} + +// Call the `main` function of the user app, via the `wrapper` function. +function js_run_app() { + const { wrapper, memory } = repl.app.exports; + + // Run the user code, and remember the result address + // We'll pass it to Rust in the next callback + try { + repl.result_addr = wrapper(); + } catch (e) { + // an exception could be that roc_panic was invoked, + // or some other crash (likely a compiler bug) + if (ROC_PANIC_INFO === null) { + throw e; + } else { + // when roc_panic set an error message, display it + const { msg, panic_tag } = ROC_PANIC_INFO; + ROC_PANIC_INFO = null; + + console.error(format_roc_panic_message(msg, panic_tag)); + } + } + + // Tell Rust how much space to reserve for its copy of the app's memory buffer. + // We couldn't know that size until we actually ran the app. + return memory.buffer.byteLength; +} + +function format_roc_panic_message(msg, panic_tag) { + switch (panic_tag) { + case 0: { + return `Roc failed with message: "${msg}"`; + } + case 1: { + return `User crash with message: "${msg}"`; + } + default: { + return `Got an invalid panic tag: "${panic_tag}"`; + } + } +} + +// After Rust has allocated space for the app's memory buffer, +// we copy it, and return the result address too +function js_get_result_and_memory(buffer_alloc_addr) { + const appMemory = new Uint8Array(repl.app.exports.memory.buffer); + const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); + compilerMemory.set(appMemory, buffer_alloc_addr); + return repl.result_addr; +} + +// ---------------------------------------------------------------------------- +// Rendering +// ---------------------------------------------------------------------------- + +function createHistoryEntry(inputText) { + const historyIndex = repl.inputHistory.length; + repl.inputHistory.push(inputText); + + const firstLinePrefix = '» '; + const otherLinePrefix = '
    '; + const inputLines = inputText.split("\n"); + if (inputLines[inputLines.length - 1] === "") { + inputLines.pop(); + } + const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix); + + const inputElem = document.createElement("div"); + inputElem.innerHTML = inputWithPrefixes; + inputElem.classList.add("input"); + + const historyItem = document.createElement("div"); + historyItem.appendChild(inputElem); + historyItem.classList.add("history-item"); + + repl.elemHistory.appendChild(historyItem); + + return historyIndex; +} + +function updateHistoryEntry(index, ok, outputText) { + const outputElem = document.createElement("div"); + outputElem.innerHTML = outputText; + outputElem.classList.add("output", ok ? "output-ok" : "output-error"); + + const historyItem = repl.elemHistory.children[index]; + historyItem.appendChild(outputElem); + + if (isHomepage) { + // Scroll the input element into view so you can see the most recent output. + // Only do this if it's currently out of view though! + const bounds = repl.elemSourceInput.getBoundingClientRect(); + const isInView = + bounds.top >= 0 && + bounds.left >= 0 && + bounds.bottom <= + (window.innerHeight || document.documentElement.clientHeight) && + bounds.right <= + (window.innerWidth || document.documentElement.clientWidth); + + if (!isInView) { + repl.elemSourceInput.scrollIntoView({ + behavior: "instant", + block: "end", + inline: "nearest", + }); + } + } else { + // Scroll the page to the bottom so you can see the most recent output. + window.scrollTo(0, document.body.scrollHeight); + } +} + +// TUTORIAL // + +const tutorialTocToggle = document.querySelector("#tutorial-toc-toggle"); + +if (tutorialTocToggle != null) { + document.querySelectorAll("#tutorial-toc li a").forEach((elem) => { + // Clicking any of the ToC links closes the ToC + elem.addEventListener("click", (event) => { + tutorialTocToggle.checked = false; }); }); - // Create a container for the copy button and append it to the document - const buttonContainer = document.createElement("div"); - buttonContainer.classList.add("button-container"); - buttonContainer.appendChild(copyButton); - codeBlock.parentNode.insertBefore(buttonContainer, codeBlock); + document.addEventListener("keydown", (event) => { + // Escape closes the ToC + if (event.key == "Escape") { + tutorialTocToggle.checked = false; + } + }); - // Hide the button container by default - buttonContainer.style.display = "none"; + const isTouchSupported = () => { + try { + document.createEvent("TouchEvent"); + return true; + } catch (e) { + return false; + } + }; - if (isTouchSupported()) { - // Show the button container on click for touch support (e.g. mobile) - document.addEventListener("click", (event) => { - if (event.target.closest("pre > samp") !== codeBlock) { - buttonContainer.style.display = "none"; - } else { + // Select all elements that are children of
     elements
    +  const codeBlocks = document.querySelectorAll("pre > samp");
    +
    +  // Iterate over each code block
    +  codeBlocks.forEach((codeBlock) => {
    +    // Create a "Copy" button
    +    const copyButton = document.createElement("button");
    +    copyButton.classList.add("copy-button");
    +    copyButton.textContent = "Copy";
    +
    +    // Add event listener to copy button
    +    copyButton.addEventListener("click", () => {
    +      const codeText = codeBlock.innerText;
    +      navigator.clipboard.writeText(codeText);
    +      copyButton.textContent = "Copied!";
    +      copyButton.classList.add("copy-button-copied");
    +      copyButton.addEventListener("mouseleave", () => {
    +        copyButton.textContent = "Copy";
    +        copyButton.classList.remove("copy-button-copied");
    +      });
    +    });
    +
    +    // Create a container for the copy button and append it to the document
    +    const buttonContainer = document.createElement("div");
    +    buttonContainer.classList.add("button-container");
    +    buttonContainer.appendChild(copyButton);
    +    codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
    +
    +    // Hide the button container by default
    +    buttonContainer.style.display = "none";
    +
    +    if (isTouchSupported()) {
    +      // Show the button container on click for touch support (e.g. mobile)
    +      document.addEventListener("click", (event) => {
    +        if (event.target.closest("pre > samp") !== codeBlock) {
    +          buttonContainer.style.display = "none";
    +        } else {
    +          buttonContainer.style.display = "block";
    +        }
    +      });
    +    } else {
    +      // Show the button container on hover for non-touch support (e.g. desktop)
    +      codeBlock.parentNode.addEventListener("mouseenter", () => {
             buttonContainer.style.display = "block";
    -      }
    +      });
    +
    +      codeBlock.parentNode.addEventListener("mouseleave", () => {
    +        buttonContainer.style.display = "none";
    +      });
    +    }
    +  });
    +}
    +
    +// HOMEPAGE //
    +
    +if (isOnMobile) {
    +  const hideDesc = () => {
    +    document.querySelectorAll(".interactive-radio").forEach((radio) => {
    +      radio.checked = false;
         });
    -  } else {
    -    // Show the button container on hover for non-touch support (e.g. desktop)
    -    codeBlock.parentNode.addEventListener("mouseenter", () => {
    -      buttonContainer.style.display = "block";
    +  };
    +
    +  hideDesc(); // On mobile, start out with all the descriptions hidden.
    +
    +  document.querySelectorAll(".interactive-example").forEach((example) => {
    +    example.querySelectorAll("label").forEach((label) => {
    +      label.addEventListener("click", (event) => {
    +        const desc = label.nextSibling; // The description node always comes next
    +
    +        // Set the position of the target element
    +        desc.style.top = label.offsetTop + label.offsetHeight + "px"; // Position below the button
    +        desc.style.left = label.offsetLeft + "px"; // Align with the left of the button
    +      });
         });
     
    -    codeBlock.parentNode.addEventListener("mouseleave", () => {
    -      buttonContainer.style.display = "none";
    -    });  
    -  }
    -});
    \ No newline at end of file
    +    example.querySelectorAll(".close-desc").forEach((button) => {
    +      button.style.display = "block";
    +      button.addEventListener("click", hideDesc);
    +    });
    +  });
    +}
    diff --git a/www/public/styles.css b/www/public/styles.css
    deleted file mode 100644
    index e5c8d31df3..0000000000
    --- a/www/public/styles.css
    +++ /dev/null
    @@ -1,488 +0,0 @@
    -:root {
    -  --link-color: #612bde;
    -  --code-link-color: #5721d4;
    -  --text-color: #333333;
    -  --code-color: #222222;
    -  --code-bg-color: #eeeeee;
    -  --body-bg-color: #fdfdfd;
    -  --border-color: #e9e9e9;
    -  --faded-color: #4c4c4c;
    -  --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
    -  --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
    -  --top-header-height: 67px;
    -  --sidebar-width: 280px;
    -  --top-bar-bg: #8257e5;
    -  --top-bar-fg: #ffffff;
    -  --nav-link-hover-color: #000000;
    -}
    -
    -a {
    -  color: #972395;
    -}
    -
    -.logo {
    -  padding: 2px 8px;
    -}
    -
    -.logo svg {
    -  height: 48px;
    -  width: 48px;
    -  fill: var(--top-bar-fg);
    -}
    -
    -.logo:hover {
    -  text-decoration: none;
    -}
    -
    -.logo svg:hover {
    -  fill: var(--nav-link-hover-color);
    -}
    -
    -.pkg-full-name {
    -  color: var(--text-color);
    -  display: flex;
    -  align-items: center;
    -  font-size: 32px;
    -  margin: 0 8px;
    -  font-weight: normal;
    -  white-space: nowrap;
    -  overflow: hidden;
    -  text-overflow: ellipsis;
    -  height: 100%;
    -}
    -
    -.pkg-full-name a {
    -  padding-top: 12px;
    -  padding-bottom: 16px;
    -}
    -
    -a {
    -  text-decoration: none;
    -}
    -
    -a:hover {
    -  text-decoration: underline;
    -}
    -
    -.pkg-and-logo {
    -  min-width: 0;
    -  /* necessary for text-overflow: ellipsis to work in descendants */
    -  display: flex;
    -  align-items: center;
    -  height: 100%;
    -  background-color: var(--top-bar-bg);
    -}
    -
    -.pkg-and-logo a,
    -.pkg-and-logo a:visited {
    -  color: var(--top-bar-fg);
    -}
    -
    -.pkg-and-logo a:hover {
    -  color: var(--nav-link-hover-color);
    -  text-decoration: none;
    -}
    -
    -.main-container {
    -  min-width: 0;
    -  /* necessary for text-overflow: ellipsis to work in descendants */
    -}
    -
    -.search-button {
    -  flex-shrink: 0;
    -  /* always shrink the package name before these; they have a relatively constrained length */
    -  padding: 12px 18px;
    -  margin-right: 42px;
    -  display: none;
    -  /* only show this in the mobile view */
    -}
    -
    -.version {
    -  padding: 18px 10px;
    -  min-width: 48px;
    -  margin-right: 8px;
    -}
    -
    -body {
    -  display: grid;
    -  grid-template-columns: [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr;
    -  grid-template-rows: [top-header] var(--top-header-height) [above-footer] auto [footer] auto;
    -  box-sizing: border-box;
    -  margin: 0;
    -  padding: 0;
    -  font-family: var(--font-sans);
    -  color: var(--text-color);
    -  background-color: var(--body-bg-color);
    -}
    -
    -main {
    -  grid-column-start: main-content;
    -  grid-column-end: main-content;
    -  grid-row-start: above-footer;
    -  grid-row-end: above-footer;
    -  box-sizing: border-box;
    -  position: relative;
    -  font-size: 18px;
    -  line-height: 1.85em;
    -  margin-top: 2px;
    -  padding: 48px;
    -}
    -
    -#sidebar-nav {
    -  grid-column-start: sidebar;
    -  grid-column-end: sidebar;
    -  grid-row-start: above-footer;
    -  grid-row-end: above-footer;
    -  position: relative;
    -  display: flex;
    -  flex-direction: column;
    -  box-sizing: border-box;
    -  padding-left: 56px;
    -  padding-top: 6px;
    -  width: 100%;
    -}
    -
    -.top-header-extension {
    -  grid-column-start: before-sidebar;
    -  grid-column-end: sidebar;
    -  grid-row-start: top-header;
    -  grid-row-end: top-header;
    -  background-color: var(--top-bar-bg);
    -}
    -
    -.top-header {
    -  grid-column-start: sidebar;
    -  grid-column-end: end;
    -  grid-row-start: top-header;
    -  grid-row-end: top-header;
    -  display: flex;
    -  flex-direction: row;
    -  align-items: center;
    -  flex-wrap: nowrap;
    -  flex-grow: 1;
    -  box-sizing: border-box;
    -  font-family: var(--font-sans);
    -  font-size: 24px;
    -  height: 100%;
    -  min-width: 0;
    -  /* necessary for text-overflow: ellipsis to work in descendants */
    -}
    -
    -.top-header-triangle {
    -  /* This used to be a clip-path, but Firefox on Android (at least as of early 2020)
    -   * rendered the page extremely slowly in that version. With this approach it's super fast.
    -   */
    -  width: 0;
    -  height: 0;
    -  border-style: solid;
    -  border-width: var(--top-header-height) 0 0 48px;
    -  border-color: transparent transparent transparent var(--top-bar-bg);
    -}
    -
    -p {
    -  overflow-wrap: break-word;
    -  margin: 24px 0;
    -}
    -
    -footer {
    -  grid-column-start: main-content;
    -  grid-column-end: main-content;
    -  grid-row-start: footer;
    -  grid-row-end: footer;
    -  max-width: var(--main-content-max-width);
    -  font-size: 14px;
    -  box-sizing: border-box;
    -  padding: 16px;
    -}
    -
    -footer p {
    -  display: inline-block;
    -  margin-top: 0;
    -  margin-bottom: 8px;
    -}
    -
    -.content {
    -  box-sizing: border-box;
    -  display: flex;
    -  flex-direction: row;
    -  justify-content: space-between;
    -}
    -
    -.sidebar-entry ul {
    -  list-style-type: none;
    -  margin: 0;
    -}
    -
    -.sidebar-entry a {
    -  box-sizing: border-box;
    -  min-height: 48px;
    -  min-width: 48px;
    -  padding: 12px 16px;
    -  font-family: var(--font-mono);
    -}
    -
    -.sidebar-sub-entries a {
    -  display: block;
    -  line-height: 24px;
    -  width: 100%;
    -  overflow: hidden;
    -  text-overflow: ellipsis;
    -  padding-left: 36px;
    -}
    -
    -.module-name {
    -  font-size: 56px;
    -  line-height: 1em;
    -  font-family: var(--font-mono);
    -  font-weight: bold;
    -  margin-top: 18px;
    -  margin-bottom: 48px;
    -}
    -
    -.module-name a,
    -.module-name a:visited {
    -  color: inherit;
    -}
    -
    -.sidebar-module-link {
    -  box-sizing: border-box;
    -  font-size: 18px;
    -  line-height: 24px;
    -  font-family: var(--font-mono);
    -  font-weight: bold;
    -  display: block;
    -  width: 100%;
    -  padding: 8px 0;
    -  white-space: nowrap;
    -  overflow: hidden;
    -  text-overflow: ellipsis;
    -}
    -
    -a,
    -a:visited {
    -  color: var(--link-color);
    -}
    -
    -h3 {
    -  font-size: 32px;
    -  margin: 48px 0 24px 0;
    -}
    -
    -h4 {
    -  font-size: 24px;
    -}
    -
    -.type-def {
    -  font-size: 24px;
    -  color: var(--link-color);
    -}
    -
    -.code-snippet {
    -  padding: 12px 16px;
    -  display: block;
    -  box-sizing: border-box;
    -  font-family: var(--font-mono);
    -  background-color: var(--code-bg-color);
    -}
    -
    -code {
    -  font-family: var(--font-mono);
    -  color: var(--code-color);
    -  background-color: var(--code-bg-color);
    -  display: inline-block;
    -  line-height: 28px;
    -}
    -
    -code a {
    -  color: var(--code-link-color);
    -}
    -
    -code a:visited {
    -  color: var(--code-link-color);
    -}
    -
    -pre {
    -  margin: 36px 0;
    -  padding: 8px;
    -  box-sizing: border-box;
    -  background-color: var(--code-bg-color);
    -  overflow-x: auto;
    -}
    -
    -.hidden {
    -  /* Use !important to win all specificity fights. */
    -  display: none !important;
    -}
    -
    -.syntax-comment {
    -  color: #ff0000;
    -}
    -
    -#module-search:placeholder-shown {
    -  padding: 0;
    -  opacity: 0;
    -  height: 0;
    -}
    -
    -#module-search,
    -#module-search:focus {
    -  opacity: 1;
    -  padding: 12px 16px;
    -  height: 48px;
    -}
    -
    -/* Show the "Search" label link when the text input has a placeholder */
    -#module-search:placeholder-shown+#search-link {
    -  display: flex;
    -}
    -
    -/* Hide the "Search" label link when the text input has focus */
    -#module-search:focus+#search-link {
    -  display: none;
    -}
    -
    -#module-search {
    -  display: block;
    -  box-sizing: border-box;
    -  background-color: var(--code-bg-color);
    -  width: 100%;
    -  box-sizing: border-box;
    -  font-size: 18px;
    -  line-height: 18px;
    -  margin-top: 6px;
    -  border: none;
    -  color: var(--faded-color);
    -  background-color: var(--code-bg-color);
    -  font-family: var(--font-serif);
    -}
    -
    -#module-search::placeholder {
    -  color: var(--faded-color);
    -  opacity: 1;
    -}
    -
    -#search-link {
    -  box-sizing: border-box;
    -  display: none;
    -  align-items: center;
    -  font-size: 18px;
    -  line-height: 18px;
    -  padding: 12px 16px;
    -  height: 48px;
    -  cursor: pointer;
    -  color: var(--link-color);
    -}
    -
    -#search-link:hover {
    -  text-decoration: underline;
    -}
    -
    -@media (prefers-color-scheme: dark) {
    -  :root {
    -    --body-bg-color: #303030;
    -    --code-bg-color: #393939;
    -    --border-color: #555555;
    -    --code-color: #eeeeee;
    -    --text-color: #cccccc;
    -    --logo-solid: #777777;
    -    --faded-color: #bbbbbb;
    -    --link-color: #c5a8ff;
    -    --code-link-color: #b894ff;
    -    --top-bar-bg: #6845b9;
    -    --top-bar-fg: #eeeeee;
    -  }
    -
    -  html {
    -    scrollbar-color: #444444 #2f2f2f;
    -  }
    -}
    -
    -@media only screen and (max-device-width: 480px) {
    -  .search-button {
    -    display: block;
    -    /* This is only visible in mobile. */
    -  }
    -
    -  .top-header {
    -    width: auto;
    -  }
    -
    -  .pkg-full-name {
    -    margin-left: 8px;
    -    margin-right: 12px;
    -    font-size: 24px;
    -    padding-bottom: 14px;
    -  }
    -
    -  .pkg-full-name a {
    -    vertical-align: middle;
    -    padding: 18px 0;
    -  }
    -
    -  .logo {
    -    padding-left: 2px;
    -    width: 50px;
    -    height: 54px;
    -  }
    -
    -  .version {
    -    margin: 0;
    -    font-weight: normal;
    -    font-size: 18px;
    -    padding-bottom: 16px;
    -  }
    -
    -  .module-name {
    -    font-size: 36px;
    -    margin-top: 8px;
    -    margin-bottom: 8px;
    -    max-width: calc(100% - 18px);
    -    overflow: hidden;
    -    text-overflow: ellipsis;
    -  }
    -
    -  main {
    -    padding: 18px;
    -    font-size: 16px;
    -  }
    -
    -  .container {
    -    margin: 0;
    -    min-width: 320px;
    -    max-width: 100%;
    -  }
    -
    -  .content {
    -    flex-direction: column;
    -  }
    -
    -  .sidebar {
    -    margin-top: 0;
    -    padding-left: 0;
    -    width: auto;
    -  }
    -
    -  #sidebar-heading {
    -    font-size: 24px;
    -    margin: 16px;
    -  }
    -
    -  h3 {
    -    font-size: 18px;
    -    margin: 0;
    -    padding: 0;
    -  }
    -
    -  h4 {
    -    font-size: 16px;
    -  }
    -
    -  .top-header {
    -    justify-content: space-between;
    -  }
    -
    -  .content {
    -    /* Display the sidebar below 
    without affecting tab index */ - flex-direction: column-reverse; - } -} diff --git a/www/wip_new_website/.gitignore b/www/wip_new_website/.gitignore deleted file mode 100644 index 9884a9c30e..0000000000 --- a/www/wip_new_website/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -roc-website -dist/ -website-builder -main diff --git a/www/wip_new_website/README.md b/www/wip_new_website/README.md deleted file mode 100644 index e2c50f32ca..0000000000 --- a/www/wip_new_website/README.md +++ /dev/null @@ -1,30 +0,0 @@ - -# Getting Started - -## Prerequisites - -- Linux or MacOS operating system, Windows users can use linux through WSL. -- Install [git](https://chat.openai.com/share/71fb3ae6-80d7-478c-8a27-a36aaa5ba921) -- Install [nix](https://nixos.org/download.html) - -## Building the website from scratch - -```bash -git clone https://github.com/roc-lang/roc.git -cd roc -nix develop -./www/build.sh -# make the roc command available -export PATH="$(pwd)/target/release/:$PATH" -cd www/wip_new_website -bash build-dev-local.sh -``` - -Open http://0.0.0.0:8080/wip in your browser. - -## After you've made a change - -In the terminal where the web server is running: -1. kill the server with Ctrl+C -2. re-run the build script -3. refresh the page in your browser \ No newline at end of file diff --git a/www/wip_new_website/content/design_goals.md b/www/wip_new_website/content/design_goals.md deleted file mode 100644 index 045bc71b78..0000000000 --- a/www/wip_new_website/content/design_goals.md +++ /dev/null @@ -1,146 +0,0 @@ - -# Design Goals - - - -Roc's goal is to be a fast, friendly, functional language. It's very much a work in progress; below, you can see the current progress towards this goal. This website is intentionally unstyled as a way to emphasize the language's current level of incompleteness. The website will become more polished after the language itself becomes more polished! - -Roc compiles to machine code or to [WebAssembly](https://webassembly.org). Eventually you'll be able to use Roc to build high-quality servers, command-line applications, graphical native desktop user interfaces, among other classes of applications. Today, only command-line interfaces have support beyond the proof-of-concept stage; the other use cases will mature over time. - -Like [Lua](https://www.lua.org/), Roc's automatic memory management doesn't require a virtual machine, and it's possible to call Roc functions directly from any language that can call [C](https://en.wikipedia.org/wiki/C_(programming_language)) functions. This makes Roc additionally useful as a language for implementing plugins, and gives you a way to incrementally transition a legacy code base from another language to Roc. - -So far, the Roc compiler has progressed past the "proof of concept" stage, but there are currently lots of known bugs and unimplemented features, and the documentation for both the language and the standard library is incomplete. The overall ecosystem is in its infancy, and the compiler is neither battle-tested nor fuzz-tested yet, so we don't recommend relying on Roc for critical projects until its development is further along. - -# Fast{#fast} - - - -## Goal - -We want Roc to run faster than any non-systems language (like C, C++, Rust, or Zig) that sees mainstream use in industry. The goal is that nobody should find themselves thinking "I should rewrite my Roc program in \[some mainstream garbage-collected language\] because that will make it run significantly faster." - -When benchmarking Roc code against similarly-optimized programs written in [Go](https://go.dev), [Swift](https://www.swift.org/), [Java](https://www.oracle.com/java/), [C#](https://learn.microsoft.com/en-us/dotnet/csharp), or [JavaScript](https://www.ecma-international.org/publications-and-standards/standards/ecma-262), we generally aim for Roc to outperform all of those languages. Outperforming systems languages like Rust, Zig, C, D, and C++ is a non-goal, as is outperforming research languages that see little or no use in industry. (Realistically, there will always be certain specific benchmarks where some popular non-systems-level languages outperform Roc, but the goal is to usually be at the front of that pack.) - -## Current progress - -Progress towards this performance goal is already quite far along. - -Roc already uses unboxed data structures and unboxed closures, monomorphizes polymorphic code, and uses LLVM as a compiler backend. These optimizations, especially unboxed closures and monomorphization, can be found in several systems-level languages (like C++ and Rust), but not in any mainstream garbage-collected languages. Roc closures in particular have the distinction of being as ergonomic as the closures found in garbage-collected languages (where they are typically boxed), but have the performance of systems language closures (which are typically unboxed, but have more complicated types). - -Because of these optimizations, in many cases Roc code already compiles to the same machine instructions that the equivalent code written in one of these systems languages would. Something we do regularly is to compare the LLVM instructions generated by Roc's compiler and by these systems languages' compilers, to check whether we're generating equivalent instructions. - -That said, there are also cases where Roc has strictly more runtime overhead than languages like C, C++, Zig, and Rust do. The most costly is automatic memory management, which Roc implements using automatic reference counting. Static reference count optimizations like elision and reuse (thanks to Morphic and [Perceus](https://www.microsoft.com/en-us/research/publication/perceus-garbage-free-reference-counting-with-reuse/)) improve things, but significant runtime overhead remains. - -Eliminating this overhead altogether would require sacrificing other design goals (e.g. it would require introducing memory-unsafe operations, or compile-time lifetime errors), and there isn't much overhead left to remove outside of automatic memory management. For example, smaller sources of overhead include mandatory array bounds checks, disallowing cyclic references (which rules out a certain niche of efficient graph data structures), and automatic opportunistic in-place mutation instead of direct mutation. Even if all of these sources of overhead were completely eliminated, it seems unlikely that typical Roc programs would see a particularly big performance boost. - -Overall, we expect Roc's performance in the use cases mentioned above (servers, CLIs, GUIs, etc.) to be about the same as the equivalent C++ code would be, if all that C++ code (including its dependencies) were written in a restricted subset of C++ which always did array bounds checks and used shared pointers for all heap allocations. The Roc code might even run somewhat faster, because its reference counts are non-atomic by default, and can be statically optimized away in some cases—but then again, Roc also has a bit of overhead to perform opportunistic in-place mutation instead of direct mutation. - -To be clear, we don't expect this because we've benchmarked a bunch of programs written in Roc and in this restricted C++ subset, and found that the numbers were about the same (although if you know C++ well enough and want to do such experiments, we'd happy to help and would be interested to see the results!) but rather because Roc's compiler and [clang](https://clang.llvm.org/) should both be generating essentially the same LLVM instructions when the C++ is restricted to that subset. - -Of course, _unrestricted_ C++ code can certainly run faster than unrestricted Roc code. The same is true when comparing other such minimal-overhead systems languages to Roc, including Rust, Zig, C, and D. The point of the comparison is to give you a general idea of what Roc compiles to, since it is quite different from the VMs and JITted bytecode interpreters found in today's most popular garbage-collected languages! - -The talk [Outperforming Imperative with Pure Functional Languages](https://youtu.be/vzfy4EKwG_Y) discusses some early results from Roc's optimizations, and [Roc at Handmade Seattle](https://media.handmade-seattle.com/roc-lang) gets into low-level details of how Roc's compiler generates programs similarly to how clang does. - -# Friendly{#friendly} - - - -## Goals - -Roc aims to be a user-friendly language with a friendly community of users. - -A programming language can be much more than a tool for writing software, it can also be a way for people to come together through shared experiences, to teach and to learn from one another, and to make new friends. - -No community is perfect, but a community where people show kindness to each another by default can be a true joy to participate in. That all starts with friendliness, especially towards beginners, and including towards people who prefer other programming languages. After all, languages are tools people use to create software, and there's no need for us to create artificial divisions between ourselves based on the tools we use! - -On a technical level, Roc aims to ship a toolset where user-friendliness is a major priority. This includes everything from helpful error messages (aiming to meet the bar set by [Elm](https://elm-lang.org)) to quality-of-life improvements inspired by dynamic languages (always being able to run your program even if there are compile errors, automatic serialization and deserialization using schemas determined by type inference, reliable hot code loading that's always enabled and requires no configuration to set up, etc.) to accessibility features in the included editor. - -Roc also aims to ship a single binary that includes not only a compiler, but also a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), package manager, test runner, debugger, static analyzer, code formatter, and a full-featured editor, all of which are designed to work seamlessly together. - -## Current Progress - -Work has not yet started on the package manager, static analyzer, debugger, or hot code loading system, and although work has started on the editor, it's not yet far enough along to be usable for practical purposes. The standard library is perhaps 80 percent complete in terms of functionality, but a lot of operations do not yet have documentation. - -The REPL fully supports entering arbitrary expressions, and will evaluate them and print the results. It remembers recent expressions entered in the current session (if you press the up arrow), but it can't yet execute effects. You can try out the REPL in a browser at [roc-lang.org/repl](https://roc-lang.org/repl) - it uses a WebAssembly build of Roc's compiler, and compiles the code you write to WebAssembly on the fly, which it then executes in the browser to display the answer. - -The compiler works well enough on a basic level to build things with it, but some error messages could use significant improvement, and it has a lot of known bugs and missing features. You can currently use it on macOS (either Intel or Apple Silicon), Linux (only x86-64 machines at the moment), and Windows (only recently supported; debugging and testing features don't work on it yet, and there are likely bugs we haven't encountered yet due to lack of battle testing). Support for other operating systems has not yet been discussed. - -The compiler doesn't yet support incremental compilation or hot code loading, and build times vary based on what machine you're building for. - -For example, suppose you run \`roc check\`, which reports errors it finds (type mismatches, naming errors, and so on) but doesn't actually build an executable, on a code base that's under a thousand lines of code. On an M1 MacBook Pro, this typically takes about 10 milliseconds. - -In contrast, if you do \`roc build\` (or \`roc run\`) on that same machine, it will take closer to 500 milliseconds instead. Almost all that extra time is spent waiting for LLVM to generate (unoptimized) machine code, and then for the system linker to assemble an executable from it. - -Fortunately, we can eliminate almost all of those extra 490 millisconds of build time by using Roc's (work in progress) development backend instead of LLVM. This compiles directly from Roc's internal representation to machine code, like most compilers did before LLVM. (LLVM can optimize code into running very fast, but even when it performs no optimization at all, LLVM itself takes a lot longer to run than generating unoptimized machine code directly.) - -The LLVM backend is currently the most feature-complete, followed closely by the WebAssembly backend (which the online REPL uses exclusively, instead of LLVM). The x86 and ARM backends still have a ways to go, but improving them can be done by anyone with the patience to read some documentation; we have issues split up for them, and are happy to help new contributors get up and running! - -Builds on Linux and Windows also use Roc's surgical linker instead of the system linker, which runs so fast that linking essentially disappears from the performance profile altogether. The surgical linker currently only works on Linux and Windows, and it currently supports building executables but not (yet) dynamic libraries, which is relevant if you're using Roc to create plugins or want to call Roc functions from existing code bases in other languages. Work has started on macOS surgical linking, but it isn't usable yet. If you're interested in working on that, please get in touch on [Roc Zulip](https://roc.zulipchat.com/)! - -The test runner currently has first-class support for running standard non-effectful tests. It does not yet have first-class support for effectful tests, property-based tests, snapshot tests, or "simulation tests" (where effects are replaced by hardcoded values during the test - similar to "mocking" in other languages), although these are all planned for the future. - -The code formatter is nearly feature-complete, although occasionally it will report an error - usually due to a comment being placed somewhere it doesn't yet know how to handle. Unlike most of the rest of the compiler, the formatter is one place where the number of known bugs is so small that fuzzing would be very helpful as a way to surface bugs we don't yet know about. (If you're interested in working on setting up fuzzing for the formatter, please let us know in the [`#contributing` channel](https://roc.zulipchat.com/#narrow/stream/316715-contributing) on Zulip! Separately, we're also very interested in fuzzing the compiler, even though we already have a sizable list of known bugs there.) - -On the community side, so far the community is a friendly bunch, and we want to keep it that way as it grows! We hope to do that by encouraging a culture of kindness and helping one another out, especially by being welcoming towards beginners. - -If you'd like to join in, the best place to do that is in our Zulip chat. Feel free to drop by the [`introductions` topic](https://roc.zulipchat.com/#narrow/stream/231634-beginners/topic/introductions) and introduce yourself! - -# Functional{#functional} - - - -## Goals - -Roc aims to be a purely functional programming language. This means all Roc functions are [pure functions](https://en.wikipedia.org/wiki/Pure_function), and all effects are [managed effects](https://medium.com/@kaw2k/managed-effects-and-elm-36b7fcd246a9) instead of side effects. - -A major motivating reason for this is to facilitate tooling. For example, in the future the goal is that Roc's test runner won't bother re-running tests whose outcomes could not possibly have changed (because they were pure functions whose inputs did not change). Tests that contain only pure functions can be trivially run in parallel, and they will never [flake](https://www.smashingmagazine.com/2021/04/flaky-tests-living-nightmare/). Additionally, having the guarantee that the application contains only pure functions can also make certain debugging tools more reliable, such as time travel and retroactive tracing. - -Roc also takes a novel approach to managed effects. In most programming languages, the standard library contains both data structures and I/O primitives (e.g. for using the file system or the network), and then you might decide to use a [framework](https://en.wikipedia.org/wiki/Application_framework) on top of that standard library. - -In Roc, every application is built on a _platform_. A platform is like a framework except that it also provides I/O primitives and behind-the-scenes memory management. (Roc's standard library only contains data structures.) In practice, this means that using Roc feels similar to using any other programming language where you've chosen to use a framework, except that the documentation for your I/O primitives comes from the framework instead of the standard library. - -This might sound like a minor distinction, but it turns out there are a lot of surprising benefits to organizing things this way, which would be impossible to achieve without having platforms as a first-class language concept. [The Edges of Cutting-Edge Languages](https://youtu.be/cpQwtwVKAfU) goes into more detail about some of these benefits. - -## Current Progress - -Today, platforms as a concept already exist, and there are a few different ones implemented. You can find them in the [`examples/`](https://github.com/roc-lang/roc/tree/main/examples) directory in the source code repository. The platform for building command-line interfaces is the most fully featured; the others are mostly in the proof-of-concept stage. - -Roc's built-in tooling is not yet far enough along to take advantage of pure functions. For example, there is a built-in test runner, but it does not yet run tests in parallel or skip running tests whose outcomes could not possibly have changed. - -Roc is already a purely functional programming language, though, so all of these benefits are ready to be unlocked as the tooling implementations progress! diff --git a/www/wip_new_website/content/docs.md b/www/wip_new_website/content/docs.md deleted file mode 100644 index c0f065a0ba..0000000000 --- a/www/wip_new_website/content/docs.md +++ /dev/null @@ -1,17 +0,0 @@ -# Documentation - -Builtin Package - [docs](https://www.roc-lang.org/builtins) - -Basic-CLI Platform - [repo](https://github.com/roc-lang/basic-cli) - [docs](https://www.roc-lang.org/packages/basic-cli) - -## Guides - -[Frequently Asked Questions](https://github.com/roc-lang/roc/blob/main/FAQ.md) - -[Roc for Elm Programmers](https://github.com/roc-lang/roc/blob/main/roc-for-elm-programmers.md) - -## Language Reference - - - - diff --git a/www/wip_new_website/content/donate.md b/www/wip_new_website/content/donate.md deleted file mode 100644 index 3990828612..0000000000 --- a/www/wip_new_website/content/donate.md +++ /dev/null @@ -1,8 +0,0 @@ -# Donate - -- [Github Sponsorship](https://github.com/sponsors/roc-lang) - - diff --git a/www/wip_new_website/content/fast.md b/www/wip_new_website/content/fast.md deleted file mode 100644 index 6eaf2cda1a..0000000000 --- a/www/wip_new_website/content/fast.md +++ /dev/null @@ -1,51 +0,0 @@ -# Fast - -Roc code is designed to build fast and run fast...but what does "fast" mean here? And how close is Roc's curent implementation to realizing that design goal? - -## Fast Programs - -What "fast" means in embedded systems is different from what it means in games, which in turn is different from what it means on the Web. To better understand Roc’s performance capabilities, let's look at the upper bound of how fast optimized Roc programs are capable of running, and the lower bound of what languages Roc should generally outperform. - -**Limiting factors: memory management and async I/O.** Part of Roc's design is that it is a [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) language with [automatic memory management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reference_counting). Automatic memory management has some unavoidable runtime overhead, and memory safety rules out certain performance optimizations—which is why [unsafe Rust](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) can outperform safe Rust. This gives Roc a lower performance ceiling than languages which support memory unsafety and manual memory management, such as C, C++, Zig, and Rust. Another part of Roc's design is that I/O operations are done using a lightweight state machine so that they can be asynchronous. This has potential performance benefits compared to synchronous I/O, but it also has some unavoidable overhead. - -**Faster than dynamic or gradual languages.** As a general rule, Roc programs should have almost strictly less runtime overhead than equivalent programs written in languages with dynamic types and automatic memory management. This doesn't mean all Roc programs will outperform all programs in these languages, just that Roc should have a higher ceiling on what performance is achievable. This is because dynamic typing (and gradual typing) requires tracking types at runtime, which has unavoidable overhead. Roc tracks types only at compile time, and tends to have [minimal (often zero) runtime overhead](https://guide.handmade-seattle.com/c/2021/roc-lang-qa/) for language constructs compared to the top performers in industry. For example, Roc's generics, records, functions, numbers, and tag unions have no more runtime overhead than they would in their Rust or C++ equivalents. - -When [benchmarking compiled Roc programs](https://www.youtube.com/watch?v=vzfy4EKwG_Y), the normal goal is to have them outperform the fastest mainstream garbage-collected languages (for example, Go, C#, Java, and JavaScript), but it's a non-goal to outperform languages that support memory unsafety or manual memory management. There will always be some individual benchmarks where mainstream garbage-collected languages outperform Roc, but the goal is for these to be uncommon cases rather than the norm. - -### Current Progress - -Most of Roc's data structures are already close to their theoretical limit in terms of performance, at least without changing their behavior or introducing memory unsafety. However, there is plenty of room for further compiler optimizations; current optimizations include only [LLVM](https://llvm.org/), [Morphic](https://www.youtube.com/watch?v=F3z39M0gdJU&t=3547s), and [Perceus](https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf). Promising examples of potential future optimizations include closure-aware [inlining](https://en.wikipedia.org/wiki/Inline_expansion), [automatic deforestation](https://www.cs.tufts.edu/~nr/cs257/archive/duncan-coutts/stream-fusion.pdf), and full [compile-time evaluation](https://en.wikipedia.org/wiki/Constant_folding) of top-level declarations. - -An open design question is how atomic reference counting should work in the context of platforms. Roc uses automatic reference counting (with some compile-time optimizations) to manage memory, and updating reference counts runs a lot faster if you don't need to guard against [data races](https://en.wikipedia.org/wiki/Race_condition#Data_race). If Roc data structures are shared across threads (which might or might not come up, depending on the platform), then in some cases [atomic](https://en.wikipedia.org/wiki/Linearizability#Primitive_atomic_instructions) reference counting might be necessary, which is slower. - -A design of "always use atomic reference counts" would be unnecessarily slow for platforms where atomicity isn't needed. A design of "never use atomic reference counts" would mean sharing Roc data structures across threads would always require deeply copying the entire data structure (which has worked well for Erlang, but which might not work as well for Roc). It seems likely that the best design is for the compiler to sometimes choose one and sometimes choose the other, but the exact design to go with is currently an open question. - -## Fast Feedback Loops - -The goal is for Roc to provide fast feedback loops by making builds normally feel "instant" except on truly massive projects. It's a concrete goal to have them almost always complete in under 1 second on the median computer being used to write Roc (assuming that system is not bogged down with other programs using up its resources), and ideally under the threshold at which humans typically find latency perceptible (around 100 milliseconds). Hot code loading can make the feedback loop even faster, by letting you see changes without having to restart your program. - -Note that although having fast "clean" builds (without the benefit of caching) is a goal, the "normally feels instant" goal refers to builds where caching was involved. After all, the main downside of build latency is that it comes up over and over in a feedback loop; the initial "clean" build comes up rarely by comparison. - -### Current Progress - -`roc check` type-checks your code and reports problems it finds. `roc build` also does this, but it additionally -builds a runnable binary of your program. You may notice that `roc build` takes much longer to complete! This is because -of two projects that are underway but not completed yet: -- *Development backend* refers to generating machine code directly instead of asking [LLVM](https://llvm.org/) to generate it. LLVM is great at generating optimized machine code, but it takes a long time to generate it—even if you turn off all the optimizations (and `roc` only has LLVM perform optimizations when the `--optimize` flag is set). The dev backend is currently implemented for WebAssembly, which you can see in the [Web REPL](https://www.roc-lang.org/repl), and in `roc repl` on x64 Linux. Work is underway to implement it for `roc build` and `roc run`, as well as macOS, Windows, and the ARM versions of all of these. -- *Surgical linking* refers to a fast way of combining the platform and application into one binary. Today, this works on Linux, Windows, and WebAssembly. `roc build` on macOS is noticeably slower because it falls back on non-surgical linking. - -Here's a table summarizing the current progress: - -Target | Dev backend | Surgical linking | ----------------------------|-------------|-------------------| -WebAssembly | yes | yes | -Linux x64 | repl only | yes | -Windows x64 | | yes | -macOS Intel (x64) | | | -Linux ARM | | | -Windows ARM | | | -macOS Apple Silicon (ARM) | | | - -Once we have full coverage, `roc build` (and `roc run` and `roc test`, which also perform builds) should take only a bit longer than `roc check`. - -The next major performance improvement will be caching. Currently, `roc` always builds everything from scratch. Most of the time, it could benefit from caching some of the work it had done in a previous build, but today it doesn't do that. There's a design for the caching system, but essentially none of the implementation has started yet. Hot code loading will be the next major improvement after caching, but it requires full dev backend coverage, and does not have a concrete design yet. diff --git a/www/wip_new_website/content/friendly.md b/www/wip_new_website/content/friendly.md deleted file mode 100644 index cc2cd2201b..0000000000 --- a/www/wip_new_website/content/friendly.md +++ /dev/null @@ -1,72 +0,0 @@ -# Friendly - -Roc prioritizes being a user-friendly language. This impacts the syntax, semantics, and tools Roc ships with. - -## Syntax and source code formatter - -Roc's syntax isn't trivial, but there also isn't much of it to learn. Its design is generally uncluttered and unambiguous. A goal is that you can normally look at a piece of code and quickly get an accurate mental model of what it means, without having to think through several layers of indirection. Here are some examples: - -- `x = combine y z` always declares a new constant `x` (Roc has [no mutable variables, reassignment, or shadowing](/functional)) to be whatever the `combine` function returns when passed the arguments `y` and `z`. (Function calls in Roc don't need parentheses or commas.) -- `user.email` always accesses the `email` field of a record named `user`. (Roc has no inheritance, subclassing, or proxying.) -- `Email.isValid` always refers to something named `isValid` exported by a module named `Email`. (Module names are always capitalized, and variables/constants never are.) Modules are always defined statically and can't be modified at runtime; there's no [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) to consider. -- `"My name is \(Str.trim name)"` uses *string interpolation* syntax: a backslash inside a string literal, followed by an expression in parentheses. This code is the same as combining the string `"My name is "` with the string returned by the function call `Str.trim name`. Because Roc's string interpolation syntax begins with a backslash (just like other backlash-escapes such as `\n` and `\"`), you can always tell which parts of a string involve special handling: the parts that begin with backslashes. Everything else works as normal. - -Roc also ships with a source code formatter that helps you maintain a consistent style with little effort. The `roc format` command neatly formats your source code according to a common style, and it's designed with the time-saving feature of having no configuration options. This feature saves you all the time that would otherwise be spent debating which stylistic tweaks to settle on! - -## Helpful compiler - -Roc's compiler is designed to help you out. It does complete type inference across all your code, and the type system is *sound*. This means you'll never get a runtime type mismatch if everything type-checked (including null exceptions; Roc doesn't have the [billion-dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)), and you also don't have to write any type annotations for the compiler to be able to infer all the types in your program. - -If there's a problem at compile time, the compiler is designed to report it in a helpful way. Here's an example: - -``` -── TYPE MISMATCH ────────────────── /home/my-roc-project/main.roc ─ - -Something is off with the `then` branch of this `if` expression: - -4│ someInteger : I64 -5│ someInteger = -6│ if someDecimal > 0 then -7│ someDecimal + 1 - ^^^^^^^^^^^^^^^ - -This branch is a fraction of type: - - Dec - -But the type annotation on `someInteger` says it should be: - - I64 - -Tip: You can convert between integers and fractions using functions like -`Num.toFrac` and `Num.round`. -``` - -If you like, you can run a program that has compile-time errors like this. (If the program reaches the error at runtime, it will crash.) This lets you do things like trying out code that's only partially finished, or running tests for one part of your code base while other parts have compile errors. (Note that this feature is only partially completed, and often errors out; it has a ways to go before it works for all compile errors!) - -## Serialization inference - -- Type inference is used for schema inference, but you can also spell it out if you like -- Reports errors immediately - -## Testing - -You can run `roc test` to run all your tests. Each test is declared with the `expect` keyword, and can be as short as one line. For example, this is a complete test: - -```roc -## One plus one should equal two. -expect 1 + 1 == 2 -``` - -If the test fails, `roc test` will show you the source code of the `expect`, along with the values of any named variables inside it, so you don't have to separately check what they were. If you write a documentation comment right before it (like `## One plus one should equal two` here), that will also be included in the test output, so you can use that to optionally describe the test if you want to. - -In the future, there are plans to add built-in support for [benchmarking](https://en.wikipedia.org/wiki/Benchmark_(computing)), [generative tests](https://en.wikipedia.org/wiki/Software_testing#Property_testing), [snapshot tests](https://en.wikipedia.org/wiki/Software_testing#Output_comparison_testing), simulated I/O (so you don't have to actually run the real I/O operations, but also don't have to change your code to accommodate the tests), and "reproduction replays"—tests generated from a recording of what actually happened during a particular run of your program, which deterministically simulate all the I/O that happened. - -- also note: future plan to cache tests so we only re-run tests whose answers could possibly have changed. also maybe note: tests that don't perform I/O are guaranteed not to flake b/c pure functions. - -## Future plans -- Package manager (Currently just URLs, content-hashed to make them immutable) with searchable index and no installation step, global cache of immutable downloads instead of per-project folders (no need to .gitignore anything) -- Step debugger with replay -- Customizable "linter" (e.g. code mods, project-specific rules to enforce) -- Editor plugin ecosystem that works across editors, where plugins ship with packages -- `roc edit` diff --git a/www/wip_new_website/content/functional.md b/www/wip_new_website/content/functional.md deleted file mode 100644 index 20a56ef278..0000000000 --- a/www/wip_new_website/content/functional.md +++ /dev/null @@ -1,123 +0,0 @@ -# Functional - -Roc is designed to have a small number of simple language primitives. This goal leads Roc to be a single-paradigm [functional](https://en.wikipedia.org/wiki/Functional_programming) language, while its [performance goals](/fast) lead to some design choices that are uncommon in functional languages. - -## Opportunistic mutation - -All Roc values are semantically immutable, but may be opportunistically mutated behind the scenes when it would improve performance (without affecting the program's behavior). For example: - -```roc -colors -|> Set.insert "Purple" -|> Set.insert "Orange" -|> Set.insert "Blue" -``` - -The [`Set.insert`](https://www.roc-lang.org/builtins/Set#insert) function takes a `Set` and returns a `Set`. The returned one has the given value inserted into it. Knowing this, it might seem like these three `Set.insert` calls would result in the creation of three brand-new sets, but Roc's *opportunistic mutation* optimizations mean this will be much more efficient than that. - -Opportunistic mutation works by detecting when a semantically immutable value can be safely mutated in-place without changing the behavior of the program. If `colors` is *unique* here—that is, nothing else is currently referencing it, and nothing else will reference it before it goes out of scope—then `Set.insert` will mutate it and then return it. Cloning it first would have no benefit, because nothing in the program could possibly tell the difference! - -If `colors` is not unique, then the first call to `Set.insert` will not mutate it. Instead, it will clone `colors`, insert `"Purple"` into the clone, and then return that. At that point, since the clone will be unique (nothing else is referencing it, since it was just created, and the only thing that will reference it in the future is the `Set.insert` function it's handed off to), the subsequent `Set.insert` calls will all mutate in-place. Roc has ways of detecting uniqueness at compile time, so this optimization will often have no runtime cost, but in some cases it instead uses automatic reference counting to tell when something that was previously shared has become unique over the course of the running program. - -## Everything is immutable (semantically) - -This design means that all Roc values are semantically immutable, even though they can still benefit from the performance of in-place mutation. In many languages, this is reversed; everything is mutable, and it's up to the programmer to "defensively" clone wherever modification is undesirable. Roc's approach means that cloning happens automatically, which can be less error-prone than defensive cloning (which might be accidentally forgotten), but which—to be fair—can also increase unintentional cloning. It's a different default with different tradeoffs. - -A performance benefit of this design compared to having direct mutation primitives is that it lets Roc rule out [reference cycles](https://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles). Any language which supports direct mutation can have reference cycles. Detecting these cycles automatically at runtime has a cost (which has similar characteristics to the cost of a tracing garbage collector), and failing to detect them can result in memory leaks. Roc's automatic reference counting neither pays for runtime cycle collection nor memory leaks from cycles, because the language's lack of direct mutation primitives lets it rule out reference cycles at language design time. - -An ergonomics benefit of having no direct mutation primitives is that functions in Roc tend to be chainable by default. For example, consider the `Set.insert` function. In many languages, this function would be written to accept a `Set` to mutate, and then return nothing. In contrast, in Roc it will necessarily be written to return a (potentially) new `Set`, even if in-place mutation will end up happening anyway if it's unique. - -This makes Roc functions naturally amenable to pipelining, as we saw in the earlier example: - -```roc -colors -|> Set.insert "Purple" -|> Set.insert "Orange" -|> Set.insert "Blue" -``` - -To be fair, direct mutation primitives have benefits too. Some algorithms are more concise or otherwise easier to read when written with direct mutation, and direct mutation can make the performance characteristics of some operations clearer. - -As such, Roc's opportunistic mutation design means that reference cycles can be ruled out, and functions will tend to be more ameanable for chaining, but also that some algorithms will be harder to express, and that performance optimization will likely tend to involve more profiling. These tradeoffs fit well with the language's overall design goals. - -## No reassignment or shadowing - -In some languages, the following is allowed. - -``` -x = 1 -x = 2 -``` - -In Roc, this will give a compile-time error. Once a name has been assigned to a value, nothing in the same scope can assign it again. (This includes [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing), which is disallowed.) This can make Roc code easier to read, because the answer to the question "might this have a different value at some later point in the scope?" is always "no." That said, this can also make Roc code take longer to write, because of needing to come up with unique names to avoid shadowing, although pipelining (as shown in the previous section) reduces how often intermediate values need to be named. - -A benefit of this design is that it makes Roc code easier to rearrange without causing regressions. Consider this code: - -```roc -func = \arg -> - greeting = "Hello" - welcome = \name -> "\(greeting), \(name)!" - # … - message = welcome "friend" - # … -``` - -Suppose I decide to extract the `welcome` function to the top level, so I can reuse it elsewhere: - -```roc -func = \arg -> - # … - message = welcome "Hello" "friend" - # … - -welcome = \prefix, name -> "\(prefix), \(name)!" -``` - -Without knowing the rest of `func`, we can be confident this change will not alter the code's behavior. In contrast, suppose Roc allowed reassignment. Then it's possible something in the `# …` parts of the code could have modified `greeting` before it was used in the `message =` declaration. For example: - -```roc -func = \arg -> - greeting = "Hello" - welcome = \name -> "\(greeting), \(name)!" - # … - if someCondition then - greeting = "Hi" - # … - else - # … - # … - message = welcome "friend" - # … -``` - -In this example, if we didn't read the whole function to see that `greeting` was later sometimes (but not always) changed from `"Hello"` to `"Hi"`, we might not have realized that changing it to `message = welcome "Hello" "friend"` would cause a regression due to having the greeting always be `"Hello"`. Because Roc disallows reassignment, this particular regression can't happen, and so the code can be confidently rearranged without checking the rest of the function. - -Even if Roc disallowed reassignment but allowed shadowing, a similar regression could happen if the `welcome` function were shadowed between when it was defined here and when `message` later called it in the same scope. Because Roc allows neither shadowing nor reassignment, these regressions can't happen, and rearranging code can be done with more confidence. - -In fairness, reassignment has benefits too. For example, using it with [early-exit control flow operations](https://en.wikipedia.org/wiki/Control_flow#Early_exit_from_loops) such as a `break` keyword can be a nice way to represent certain types of logic without incurring extra runtime overhead. Roc does not have these operations; looping is done either with convenience functions like [`List.walkUntil`](https://www.roc-lang.org/builtins/List#walkUntil) or with recursion (Roc implements [tail-call optimization](https://en.wikipedia.org/wiki/Tail_call), including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)), but early-exit operators can potentially make some code easier to follow (and potentially even slightly more efficient) when used in scenarios where breaking out of nested loops with a single instruction is desirable. - -## Managed effects over side effects - -Many languages support first-class [asynchronous](https://en.wikipedia.org/wiki/Asynchronous_I/O) effects, which can improve a system's throughput (usually at the cost of some latency) especially in the presence of long-running I/O operations like network requests. Asynchronous effects are commonly represented by a value such as a [Promise or Future](https://en.wikipedia.org/wiki/Futures_and_promises) (Roc calls these Tasks), which represent an effect to be performed. These values can be composed together, potentially while customizing their concurrency properties and supporting I/O interruptions like cancellation and timeouts. - -Most languages also have a separate system for synchronous effects, namely [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). Having demand for two ways to perform the same effect can lead to a great deal of duplication across a language's ecosystem. - -Instead of having [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), Roc functions exclusively use *managed effects* in which they return descriptions of effects to run, in the form of Tasks. Tasks can be composed and chained together, until they are ultimately handed off (usually via a `main` function or something similar) to an effect runner outside the program, which actually performs the effects the tasks describe. - -Having only (potentially asynchronous) *managed effects* and no (synchronous) *side effects* both simplifies the language's ecosystem and makes certain guarantees possible. For example, the combination of managed effects and semantically immutable values means all Roc functions are [pure](https://en.wikipedia.org/wiki/Pure_function)—that is, they have no side effects and always return the same answer when called with the same arguments. - -## Pure functions - -Pure functions have a number of implementation benefits, such as [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency) and being trivial to [memoize](https://en.wikipedia.org/wiki/Memoization). They also have testing benefits; for example, Roc tests which either use simulated effects (or which do not involve tasks at all) never flake. They either consistently pass or consistently fail. Because of this, their results can be cached, so `roc test` can skip re-running them unless their source code (including dependencies) changed. (This caching has not yet been implemented, but is planned.) - -Roc does support [tracing](https://en.wikipedia.org/wiki/Tracing_(software)) via the `dbg` keyword, an essential [debugging](https://en.wikipedia.org/wiki/Debugging) tool which is unusual among side effects in that—similarly to opportunistic mutation—using it should not affect the behavior of the program. As such, it usually does not impact the guarantees of pure functions aside from potentially impeding optimizations. (An example of an exception to this would be if a program sent its `dbg` traces to log files which the program itself then read back in. This is not recommended.) - -Pure functions are notably amenable to compiler optimizations. Roc already takes advantage of them to implement [function-level dead code elimination](https://elm-lang.org/news/small-assets-without-the-headache). Here are some other examples of optimizations that will benefit from this in the future; these are planned, but not yet implemented: - -- [Loop fusion](https://en.wikipedia.org/wiki/Loop_fission_and_fusion), which can do things like combining consecutive `List.map` calls (potentially intermingled with other operations that traverse the list) into one pass over the list. -- [Compile-time evaluation](https://en.wikipedia.org/wiki/Compile-time_function_execution), which basically takes [constant folding](https://en.wikipedia.org/wiki/Constant_folding) to its natural limit: anything that can be evaluated at compile time is evaluated then. This saves work at runtime, and is easy to opt out of: if you want evaluation to happen at runtime, you can instead wrap the logic in a function and call it as needed. -- [Hoisting](https://en.wikipedia.org/wiki/Loop-invariant_code_motion), which moves certain operations outside loops to prevent them from being re-evaluated unnecessarily on each step of the loop. It's always safe to hoist calls to pure functions, and in some cases they can be hoisted all the way to the top level, at which point they become eligible for compile-time evaluation. - -## Get started - -If this design sounds interesting to you, you can give Roc a try by heading over to the [tutorial](https://www.roc-lang.org/tutorial)! diff --git a/www/wip_new_website/content/install.md b/www/wip_new_website/content/install.md deleted file mode 100644 index e6cb3e9e30..0000000000 --- a/www/wip_new_website/content/install.md +++ /dev/null @@ -1,80 +0,0 @@ -# Install Roc - -Roc is a work in progress. It doesn't have a numbered release yet, but it does have nightly builds that you can download. - -There are currently a few OS-specific issues: -* macOS: There are no known compatibility issues, but the compiler doesn't run as fast as it does on Linux or Windows, because we don't (yet) do our own linking like we do on those targets. (Linking works similarly on Linux and Windows, but the way macOS does it is both different and significantly more complicated.) -* Windows: There are some known Windows-specific compiler bugs, and probably some other unknown ones because more people have tried out Roc on Mac and Linux than on Windows. -* Linux: The nightlies are built with glibc, so they aren't usable on distros that don't use (dynamically linked) glibc, like Alpine or NixOS. In the future we plan to build Linux releases with [musl libc](https://wiki.musl-libc.org/) to address this, but this requires [building LLVM from source with musl](https://wiki.musl-libc.org/building-llvm.html). -* Other operating systems: Roc has not been built on any other operating systems. Building from source on them might work, but hasn't been tried. - - - -- [Linux x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/linux_x86_64.md) -- [MacOS Apple Silicon](https://github.com/roc-lang/roc/blob/main/getting_started/macos_apple_silicon.md) -- [MacOS x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/macos_x86_64.md) -- [Windows](https://github.com/roc-lang/roc/blob/main/getting_started/windows.md) -- [Other Systems](https://github.com/roc-lang/roc/blob/main/getting_started/other.md) - -## Nightly - - - -## Roc CLI - - -- Script `roc myApp.roc` -- Develop `roc dev` -- Test `roc test` -- Run `roc run` -- Build `roc build` -- Format `roc format` -- Documentation `roc docs` - -## Package Management - -You can include packages using an URL: - -```roc -app "hello" - packages { - # basic-cli platform - pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br", - # json package - # TODO update to json 0.3.0 - json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.2.0/gh4zvR8xyEsef0R961Fcv5vxFEZJ-GJF-7bQwgL2Xz8.tar.br", - } - imports [ - pf.Stdout, - json.Core, - ] - provides [main] to pf -``` - -A full Roc package manager will be developed in the future. - - - -## Editor Support - - - -- Language Server -- Neo(Vim) diff --git a/www/wip_new_website/content/tutorial.md b/www/wip_new_website/content/tutorial.md deleted file mode 100644 index 5024800a9d..0000000000 --- a/www/wip_new_website/content/tutorial.md +++ /dev/null @@ -1,1989 +0,0 @@ -# Tutorial - -This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and much more! - -## [Strings and Numbers](#strings-and-numbers) {#strings-and-numbers} - -Let's start by getting acquainted with Roc's [_Read-Eval-Print-Loop_](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), or **REPL** for short. Run this in a terminal: - -roc repl - -If Roc is [installed](/wip/install.html), you should see this: - -
    The rockin’ roc repl
    - -So far, so good! - -### [Hello, World!](#hello-world) {#hello-world} - -Try typing this in the REPL and pressing Enter: - -"Hello, World!" - -The REPL should cheerfully display the following: - -
    "Hello, World!" : Str
    - -Congratulations! You've just written your first Roc code. - -### [Naming Things](#naming-things) {#naming-things} - -When you entered the _expression_ `"Hello, World!"`, the REPL printed it back out. It also printed `: Str`, because `Str` is that expression's type. We'll talk about types later; for now, let's ignore the `:` and whatever comes after it whenever we see them. - -Let's try that out. Put this into the repl and press Enter: - -
    val1
    - -You should see the same `"Hello, World!"` line as before. - -You can also assign specific names to expressions. Try entering these lines: - -
    greeting = "Hi"
    -
    audience = "World"
    - -From now until you exit the REPL, you can refer to either `greeting` or `audience` by those names! We'll use these later on in the tutorial. - -### [Arithmetic](#arithmetic) {#arithmetic} - -Now let's try using an _operator_, specifically the `+` operator. Enter this: - -
    1 + 1
    - -You should see this output: - -
    2 : Num *                # val2
    - -According to the REPL, one plus one equals two. Sounds right! - -Roc will respect [order of operations](https://en.wikipedia.org/wiki/Order_of_operations) when using multiple arithmetic operators like `+` and `-`, but you can use parentheses to specify exactly how they should be grouped. - -
    1 + 2 * (3 - 4)
    -
    --1 : Num *
    -
    - -### [Calling Functions](#calling-functions) {#calling-functions} - -Remember back in the [string interpolation](#string-interpolation) section when we mentioned other ways to combine strings? Here's one of them: - -
    Str.concat "Hi " "there!"
    -
    -"Hi there!" : Str
    -
    - -Here we're calling the `Str.concat` function and passing two arguments: the string `"Hi "` and the string `"there!"`. This _concatenates_ the two strings together (that is, it puts one after the other) and returns the resulting combined string of `"Hi there!"`. - -Note that in Roc, we don't need parentheses or commas to call functions. We don't write `Str.concat("Hi ", "there!")` but rather `Str.concat "Hi " "there!"`. - -That said, just like in the arithmetic example above, we can use parentheses to specify how nested function calls should work. For example, we could write this: - -
    Str.concat "Birds: " (Num.toStr 42)
    -
    -"Birds: 42" : Str
    -
    - -This calls `Num.toStr` on the number `42`, which converts it into the string `"42"`, and then passes that string as the second argument to `Str.concat`. - -The parentheses are important here to specify how the function calls nest. Try removing them, and see what happens: - -
    Str.concat "Birds: " Num.toStr 42
    -
    -<error>
    -
    - -The error tells us that we've given `Str.concat` too many arguments. Indeed we have! We've passed it three arguments: - -1. The string `"Birds"` -2. The function `Num.toStr` -3. The number `42` - -That's not what we intended to do. Putting parentheses around the `Num.toStr 42` call clarifies that we want it to be evaluated as its own expression, rather than being two arguments to `Str.concat`. - -Both the `Str.concat` function and the `Num.toStr` function have a dot in their names. In `Str.concat`, `Str` is the name of a _module_, and `concat` is the name of a function inside that module. Similarly, `Num` is a module, and `toStr` is a function inside that module. - -We'll get into more depth about modules later, but for now you can think of a module as a named collection of functions. Eventually we'll discuss how to use them for more than that. - -### [String Interpolation](#string-interpolation) {#string-interpolation} - -An alternative syntax for `Str.concat` is _string interpolation_, which looks like this: - -
    "\(greeting) there, \(audience)!"
    - -This is syntax sugar for calling `Str.concat` several times, like so: - -```roc -Str.concat greeting (Str.concat " there, " (Str.concat audience "!")) -``` - -You can put entire single-line expressions inside the parentheses in string interpolation. For example: - -
    "Two plus three is: \(Num.toStr (2 + 3))"
    - -By the way, there are many other ways to put strings together! Check out the [documentation](https://www.roc-lang.org/builtins/Str) for the `Str` module for more. - -## [Building an Application](#building-an-application) {#building-an-application} - -Let's move out of the REPL and create our first Roc application! - -Make a file named `main.roc` and put this in it: - -```roc -app "hello" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } - imports [pf.Stdout] - provides [main] to pf - -main = - Stdout.line "I'm a Roc application!" -``` - -Try running this with: - -roc dev - -You should see a message about a file being downloaded, followed by this: - -I'm a Roc application! - -Congratulations, you've written your first Roc application! We'll go over what the parts above `main` do later, but let's play around a bit first. - -### [Defs](#defs) {#defs} - -Try replacing the `main` line with this: - -```roc -birds = 3 - -iguanas = 2 - -total = Num.toStr (birds + iguanas) - -main = - Stdout.line "There are \(total) animals." -``` - -Now run `roc dev` again. This time the "Downloading ..." message won't appear; the file has been cached from last time, and won't need to be downloaded again. - -You should see this: - -There are 5 animals. - -`main.roc` now has four definitions (_defs_ for short) `birds`, `iguanas`, `total`, and `main`. - -A definition names an expression. - -- The first two defs assign the names `birds` and `iguanas` to the expressions `3` and `2`. -- The next def assigns the name `total` to the expression `Num.toStr (birds + iguanas)`. -- The last def assigns the name `main` to an expression which returns a `Task`. We'll [discuss tasks later](#tasks). - -Once we have a def, we can use its name in other expressions. For example, the `total` expression refers to `birds` and `iguanas`, and `Stdout.line "There are \(total) animals."` refers to `total`. - -You can name a def using any combination of letters and numbers, but they have to start with a letter. - -**Note:** Defs are constant; they can't be reassigned. We'd get an error if we wrote these two defs in the same scope: - -```roc -birds = 3 -birds = 2 -``` - -### [Defining Functions](#defining-functions) {#defining-functions} - -So far we've called functions like `Num.toStr`, `Str.concat`, and `Stdout.line`. Next let's try defining a function of our own. - -```roc -birds = 3 - -iguanas = 2 - -total = addAndStringify birds iguanas - -main = - Stdout.line "There are \(total) animals." - -addAndStringify = \num1, num2 -> - Num.toStr (num1 + num2) -``` - -This new `addAndStringify` function we've defined accepts two numbers, adds them, calls `Num.toStr` on the result, and returns that. - -The `\num1, num2 ->` syntax defines a function's arguments, and the expression after the `->` is the body of the function. Whenever a function gets called, its body expression gets evaluated and returned. - -### [if-then-else](#if-then-else) {#if-then-else} - -Let's modify this function to return an empty string if the numbers add to zero. - -```roc -addAndStringify = \num1, num2 -> - sum = num1 + num2 - - if sum == 0 then - "" - else - Num.toStr (num1 + num2) -``` - -We did two things here: - -- We introduced a _local def_ named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `addAndStringify`, it's _local_ to that scope and can't be accessed outside that function. -- We added an `if`\-`then`\-`else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`. - -Every `if` must be accompanied by both `then` and also `else`. Having an `if` without an `else` is an error, because `if` is an expression, and all expressions must evaluate to a value. If there were ever an `if` without an `else`, that would be an expression that might not evaluate to a value! - -### [else if](#else-if) {#else-if} - -We can combine `if` and `else` to get `else if`, like so: - -```roc -addAndStringify = \num1, num2 -> - sum = num1 + num2 - - if sum == 0 then - "" - else if sum < 0 then - "negative" - else - Num.toStr (num1 + num2) -``` - -Note that `else if` is not a separate language keyword! It's just an `if`/`else` where the `else` branch contains another `if`/`else`. This is easier to see with different indentation: - -```roc -addAndStringify = \num1, num2 -> - sum = num1 + num2 - - if sum == 0 then - "" - else - if sum < 0 then - "negative" - else - Num.toStr (num1 + num2) -``` - -This differently-indented version is equivalent to writing `else if sum < 0 then` on the same line, although the convention is to use the original version's style. - -### [Comments](#comments) {#comments} - -This is a comment in Roc: - -```roc -# The 'name' field is unused by addAndStringify -``` - -Whenever you write `#` it means that the rest of the line is a comment, and will not affect the -running program. Roc does not have multiline comment syntax. - -### [Doc Comments](#doc-comments) {#doc-comments} - -Comments that begin with `##` are "doc comments" which will be included in generated documentation (`roc docs`). They can include code blocks by adding five spaces after `##`. - -```roc -## This is a comment for documentation, and includes a code block. -## -## x = 2 -## expect x == 2 -``` - -Like other comments, doc comments do not affect the running program. - -## [Debugging](#debugging) {#debugging} - -[Print debugging](https://en.wikipedia.org/wiki/Debugging#Techniques) is the most common debugging technique in the history of programming, and Roc has a `dbg` keyword to facilitate it. Here's an example of how to use `dbg`: - -```roc -pluralize = \singular, plural, count -> - dbg count - - if count == 1 then - singular - else - plural -``` - -Whenever this `dbg` line of code is reached, the value of `count` will be printed to [stderr](), along with the source code file and line number where the `dbg` itself was written: - -[pluralize.roc 6:8] 5 - -Here, `[pluralize.roc 6:8]` tells us that this `dbg` was written in the file `pluralize.roc` on line 6, column 8. - -You can give `dbg` any expression you like, for example: - -```roc -dbg Str.concat singular plural -``` - -An easy way to print multiple values at a time is to wrap them in a tag, for example a concise tag like `T`: - -```roc -dbg T "the value of count is:" count -``` - -> **Note:** `dbg` is a debugging tool, and is only available when running your program via a `roc` subcommand (for example using `roc dev`, `roc run`, or `roc test`). When you build a standalone application with `roc build`, any uses of `dbg` won't be included! - -## [Records](#records) {#records} - -Currently our `addAndStringify` function takes two arguments. We can instead make it take one argument like so: - -```roc -total = addAndStringify { birds: 5, iguanas: 7 } - -addAndStringify = \counts -> - Num.toStr (counts.birds + counts.iguanas) -``` - -The function now takes a _record_, which is a group of named values. Records are not [objects](https://en.wikipedia.org/wiki/Object_(computer_science)); they don't have methods or inheritance, they just store information. - -The expression `{ birds: 5, iguanas: 7 }` defines a record with two _fields_ (the `birds` field and the `iguanas` field) and then assigns the number `5` to the `birds` field and the number `7` to the `iguanas` field. Order doesn't matter with record fields; we could have also specified `iguanas` first and `birds` second, and Roc would consider it the exact same record. - -When we write `counts.birds`, it accesses the `birds` field of the `counts` record, and when we write `counts.iguanas` it accesses the `iguanas` field. - -When we use [`==`](/builtins/Bool#isEq) on records, it compares all the fields in both records with [`==`](/builtins/Bool#isEq), and only considers the two records equal if all of their fields are equal. If one record has more fields than the other, or if the types associated with a given field are different between one field and the other, the Roc compiler will give an error at build time. - -> **Note:** Some other languages have a concept of "identity equality" that's separate from the "structural equality" we just described. Roc does not have a concept of identity equality; this is the only way equality works! - -### [Accepting extra fields](#accepting-extra-fields) {#accepting-extra-fields} - -The `addAndStringify` function will accept any record with at least the fields `birds` and `iguanas`, but it will also accept records with more fields. For example: - -```roc -total = addAndStringify { birds: 5, iguanas: 7 } - -# The `note` field is unused by addAndStringify -totalWithNote = addAndStringify { birds: 4, iguanas: 3, note: "Whee!" } - -addAndStringify = \counts -> - Num.toStr (counts.birds + counts.iguanas) -``` - -This works because `addAndStringify` only uses `counts.birds` and `counts.iguanas`. If we were to use `counts.note` inside `addAndStringify`, then we would get an error because `total` is calling `addAndStringify` passing a record that doesn't have a `note` field. - -### [Record shorthands](#record-shorthands) {#record-shorthands} - -Roc has a couple of shorthands you can use to express some record-related operations more concisely. - -Instead of writing `\record -> record.x` we can write `.x` and it will evaluate to the same thing: a function that takes a record and returns its `x` field. You can do this with any field you want. For example: - -```roc -# returnFoo is a function that takes a record -# and returns the `foo` field of that record. -returnFoo = .foo - -returnFoo { foo: "hi!", bar: "blah" } -# returns "hi!" -``` - -Sometimes we assign a def to a field that happens to have the same name—for example, `{ x: x }`. -In these cases, we shorten it to writing the name of the def alone—for example, `{ x }`. We can do this with as many fields as we like; here are several different ways to define the same record: - -- `{ x: x, y: y }` -- `{ x, y }` -- `{ x: x, y }` -- `{ x, y: y }` - -### [Record destructuring](#record-destructuring) {#record-destructuring} - -We can use _destructuring_ to avoid naming a record in a function argument, instead giving names to its individual fields: - -```roc -addAndStringify = \{ birds, iguanas } -> - Num.toStr (birds + iguanas) -``` - -Here, we've _destructured_ the record to create a `birds` def that's assigned to its `birds` field, and an `iguanas` def that's assigned to its `iguanas` field. We can customize this if we like: - -```roc -addAndStringify = \{ birds, iguanas: lizards } -> - Num.toStr (birds + lizards) -``` - -In this version, we created a `lizards` def that's assigned to the record's `iguanas` field. (We could also do something similar with the `birds` field if we like.) - -Finally, destructuring can be used in defs too: - -```roc -{ x, y } = { x: 5, y: 10 } -``` - -### [Making records from other records](#making-records-from-other-records) {#making-records-from-other-records} - -So far we've only constructed records from scratch, by specifying all of their fields. We can also construct new records by using another record to use as a starting point, and then specifying only the fields we want to be different. For example, here are two ways to get the same record: - -```roc -original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 } -fromScratch = { birds: 4, zebras: 2, iguanas: 3, goats: 1 } -fromOriginal = { original & birds: 4, iguanas: 3 } -``` - -The `fromScratch` and `fromOriginal` records are equal, although they're defined in different ways. - -- `fromScratch` was built using the same record syntax we've been using up to this point. -- `fromOriginal` created a new record using the contents of `original` as defaults for fields that it didn't specify after the `&`. - -Note that `&` can't introduce new fields to a record, or change the types of existing fields. -(Trying to do either of these will result in an error at build time!) - -## [Optional Record Fields](#optional-record-fields) {#optional-record-fields} - -Roc supports optional record fields using the `?` operator. This can be a useful pattern where you pass a function a record of configuration values, some of which you'd like to provide defaults for. - -In Roc you can write a function like: - -```roc -table = \{ - height, - width, - title? "oak", - description? "a wooden table" - } - -> -``` - -This is using *optional field destructuring* to destructure a record while -also providing default values for any fields that might be missing. - -Here's the type of `table`: - -```roc -table : - { - height : Pixels, - width : Pixels, - title ? Str, - description ? Str, - } - -> Table -``` - -This says that `table` takes a record with two *required* fields, `height` and -`width`, and two *optional* fields, `title` and `description`. It also says that -the `height` and `width` fields have the type `Pixels`, a type alias for some -numeric type, and the `title` and `description` fields have the type `Str`. -This means you can choose to omit the `title`, `description`, or both fields, when calling the function... but if you provide them, they must have the type `Str`. - -This is also the type that would have been inferred for `table` if no annotation -had been written. Roc's compiler can tell from the destructuring syntax -`title ? ""` that `title` is an optional field, and that it has the type `Str`. -These default values can reference other expressions in the record destructure; if you wanted, you could write `{ height, width, title ? "", description ? Str.concat "A table called " title }`. - -Destructuring is the only way to implement a record with optional fields. For example, if you write the expression `config.title` and `title` is an -optional field, you'll get a compile error. - -This means it's never possible to end up with an *optional value* that exists -outside a record field. Optionality is a concept that exists only in record -fields, and it's intended for the use case of config records like this. The -ergonomics of destructuring mean this wouldn't be a good fit for data modeling, consider using a `Result` type instead. - -## [Tags](#tags) {#tags} - -Sometimes we want to represent that something can have one of several values. For example: - -```roc -stoplightColor = - if something > 0 then - Red - else if something == 0 then - Yellow - else - Green -``` - -Here, `stoplightColor` can have one of three values: `Red`, `Yellow`, or `Green`. The capitalization is very important! If these were lowercase (`red`, `yellow`, `green`), then they would refer to defs. However, because they are capitalized, they instead refer to _tags_. - -A tag is a literal value just like a number or a string. Similarly to how I can write the number `42` or the string `"forty-two"` without defining them first, I can also write the tag `FortyTwo` without defining it first. Also, similarly to how `42 == 42` and `"forty-two" == "forty-two"`, it's also the case that `FortyTwo == FortyTwo`. - -Let's say we wanted to turn `stoplightColor` from a `Red`, `Green`, or `Yellow` into a string. Here's one way we could do that: - -```roc -stoplightStr = - if stoplightColor == Red then - "red" - else if stoplightColor == Green then - "green" - else - "yellow" -``` - -We can express this logic more concisely using `when`/`is` instead of `if`/`then`: - -```roc -stoplightStr = - when stoplightColor is - Red -> "red" - Green -> "green" - Yellow -> "yellow" -``` - -This results in the same value for `stoplightStr`. In both the `when` version and the `if` version, we have three conditional branches, and each of them evaluates to a string. The difference is how the conditions are specified; here, we specify between `when` and `is` that we're making comparisons against `stoplightColor`, and then we specify the different things we're comparing it to: `Red`, `Green`, and `Yellow`. - -Besides being more concise, there are other advantages to using `when` here. - -1. We don't have to specify an `else` branch, so the code can be more self-documenting about exactly what all the options are. -2. We get more compiler help. If we try deleting any of these branches, we'll get a compile-time error saying that we forgot to cover a case that could come up. For example, if we delete the `Green ->` branch, the compiler will say that we didn't handle the possibility that `stoplightColor` could be `Green`. It knows this because `Green` is one of the possibilities in our `stoplightColor = if ...` definition. - -We can still have the equivalent of an `else` branch in our `when` if we like. Instead of writing `else`, we write `_ ->` like so: - -```roc -stoplightStr = - when stoplightColor is - Red -> "red" - _ -> "not red" -``` - -This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not in the way we'd decide to if the compiler had drawn our attention to it! - -We can make this `when` _exhaustive_ (that is, covering all possibilities) without using `_ ->` by using `|` to specify multiple matching conditions for the same branch: - -```roc -stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow -> "not red" -``` - -You can read `Green | Yellow` as "either `Green` or `Yellow`". By writing it this way, if we introduce the possibility that `stoplightColor` can be `Orange`, we'll get a compiler error telling us we forgot to cover that case in this `when`, and then we can handle it however we think is best. - -We can also combine `if` and `when` to make branches more specific: - -```roc -stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow if contrast > 75 -> "not red, but very high contrast" - Green | Yellow if contrast > 50 -> "not red, but high contrast" - Green | Yellow -> "not red" -``` - -This will give the same answer for `stoplightStr` as if we had written the following: - -```roc -stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow -> - if contrast > 75 then - "not red, but very high contrast" - else if contrast > 50 then - "not red, but high contrast" - else - "not red" -``` - -Either style can be a reasonable choice depending on the circumstances. - -### [Tags with payloads](#tags-with-payloads) {#tags-with-payloads} - -Tags can have _payloads_—that is, values inside them. For example: - -```roc -stoplightColor = - if something > 100 then - Red - else if something > 0 then - Yellow - else if something == 0 then - Green - else - Custom "some other color" - -stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow -> "not red" - Custom description -> description -``` - -This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. - -1. We sometimes chose to set `stoplightColor` to be `Custom "some other color"`. When we did this, we gave the `Custom` tag a _payload_ of the string `"some other color"`. -2. We added a `Custom` tag in our `when`, with a payload which we named `description`. Because we did this, we were able to refer to `description` in the body of the branch (that is, the part after the `->`) just like a def or a function argument. - -Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to _destructure_ the record, and then accessed these `r`, `g`, and `b` defs after the `->` instead. - -A tag can also have a payload with more than one value. Instead of `Custom { r: 40, g: 60, b: 80 }` we could write `Custom 40 60 80`. If we did that, then instead of destructuring a record with `Custom { r, g, b } ->` inside a `when`, we would write `Custom r g b ->` to destructure the values directly out of the payload. - -We refer to whatever comes before a `->` in a `when` expression as a _pattern_—so for example, in the `Custom description -> description` branch, `Custom description` would be a pattern. In programming, using patterns in branching conditionals like `when` is known as [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching). You may hear people say things like "let's pattern match on `Custom` here" as a way to suggest making a `when` branch that begins with something like `Custom description ->`. - -### [Pattern Matching on Lists](#pattern-matching-on-lists) {#pattern-matching-on-lists} - -You can also pattern match on lists, like so: - -```roc -when myList is - [] -> 0 # the list is empty - [Foo, ..] -> 1 # it starts with a Foo tag - [_, ..] -> 2 # it contains at least one element, which we ignore - [Foo, Bar, ..] -> 3 # it starts with a Foo tag followed by a Bar tag - [Foo, Bar, Baz] -> 4 # it has exactly 3 elements: Foo, Bar, and Baz - [Foo, a, ..] -> 5 # its first element is Foo, and its second we name `a` - [Ok a, ..] -> 6 # it starts with an Ok containing a payload named `a` - [.., Foo] -> 7 # it ends with a Foo tag - [A, B, .., C, D] -> 8 # it has certain elements at the beginning and end -``` - -This can be both more concise and more efficient (at runtime) than calling [`List.get`](https://www.roc-lang.org/builtins/List#get) multiple times, since each call to `get` requires a separate conditional to handle the different `Result`s they return. - -> **Note:** Each list pattern can only have one `..`, which is known as the "rest pattern" because it's where the _rest_ of the list goes. - -## [Booleans](#booleans) {#booleans} - -In many programming languages, `true` and `false` are special language keywords that refer to the two [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) values. In Roc, booleans do not get special keywords; instead, they are exposed as the ordinary values `Bool.true` and `Bool.false`. - -This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended to be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`&&`, `||`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear. - -As an example of why tags are encouraged for data modeling, in many languages it would be common to write a record like `{ name: "Richard", isAdmin: Bool.true }`, but in Roc it would be preferable to write something like `{ name: "Richard", role: Admin }`. At first, the `role` field might only ever be set to `Admin` or `Normal`, but because the data has been modeled using tags instead of booleans, it's much easier to add other alternatives in the future, like `Guest` or `Moderator` - some of which might also want payloads. - -## [Lists](#lists) {#lists} - -Another thing we can do in Roc is to make a _list_ of values. Here's an example: - -```roc -names = ["Sam", "Lee", "Ari"] -``` - -This is a list with three elements in it, all strings. We can add a fourth element using `List.append` like so: - -```roc -List.append names "Jess" -``` - -This returns a **new** list with `"Jess"` after `"Ari"`, and doesn't modify the original list at all. All values in Roc (including lists, but also records, strings, numbers, and so on) are immutable, meaning whenever we want to "change" them, we want to instead pass them to a function which returns some variation of what was passed in. - -### [List.map](#list-map) {#list-map} - -A common way to transform one list into another is to use `List.map`. Here's an example of how to use it: - -```roc -List.map [1, 2, 3] \num -> num * 2 -``` - -This returns `[2, 4, 6]`. - -`List.map` takes two arguments: - -1. An input list -2. A function that will be called on each element of that list - -It then returns a list which it creates by calling the given function on each element in the input list. In this example, `List.map` calls the function `\num -> num * 2` on each element in `[1, 2, 3]` to get a new list of `[2, 4, 6]`. - -We can also give `List.map` a named function, instead of an anonymous one: - -```roc -List.map [1, 2, 3] Num.isOdd -``` - -This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns `Bool.true` and `Num.isOdd 2` returns `Bool.false`. - -As such, calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[Bool.true, Bool.false, Bool.true]`. - -### [List element type compatibility](#list-element-type-compatibility) {#list-element-type-compatibility} - -If we tried to give `List.map` a function that didn't work on the elements in the list, then we'd get an error at compile time. Here's a valid, and then an invalid example: - -```roc -# working example -List.map [-1, 2, 3, -4] Num.isNegative -# returns [Bool.true, Bool.false, Bool.false, Bool.true] -``` - -```roc -# invalid example -List.map ["A", "B", "C"] Num.isNegative -# error: isNegative doesn't work on strings! -``` - -Because `Num.isNegative` works on numbers and not strings, calling `List.map` with `Num.isNegative` and a list of numbers works, but doing the same with a list of strings doesn't work. - -This wouldn't work either: - -```roc -List.map ["A", "B", "C", 1, 2, 3] Num.isNegative -``` - -Every element in a Roc list has to share the same type. For example, we can have a list of strings like `["Sam", "Lee", "Ari"]`, or a list of numbers like `[1, 2, 3, 4, 5]` but we can't have a list which mixes strings and numbers like `["Sam", 1, "Lee", 2, 3]`, that would be a compile-time error. - -Ensuring that all elements in a list share a type eliminates entire categories of problems. For example, it means that whenever you use `List.append` to add elements to a list, as long as you don't have any compile-time errors, you won't get any runtime errors from calling `List.map` afterwards, no matter what you appended to the list! More generally, it's safe to assume that unless you run out of memory, `List.map` will run successfully unless you got a compile-time error about an incompatibility (like `Num.neg` on a list of strings). - -### [Lists that hold elements of different types](#lists-that-hold-elements-of-different-types) {#lists-that-hold-elements-of-different-types} - -We can use tags with payloads to make a list that contains a mixture of different types. For example: - -```roc -List.map [StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3] \elem -> - when elem is - NumElem num -> Num.isNegative num - StrElem str -> Str.isCapitalized str -# returns [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true] -``` - -Compare this with the example from earlier, which caused a compile-time error: - -```roc -List.map ["A", "B", "C", 1, 2, 3] Num.isNegative -``` - -The version that uses tags works because we aren't trying to call `Num.isNegative` on each element. Instead, we're using a `when` to tell when we've got a string or a number, and then calling either `Num.isNegative` or `Str.isCapitalized` depending on which type we have. - -We could take this as far as we like, adding more different tags (e.g. `BoolElem Bool.true`) and then adding more branches to the `when` to handle them appropriately. - -### [Using tags as functions](#using-tags-as-functions) {#using-tags-as-functions} - -Let's say I want to apply a tag to a bunch of elements in a list. For example: - -```roc -List.map ["a", "b", "c"] \str -> Foo str -``` - -This is a perfectly reasonable way to write it, but I can also write it like this: - -```roc -List.map ["a", "b", "c"] Foo -``` - -These two versions compile to the same thing. As a convenience, Roc lets you specify a tag name where a function is expected; when you do this, the compiler infers that you want a function which uses all of its arguments as the payload to the given tag. - -### [List.any and List.all](#list-any-and-list-all) {#list-any-and-list-all} - -There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `Bool.true`: - -```roc -List.any [1, 2, 3] Num.isOdd -# returns `Bool.true` because 1 and 3 are odd -``` - -```roc -List.any [1, 2, 3] Num.isNegative -# returns `Bool.false` because none of these is negative -``` - -There's also `List.all` which only returns `Bool.true` if all the elements in the list pass the test: - -```roc -List.all [1, 2, 3] Num.isOdd -# returns `Bool.false` because 2 is not odd -``` - -```roc -List.all [1, 2, 3] Num.isPositive -# returns `Bool.true` because all of these are positive -``` - -### [Removing elements from a list](#removing-elements-from-a-list) {#removing-elements-from-a-list} - -You can also drop elements from a list. One way is `List.dropAt` - for example: - -```roc -List.dropAt ["Sam", "Lee", "Ari"] 1 -# drops the element at offset 1 ("Lee") and returns ["Sam", "Ari"] -``` - -Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `Bool.true`. - -```roc -List.keepIf [1, 2, 3, 4, 5] Num.isEven -# returns [2, 4] -``` - -There's also `List.dropIf`, which does the opposite: - -```roc -List.dropIf [1, 2, 3, 4, 5] Num.isEven -# returns [1, 3, 5] -``` - -### [Getting an individual element from a list](#getting-an-individual-element-from-a-list) {#getting-an-individual-element-from-a-list} - -Another thing we can do with a list is to get an individual element out of it. `List.get` is a common way to do this; it takes a list and an index, and then returns the element at that index... if there is one. But what if there isn't? - -For example, what do each of these return? - -```roc -List.get ["a", "b", "c"] 1 -``` - -```roc -List.get ["a", "b", "c"] 100 -``` - -The answer is that the first one returns `Ok "b"` and the second one returns `Err OutOfBounds`. They both return tags! This is done so that the caller becomes responsible for handling the possibility that the index is outside the bounds of that particular list. - -Here's how calling `List.get` can look in practice: - -```roc -when List.get ["a", "b", "c"] index is - Ok str -> "I got this string: \(str)" - Err OutOfBounds -> "That index was out of bounds, sorry!" -``` - -There's also `List.first`, which always gets the first element, and `List.last` which always gets the last. They return `Err ListWasEmpty` instead of `Err OutOfBounds`, because the only way they can fail is if you pass them an empty list! - -These functions demonstrate a common pattern in Roc: operations that can fail returning either an `Ok` tag with the answer (if successful), or an `Err` tag with another tag describing what went wrong (if unsuccessful). In fact, it's such a common pattern that there's a whole module called `Result` which deals with these two tags. Here are some examples of `Result` functions: - -```roc -Result.withDefault (List.get ["a", "b", "c"] 100) "" -# returns "" because that's the default we said to use if List.get returned an Err -``` -```roc -Result.isOk (List.get ["a", "b", "c"] 1) -# returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.) - -# Note: There's a Result.isErr function that works similarly. -``` - -### [Walking the elements in a list](#walking-the-elements-in-a-list) {#walking-the-elements-in-a-list} - -We've now seen a few different ways you can transform lists. Sometimes, though, there's nothing -that quite does what you want, and you might find yourself calling `List.get` repeatedly to -retrieve every element in the list and use it to build up the new value you want. That approach -can work, but it has a few downsides: - -* Each `List.get` call returns a `Result` that must be dealt with, even though you plan to use every element in the list anyway -* There's a runtime performance overhead associated with each of these `Result`s, which you won't find in other "look at every element in the list" operations like `List.keepIf`. -* It's more verbose than the alternative we're about to discuss - -The `List.walk` function gives you a way to walk over the elements in a list and build up whatever -return value you like. It's a great alternative to calling `List.get` on every element in the list -because it's more concise, runs faster, and doesn't give you any `Result`s to deal with. - -Here's an example: - -```roc -List.walk [1, 2, 3, 4, 5] { evens: [], odds: [] } \state, elem -> - if Num.isEven elem then - { state & evens: List.append state.evens elem } - else - { state & odds: List.append state.odds elem } - -# returns { evens: [2, 4], odds: [1, 3, 5] } -``` - -In this example, we walk over the list `[1, 2, 3, 4, 5]` and add each element to either the `evens` or `odds` field of a `state` record: `{ evens, odds }`. By the end, that record has a list of all the even numbers in the list and a list of all the odd numbers. - -`List.walk` takes a few ingredients: - -1. A list. (`[1, 2, 3, 4, 5]`) -2. An initial `state` value. (`{ evens: [], odds: [] }`) -3. A function which takes the current `state` and element, and returns a new `state`. (`\state, elem -> ...`) - -It then proceeds to walk over each element in the list and call that function. Each time, the state that function returns becomes the argument to the next function call. Here are the arguments the function will receive, and what it will return, as `List.walk` walks over the list `[1, 2, 3, 4, 5]`: - -| State | Element | Return Value | -| --------------------------------- | ------- | ------------------------------------ | -| `{ evens: [], odds: [] }` | `1` | `{ evens: [], odds: [1] }` | -| `{ evens: [], odds: [1] }` | `2` | `{ evens: [2], odds: [1] }` | -| `{ evens: [2], odds: [1] }` | `3` | `{ evens: [2], odds: [1, 3] }` | -| `{ evens: [2], odds: [1, 3] }` | `4` | `{ evens: [2, 4], odds: [1, 3] }` | -| `{ evens: [2, 4], odds: [1, 3] }` | `5` | `{ evens: [2, 4], odds: [1, 3, 5] }` | - -Note that the initial `state` argument is `{ evens: [], odds: [] }` because that's the argument -we passed `List.walk` for its initial state. From then on, each `state` argument is whatever the -previous function call returned. - -Once the list has run out of elements, `List.walk` returns whatever the final function call returned—in this case, `{ evens: [2, 4], odds: [1, 3, 5] }`. (If the list was empty, the function never gets called and `List.walk` returns the initial state.) - -Note that the state doesn't have to be a record; it can be anything you want. For example, if you made it a `Bool`, you could implement `List.any` using `List.walk`. You could also make the state be a list, and implement `List.map`, `List.keepIf`, or `List.dropIf`. There are a lot of things you can do with `List.walk`! - -A helpful way to remember the argument order for `List.walk` is that that its arguments follow the same pattern as what we've seen with `List.map`, `List.any`, `List.keepIf`, and `List.dropIf`: the first argument is a list, and the last argument is a function. The difference here is that `List.walk` has one more argument than those other functions; the only place it could go while preserving that pattern is in the middle! - -> **Note:** Other languages give this operation different names, such as `fold`, `reduce`, `accumulate`, `aggregate`, `compress`, and `inject`. Some languages also have operations like `forEach` or `for...in` syntax, which walk across every element and perform potentially side-effecting operations on them; `List.walk` can be used to replace these too, if you include a `Task` in the state. We'll talk about tasks, and how to use them with `List.walk`, later on. - -### [The pipe operator](#the-pipe-operator) {#the-pipe-operator} - -When you have nested function calls, sometimes it can be clearer to write them in a "pipelined" style using the `|>` operator. Here are three examples of writing the same expression; they all compile to exactly the same thing, but two of them use the `|>` operator to change how the calls look. - -```roc -Result.withDefault (List.get ["a", "b", "c"] 1) "" -``` -```roc -List.get ["a", "b", "c"] 1 -|> Result.withDefault "" -``` - -The `|>` operator takes the value that comes before the `|>` and passes it as the first argument to whatever comes after the `|>`. So in the example above, the `|>` takes `List.get ["a", "b", "c"] 1` and passes that value as the first argument to `Result.withDefault`, making `""` the second argument to `Result.withDefault`. - -We can take this a step further like so: - -```roc -["a", "b", "c"] -|> List.get 1 -|> Result.withDefault "" -``` - -This is still equivalent to the first expression. Since `|>` is known as the "pipe operator," we can read this as "start with `["a", "b", "c"]`, then pipe it to `List.get`, then pipe it to `Result.withDefault`." - -One reason the `|>` operator injects the value as the first argument is to make it work better with functions where argument order matters. For example, these two uses of `List.append` are equivalent: - -```roc -List.append ["a", "b", "c"] "d" -``` -```roc -["a", "b", "c"] -|> List.append "d" -``` - -Another example is `Num.div`. All three of the following do the same thing, because `a / b` in Roc is syntax sugar for `Num.div a b`: - -```roc -first / second -``` -```roc -Num.div first second -``` -```roc -first |> Num.div second -``` - -All operators in Roc are syntax sugar for normal function calls. See the [Operator Desugaring Table](https://www.roc-lang.org/tutorial#operator-desugaring-table) at the end of this tutorial for a complete list of them. - -## [Types](#types) {#types} - -Sometimes you may want to document the type of a definition. For example, you might write: - -```roc -# Takes a firstName string and a lastName string, and returns a string -fullName = \firstName, lastName -> - "\(firstName) \(lastName)" -``` - -Comments can be valuable documentation, but they can also get out of date and become misleading. If someone changes this function and forgets to update the comment, it will no longer be accurate. - -### [Type Annotations](#type-annotations) {#type-annotations} - -Here's another way to document this function's type, which doesn't have that problem: - -```roc -fullName : Str, Str -> Str -fullName = \firstName, lastName -> - "\(firstName) \(lastName)" -``` - -The `fullName :` line is a _type annotation_. It's a strictly optional piece of metadata we can add above a def to describe its type. Unlike a comment, the Roc compiler will check type annotations for accuracy. If the annotation ever doesn't fit with the implementation, we'll get a compile-time error. - -The annotation `fullName : Str, Str -> Str` says "`fullName` is a function that takes two strings as arguments and returns a string." - -We can give type annotations to any value, not just functions. For example: - -```roc -firstName : Str -firstName = "Amy" - -lastName : Str -lastName = "Lee" -``` - -These annotations say that both `firstName` and `lastName` have the type `Str`. - -We can annotate records similarly. For example, we could move `firstName` and `lastName` into a record like so: - -```roc -amy : { firstName : Str, lastName : Str } -amy = { firstName: "Amy", lastName: "Lee" } - -jen : { firstName : Str, lastName : Str } -jen = { firstName: "Jen", lastName: "Majura" } -``` - -### [Type Aliases](#type-aliases) {#type-aliases} - -When we have a recurring type annotation like this, it can be nice to give it its own name. We do this like so: - -```roc -Musician : { firstName : Str, lastName : Str } - -amy : Musician -amy = { firstName: "Amy", lastName: "Lee" } - -simone : Musician -simone = { firstName: "Simone", lastName: "Simons" } -``` - -Here, `Musician` is a _type alias_. A type alias is like a def, except it gives a name to a type instead of to a value. Just like how you can read `name : Str` as "`name` has the type `Str`," you can also read `Musician : { firstName : Str, lastName : Str }` as "`Musician` has the type `{ firstName : Str, lastName : Str }`." - -### [Type Parameters](#type-parameters) {#type-parameters} - -Annotations for lists must specify what type the list's elements have: - -```roc -names : List Str -names = ["Amy", "Simone", "Tarja"] -``` - -You can read `List Str` as "a list of strings." Here, `Str` is a _type parameter_ that tells us what type of `List` we're dealing with. `List` is a _parameterized type_, which means it's a type that requires a type parameter. There's no way to give something a type of `List` without a type parameter. You have to specify what type of list it is, such as `List Str` or `List Bool` or `List { firstName : Str, lastName : Str }`. - -### [Wildcard Types (\*)](#wildcard-type) {#wildcard-type} - -There are some functions that work on any list, regardless of its type parameter. For example, `List.isEmpty` has this type: - -```roc -isEmpty : List * -> Bool -``` - -The `*` is a _wildcard type_; a type that's compatible with any other type. `List *` is compatible with any type of `List` like `List Str`, `List Bool`, and so on. So you can call `List.isEmpty ["I am a List Str"]` as well as `List.isEmpty [Bool.true]`, and they will both work fine. - -The wildcard type also comes up with empty lists. Suppose we have one function that takes a `List Str` and another function that takes a `List Bool`. We might reasonably expect to be able to pass an empty list (that is, `[]`) to either of these functions, and we can! This is because a `[]` value has the type `List *`. It is a "list with a wildcard type parameter", or a "list whose element type could be anything." - -### [Type Variables](#type-variables) {#type-variables} - -`List.reverse` works similarly to `List.isEmpty`, but with an important distinction. As with `isEmpty`, we can call `List.reverse` on any list, regardless of its type parameter. However, consider these calls: - -```roc -strings : List Str -strings = List.reverse ["a", "b"] - -bools : List Bool -bools = List.reverse [Bool.true, Bool.false] -``` - -In the `strings` example, we have `List.reverse` returning a `List Str`. In the `bools` example, it's returning a `List Bool`. So what's the type of `List.reverse`? - -We saw that `List.isEmpty` has the type `List * -> Bool`, so we might think the type of `List.reverse` would be `reverse : List * -> List *`. However, remember that we also saw that the type of the empty list is `List *`? `List * -> List *` is actually the type of a function that always returns empty lists! That's not what we want. - -What we want is something like one of these: - -```roc -reverse : List elem -> List elem -``` - -```roc -reverse : List value -> List value -``` - -```roc -reverse : List a -> List a -``` - -Any of these will work, because `elem`, `value`, and `a` are all _type variables_. A type variable connects two or more types in the same annotation. So you can read `List elem -> List elem` as "takes a list and returns a list that has **the same element type**." Just like `List.reverse` does! - -You can choose any name you like for a type variable, but it has to be lowercase. (You may have noticed all the types we've used until now are uppercase; that is no accident! Lowercase types are always type variables, so all other named types have to be uppercase.) All three of the above type annotations are equivalent; the only difference is that we chose different names (`elem`, `value`, and `a`) for their type variables. - -You can tell some interesting things about functions based on the type parameters involved. For example, any function that returns `List *` definitely always returns an empty list. You don't need to look at the rest of the type annotation, or even the function's implementation! The only way to have a function that returns `List *` is if it returns an empty list. - -Similarly, the only way to have a function whose type is `a -> a` is if the function's implementation returns its argument without modifying it in any way. This is known as [the identity function](https://en.wikipedia.org/wiki/Identity_function). - -### [Tag Union Types](#tag-union-types) {#tag-union-types} - -We can also annotate types that include tags: - -```roc -colorFromStr : Str -> [Red, Green, Yellow] -colorFromStr = \string -> - when string is - "red" -> Red - "green" -> Green - _ -> Yellow -``` - -You can read the type `[Red, Green, Yellow]` as "a tag union of the tags `Red`, `Green`, and `Yellow`." - -Some tag unions have only one tag in them. For example: - -```roc -redTag : [Red] -redTag = Red -``` - -### [Accumulating Tag Types](#accumulating-tag-types) {#accumulating-tag-types} - -Tag union types can accumulate more tags based on how they're used. Consider this `if` expression: - -```roc -\str -> - if Str.isEmpty str then - Ok "it was empty" - else - Err ["it was not empty"] -``` - -Here, Roc sees that the first branch has the type `[Ok Str]` and that the `else` branch has the type `[Err (List Str)]`, so it concludes that the whole `if` expression evaluates to the combination of those two tag unions: `[Ok Str, Err (List Str)]`. - -This means this entire `\str -> ...` function has the type `Str -> [Ok Str, Err (List Str)]`. However, it would be most common to annotate it as `Result Str (List Str)` instead, because the `Result` type (for operations like `Result.withDefault`, which we saw earlier) is a type alias for a tag union with `Ok` and `Err` tags that each have one payload: - -```roc -Result ok err : [Ok ok, Err err] -``` - -We just saw how tag unions get combined when different branches of a conditional return different tags. Another way tag unions can get combined is through pattern matching. For example: - -```roc -when color is - Red -> "red" - Yellow -> "yellow" - Green -> "green" -``` - -Here, Roc's compiler will infer that `color`'s type is `[Red, Yellow, Green]`, because those are the three possibilities this `when` handles. - -### [Opaque Types](#opaque-types) {#opaque-types} - -A type can be defined to be opaque to hide its internal structure. This is a lot more amazing than it may seem. It can make your code more modular, robust, and easier to read: -- If a type is opaque you can modify its internal structure and be certain that no dependencies need to be updated. -- You can prevent that data needs to be checked multiple times. For example, you can create an opaque `NonEmptyList` from a `List` after you've checked it. Now all functions that you pass this `NonEmptyList` to do not need to handle the empty list case. -- Having the type `Username` in a type signature gives you more context compared to `Str`. Even if the `Username` is an opaque type for `Str`. - -You can create an opaque type with the `:=` operator. Let's make one called `Username`: - -```roc -Username := Str - -fromStr : Str -> Username -fromStr = \str -> - @Username str - -toStr : Username -> Str -toStr = \@Username str -> - str -``` - -The `fromStr` function turns a string into a `Username` by calling `@Username` on that string. The `toStr` function turns a `Username` back into a string by pattern matching `@Username str` to unwrap the string from the `Username` opaque type. - -Now we can expose the `Username` opaque type so that other modules can use it in type annotations. However, other modules can't use the `@Username` syntax to wrap or unwrap `Username` values. That operation is only available in the same scope where `Username` itself was defined; trying to use it outside that scope will give an error. - -Note that if we define `Username := Str` inside another module (e.g. `Main`) and also use `@Username`, this will compile, however the new `Username` type in main would not be equal to the one defined in the `Username` module. Although both opaque types have the name `Username`, they were defined in different modules and so they are type-incompatible with each other, and even attempting to use `==` to compare them would be a type mismatch. - -## [Numeric types](#numeric-types) {#numeric-types} - -Roc has different numeric types that each have different tradeoffs. They can all be broken down into two categories: [fractions](https://en.wikipedia.org/wiki/Fraction), and [integers](https://en.wikipedia.org/wiki/Integer). In Roc we call these `Frac` and `Int` for short. - -### [Integers](#integers) {#integers} - -Roc's integer types have two important characteristics: their _size_ and their [_signedness_](https://en.wikipedia.org/wiki/Signedness). Together, these two characteristics determine the range of numbers the integer type can represent. - -For example, the Roc type `U8` can represent the numbers 0 through 255, whereas the `I16` type can represent the numbers -32768 through 32767. You can actually infer these ranges from their names (`U8` and `I16`) alone! - -The `U` in `U8` indicates that it's _unsigned_, meaning that it can't have a minus [sign](), and therefore can't be negative. The fact that it's unsigned tells us immediately that its lowest value is zero. The 8 in `U8` means it is 8 [bits](https://en.wikipedia.org/wiki/Bit) in size, which means it has room to represent 2⁸ (=256) different numbers. Since one of those 256 different numbers is 0, we can look at `U8` and know that it goes from `0` (since it's unsigned) to `255` (2⁸ - 1, since it's 8 bits). - -If we change `U8` to `I8`, making it a _signed_ 8-bit integer, the range changes. Because it's still 8 bits, it still has room to represent 2⁸ different numbers. However, now in addition to one of those 256 numbers being zero, about half of the rest will be negative, and the others positive. So instead of ranging from, say -255 to 255 (which, counting zero, would represent 511 different numbers; too many to fit in 8 bits!) an `I8` value ranges from -128 to 127. - -Notice that the negative extreme is `-128` versus `127` (not `128`) on the positive side. That's because of needing room for zero; the slot for zero is taken from the positive range because zero doesn't have a minus sign. - -Following this pattern, the 16 in `I16` means that it's a signed 16-bit integer. That tells us it has room to represent 2¹⁶ (=65536) different numbers. Half of 65536 is 32768, so the lowest `I16` would be -32768, and the highest would be 32767. - -Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider: - -- Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! -- Smaller integer sizes take up less memory. These savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck. -- Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly! - -Here are the different fixed-size integer types that Roc supports: - -| Range | Type | -|-------------------------------------------------------------------------------------------------------------------|--------| -| `-128`
    `127` | `I8` | -| `0`
    `255` | `U8` | -| `-32_768`
    `32_767` | `I16` | -| `0`
    `65_535` | `U16` | -| `-2_147_483_648`
    `2_147_483_647` | `I32` | -| `0`
    (over 4 billion) `4_294_967_295` | `U32` | -| `-9_223_372_036_854_775_808`
    `9_223_372_036_854_775_807` | `I64` | -| `0`
    _(over 18 quintillion)_`18_446_744_073_709_551_615` | `U64` | -| `-170_141_183_460_469_231_731_687_303_715_884_105_728`
    `170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | -| `0`
    _(over 340 undecillion)_`340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | - -Roc also has one variable-size integer type: `Nat` (short for "natural number"). The size of `Nat` is equal to the size of a memory address, which varies by system. For example, when compiling for a 64-bit system, `Nat` works the same way as `U64`. When compiling for a 32-bit system, it works the same way as `U32`. Most popular computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly, `Nat` will work like a `U32` in that program. - -A common use for `Nat` is to store the length of a collection like a `List`; there's a function `List.len : List * -> Nat` which returns the length of the given list. 64-bit systems can represent longer lists in memory than 32-bit systems can, which is why the length of a list is represented as a `Nat`. - -If any operation would result in an integer that is either too big or too small to fit in that range (e.g. calling `Int.maxI32 + 1`, which adds 1 to the highest possible 32-bit integer), then the operation will [overflow](https://en.wikipedia.org/wiki/Integer_overflow). When an overflow occurs, the program will crash. - -As such, it's very important to design your integer operations not to exceed these bounds! - -### [Fractions](#fractions) {#fractions} - -Roc has three fractional types: - -- `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) -- `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) -- `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) - -These are different from integers, they can represent numbers with fractional components, such as 1.5 and -0.123. - -`Dec` is the best default choice for representing [base-10 decimal numbers](https://en.wikipedia.org/wiki/Decimal) like [currency](https://en.wikipedia.org/wiki/Currency), because it is base-10 under the hood. In contrast, `F64` and `F32` are [base-2](https://en.wikipedia.org/wiki/Binary_number) under the hood, which can lead to decimal precision loss even when doing addition and subtraction. For example, when using `F64`, running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, whereas when using `Dec`, 0.1 + 0.2 returns 0.3. - -`F32` and `F64` have direct hardware support on common processors today. There is no hardware support for fixed-point decimals, so under the hood, a `Dec` is an `I128`; operations on it perform [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) with 18 decimal places of precision. - -This means a `Dec` can represent whole numbers up to slightly over 170 quintillion, along with 18 decimal places. (To be precise, it can store numbers between `-170_141_183_460_469_231_731.687303715884105728` and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 decimal places? It's the highest number of decimal places where you can still convert any `U64` to a `Dec` without losing information. - -While the fixed-point `Dec` has a fixed range, the floating-point `F32` and `F64` do not. Instead, outside of a certain range they start to lose precision instead of immediately overflowing the way integers and `Dec` do. `F64` can represent [between 15 and 17 significant digits](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) before losing precision, whereas `F32` can only represent [between 6 and 9](https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32). - -There are some use cases where `F64` and `F32` can be better choices than `Dec` despite their precision drawbacks. For example, in graphical applications they can be a better choice for representing coordinates because they take up less memory, various relevant calculations run faster, and decimal precision loss isn't as big a concern when dealing with screen coordinates as it is when dealing with something like currency. - -### [Num, Int, and Frac](#num-int-and-frac) {#num-int-and-frac} - -Some operations work on specific numeric types - such as `I64` or `Dec` - but operations support multiple numeric types. For example, the `Num.abs` function works on any number, since you can take the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of integers and fractions alike. Its type is: - -```roc -abs : Num a -> Num a -``` - -This type says `abs` takes a number and then returns a number of the same type. Remember that we can see the type of number is the same because the [type variable](#type-variables) `a` is used on both sides. That's because the `Num` type is compatible with both integers and fractions. - -There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only compatible with fractions. For example: - -```roc -Num.xor : Int a, Int a -> Int a -``` -```roc -Num.cos : Frac a -> Frac a -``` -When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1` and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type `Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`. - -### [Number Literals](#number-literals) {#number-literals} - -By default, a number literal with no decimal point has the type `Num *`—that is, we know it's "a number" but nothing more specific. (Number literals with decimal points have the type `Frac *` instead.) - -You can give a number literal a more specific type by adding the type you want as a lowercase suffix. For example, `1u8` specifies `1` with the type `U8`, and `5dec` specifies `5` with the type `Dec`. - -The full list of possible suffixes includes: - -`u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, `nat`, `f32`, `f64`, `dec` - -Integer literals can be written in [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) form by prefixing with `0x` followed by hexadecimal characters (`a` - `f` in addition to `0` - `9`). For example, writing `0xfe` is the same as writing `254`. Similarly, the prefix `0b` specifies binary integers. Writing `0b0000_1000` is the same as writing `8`. - -## [Crashing](#crashing) {#crashing} - -Ideally, Roc programs would never crash. However, there are some situations where they may. For example: - -1. When doing normal integer arithmetic (e.g. `x + y`) that [overflows](https://en.wikipedia.org/wiki/Integer_overflow). -2. When the system runs out of memory. -3. When a variable-length collection (like a `List` or `Str`) gets too long to be representable in the operating system's address space. (A 64-bit operating system's address space can represent several [exabytes](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) of data, so this case should not come up often.) - -Crashes in Roc are not like [try/catch exceptions](https://en.wikipedia.org/wiki/Exception_handling) found in some other programming languages. There is no way to "catch" a crash. It immediately ends the program, and what happens next is defined by the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform). For example, a command-line interface platform might exit with a nonzero [exit code](https://en.wikipedia.org/wiki/Exit_status), whereas a web server platform might have the current request respond with a [HTTP 500 error](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500). - -### [Crashing in unreachable branches](#crashing-in-unreachable-branches) {#crashing-in-unreachable-branches} - -You can intentionally crash a Roc program, for example inside a conditional branch that you believe is unreachable. Suppose you're certain that a particular `List U8` contains valid UTF-8 bytes, which means when you call `Str.fromUtf8` on it, the `Result` it returns will always be `Ok`. In that scenario, you can use the `crash` keyword to handle the `Err` case like so: - -```roc -answer : Str -answer = - when Str.fromUtf8 definitelyValidUtf8 is - Ok str -> str - Err _ -> crash "This should never happen!" -``` - -If the unthinkable happens, and somehow the program reaches this `Err` branch even though that was thought to be impossible, then it will crash - just like if the system had run out of memory. The string passed to `crash` will be provided to the platform as context; each platform may do something different with it. - -> **Note:** `crash` is a language keyword and not a function; you can't assign `crash` to a variable or pass it to a function. - -### [Crashing for TODOs](#crashing-for-todos) {#crashing-for-todos} - -Another use for `crash` is as a TODO marker when you're in the middle of building something: - -```roc -if x > y then - transmogrify (x * 2) -else - crash "TODO handle the x <= y case" -``` - -This lets you do things like write tests for the non-`crash` branch, and then come back and finish the other branch later. - -### [Crashing for error handling](#crashing-for-error-handling) {#crashing-for-error-handling} - -`crash` is not for error handling. - -The reason Roc has a `crash` keyword is for scenarios where it's expected that no error will ever happen (like in [unreachable branches](#crashing-in-unreachable-branches)), or where graceful error handling is infeasible (like running out of memory). - -Errors that are recoverable should be represented using normal Roc types (like [Result](https://www.roc-lang.org/builtins/Result)) and then handled without crashing. For example, by having the application report that something went wrong, and then continue running from there. - -## [Tests and expectations](#tests-and-expectations) {#tests-and-expectations} - -You can write automated tests for your Roc code like so: - -```roc -pluralize = \singular, plural, count -> - countStr = Num.toStr count - - if count == 1 then - "\(countStr) \(singular)" - else - "\(countStr) \(plural)" - -expect pluralize "cactus" "cacti" 1 == "1 cactus" - -expect pluralize "cactus" "cacti" 2 == "2 cacti" -``` - -If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `Bool.false`. - -If a test fails, it will not show the actual value that differs from the expected value. To show the actual value, you can write the expect like this: - -```roc -expect - funcOut = pluralize "cactus" "cacti" 1 - - funcOut == "2 cactus" -``` - -### [Inline Expectations](#inline-expects) {#inline-expects} - -Expects do not have to be at the top level: - -```roc -pluralize = \singular, plural, count -> - countStr = Num.toStr count - - if count == 1 then - "\(countStr) \(singular)" - else - expect count > 0 - - "\(countStr) \(plural)" -``` - -This `expect` will fail if you call `pluralize` passing a count of 0. - -Note that inline `expect`s do not halt the program! They are designed to inform, not to affect control flow. In fact, if you do `roc build`, they are not even included in the final binary. -So you'll want to use `roc dev` or `roc test` to get the output for `expect`. - -## [Modules](#modules) {#modules} - -Each `.roc` file is a separate module and contains Roc code for different purposes. Here are all of the different types of modules that Roc supports; - -- **Builtins** provide functions that are automatically imported into every module. -- **Applications** are combined with a platform and compiled into an executable. -- **Interfaces** provide functions which can be imported into other modules. -- **Packages** organise modules to share functionality across applications and platforms. -- **Platforms** provide effects such as IO to interface with the outside world. -- **Hosted** *note this module type is likely to be deprecated soon*. - -### [Builtin Modules](#builtin-modules) {#builtin-modules} - -There are several modules that are built into the Roc compiler, which are imported automatically into every Roc module. They are: - -1. `Bool` -2. `Str` -3. `Num` -4. `List` -5. `Result` -6. `Dict` -7. `Set` - -You may have noticed that we already used the first five. For example, when we wrote `Str.concat` and `Num.isEven`, we were referencing functions stored in the `Str` and `Num` modules. - -These modules are not ordinary `.roc` files that live on your filesystem. Rather, they are built directly into the Roc compiler. That's why they're called "builtins!" - -Besides being built into the compiler, the builtin modules are different from other modules in that: - -- They are always imported. You never need to add them to `imports`. -- All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (the same is true for all the other types in the `Num` module. - -### [App Module Header](#app-module-header) {#app-module-header} - -Let's take a closer look at the part of `main.roc` above the `main` def: - -```roc -app "hello" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } - imports [pf.Stdout] - provides [main] to pf -``` - -This is known as a _module header_. Every `.roc` file is a _module_, and there are different types of modules. We know this particular one is an _application module_ because it begins with the `app` keyword. - -The line `app "hello"` states that this module defines a Roc application, and that building this application should produce an executable named `hello`. This means when you run `roc dev`, the Roc compiler will build an executable named `hello` (or `hello.exe` on Windows) and run it. You can also build the executable without running it by running `roc build`. - -The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on: - -```roc -packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } - imports [pf.Stdout] - provides [main] to pf -``` - -The `packages { pf: "https://...tar.br" }` part says three things: - -- We're going to be using a _package_ (a collection of modules) that can be downloaded from the URL `"https://...tar.br"` -- That package's [base64](https://en.wikipedia.org/wiki/Base64#URL_applications)\-encoded [BLAKE3]() cryptographic hash is the long string at the end (before the `.tar.br` file extension). Once the file has been downloaded, its contents will be verified against this hash, and it will only be installed if they match. This way, you can be confident the download was neither corrupted nor changed since it was originally published. -- We're going to name that package `pf` so we can refer to it more concisely in the future. - -The `imports [pf.Stdout]` line says that we want to import the `Stdout` module from the `pf` package, and make it available in the current module. - -This import has a direct interaction with our definition of `main`. Let's look at that again: - -```roc -main = Stdout.line "I'm a Roc application!" -``` - -Here, `main` is calling a function called `Stdout.line`. More specifically, it's calling a function named `line` which is exposed by a module named `Stdout`. - -When we write `imports [pf.Stdout]`, it specifies that the `Stdout` module comes from the package we named `pf` in the `packages { pf: ... }` section. - -If we would like to include other modules in our application, say `AdditionalModule.roc` and `AnotherModule.roc`, then they can be imported directly in `imports` like this: - -```roc -imports [pf.Stdout, AdditionalModule, AnotherModule] -``` - -You can find documentation for the `Stdout.line` function in the [Stdout](https://www.roc-lang.org/packages/basic-cli/Stdout#line) module documentation. - -### [Package Modules](#interface-modules) {#interface-modules} - -Package modules enable Roc code to be easily re-used and shared. This is achieved by organizing code into different Interface modules and then including these in the `exposes` field of the package file structure, `package "name" exposes [ MyInterface ] packages {}`. The modules that are listed in the `exposes` field are then available for use in applications, platforms, or other packages. Internal modules that are not listed will be unavailable for use outside of the package. - -See [Parser Package](https://github.com/roc-lang/roc/tree/main/examples/parser/package) for an example. - -Package documentation can be generated using the Roc cli with `roc docs /package/*.roc`. - -Build a package for distribution with `roc build --bundle .tar.br /package/main.roc`. This will create a single tarball that can then be easily shared online using a URL. - -You can import a package that is available either locally, or from a URL into a Roc application or platform. This is achieved by specifying the package in the `packages` section of the application or platform file structure. For example, `packages { .., parser: "" }` is an example that imports a parser module from a URL. - -How does the Roc cli import and download a package from a URL? - -1. First it checks to see whether the relevant folder already exists in the local filesystem and if not, creates it. If there is a package already downloaded then there is no need to download or extract anything. Packages are cached in a directory, typically `~/.cache/roc` on UNIX, and `%APPDATA%\\Roc` on Windows. -2. It then downloads the file at that URL and verifies that the hash of the file matches the hash at the end of the URL. -3. If the hash of the file matches the hash in the URL, then decompress and extract its contents into the cache folder so that it can be used. - -Why is a Roc package URL so long? - -Including the hash solves a number of problems: - -1. The package at the URL can not suddenly change and cause different behavior. -2. Because of 1. there is no need to check the URL on every compilation to see if we have the latest version. -3. If the domain of the URL expires, a malicious actor can change the package but the hash will not match so the roc cli will reject it. - -### [Interface Modules](#interface-modules) {#interface-modules} - -\[This part of the tutorial has not been written yet. Coming soon!\] - -See [Html Interface](https://github.com/roc-lang/roc/blob/main/examples/virtual-dom-wip/platform/Html.roc) for an example. - -### [Platform Modules](#interface-modules) {#interface-modules} - -\[This part of the tutorial has not been written yet. Coming soon!\] - -See [Platform Switching Rust](https://github.com/roc-lang/roc/blob/main/examples/platform-switching/rust-platform/main.roc) for an example. - -## [Tasks](#tasks) {#tasks} - -Tasks are technically not part of the Roc language, but they're very common in platforms. Let's continue using the [basic-cli](https://github.com/roc-lang/basic-cli) platform we've been using up to this point as an example! - -In the `basic-cli` platform, we have four operations we can do: - -- Write a string to the terminal -- Read a string from user input -- Write a string to a file -- Read a string from a file - -We'll use these four operations to learn about tasks. - -Let's start with a basic "Hello World" program. - -```roc -app "cli-tutorial" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } - imports [pf.Stdout] - provides [main] to pf - -main = - Stdout.line "Hello, World!" -``` - -The `Stdout.line` function takes a `Str` and writes it to [standard output](). It has this type: - -```roc -Stdout.line : Str -> Task {} * -``` - -A `Task` represents an _effect_; an interaction with state outside your Roc program, such as the terminal's standard output, or a file. - -When we set `main` to be a `Task`, the task will get run when we run our program. Here, we've set `main` to be a task that writes `"Hello, World!"` to `stdout` when it gets run, so that's what our program does! - -`Task` has two type parameters: the type of value it produces when it finishes running, and any errors that might happen when running it. `Stdout.line` has the type `Task {} *` because it doesn't produce any values when it finishes (hence the `{}`) and there aren't any errors that can happen when it runs (hence the `*`). - -In contrast, `Stdin.line` produces a `Str` when it finishes reading from [standard input](). That `Str` is reflected in its type: - -```roc -Stdin.line : Task Str * -``` - -Let's change `main` to read a line from `stdin`, and then print it back out again: - -```roc -app "cli-tutorial" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } - imports [pf.Stdout, pf.Stdin, pf.Task] - provides [main] to pf - -main = - Task.await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -``` - -If you run this program, at first it won't do anything. It's waiting for you to type something in and press Enter! Once you do, it should print back out what you entered. - -The `Task.await` function combines two tasks into one bigger `Task` which first runs one of the given tasks and then the other. In this case, it's combining a `Stdin.line` task with a `Stdout.line` task into one bigger `Task`, and then setting `main` to be that bigger task. - -The type of `Task.await` is: - -```roc -Task.await : Task a err, (a -> Task b err) -> Task b err -``` - -The second argument to `Task.await` is a "callback function" which runs after the first task completes. This callback function receives the output of that first task, and then returns the second task. This means the second task can make use of output from the first task, like we did in our `\text -> ...` callback function here: - -```roc -\text -> - Stdout.line "You just entered: \(text)" -``` - -Notice that, just like before, we're still building `main` from a single `Task`. This is how we'll always do it! We'll keep building up bigger and bigger `Task`s out of smaller tasks, and then setting `main` to be that one big `Task`. - -For example, we can print a prompt before we pause to read from `stdin`, so it no longer looks like the program isn't doing anything when we start it up: - -```roc -task = - Task.await (Stdout.line "Type something press Enter:") \_ -> - Task.await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -``` - -This works, but we can make it a little nicer to read. Let's change it to the following: - -```roc -app "cli-tutorial" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } - imports [pf.Stdout, pf.Stdin, pf.Task.{ await }] - provides [main] to pf - -main = - await (Stdout.line "Type something press Enter:") \_ -> - await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -``` - -Here we've changed how we're importing the `Task` module. Before it was `pf.Task` and now it's `pf.Task.{ await }`. The difference is that we're importing `await` in an _unqualified_ way, meaning that whenever we write `await` in this module, it will refer to `Task.await`. Now we no longer need to write `Task.` every time we want to use `await`. - -It's most common in Roc to call functions from other modules in a _qualified_ way (`Task.await`) rather than unqualified (`await`) like this, but it can be nice for a function with an uncommon name (like "await") which often gets called repeatedly across a small number of lines of code. - -Speaking of calling `await` repeatedly, if we keep calling it more and more on this code, we'll end up doing a lot of indenting. If we'd rather not indent so much, we can rewrite `task` into this style which looks different but does the same thing: - -```roc -task = - _ <- await (Stdout.line "Type something press Enter:") - text <- await Stdin.line - - Stdout.line "You just entered: \(text)" -``` - -## [Backpassing](#backpassing) {#backpassing} - -This `<-` syntax is called _backpassing_. The `<-` is a way to define an anonymous function, just like `\ ... ->` is. - -Here, we're using backpassing to define two anonymous functions. Here's one of them: - -```roc -text <- - -Stdout.line "You just entered: \(text)" -``` - -It may not look like it, but this code is defining an anonymous function! You might remember it as the anonymous function we previously defined like this: - -```roc -\text -> - Stdout.line "You just entered: \(text)" -``` - -These two anonymous functions are the same, just defined using different syntax. - -The reason the `<-` syntax is called _backpassing_ is because it both defines a function and passes that function _back_ as an argument to whatever comes after the `<-` (which in this case is `await Stdin.line`). - -Let's look at these two complete expressions side by side. They are both saying exactly the same thing, with different syntax! - -Here's the original: - -```roc -await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -``` - -And here's the equivalent expression with backpassing syntax: - -```roc -text <- await Stdin.line - -Stdout.line "You just entered: \(text)" -``` - -Here's the other function we're defining with backpassing: - -```roc -_ <- -text <- await Stdin.line - -Stdout.line "You just entered: \(text)" -``` - -We could also have written that function this way if we preferred: - -```roc -_ <- - -await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -``` - -This is using a mix of a backpassing function `_ <-` and a normal function `\text ->`, which is totally allowed! Since backpassing is nothing more than syntax sugar for defining a function and passing back as an argument to another function, there's no reason we can't mix and match if we like. - -That said, the typical style in which this `task` would be written in Roc is using backpassing for all the `await` calls, like we had above: - -```roc -task = - _ <- await (Stdout.line "Type something press Enter:") - text <- await Stdin.line - - Stdout.line "You just entered: \(text)" -``` - -This way, it reads like a series of instructions: - -1. First, run the `Stdout.line` task and await its completion. Ignore its output (hence the underscore in `_ <-`) -2. Next, run the `Stdin.line` task and await its completion. Name its output `text`. -3. Finally, run the `Stdout.line` task again, using the `text` value we got from the `Stdin.line` effect. - -Some important things to note about backpassing and `await`: - -- `await` is not a language keyword in Roc! It's referring to the `Task.await` function, which we imported unqualified by writing `Task.{ await }` in our module imports. (That said, it is playing a similar role here to the `await` keyword in languages that have `async`/`await` keywords, even though in this case it's a function instead of a special keyword.) -- Backpassing syntax does not need to be used with `await` in particular. It can be used with any function. -- Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you! - -## [Abilities](#abilities) {#abilities} - -\[This part of the tutorial has not been written yet. Coming soon!\] - -## [Appendix: Advanced Concepts](#appendix-advanced-concepts) {#appendix-advanced-concepts} - -Here are some concepts you likely won't need as a beginner, but may want to know about eventually. This is listed as an appendix rather than the main tutorial, to emphasize that it's totally fine to stop reading here and go build things! - -### [Open Records and Closed Records](#open-records-and-closed-records) {#open-records-and-closed-records} - -Let's say I write a function which takes a record with a `firstName` and `lastName` field, and puts them together with a space in between: - -```roc -fullName = \user -> - "\(user.firstName) \(user.lastName)" -``` - -I can pass this function a record that has more fields than just `firstName` and `lastName`, as long as it has _at least_ both of those fields (and both of them are strings). So any of these calls would work: - -- `fullName { firstName: "Sam", lastName: "Sample" }` -- `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }` -- `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }` - -This `user` argument is an _open record_ - that is, a description of a minimum set of fields on a record, and their types. When a function takes an open record as an argument, it's okay if you pass it a record with more fields than just the ones specified. - -In contrast, a _closed record_ is one that requires an exact set of fields (and their types), with no additional fields accepted. - -If we add a type annotation to this `fullName` function, we can choose to have it accept either an open record or a closed record: - -```roc -# Closed record -fullName : { firstName : Str, lastName : Str } -> Str -fullName = \user -> - "\(user.firstName) \(user.lastName)" -``` - -```roc -# Open record (because of the `*`) -fullName : { firstName : Str, lastName : Str }* -> Str -fullName = \user -> - "\(user.firstName) \(user.lastName)" -``` - -The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an open record type. This `*` is the _wildcard type_ we saw earlier with empty lists. (An empty list has the type `List *`, in contrast to something like `List Str` which is a list of strings.) - -This is because record types can optionally end in a type variable. Just like how we can have `List *` or `List a -> List a`, we can also have `{ first : Str, last : Str }*` or `{ first : Str, last : Str }a -> { first : Str, last : Str }a`. The differences are that in `List a`, the type variable is required and appears with a space after `List`; in a record, the type variable is optional, and appears (with no space) immediately after `}`. - -If the type variable in a record type is a `*` (such as in `{ first : Str, last : Str }*`), then it's an open record. If the type variable is missing, then it's a closed record. You can also specify a closed record by putting a `{}` as the type variable (so for example, `{ email : Str }{}` is another way to write `{ email : Str }`). In practice, closed records are basically always written without the `{}` on the end, but later on we'll see a situation where putting types other than `*` in that spot can be useful. - -### [Constrained Records](#constrained-records) {#constrained-records} - -The type variable can also be a named type variable, like so: - -```roc -addHttps : { url : Str }a -> { url : Str }a -addHttps = \record -> - { record & url: "https://\(record.url)" } -``` - -This function uses _constrained records_ in its type. The annotation is saying: - -- This function takes a record which has at least a `url` field, and possibly others -- That `url` field has the type `Str` -- It returns a record of exactly the same type as the one it was given - -So if we give this function a record with five fields, it will return a record with those same five fields. The only requirement is that one of those fields must be `url: Str`. - -In practice, constrained records appear in type annotations much less often than open or closed records do. - -Here's when you can typically expect to encounter these three flavors of type variables in records: - -- _Open records_ are what the compiler infers when you use a record as an argument, or when destructuring it (for example, `{ x, y } =`). -- _Closed records_ are what the compiler infers when you create a new record (for example, `{ x: 5, y: 6 }`) -- _Constrained records_ are what the compiler infers when you do a record update (for example, `{ user & email: newEmail }`) - -Of note, you can pass a closed record to a function that accepts a smaller open record, but not the reverse. So a function `{ a : Str, b : Bool }* -> Str` can accept an `{ a : Str, b : Bool, c : Bool }` record, but a function `{ a : Str, b : Bool, c : Bool } -> Str` would not accept an `{ a : Str, b : Bool }*` record. - -This is because if a function accepts `{ a : Str, b : Bool, c : Bool }`, that means it might access the `c` field of that record. So if you passed it a record that was not guaranteed to have all three of those fields present (such as an `{ a : Str, b : Bool }*` record, which only guarantees that the fields `a` and `b` are present), the function might try to access a `c` field at runtime that did not exist! - -### [Type Variables in Record Annotations](#type-variables-in-record-annotations) {#type-variables-in-record-annotations} - -You can add type annotations to make record types less flexible than what the compiler infers, but not more flexible. For example, you can use an annotation to tell the compiler to treat a record as closed when it would be inferred as open (or constrained), but you can't use an annotation to make a record open when it would be inferred as closed. - -If you like, you can always annotate your functions as accepting open records. However, in practice this may not always be the nicest choice. For example, let's say you have a `User` type alias, like so: - -```roc -User : { - email : Str, - firstName : Str, - lastName : Str, -} -``` - -This defines `User` to be a closed record, which in practice is the most common way records named `User` tend to be defined. - -If you want to have a function take a `User`, you might write its type like so: - -```roc -isValid : User -> Bool -``` - -If you want to have a function return a `User`, you might write its type like so: - -```roc -userFromEmail : Str -> User -``` - -A function which takes a user and returns a user might look like this: - -```roc -capitalizeNames : User -> User -``` - -This is a perfectly reasonable way to write all of these functions. However, I might decide that I really want the `isValid` function to take an open record; a record with _at least_ the fields of this `User` record, but possibly others as well. - -Since open records have a type variable (like `*` in `{ email : Str }*` or `a` in `{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a type variable to the `User` type alias: - -```roc -User a : { - email : Str - firstName : Str - lastName : Str -}a -``` - -Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the record type! - -Using `User a` type alias, I can still write the same three functions, but now their types need to look different. This is what the first one would look like: - -```roc -isValid : User * -> Bool -``` - -Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, which takes it from `{ email : Str, ... }a` to `{ email : Str, ... }*`. Now I can pass it any record that has at least the fields in `User`, and possibly others as well, which was my goal. - -```roc -userFromEmail : Str -> User {} -``` - -Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, which takes it from `{ email : Str, ... }a` to `{ email : Str, ... }{}`. As noted earlier, this is another way to specify a closed record: putting a `{}` after it, in the same place that you'd find a `*` in an open record. - -> **Aside:** This works because you can form new record types by replacing the type variable with other record types. For example, `{ a : Str, b : Str }` can also be written `{ a : Str }{ b : Str }`. You can chain these more than once, e.g. `{ a : Str }{ b : Str }{ c : Str, d : Str }`. This is more useful when used with type annotations; for example, `{ a : Str, b : Str }User` describes a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`. - -This function still returns the same record as it always did, it just needs to be annotated as `User {}` now instead of just `User`, because the `User` type alias has a variable in it that must be specified. - -The third function might need to use a named type variable: - -```roc -capitalizeNames : User a -> User a -``` - -If this function does a record update on the given user, and returns that - for example, if its definition were `capitalizeNames = \user -> { user & email: "blah" }` - then it needs to use the same named type variable for both the argument and return value. - -However, if returns a new `User` that it created from scratch, then its type could instead be: - -```roc -capitalizeNames : User * -> User {} -``` - -This says that it takes a record with at least the fields specified in the `User` type alias, and possibly others...and then returns a record with exactly the fields specified in the `User` type alias, and no others. - -These three examples illustrate why it's relatively uncommon to use open records for type aliases: it makes a lot of types need to incorporate a type variable that otherwise they could omit, all so that `isValid` can be given something that has not only the fields `User` has, but some others as well. (In the case of a `User` record in particular, it may be that the extra fields were included due to a mistake rather than on purpose, and accepting an open record could prevent the compiler from raising an error that would have revealed the mistake.) - -That said, this is a useful technique to know about if you want to (for example) make a record type that accumulates more and more fields as it progresses through a series of operations. - -### [Open and Closed Tag Unions](#open-and-closed-tag-unions) {#open-and-closed-tag-unions} - -Just like how Roc has open records and closed records, it also has open and closed tag unions. - -The _open tag union_ (or _open union_ for short) `[Foo Str, Bar Bool]*` represents a tag that might be `Foo Str` and might be `Bar Bool`, but might also be some other tag whose type isn't known at compile time. - -Because an open union represents possibilities that are impossible to know ahead of time, any `when` I use on a `[Foo Str, Bar Bool]*` value must include a catch-all `_ ->` branch. Otherwise, if one of those unknown tags were to come up, the `when` would not know what to do with it! For example: - -```roc -example : [Foo Str, Bar Bool]* -> Bool -example = \tag -> - when tag is - Foo str -> Str.isEmpty str - Bar bool -> bool - _ -> Bool.false -``` - -In contrast, a _closed tag union_ (or _closed union_) like `[Foo Str, Bar Bool]` (without the `*`) represents the set of all possible tags. If I use a `when` on one of these, I can match on `Foo` only and then on `Bar` only, with no need for a catch-all branch. For example: - -```roc -example : [Foo Str, Bar Bool] -> Bool -example = \tag -> - when tag is - Foo str -> Str.isEmpty str - Bar bool -> bool -``` - -If we were to remove the type annotations from the previous two code examples, Roc would infer the same types for them anyway. - -It would infer `tag : [Foo Str, Bar Bool]` for the latter example because the `when tag is` expression only includes a `Foo Str` branch and a `Bar Bool` branch, and nothing else. Since the `when` doesn't handle any other possibilities, these two tags must be the only possible ones. - -It would infer `tag : [Foo Str, Bar Bool]*` for the former example because the `when tag is` expression includes a `Foo Str` branch and a `Bar Bool` branch but also a `_ ->` branch, indicating that there may be other tags we don't know about. Since the `when` is flexible enough to handle all possible tags, `tag` gets inferred as an open union. - -Putting these together, whether a tag union is inferred to be open or closed depends on which possibilities the implementation actually handles. - -> **Aside:** As with open and closed records, we can use type annotations to make tag union types less flexible than what would be inferred. If we added a `_ ->` branch to the second example above, the compiler would still accept `example : [Foo Str, Bar Bool] -> Bool` as the type annotation, even though the catch-all branch would permit the more flexible `example : [Foo Str, Bar Bool]* -> Bool` annotation instead. - -### [Combining Open Unions](#combining-open-unions) {#combining-open-unions} - -When we make a new record, it's inferred to be a closed record. For example, in `foo { a: "hi" }`, the type of `{ a: "hi" }` is inferred to be `{ a : Str }`. In contrast, when we make a new tag, it's inferred to be an open union. So in `foo (Bar "hi")`, the type of `Bar "hi"` is inferred to be `[Bar Str]*`. - -This is because open unions can accumulate additional tags based on how they're used in the program, whereas closed unions cannot. For example, let's look at this conditional: - -```roc -if x > 5 then - "foo" -else - 7 -``` - -This will be a type mismatch because the two branches have incompatible types. Strings and numbers are not type-compatible! Now let's look at another example: - -```roc -if x > 5 then - Ok "foo" -else - Err "bar" -``` - -This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok "foo"` to be the closed union `[Ok Str]`, and likewise for `Err "bar"` and `[Err Str]`, then this would have to be a type mismatch - because those two closed unions are incompatible. - -Instead, the compiler infers `Ok "foo"` to be the open union `[Ok Str]*`, and `Err "bar"` to be the open union `[Err Str]*`. Then, when using them together in this conditional, the inferred type of the conditional becomes `[Ok Str, Err Str]*` - that is, the combination of the unions in each of its branches. (Branches in a `when` work the same way with open unions.) - -Earlier we saw how a function which accepts an open union must account for more possibilities, by including catch-all `_ ->` patterns in its `when` expressions. So _accepting_ an open union means you have more requirements. In contrast, when you already _have_ a value which is an open union, you have fewer requirements. A value which is an open union (like `Ok "foo"`, which has the type `[Ok Str]*`) can be provided to anything that's expecting a tag union (no matter whether it's open or closed), as long as the expected tag union includes at least the tags in the open union you're providing. - -So if I have an `[Ok Str]*` value, I can pass it to functions with any of these types (among others): - -| Function Type | Can it receive `[Ok Str]*`? | -| --------------------------------------- | --------------------------- | -| `[Ok Str]* -> Bool` | Yes | -| `[Ok Str] -> Bool` | Yes | -| `[Ok Str, Err Bool]* -> Bool` | Yes | -| `[Ok Str, Err Bool] -> Bool` | Yes | -| `[Ok Str, Err Bool, Whatever]* -> Bool` | Yes | -| `[Ok Str, Err Bool, Whatever] -> Bool` | Yes | -| `Result Str Bool -> Bool` | Yes | -| `[Err Bool, Whatever]* -> Bool` | Yes | - -That last one works because a function accepting an open union can accept any unrecognized tag (including `Ok Str`) even though it is not mentioned as one of the tags in `[Err Bool, Whatever]*`! Remember, when a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch, which is the branch that will end up handling the `Ok Str` value we pass in. - -However, I could not pass an `[Ok Str]*` to a function with a _closed_ tag union argument that did not mention `Ok Str` as one of its tags. So if I tried to pass `[Ok Str]*` to a function with the type `[Err Bool, Whatever] -> Str`, I would get a type mismatch - because a `when` in that function could be handling the `Err Bool` possibility and the `Whatever` possibility, and since it would not necessarily have a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it received one. - -> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles "all possible tags." For example, if I have a function `[Ok Str]* -> Bool` and I pass it `Ok 5`, that will still be a type mismatch. If you think about it, a `when` in that function might have the branch `Ok str ->` which assumes there's a string inside that `Ok`, and if `Ok 5` type-checked, then that assumption would be false and things would break! -> -> So `[Ok Str]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag, but if it is an `Ok` tag, then it's guaranteed to have a payload of exactly `Str`." - -In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting: - -- If you _have_ a closed union, that means it has all the tags it ever will, and can't accumulate more. -- If you _have_ an open union, that means it can accumulate more tags through conditional branches. -- If you _accept_ a closed union, that means you only have to handle the possibilities listed in the union. -- If you _accept_ an open union, that means you have to handle the possibility that it has a tag you can't know about. - -### [Type Variables in Tag Unions](#type-variables-in-tag-unions) {#type-variables-in-tag-unions} - -Earlier we saw these two examples, one with an open tag union and the other with a closed one: - -```roc -example : [Foo Str, Bar Bool]* -> Bool -example = \tag -> - when tag is - Foo str -> Str.isEmpty str - Bar bool -> bool - _ -> Bool.false -``` - -```roc -example : [Foo Str, Bar Bool] -> Bool -example = \tag -> - when tag is - Foo str -> Str.isEmpty str - Bar bool -> bool -``` - -Similarly to how there are open records with a `*`, closed records with nothing, and constrained records with a named type variable, we can also have _constrained tag unions_ with a named type variable. Here's an example: - -```roc -example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a -example = \tag -> - when tag is - Foo str -> Bar (Str.isEmpty str) - Bar bool -> Bar Bool.false - other -> other -``` - -This type says that the `example` function will take either a `Foo Str` tag, or a `Bar Bool` tag, or possibly another tag we don't know about at compile time and it also says that the function's return type is the same as the type of its argument. - -So if we give this function a `[Foo Str, Bar Bool, Baz (List Str)]` argument, then it will be guaranteed to return a `[Foo Str, Bar Bool, Baz (List Str)]` value. This is more constrained than a function that returned `[Foo Str, Bar Bool]*` because that would say it could return _any_ other tag (in addition to the `Foo Str` and `Bar Bool` we already know about). - -If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway. This may be surprising if you look closely at the body of the function, because: - -- The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[Bar Bool]a` instead? -- The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it? - -The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question: "What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one type before the `->` and another type after it; whenever you see a named value in Roc, it is guaranteed to have the same type everywhere it appears in that scope. - -For this reason, any time you see a function that only runs a `when` on its only argument, and that `when` includes a branch like `x -> x` or `other -> other`, the function's argument type and return type must necessarily be equivalent. - -> **Note:** Just like with records, you can also replace the type variable in tag union types with a concrete type. For example, `[Foo Str][Bar Bool][Baz (List Str)]` is equivalent to `[Foo Str, Bar Bool, Baz (List Str)]`. -> -> Also just like with records, you can use this to compose tag union type aliases. For example, you can write `NetworkError : [Timeout, Disconnected]` and then `Problem : [InvalidInput, UnknownFormat]NetworkError` - -### [Phantom Types](#phantom-types) {#phantom-types} - -\[This part of the tutorial has not been written yet. Coming soon!\] - -### [Operator Desugaring Table](#operator-desugaring-table) {#operator-desugaring-table} - -Here are various Roc expressions involving operators, and what they desugar to. - -| Expression | Desugars To | -| ----------------------------- | ------------------ | -| `a + b` | `Num.add a b` | -| `a - b` | `Num.sub a b` | -| `a * b` | `Num.mul a b` | -| `a / b` | `Num.div a b` | -| `a // b` | `Num.divTrunc a b` | -| `a ^ b` | `Num.pow a b` | -| `a % b` | `Num.rem a b` | -| `-a` | `Num.neg a` | -| `a == b` | `Bool.isEq a b` | -| `a != b` | `Bool.isNotEq a b` | -| `a && b` | `Bool.and a b` | -| a \|\| b | `Bool.or a b` | -| `!a` | `Bool.not a` | -| a \|> b | `b a` | -| a b c \|> f x y | `f (a b c) x y` | - - ### [Language Keywords](#language-keywords) {#language-keywords} - -These are all of the language keywords supported by Roc; - -`if`,`then`,`else`,`when`,`as`,`is`,`dbg`,`expect`,`expect-fx`,`crash`,`interface`,`app`,`package`,`platform`,`hosted`,`exposes`,`imports`,`with`,`generates`,`packages`,`requires`,`provides`,`to` diff --git a/www/wip_new_website/static/repl.css b/www/wip_new_website/static/repl.css deleted file mode 100644 index c238cd0d8c..0000000000 --- a/www/wip_new_website/static/repl.css +++ /dev/null @@ -1,164 +0,0 @@ -#repl { - position: relative; - display: flex; - flex-direction: column; - font-size: 18px; -} - -#repl-prompt { - position: relative; - left: 16px; - top: 25px; - line-height: 16px; - height: 0; - z-index: 2; - font-family: var(--font-mono); - color: var(--light-cyan); - /* Let clicks pass through to the textarea */ - pointer-events: none; - user-select: none; -} - -#source-input { - width: 100%; - font-family: var(--font-mono); - color: var(--code-color); - background-color: var(--code-bg); - display: inline-block; - height: 78px; - padding: 16px; - padding-left: 36px; - border: 1px solid transparent; - margin: 0; - margin-bottom: 2em; - box-sizing: border-box; - font-size: 18px; - resize: none; -} - -#source-input:focus { - outline: 2px solid var(--primary-1); - box-sizing: border-box; -} - -.history { - padding: 1em; - padding-bottom: 0; - flex: 1; -} - -#help-text, -#history-text { - white-space: pre-wrap; -} - -#history-text { - margin-top: 16px; - min-height: 26px; -} - -#loading-message { - text-align: center; - /* approximately match height after loading and printing help message */ - height: 96px; -} - -.history-item { - margin-bottom: 24px; - overflow-x: hidden; -} - -.history-item .input { - margin: 0; - margin-bottom: 8px; -} - -.history-item .output { - margin: 0; -} - -.panic { - color: #ff6666; -} - -.input-line-prefix { - color: var(--cyan); -} - -.color-red { - color: #ff6666; -} - -.color-green { - color: var(--green); -} - -.color-yellow { - color: var(--orange); -} - -.color-blue { - color: var(--cyan); -} - -.color-magenta { - color: var(--primary-1); -} - -.color-cyan { - color: var(--cyan); -} - -.color-white { - /* Really this isn't white so much as "default text color." For the repl, this should be black - in a light color scheme, and only white in dark mode. The name could be better! */ - color: black; -} - -@media (prefers-color-scheme: dark) { - .color-white { - color: white; - } -} - -.bold { - font-weight: bold; -} - -.underline { - text-decoration: underline; -} - -/* Mobile-friendly screen width */ -@media only screen and (max-width: 767px) { - #repl { - margin: 0; - padding: 0; - min-height: calc(100vh - var(--top-bar-height)); - } - - code.history { - flex-grow: 1; - } - - #source-input { - margin: 0; - } - - #loading-message { - margin: 0; - } - - #homepage-repl-container { - flex-direction: column; - } - - #homepage-repl-container #repl-description { - padding: 0; - margin-bottom: 1.5em; - } - - #repl-arrow { - display: none; - } -} diff --git a/www/wip_new_website/static/site.css b/www/wip_new_website/static/site.css deleted file mode 100644 index f55decc5b3..0000000000 --- a/www/wip_new_website/static/site.css +++ /dev/null @@ -1,1053 +0,0 @@ -:root { - /* WCAG AAA Compliant colors */ - --gray-bg: #f4f8f9; - --gray: #717171; - --orange: #bf5000; - --green: #0b8400; - --light-cyan: #8af4e6; - --dark-cyan: #4eefd9; - --blue: #05006d; - --violet: #7c38f5; - --violet-bg: #ece2fd; - --magenta: #ff32cf; - - --primary-1: #9b6bf2; - --primary-2: #7c38f5; - --highlight: #1bd6bd; - --code-color: white; - --link-color: var(--primary-2); - --code-link-color: var(--primary-2); - --text-color: #000; - --heading-color: #333; - --text-hover-color: var(--primary-2); - --body-bg-color: #ffffff; - --border-color: #717171; - --faded-color: #4c4c4c; - --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, - sans-serif; - --font-mono: "Source Code Pro", SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, - monospace; - --top-header-height: 67px; - --sidebar-width: 280px; - - --font-size-normal: 18px; - --body-max-width: 1024px; - --top-bar-height: 50px; - --homepage-intro-box-width: 367px; - --dark-code-bg: #202746; -} - -html { - line-height: 1.5rem; - background: var(--body-bg-color); - color: var(--text-color); - font-family: "Lato", sans-serif; -} - -html, -body { - margin: 0; - padding: 0; - width: 100%; - height: 100%; - box-sizing: border-box; -} - -p { - margin-top: 0; -} - -footer { - width: 100%; - color: var(--text-color); - text-align: center; - font-size: var(--font-size-normal); - padding: 20px; - box-sizing: border-box; -} - -#footer { - max-width: var(--body-max-width); - margin: 0 auto; -} - -hr { - color: var(--primary-1); - margin-bottom: 1rem; -} - -.svg-text { - fill: #000; -} - -.logo-dark { - fill: #612bde; -} - -.logo-light { - fill: #8257e5; -} - -.btn-small { - white-space: nowrap; - background: #7c38f5; - border: 4px solid #9b6bf2; - color: #fff !important; - cursor: pointer; - text-decoration: none !important; - padding: 12px; -} - -.btn-small .roc-logo { - fill: #fff; - position: relative; - left: -4px; - top: 11px; -} - -.btn-small:hover { - background: #9b6bf2; - border-color: #7c38f5; -} - -#individual-sponsors { - list-style-type: none; - padding: 24px 40px; -} - -#individual-sponsors li { - display: inline; - white-space: nowrap; -} - -#individual-sponsors li::after { - content: ","; - white-space: pre; /* Preserve the space after the comma */ -} - -#individual-sponsors li:last-child::after { - content: ""; /* No comma after the last one */ -} - -#sponsor-logos { - padding: 24px 36px; - padding-bottom: 36px; - min-width: 308px; /* Widest logo plus padding - Firefox on Android needs this */ -} - -#sponsor-logos svg { - height: 54px; - margin-right: 72px; -} - -#sponsor-logos .logo-rwx { - position: relative; - top: 6px; -} - -#sponsor-logos .logo-tweede-golf { - position: relative; - top: 18px; - height: 76px; -} - -#sponsor-logos + p { - margin-bottom: 3em; -} - -#final-tutorial-link { - padding-top: 12px; -} - -/* Used for e.g. displaying the instruction "Click" on desktop and "Touch" on mobile. - * When we think we're on mobile (based on max-width), we can switch the instruction. -*/ -.desktop { - display: inline; -} - -.mobile { - display: none; -} - -section p:last-child { - margin-bottom: 0; -} - -aside { - margin-left: 4rem; -} - -a { - text-decoration: none; - color: var(--link-color); -} - -a:hover { - text-decoration: underline; -} - -li { - margin-bottom: 0.5rem; -} - -h1, -h2, -h3, -h4 { - font-weight: bold; -} - -h1 { - font-size: 5rem; -} - -h2 { - display: inline-block; - font-size: 3rem; - border-bottom: 4px solid var(--dark-cyan); - margin: 0; - margin-top: 72px; - margin-bottom: 36px; - padding: 36px 0; - color: var(--heading-color); -} - -h2 a { - color: var(--heading-color); -} - -h2:hover a { - color: var(--link-color); - text-decoration: none; -} - -h3 { - font-size: 1.5rem; -} - -#top-bar { - background-color: var(--gray-bg); - height: var(--top-bar-height); - box-sizing: border-box; - width: 100%; - /* On iPhone SE, without this, we end up with */ - min-width: 392px; -} - -#top-bar nav { - max-width: var(--body-max-width); - margin: 0 auto; - display: flex; - justify-content: space-between; - padding-right: 9px; -} - -#nav-home-link { - display: flex; - color: var(--top-bar-fg); - font-size: 1.8rem; - padding: 4px; -} - -#top-bar-links { - display: flex; -} - -#top-bar-links a, -#top-bar-links label { - box-sizing: border-box; - color: var(--top-bar-fg); - font-size: var(--font-size-normal); - display: block; - height: var(--top-bar-height); - padding: 12px 16px; - margin: 0 2px; -} - -main { - max-width: var(--body-max-width); - margin: auto; - padding: 12px; - box-sizing: border-box; -} - -.welcome-to-roc { - white-space: nowrap; - overflow-x: hidden; - padding-right: 60px; - margin-bottom: 12px; -} - -code, -samp { - font-family: var(--font-mono); - color: var(--text-color); - background-color: var(--gray-bg); - display: inline-block; - padding: 5px; -} - -p code, -td code, -li code, -th code { - padding: 0 8px; -} - -code a, -a code { - text-decoration: none; - color: var(--code-link-color); - background: none; - padding: 0; -} - -code a:visited, -a:visited code { - color: var(--code-link-color); -} - -pre { - position: relative; - margin-bottom: 16px; - padding: 8px 16px; - box-sizing: border-box; - background-color: var(--gray-bg); - overflow-x: hidden; - word-wrap: normal; - font-size: var(--font-size-normal); - line-height: 1.76em; - white-space: pre; - background-color: var(--dark-code-bg); -} - -pre > samp, -pre > code { - overflow-x: auto; - display: block; - background-color: var(--dark-code-bg); - color: var(--code-color); -} - -/* The repl won't work at all if you have JS disabled. */ -.no-js #try-roc { - display: none !important; -} - -#homepage-repl-container { - display: flex; - flex-direction: row-reverse; -} - -#homepage-repl-container #repl-description { - padding: 0 30px; - margin-top: 2px; - flex: 1; -} - -#homepage-repl-container #repl-description a { - color: inherit; - text-decoration: underline; -} - -#homepage-repl-container #repl-description a:hover { - color: var(--primary-1); -} - -#homepage-repl-container #repl { - flex: 1; - border: 2px solid #444; - font-size: var(--font-size-normal); - min-height: 0; /* On /repl on mobile, this expands to fill the screen, which we don't want */ - margin-right: 6px; - max-width: 50%; -} - -#homepage-repl-container #repl, -#homepage-repl-container #repl code { - color: white; - background-color: var(--dark-code-bg); -} - -#homepage-repl-container #source-input { - margin-bottom: 0; - margin-top: 6px; - font-size: var(--font-size-normal); - height: 57px; -} - -#homepage-repl-container p { - position: relative; /* Needed for the repl arrow's position: absolute */ -} - -#homepage-repl-container #repl-arrow { - cursor: default; - font-weight: bold; - font-size: 48px; - position: absolute; - top: -9px; - left: -79px; - text-shadow: 1px 1px 1px #444; - z-index: 3; - fill: var(--primary-1); -} - -.repl-err { - color: var(--magenta); -} - -/* Tables */ - -table { - border-collapse: collapse; - overflow-x: auto; - border: 2px solid #f0f0f0; -} - -thead { - border: none; -} - -tbody { - border: none; -} - -tr { - border: none; - border-top: 2px solid #f0f0f0; -} - -th, -td { - border: none; - border-right: 2px solid #f0f0f0; - padding: 12px; -} - -th:last-child, -td:last-child { - border-right: none; -} - -p, -aside, -li { - font-size: var(--font-size-normal); - line-height: 1.85rem; -} - -/* Homepage */ -#homepage-intro-box { - position: relative; - margin: 60px auto; - width: var(--homepage-intro-box-width); -} - -#homepage-h1 { - color: #222; - text-shadow: none; - font-family: inherit; - font-size: 64px; - padding: 0; - padding-top: 60px; - position: relative; - left: -5px; -} - -#homepage-logo { - height: 176px; - width: auto; - position: absolute; - left: -200px; - top: -100px; -} - -#first-code-sample { - width: var(--homepage-intro-box-width); - margin-top: 60px; - line-height: 1.85em; - color: #fcf9fd; -} - -#first-code-sample .kw, -#first-code-sample .punctuation, -.interactive-example .kw, -.interactive-example .punctuation { - color: #9c7cea; -} - -#first-code-sample, -#first-code-sample .code-snippet { - background-color: var(--dark-code-bg); -} - -#homepage-tagline { - font-size: 20px; -} - -/* Mobile-friendly screen width */ -@media only screen and (max-width: 1024px) { - :root { - --font-size-normal: 16px; - } - /* Used for e.g. displaying the instruction "Click" on desktop and "Touch" on mobile. */ - .desktop { - display: none; - } - - .mobile { - display: inline; - } - - .close-desc { - display: block !important; - } - - h2 { - margin-top: 48px; - padding: 12px 0; - } - - h1 code, - h2 code, - h3 code, - h4 code, - h5 code { - font-size: inherit; - } - - code { - white-space: normal; - } - - .nobreak-on-mobile { - white-space: nowrap; - } - - /* Homepage */ - - #homepage-intro-box { - margin: 30px auto; - } - - #homepage-tagline { - margin-top: 0; - } - - #homepage-h1 { - /* TODO remove !important from repl.css (increasing specificity instead), and then this one too. */ - font-size: 48px !important; - padding: 0; - margin: 0; - text-align: left; - } - - #homepage-logo { - height: 116px; - left: 262px; - top: -14px; - } - - #first-code-sample { - margin: 64px auto; - } - - #homepage-tagline { - font-size: 16px; - } - - .home-goals-container, .home-examples-container { - /* It's unclear why this !important is necessary, since its specificity - should theoretically be higher than what it's overriding. In practice, - at least in Chrome, removing the !important breaks this. */ - display: grid !important; - grid-template-columns: 1fr; - } - - /* Tutorial */ - - #tutorial-toc-toggle-label, - #close-tutorial-toc { - display: block; - } - - #tutorial-toc-toggle:checked + #tutorial-toc { - display: block; - } - - #tutorial-toc { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow-y: auto; - margin: 0; - padding-right: 120px; - border: 0; - } - - h1, - h2, - h3, - h4, - h5, - h6, - p, - code { - word-break: break-word !important; - } - - h1, - h2, - h3, - h4, - h5 { - line-height: 1.2em !important; - font-size: 2rem !important; - } - - #top-bar-links a, - #top-bar-links label { - padding: 12px 8px; - margin: 0; - } - - #homepage-repl-container #repl { - max-width: none; - } -} - -/* iPhone SE and similar */ -@media only screen and (max-width: 350px) { - #homepage-logo { - /* The bird runs off the screen unless we shrink it */ - height: 75px; - width: auto; - position: absolute; - left: 227px; - top: -14px; - } -} - -/* latin-ext */ -@font-face { - font-family: "Lato"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2") format("woff2"), - url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff") format("woff"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, - U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -/* latin */ -@font-face { - font-family: "Lato"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff2") format("woff2"), - url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff") format("woff"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - -/* latin-ext */ -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2") format("woff2"), - url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff") format("woff"); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, - U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -/* latin */ -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2") format("woff2"), - url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff") format("woff"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - -@media (prefers-color-scheme: dark) { - :root { - /* WCAG AAA Compliant colors */ - /* WCAG AAA Compliant colors */ - --gray-bg: #202746; - --gray: #b6b6b6; - --orange: #fd6e08; - --green: #8ecc88; - --cyan: #12c9be; - --blue: #b1afdf; - --violet-bg: #332944; - --magenta: #f39bac; - - --primary-1: #9c7cea; - --primary-2: #1bd6bd; - --text-color: #ccc; - --body-bg-color: #0e0e0f; - --border-color: var(--gray); - --code-color: #eeeeee; - --logo-solid: #8f8f8f; - --faded-color: #bbbbbb; - --gray: #6e6e6e; - --heading-color: #eee; - } - - .logo-dark { - fill: #6c3bdc; - } - - .logo-light { - fill: #8a66de; - } - - .svg-text { - fill: #fff; - } - - #homepage-h1 { - color: #fcf9fd; - } - - h3 { - color: var(--primary-2); - } - - h1, - h2, - h3, - h4, - h5 { - text-shadow: none; - } - - html { - scrollbar-color: #444444 #2f2f2f; - } - - table, - tr, - th, - td { - border-color: var(--gray); - } - - #first-code-sample, - #homepage-repl-container #repl { - border: 1px solid #ddd; - } - - .home-goals-content:hover { - background-color: #481870 !important; - } -} - -/* Comments `#` and Documentation comments `##` */ -samp .comment, -code .comment { - color: #ccc; -} - -/* Number, String, Tag literals */ -samp .storage.type, -code .storage.type, -samp .string, -code .string, -samp .string.begin, -code .string.begin, -samp .string.end, -code .string.end, -samp .constant, -code .constant, -samp .literal, -code .literal { - color: var(--dark-cyan); -} - -/* Keywords and punctuation */ -samp .keyword, -code .keyword, -samp .punctuation.section, -code .punctuation.section, -samp .punctuation.separator, -code .punctuation.separator, -samp .punctuation.terminator, -code .punctuation.terminator, -samp .kw, -code .kw { - color: var(--primary-1); -} - -/* Operators */ -samp .op, -code .op, -samp .keyword.operator, -code .keyword.operator { - color: var(--primary-1); -} - -/* Delimieters */ -samp .delimeter, -code .delimeter { - color: var(--primary-1); -} - -/* Variables modules and field names */ -samp .function, -code .function, -samp .meta.group, -code .meta.group, -samp .meta.block, -code .meta.block, -samp .lowerident, -code .lowerident { - color: white; -} - -/* Types, Tags, and Modules */ -samp .type, -code .type, -samp .meta.path, -code .meta.path, -samp .upperident, -code .upperident { - color: var(--dark-cyan); -} - -samp .dim, -code .dim { - opacity: 0.55; -} - -.button-container { - position: absolute; - top: 0; - right: 0; -} - -.copy-button { - background: var(--dark-code-bg); - border: 1px solid var(--dark-cyan); - color: var(--dark-cyan); - display: inline-block; - cursor: pointer; - margin: 8px; -} - -.copy-button:hover { - border-color: var(--code-color); - color: var(--code-color); -} - -.roc-logo { - width: 40px; - height: 40px; - margin: 0 auto; - fill: var(--primary-1); - text-shadow: 1px 1px 1px #010101; -} - -/* HOME GOALS */ - -.home-goals-container { - display: flex; - flex-direction: row; - justify-content: space-between; - gap: 48px; - width: 100%; -} - -.home-goals-column { - display: flex; - flex-direction: column; - flex: 1; - width: 100%; -} - -.home-goals-content { - flex: 1; - display: flex; - flex-direction: column; - padding: 20px; - border: 4px solid var(--light-cyan); - color: inherit; - cursor: pointer; -} - -.home-goals-content:hover { - text-decoration: none; - cursor: pointer; - background-color: var(--light-cyan); -} - -.home-goals-content:hover h3 { - color: #fff; -} - -.home-goals-learn-more { - text-decoration: underline; -} - -.home-goals-content:hover .home-goals-learn-more { - color: var(--primary-2); -} - -.home-examples-container { - display: flex; - flex-direction: row; - justify-content: space-between; - gap: 48px; - width: 100%; -} - -.home-examples-column { - display: flex; - flex-direction: column; - flex: 1; - width: 100%; -} - -/* Wider screens */ -@media only screen and (min-width: 768px) { - .home-goals-column { - margin-bottom: 0; - } - - .home-goals-column:last-child { - margin-right: 0; - } -} - -.home-goals-learn-more { - margin-top: auto; - white-space: nowrap; -} - -.home-goals-title { - padding: 0; - font-weight: bold; - margin-bottom: 10px; - font-size: 32px; - border-bottom: none; - font-family: inherit; - text-transform: lowercase; - padding-bottom: 42px; - padding-top: 2px; - font-style: italic; - text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; - letter-spacing: 1px; - word-spacing: 3px; - margin: 0; - color: var(--light-cyan); -} - -.home-goals-title a { - color: var(--light-cyan); - text-decoration: none; -} - -.home-goals-title a:hover { - color: black; - text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, - 1px 1px 0 #fff; -} - -.home-goals-learn-more a:hover { - text-decoration: none; -} - -.home-goals-description { - line-height: 1.5; - margin-bottom: 2em; -} - -/* Interactive example on homepage */ - -.interactive-example { - font-size: var(--font-size-normal); -} - -.interactive-example, -.interactive-example samp { - background-color: #202746; - color: white; -} - -.interactive-example samp { - position: relative; - display: block; - width: 100%; - height: 580px; - padding-right: 300px; - cursor: default; -} - -.interactive-example label:hover, -.interactive-radio:checked + label { - background-color: #000; - cursor: pointer; -} - -.interactive-desc { - display: none; - position: absolute; - top: 0; - right: 300px; - width: 300px; - background-color: #ede6ff; - border: 1px solid black; - color: black; - padding: 0 16px; - padding-top: 12px; - margin-top: 9px; - cursor: text; - white-space: normal; - font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial; -} - -.interactive-desc a { - color: #7c38f5; -} - -.interactive-radio { - display: none; -} - -.interactive-radio:checked + label + .interactive-desc { - display: block; -} - -.close-desc { - display: none; - position: absolute; - height: 40px; - width: 40px; - font-size: 24px; - top: -12px; - right: -12px; - color: #fff; - background: #9b6bf2; - border: 2px solid #7c38f5; - border-radius: 100%; - z-index: 4; -} - -.close-desc:hover { - color: #222; - background: var(--light-cyan); - border-color: var(--light-cyan); -} diff --git a/www/wip_new_website/static/site.js b/www/wip_new_website/static/site.js deleted file mode 100644 index 3e5fda0d78..0000000000 --- a/www/wip_new_website/static/site.js +++ /dev/null @@ -1,515 +0,0 @@ -const isOnMobile = window.innerWidth <= 1024; - -// The only way we can provide values to wasm_bindgen's generated code is to set globals -window.js_create_app = js_create_app; -window.js_run_app = js_run_app; -window.js_get_result_and_memory = js_get_result_and_memory; - -// The only place we use console.error is in wasm_bindgen, where it gets a single string argument. -console.error = function displayErrorInHistoryPanel(string) { - const html = `
    ${string}
    `; - updateHistoryEntry(repl.inputHistoryIndex, false, html); -}; - -import * as roc_repl_wasm from "./roc_repl_wasm.js"; - -const isHomepage = document.getElementById("homepage-repl-container") != null; - -const tutorialButtonSvg = ``; - -// ---------------------------------------------------------------------------- -// REPL state -// ---------------------------------------------------------------------------- - -const repl = { - elemHistory: document.getElementById("history-text"), - elemSourceInput: document.getElementById("source-input"), - description: document.getElementById("repl-description"), - - inputQueue: [], - inputHistory: [], - inputHistoryIndex: 0, - inputStash: "", // stash the user input while we're toggling through history with up/down arrows - - // Current progress through the repl tutorial - tutorialStep: 0, - tutorialSteps: [ - { - match: (input) => input.replace(/ /g, "") === "0.1+0.2", - show: '

    Was this the answer you expected? (If so, try this in other programming languages and see what their answers are.)

    Roc has a decimal type as well as floating-point for when performance is more important than decimal precision.

    Next, enter name = "(put your name here)"

    ', - }, - { - match: (input) => input.replace(/ /g, "").match(/^name="/i), - show: '

    This created a new definitionname is now defined to be equal to the string you entered.

    Try using this definition by entering "Hi, \\(name)!"

    ', - }, - { - match: (input) => input.match(/^["][^\\]+\\\(name\)/i), - show: `

    Nicely done! This is an example of string interpolation, which replaces part of a string with whatever you put inside the parentheses after a \\.

    Now that you’ve written a few expressions, you can either continue exploring in this REPL, or move on to the tutorial to learn how to make full programs.

    Welcome to Roc! ${tutorialButtonSvg} Start Tutorial

    `, - }, - ], - - textDecoder: new TextDecoder(), - textEncoder: new TextEncoder(), - - compiler: null, - app: null, - - // Temporary storage for the address of the result of running the user's code. - // Used while control flow returns to Rust to allocate space to copy the app's memory buffer. - result_addr: 0, -}; - -// Initialise -repl.elemSourceInput.value = ""; // Some browsers remember the input across refreshes -resetSourceInputHeight(); -repl.elemSourceInput.addEventListener("input", resetSourceInputHeight); -repl.elemSourceInput.addEventListener("keydown", onInputKeydown); -repl.elemSourceInput.addEventListener("keyup", onInputKeyup); -roc_repl_wasm.default("/wip/roc_repl_wasm_bg.wasm").then(async (instance) => { - const loadingMessage = repl.elemHistory.querySelector("#loading-message"); - - if (loadingMessage != null) { - loadingMessage.remove(); - } - - repl.elemSourceInput.placeholder = "Type some Roc code and press Enter."; - repl.compiler = instance; - - // Get help text from the compiler, and display it at top of the history panel - try { - const helpText = await roc_repl_wasm.entrypoint_from_js(":help"); - const helpElem = document.getElementById("help-text"); - - if (helpElem != null) { - helpElem.innerHTML = helpText.trim(); - } - } catch (e) { - // Print error for Roc devs. Don't use console.error, we overrode that above to display on the page! - console.warn(e); - } -}); - -// Focus the repl input the first time it scrolls into view -// (but not on mobile, because that would pop up the whole keyboard abruptly) -if (!isOnMobile) { - // Function to be called when the input enters the viewport - function handleIntersect(entries, observer) { - entries.forEach((entry) => { - // Check if the input is intersecting - if (entry.isIntersecting) { - // Apply focus to it, then unobserve it because we only want to do this once. - entry.target.focus(); - observer.unobserve(entry.target); - } - }); - } - - // Set up the Intersection Observer - let observer = new IntersectionObserver(handleIntersect, { - // Use the whole viewport for the intersection - root: null, - // Trigger the callback when the input is fully visible - threshold: 1.0, - }); - - observer.observe(repl.elemSourceInput); -} - -// ---------------------------------------------------------------------------- -// Handle inputs -// ---------------------------------------------------------------------------- - -function resetSourceInputHeight() { - repl.elemSourceInput.style.height = - repl.elemSourceInput.scrollHeight + 2 + "px"; // +2 for the border -} - -function onInputKeydown(event) { - const ENTER = 13; - - const { keyCode } = event; - - if (keyCode === ENTER) { - if (!event.shiftKey && !event.ctrlKey && !event.altKey) { - // Don't advance the caret to the next line - event.preventDefault(); - - const inputText = repl.elemSourceInput.value.trim(); - - repl.elemSourceInput.value = ""; - repl.elemSourceInput.style.height = ""; - - repl.inputQueue.push(inputText); - if (repl.inputQueue.length === 1) { - processInputQueue(); - } - - // Hide the arrow on the homepage that prompts you to enter something - const replArrow = document.getElementById("repl-arrow"); - - if (replArrow != null) { - replArrow.style.display = "none"; - } - } - } -} - -function onInputKeyup(event) { - const UP = 38; - const DOWN = 40; - - const { keyCode } = event; - - const el = repl.elemSourceInput; - - switch (keyCode) { - case UP: - if (repl.inputHistory.length === 0) { - return; - } - if (repl.inputHistoryIndex == repl.inputHistory.length - 1) { - repl.inputStash = el.value; - } - setInput(repl.inputHistory[repl.inputHistoryIndex]); - - if (repl.inputHistoryIndex > 0) { - repl.inputHistoryIndex--; - } - break; - - case DOWN: - if (repl.inputHistory.length === 0) { - return; - } - if (repl.inputHistoryIndex === repl.inputHistory.length - 1) { - setInput(repl.inputStash); - } else { - repl.inputHistoryIndex++; - setInput(repl.inputHistory[repl.inputHistoryIndex]); - } - break; - - default: - break; - } -} - -function setInput(value) { - const el = repl.elemSourceInput; - el.value = value; - el.selectionStart = value.length; - el.selectionEnd = value.length; -} - -function showNextReplTutorialEntry(inputText) { - const nextStep = repl.tutorialSteps[repl.tutorialStep]; - - if (typeof nextStep === "object" && nextStep.match(inputText)) { - repl.description.innerHTML = - repl.description.innerHTML + "
    " + nextStep.show; - - repl.tutorialStep = repl.tutorialStep + 1; - } -} - -// Use a queue just in case we somehow get inputs very fast -// We want the REPL to only process one at a time, since we're using some global state. -// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? -async function processInputQueue() { - while (repl.inputQueue.length) { - const inputText = repl.inputQueue[0]; - - if (inputText) { - repl.inputHistoryIndex = createHistoryEntry(inputText); - repl.inputStash = ""; - - let outputText = ""; - let ok = true; - try { - outputText = await roc_repl_wasm.entrypoint_from_js(inputText); - } catch (e) { - outputText = `${e}`; - ok = false; - } - - updateHistoryEntry(repl.inputHistoryIndex, ok, outputText); - showNextReplTutorialEntry(inputText); - } - - repl.inputQueue.shift(); - } -} - -// ---------------------------------------------------------------------------- -// Callbacks to JS from Rust -// ---------------------------------------------------------------------------- - -var ROC_PANIC_INFO = null; - -function send_panic_msg_to_js(rocstr_ptr, panic_tag) { - const { memory } = repl.app.exports; - - const rocStrBytes = new Int8Array(memory.buffer, rocstr_ptr, 12); - const finalByte = rocStrBytes[11]; - - let stringBytes = ""; - if (finalByte < 0) { - // small string - - // bitwise ops on negative JS numbers are weird. This clears the bit that we - // use to indicate a small string. In rust it's `finalByte as u8 ^ 0b1000_0000` - const length = finalByte + 128; - stringBytes = new Uint8Array(memory.buffer, rocstr_ptr, length); - } else { - // big string - const rocStrWords = new Uint32Array(memory.buffer, rocstr_ptr, 3); - const [ptr, len, _cap] = rocStrWords; - - const SEAMLESS_SLICE_BIT = 1 << 31; - const length = len & ~SEAMLESS_SLICE_BIT; - - stringBytes = new Uint8Array(memory.buffer, ptr, length); - } - - const decodedString = repl.textDecoder.decode(stringBytes); - - ROC_PANIC_INFO = { - msg: decodedString, - panic_tag: panic_tag, - }; -} - -// Load Wasm code into the browser's virtual machine, so we can run it later. -// This operation is async, so we call it before entering any code shared -// with the command-line REPL, which is sync. -async function js_create_app(wasm_module_bytes) { - const { instance } = await WebAssembly.instantiate(wasm_module_bytes, { - env: { - send_panic_msg_to_js: send_panic_msg_to_js, - }, - }); - - // Keep the instance alive so we can run it later from shared REPL code - repl.app = instance; -} - -// Call the `main` function of the user app, via the `wrapper` function. -function js_run_app() { - const { wrapper, memory } = repl.app.exports; - - // Run the user code, and remember the result address - // We'll pass it to Rust in the next callback - try { - repl.result_addr = wrapper(); - } catch (e) { - // an exception could be that roc_panic was invoked, - // or some other crash (likely a compiler bug) - if (ROC_PANIC_INFO === null) { - throw e; - } else { - // when roc_panic set an error message, display it - const { msg, panic_tag } = ROC_PANIC_INFO; - ROC_PANIC_INFO = null; - - console.error(format_roc_panic_message(msg, panic_tag)); - } - } - - // Tell Rust how much space to reserve for its copy of the app's memory buffer. - // We couldn't know that size until we actually ran the app. - return memory.buffer.byteLength; -} - -function format_roc_panic_message(msg, panic_tag) { - switch (panic_tag) { - case 0: { - return `Roc failed with message: "${msg}"`; - } - case 1: { - return `User crash with message: "${msg}"`; - } - default: { - return `Got an invalid panic tag: "${panic_tag}"`; - } - } -} - -// After Rust has allocated space for the app's memory buffer, -// we copy it, and return the result address too -function js_get_result_and_memory(buffer_alloc_addr) { - const appMemory = new Uint8Array(repl.app.exports.memory.buffer); - const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); - compilerMemory.set(appMemory, buffer_alloc_addr); - return repl.result_addr; -} - -// ---------------------------------------------------------------------------- -// Rendering -// ---------------------------------------------------------------------------- - -function createHistoryEntry(inputText) { - const historyIndex = repl.inputHistory.length; - repl.inputHistory.push(inputText); - - const firstLinePrefix = '» '; - const otherLinePrefix = '
    '; - const inputLines = inputText.split("\n"); - if (inputLines[inputLines.length - 1] === "") { - inputLines.pop(); - } - const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix); - - const inputElem = document.createElement("div"); - inputElem.innerHTML = inputWithPrefixes; - inputElem.classList.add("input"); - - const historyItem = document.createElement("div"); - historyItem.appendChild(inputElem); - historyItem.classList.add("history-item"); - - repl.elemHistory.appendChild(historyItem); - - return historyIndex; -} - -function updateHistoryEntry(index, ok, outputText) { - const outputElem = document.createElement("div"); - outputElem.innerHTML = outputText; - outputElem.classList.add("output", ok ? "output-ok" : "output-error"); - - const historyItem = repl.elemHistory.children[index]; - historyItem.appendChild(outputElem); - - if (isHomepage) { - // Scroll the input element into view so you can see the most recent output. - // Only do this if it's currently out of view though! - const bounds = repl.elemSourceInput.getBoundingClientRect(); - const isInView = - bounds.top >= 0 && - bounds.left >= 0 && - bounds.bottom <= - (window.innerHeight || document.documentElement.clientHeight) && - bounds.right <= - (window.innerWidth || document.documentElement.clientWidth); - - if (!isInView) { - repl.elemSourceInput.scrollIntoView({ - behavior: "instant", - block: "end", - inline: "nearest", - }); - } - } else { - // Scroll the page to the bottom so you can see the most recent output. - window.scrollTo(0, document.body.scrollHeight); - } -} - -// TUTORIAL // - -const tutorialTocToggle = document.querySelector("#tutorial-toc-toggle"); - -if (tutorialTocToggle != null) { - document.querySelectorAll("#tutorial-toc li a").forEach((elem) => { - // Clicking any of the ToC links closes the ToC - elem.addEventListener("click", (event) => { - tutorialTocToggle.checked = false; - }); - }); - - document.addEventListener("keydown", (event) => { - // Escape closes the ToC - if (event.key == "Escape") { - tutorialTocToggle.checked = false; - } - }); - - const isTouchSupported = () => { - try { - document.createEvent("TouchEvent"); - return true; - } catch (e) { - return false; - } - }; - - // Select all elements that are children of
     elements
    -  const codeBlocks = document.querySelectorAll("pre > samp");
    -
    -  // Iterate over each code block
    -  codeBlocks.forEach((codeBlock) => {
    -    // Create a "Copy" button
    -    const copyButton = document.createElement("button");
    -    copyButton.classList.add("copy-button");
    -    copyButton.textContent = "Copy";
    -
    -    // Add event listener to copy button
    -    copyButton.addEventListener("click", () => {
    -      const codeText = codeBlock.innerText;
    -      navigator.clipboard.writeText(codeText);
    -      copyButton.textContent = "Copied!";
    -      copyButton.classList.add("copy-button-copied");
    -      copyButton.addEventListener("mouseleave", () => {
    -        copyButton.textContent = "Copy";
    -        copyButton.classList.remove("copy-button-copied");
    -      });
    -    });
    -
    -    // Create a container for the copy button and append it to the document
    -    const buttonContainer = document.createElement("div");
    -    buttonContainer.classList.add("button-container");
    -    buttonContainer.appendChild(copyButton);
    -    codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
    -
    -    // Hide the button container by default
    -    buttonContainer.style.display = "none";
    -
    -    if (isTouchSupported()) {
    -      // Show the button container on click for touch support (e.g. mobile)
    -      document.addEventListener("click", (event) => {
    -        if (event.target.closest("pre > samp") !== codeBlock) {
    -          buttonContainer.style.display = "none";
    -        } else {
    -          buttonContainer.style.display = "block";
    -        }
    -      });
    -    } else {
    -      // Show the button container on hover for non-touch support (e.g. desktop)
    -      codeBlock.parentNode.addEventListener("mouseenter", () => {
    -        buttonContainer.style.display = "block";
    -      });
    -
    -      codeBlock.parentNode.addEventListener("mouseleave", () => {
    -        buttonContainer.style.display = "none";
    -      });
    -    }
    -  });
    -}
    -
    -// HOMEPAGE //
    -
    -if (isOnMobile) {
    -  const hideDesc = () => {
    -    document.querySelectorAll(".interactive-radio").forEach((radio) => {
    -      radio.checked = false;
    -    });
    -  };
    -
    -  hideDesc(); // On mobile, start out with all the descriptions hidden.
    -
    -  document.querySelectorAll(".interactive-example").forEach((example) => {
    -    example.querySelectorAll("label").forEach((label) => {
    -      label.addEventListener("click", (event) => {
    -        const desc = label.nextSibling; // The description node always comes next
    -
    -        // Set the position of the target element
    -        desc.style.top = label.offsetTop + label.offsetHeight + "px"; // Position below the button
    -        desc.style.left = label.offsetLeft + "px"; // Align with the left of the button
    -      });
    -    });
    -
    -    example.querySelectorAll(".close-desc").forEach((button) => {
    -      button.addEventListener("click", hideDesc);
    -    });
    -  });
    -}