Merge remote-tracking branch 'origin/main' into more-dollars

This commit is contained in:
Richard Feldman 2024-02-26 23:03:14 -05:00
commit 6f84e24fa5
No known key found for this signature in database
GPG Key ID: F1F21AA5B1D9E43B
450 changed files with 14625 additions and 29764 deletions

4
.gitattributes vendored
View File

@ -1,2 +1,4 @@
# Require roc files to be checlked out with Unix line endings, even on windows
# Require roc files to be checked out with Unix line endings, even on windows
*.roc text eol=lf
crates/compiler/test_mono/generated/* linguist-generated=true

View File

@ -6,7 +6,7 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.br
@ -17,7 +17,7 @@ jobs:
prepare:
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
repository: roc-lang/basic-cli
@ -40,7 +40,7 @@ jobs:
- 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
uses: actions/upload-artifact@v4
with:
path: roc_nightly-*
@ -48,18 +48,18 @@ jobs:
runs-on: [ubuntu-20.04]
needs: [prepare]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: build basic-cli with surgical linker and also with legacy linker
env:
CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
run: ./ci/build_basic_cli.sh linux_x86_64 "--linker legacy"
- name: Save .rh, .rm and .o file
uses: actions/upload-artifact@v3
- name: Save .rh, .rm and .o file
uses: actions/upload-artifact@v4
with:
name: linux-x86_64-files
path: |
@ -72,10 +72,10 @@ jobs:
runs-on: [self-hosted, Linux, ARM64]
needs: [prepare]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: build basic-cli
env:
@ -85,8 +85,8 @@ jobs:
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
- name: Save .o file
uses: actions/upload-artifact@v4
with:
name: linux-arm64-files
path: |
@ -96,15 +96,15 @@ jobs:
runs-on: [macos-11] # I expect the generated files to work on macOS 12 and up
needs: [prepare]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- run: ./ci/build_basic_cli.sh macos_x86_64
- name: Save .o files
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: macos-x86_64-files
path: |
@ -115,15 +115,15 @@ jobs:
runs-on: [self-hosted, macOS, ARM64]
needs: [prepare]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- run: ./ci/build_basic_cli.sh macos_apple_silicon
- name: Save macos-arm64.o file
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: macos-apple-silicon-files
path: |
@ -134,13 +134,13 @@ jobs:
name: create release archive
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- 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
uses: actions/download-artifact@v4
- 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
@ -157,7 +157,7 @@ jobs:
- run: git clone https://github.com/roc-lang/basic-cli.git
- run: cp macos-apple-silicon-files/* ./basic-cli/platform
- run: cp linux-x86_64-files/* ./basic-cli/platform
- run: cp linux-arm64-files/* ./basic-cli/platform
@ -177,14 +177,14 @@ jobs:
- run: echo "TAR_FILENAME=$(ls -d basic-cli/platform/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV
- name: Upload platform archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: basic-cli-platform
path: |
${{ env.TAR_FILENAME }}
- name: Upload docs archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: release-assets-docs
path: |
@ -196,7 +196,7 @@ jobs:
steps:
- name: Download the previously uploaded files
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- 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
@ -232,7 +232,7 @@ jobs:
mkdir platform
# move all files to platform dir
find . -maxdepth 1 -type f -exec mv {} platform/ \;
mkdir temp-basic-cli
cd temp-basic-cli
git clone https://github.com/roc-lang/basic-cli.git
@ -242,9 +242,8 @@ jobs:
cp -r ci ../..
cp -r LICENSE ../..
# LICENSE is necessary for command test
- name: run tests
run: |
cd basic-cli-platform
ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ROC_BUILD_FLAGS=--prebuilt-platform ./ci/all_tests.sh

View File

@ -11,7 +11,7 @@ jobs:
runs-on: [self-hosted, Linux, ARM64]
steps:
- name: clone basic-cli repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: roc-lang/basic-cli
ref: main
@ -19,7 +19,7 @@ jobs:
- name: get latest roc nightly
run: |
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
- name: rename nightly tar
run: mv $(ls | grep "roc_nightly.*tar\.gz") roc_nightly.tar.gz
@ -35,7 +35,7 @@ jobs:
- run: expect -v
# Run all tests
# Run all tests
- run: ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ./ci/all_tests.sh
######
@ -44,7 +44,7 @@ jobs:
- name: Remove roc_nightly folder to keep things simple (we'll download it again later)
run: rm -rf roc_nightly
- name: Get the repo of the latest basic-cli release
run: |
git clone --depth 1 https://github.com/roc-lang/basic-cli

View File

@ -6,7 +6,7 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.br
@ -16,7 +16,7 @@ jobs:
fetch-releases:
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- 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
@ -24,7 +24,7 @@ jobs:
- 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
uses: actions/upload-artifact@v4
with:
path: roc_nightly-*
@ -32,18 +32,18 @@ jobs:
runs-on: [ubuntu-20.04]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: build basic-webserver 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
- name: Save .rh, .rm and .o file
uses: actions/upload-artifact@v4
with:
name: linux-x86_64-files
path: |
@ -56,10 +56,10 @@ jobs:
runs-on: [self-hosted, Linux, ARM64]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: build basic-webserver
env:
@ -69,8 +69,8 @@ jobs:
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
- name: Save .o file
uses: actions/upload-artifact@v4
with:
name: linux-arm64-files
path: |
@ -80,15 +80,15 @@ jobs:
runs-on: [macos-11] # I expect the generated files to work on macOS 12 and 13
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- run: ./ci/build_basic_webserver.sh macos_x86_64
- name: Save .o files
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: macos-x86_64-files
path: |
@ -99,15 +99,15 @@ jobs:
runs-on: [self-hosted, macOS, ARM64]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- run: ./ci/build_basic_webserver.sh macos_apple_silicon
- name: Save macos-arm64.o file
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: macos-apple-silicon-files
path: |
@ -118,13 +118,13 @@ jobs:
name: create release archive
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- 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
uses: actions/download-artifact@v4
- 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
@ -145,7 +145,7 @@ jobs:
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
@ -157,7 +157,7 @@ jobs:
- run: echo "TAR_FILENAME=$(ls -d basic-webserver/platform/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV
- name: Upload platform archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: basic-webserver-platform
path: |

View File

@ -15,7 +15,7 @@ jobs:
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: "main"
clean: "true"
@ -23,7 +23,7 @@ jobs:
- name: on main; prepare a self-contained benchmark folder
run: nix develop -c ./ci/benchmarks/prep_folder.sh main
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
clean: "false" # we want to keep the benchmark folder

View File

@ -15,16 +15,43 @@ jobs:
run_tests: ${{ steps.filecheck.outputs.run_tests }}
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Check if only css, html or md files changed
id: filecheck
id: check_ignored_files
run: |
git fetch origin ${{ github.base_ref }}
if git diff --name-only origin/${{ github.base_ref }} HEAD | grep -qvE '(\.md$|\.css$|\.html$|^AUTHORS$)'; then
echo "run_tests=full" >> $GITHUB_OUTPUT
echo "should_run_tests=y" >> $GITHUB_OUTPUT
else
echo "should_run_tests=n" >> $GITHUB_OUTPUT
fi
- name: Check if only comments changed in rust files
id: check_rs_comments
run: |
git fetch origin ${{ github.base_ref }}
if git diff --name-only origin/${{ github.base_ref }} HEAD | grep -qvE '(\.md$|\.css$|\.html$|^AUTHORS$|\.rs)'; then
echo "should_run_tests=y" >> $GITHUB_OUTPUT
else
if git diff --unified=0 origin/${{ github.base_ref }} HEAD '*.rs' | grep -E --color=never '^[+-]' | grep -qvE '^(\+\+\+|\-\-\-|[+-]\s*($|\/\/|[+-]|\/\*.*\*\/\s*$))'; then
echo "should_run_tests=y" >> $GITHUB_OUTPUT
else
echo "should_run_tests=n" >> $GITHUB_OUTPUT
fi
fi
- name: Check if tests need to run based on earlier checks
id: filecheck
run: |
if [ ${{ steps.check_ignored_files.outputs.should_run_tests }} = "y" ]; then
if [ ${{ steps.check_rs_comments.outputs.should_run_tests }} = "y" ]; then
echo "run_tests=full" >> $GITHUB_OUTPUT
else
echo "run_tests=none" >> $GITHUB_OUTPUT
fi
else
echo "run_tests=none" >> $GITHUB_OUTPUT
fi
- run: echo "debug output ${{ steps.filecheck.outputs.run_tests }}"
@ -95,7 +122,7 @@ jobs:
needs: [check-changes]
if: needs.check-changes.outputs.run_tests == 'none'
steps:
- run: echo "Only non-code files changed. CI manager did not run any workflows."
- run: echo "Only non-code files changed and/or rs comment lines changed. CI manager did not run any workflows."
# we need a single end job for the required checks under branch protection rules
finish:
@ -112,7 +139,3 @@ jobs:
fi
- run: echo "Workflow succeeded :)"

View File

@ -1,6 +1,6 @@
on:
pull_request:
name: devtools nix files test - linux
concurrency:
@ -13,7 +13,7 @@ jobs:
runs-on: [ubuntu-20.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Only run all steps if a nix file changed
run: |
@ -25,7 +25,7 @@ jobs:
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.nix_changed == 'true'
@ -53,6 +53,3 @@ jobs:
echo "locally deleting devtools/flake.lock and following the"
echo "instructions in devtools/README.md. This will create a"
echo "new flake.lock you should use to replace the old devtools/flake.lock"

View File

@ -13,7 +13,7 @@ jobs:
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Only run all steps if a nix file changed
run: |
@ -47,6 +47,3 @@ jobs:
echo "locally deleting devtools/flake.lock and following the"
echo "instructions in devtools/README.md. This will create a"
echo "new flake.lock you should use to replace the old devtools/flake.lock"

View File

@ -9,7 +9,7 @@ jobs:
runs-on: [ubuntu-22.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Copy example docker file
run: cp docker/nightly-ubuntu-latest/docker-compose.example.yml docker/nightly-ubuntu-latest/docker-compose.yml
@ -26,7 +26,7 @@ jobs:
runs-on: [ubuntu-22.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Copy example docker file
run: cp docker/nightly-ubuntu-2204/docker-compose.example.yml docker/nightly-ubuntu-2204/docker-compose.yml
@ -42,7 +42,7 @@ jobs:
runs-on: [ubuntu-22.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Copy example docker file
run: cp docker/nightly-ubuntu-2004/docker-compose.example.yml docker/nightly-ubuntu-2004/docker-compose.yml
@ -58,7 +58,7 @@ jobs:
runs-on: [ubuntu-22.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Copy example docker file
run: cp docker/nightly-debian-latest/docker-compose.example.yml docker/nightly-debian-latest/docker-compose.yml
@ -74,7 +74,7 @@ jobs:
runs-on: [ubuntu-22.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Copy example docker file
run: cp docker/nightly-debian-bookworm/docker-compose.example.yml docker/nightly-debian-bookworm/docker-compose.yml
@ -90,7 +90,7 @@ jobs:
runs-on: [ubuntu-22.04]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Copy example docker file
run: cp docker/nightly-debian-buster/docker-compose.example.yml docker/nightly-debian-buster/docker-compose.yml
@ -100,4 +100,3 @@ jobs:
- name: Run hello world test
run: docker-compose -f docker/nightly-debian-buster/docker-compose.yml run roc examples/helloWorld.roc

View File

@ -1,33 +1,33 @@
on:
workflow_call:
workflow_call:
name: Macos x86-64 rust tests
env:
RUST_BACKTRACE: 1
RUST_BACKTRACE: 1
jobs:
test-rust-macos-x86-64:
runs-on: [self-hosted, macOS, X64]
timeout-minutes: 90
env:
RUSTC_WRAPPER: /Users/username1/.cargo/bin/sccache
steps:
- uses: actions/checkout@v3
test-rust-macos-x86-64:
runs-on: [self-hosted, macOS, X64]
timeout-minutes: 90
env:
RUSTC_WRAPPER: /Users/username1/.cargo/bin/sccache
steps:
- uses: actions/checkout@v4
- name: set LLVM_SYS_160_PREFIX
run: echo "LLVM_SYS_160_PREFIX=$(brew --prefix llvm@16)" >> $GITHUB_ENV
- name: set LLVM_SYS_160_PREFIX
run: echo "LLVM_SYS_160_PREFIX=$(brew --prefix llvm@16)" >> $GITHUB_ENV
- name: Update PATH to use zig 11
run: |
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
- run: zig version
- name: Update PATH to use zig 11
run: |
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
- name: test_gen llvm tests
run: cargo nextest-gen-llvm --release --no-fail-fast --locked -E "package(test_gen) - test(gen_str::str_append_scalar)"
- run: zig version
- name: regular rust tests
run: cargo test --locked --release -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_str::str_append_scalar --skip gen_tags::phantom_polymorphic_record && sccache --show-stats
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- name: test_gen llvm tests
run: cargo nextest-gen-llvm --release --no-fail-fast --locked -E "package(test_gen) - test(gen_str::str_append_scalar)"
- name: regular rust tests
run: cargo test --locked --release -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_tags::phantom_polymorphic_record && sccache --show-stats
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode

View File

@ -13,7 +13,7 @@ jobs:
markdown-link-check:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'

View File

@ -5,7 +5,7 @@ on:
- cron: '0 9 * * *'
name: Nightly Release Linux arm64/aarch64
jobs:
build:
name: build and package nightly release
@ -13,26 +13,26 @@ jobs:
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Update PATH to use zig 11
run: |
echo "PATH=/home/username/Downloads/zig-linux-aarch64-0.11.0:$PATH" >> $GITHUB_ENV
- run: zig version
- name: create version.txt
run: ./ci/write_version.sh
- name: build release with lto
run: cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
run: cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
@ -42,12 +42,12 @@ jobs:
# this makes the roc binary a lot smaller
- name: strip debug info
run: strip ./target/release-with-lto/roc
- name: Make nightly release tar archive
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz

View File

@ -5,32 +5,32 @@ on:
- cron: '0 9 * * *'
name: Nightly Release Linux x86_64
jobs:
build:
name: build and package nightly release
runs-on: [self-hosted, i7-6700K]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Update PATH to use zig 11
run: |
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
- run: zig version
- name: create version.txt
run: ./ci/write_version.sh
- name: build release with lto
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. This was also faster in our tests: https://roc.zulipchat.com/#narrow/stream/231635-compiler-development/topic/.2Ecargo.2Fconfig.2Etoml/near/325726299
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
@ -38,12 +38,12 @@ jobs:
run: ./ci/www-repl.sh
- name: Upload wasm repl tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: roc_repl_wasm.tar.gz
path: roc_repl_wasm.tar.gz
retention-days: 4
- name: build file name
env:
DATE: ${{ env.DATE }}
@ -53,12 +53,12 @@ jobs:
# this makes the roc binary a lot smaller
- name: strip debug info
run: strip ./target/release-with-lto/roc
- name: Make nightly release tar archive
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz

View File

@ -16,8 +16,8 @@ jobs:
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: zig version
- name: llvm version
@ -25,24 +25,24 @@ jobs:
- name: run tests
run: cargo test --locked --release
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_apple_silicon-$DATE-$SHA" >> $GITHUB_ENV
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_apple_silicon-$DATE-$SHA" >> $GITHUB_ENV
- name: write version to file
run: ./ci/write_version.sh
- name: build nightly release
run: cargo build --locked --profile=release-with-lto --bin roc --bin roc_ls
run: cargo build --locked --profile=release-with-lto --bin roc --bin roc_language_server
- name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
@ -61,7 +61,7 @@ jobs:
- name: print date
run: date
- name: Upload artifact Actually uploading to github releases has to be done manually
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz

View File

@ -1,63 +1,62 @@
on:
# pull_request:
workflow_dispatch:
schedule:
- cron: '0 9 * * *' # 9=9am utc+0
# pull_request:
workflow_dispatch:
schedule:
- cron: "0 9 * * *" # 9=9am utc+0
name: Nightly Release macOS x86_64
env:
LLVM_SYS_160_PREFIX: /usr/local/opt/llvm@16
LLVM_SYS_160_PREFIX: /usr/local/opt/llvm@16
jobs:
test-build-upload:
name: build, test, package and upload nightly release
runs-on: [self-hosted, macOS, X64]
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
test-build-upload:
name: build, test, package and upload nightly release
runs-on: [self-hosted, macOS, X64]
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Update PATH to use zig 11
run: |
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
- run: zig version
- name: write version to file
run: ./ci/write_version.sh
- name: execute rust tests
run: cargo test --release --locked -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_str::str_append_scalar --skip gen_tags::phantom_polymorphic_record
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- name: Update PATH to use zig 11
run: |
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
- name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
# target-cpu=x86-64 -> For maximal compatibility for all CPU's.
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
- run: zig version
# this makes the roc binary a lot smaller
- name: strip debug info
run: strip ./target/release-with-lto/roc
- name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
- name: Upload artifact. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4
- name: write version to file
run: ./ci/write_version.sh
- name: execute rust tests
run: cargo test --release --locked -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_tags::phantom_polymorphic_record
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
# target-cpu=x86-64 -> For maximal compatibility for all CPU's.
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
# this makes the roc binary a lot smaller
- name: strip debug info
run: strip ./target/release-with-lto/roc
- name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
- name: Upload artifact. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View File

@ -5,21 +5,21 @@ on:
- cron: '0 9 * * *'
name: Nightly Release Old Linux arm64 using Earthly
jobs:
build:
name: build and package nightly release
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 180
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
@ -32,7 +32,7 @@ jobs:
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --ZIG_ARCH=aarch64
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz

View File

@ -5,37 +5,37 @@ on:
- cron: '0 9 * * *'
name: Nightly Release Old Linux x86_64 using Earthly
jobs:
build:
name: build and package nightly release
runs-on: [ubuntu-20.04]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-old_linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV
- name: install earthly
run: sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'
- run: earthly --version
- name: build release with earthly
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --RUSTFLAGS="-C target-cpu=x86-64"
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz

View File

@ -1,6 +1,6 @@
on:
workflow_call:
name: test cargo build on linux arm64 inside nix
env:
@ -12,11 +12,11 @@ jobs:
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 150
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: test release build
run: nix develop -c cargo build --release --locked
# TODO
# TODO
#- name: build tests without running
# run: cargo test --no-run --release

View File

@ -1,7 +1,7 @@
on:
workflow_call:
name: test default.nix on linux arm64
name: test default.nix on linux arm64
env:
RUST_BACKTRACE: 1
@ -12,7 +12,7 @@ jobs:
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 150
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: test building default.nix
run: nix-build

View File

@ -12,10 +12,10 @@ jobs:
runs-on: [self-hosted, i5-4690K]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: test building default.nix
run: nix-build
run: nix-build
- name: execute tests with --release
run: nix develop -c cargo test --locked --release

View File

@ -12,7 +12,7 @@ jobs:
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# These started to accumulate quickly since #5990, not sure why
- name: Clean up old nix shells

View File

@ -12,7 +12,7 @@ jobs:
runs-on: [macos-12]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v22

View File

@ -18,7 +18,7 @@ jobs:
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: cargo install typos-cli --version 1.0.11

View File

@ -9,7 +9,7 @@ jobs:
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: get the latest release archive
run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
@ -45,6 +45,3 @@ jobs:
cp target/release/repl_basic_test ../../roc_nightly
cd ../../roc_nightly
./repl_basic_test

View File

@ -13,14 +13,14 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: goto-bus-stop/setup-zig@v2
with:
version: 0.11.0
- name: get the latest release archive for linux (x86_64)
if: startsWith(matrix.os, 'ubuntu')
run: |
run: |
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
@ -40,7 +40,3 @@ jobs:
rm -rf roc_nightly
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-old_linux_x86_64-latest.tar.gz
./ci/basic_nightly_test.sh

View File

@ -14,7 +14,7 @@ jobs:
env:
RUSTC_WRAPPER: /home/big-ci-user/.cargo/bin/sccache
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Check for duplicate AUTHORS
run: diff <(sort AUTHORS) <(sort AUTHORS | uniq) # The < operator treats a string as a file. diff 'succeeds' if no difference.
@ -22,7 +22,7 @@ jobs:
- name: Update PATH to use zig 11
run: |
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
- run: zig version
- name: zig fmt check, zig tests
@ -40,7 +40,7 @@ jobs:
- name: check that the platform`s produced dylib is loadable
run: cd examples/platform-switching/rust-platform && LD_LIBRARY_PATH=. cargo test --release --locked
- name: test the dev backend # these tests require an explicit feature flag
run: cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats

View File

@ -15,7 +15,7 @@ jobs:
timeout-minutes: 150
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
@ -29,8 +29,8 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.71.0
run: rustup install nightly-2023-05-28
- name: install rust nightly 1.72.0
run: rustup install nightly-2023-07-09
- name: set up llvm 16
run: |

View File

@ -15,7 +15,7 @@ jobs:
timeout-minutes: 150
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
@ -37,29 +37,26 @@ jobs:
cd crates\compiler\builtins\bitcode\
zig build test
- name: install rust nightly 1.71.0
run: rustup install nightly-2023-05-28
- name: install rust nightly 1.72.0
run: rustup install nightly-2023-07-09
- name: set up llvm 16
run: |
curl.exe -f -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v16.0.6/LLVM-16.0.6-win64.7z
7z x LLVM-16.0.6-win64.7z -oC:\LLVM-16.0.6-win64
- name: Build tests --release without running.
- name: Build tests --release without running.
run: cargo test --locked --release --no-run
# Why are these tests not build with previous command? => fingerprint error. Use `CARGO_LOG=cargo::core::compiler::fingerprint=info` to investigate
- name: Build specific tests without running.
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli -p test_gen
- name: Build specific tests without running.
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli -p test_gen
- name: Test setjmp/longjmp logic
run: cargo test-gen-dev --locked --release nat_alias && cargo test-gen-dev --locked --release a_crash
- name: Run gen tests
run: cargo test-gen-llvm --locked --release gen_str
run: cargo test-gen-llvm --locked --release gen_str
- name: Actually run the tests.
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli

37
.gitignore vendored
View File

@ -1,3 +1,27 @@
### Do not modify these first three ignore rules. Needed to ignore files with no extension ###
# Ignore all files including binary files that have no extension
*
# Unignore all files with extensions
!*.*
# Unignore all directories
!*/
# Specifically keep these files with no extension
!Earthfile
!AUTHORS
!LICENSE*
!LEGAL*
!Dockerfile
# .reuse/dep5 see https://reuse.software/
!dep5
# NotARocFile is used for formatter test
!NotARocFile
# also includes keeping one exe benchmark file
!dynhost_benchmarks*
### Add specific file extensions and directories below ###
# Ignore the following directories and file extensions
target
generated-docs
zig-cache
@ -63,7 +87,7 @@ result
# 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.
# This remove unnecessary lock file versioning.
# It also ensures the compiler can always pull in 1 version of things and doesn't get restricted by sub lockfiles.
/**/Cargo.lock
!/Cargo.lock
@ -83,4 +107,13 @@ www/dist
# ignore the examples folder in the WIP website, this is copied from roc-lang/examples in when building the site
www/content/examples
www/examples-main.zip
www/examples-main
www/examples-main
examples/static-site-gen/**/*.html
# glue auto-generated fixture code
crates/glue/tests/fixtures/*/Cargo.toml
crates/glue/tests/fixtures/*/build.rs
crates/glue/tests/fixtures/*/host.c
crates/glue/tests/fixtures/*/src/main.rs
crates/glue/tests/fixtures/*/test_glue/

View File

@ -10,7 +10,7 @@ On MacOS and Linux, we highly recommend Using [nix](https://nixos.org/download.h
### On Linux x86_64 or MacOS aarch64/arm64/x86_64
#### Install
#### Installing Nix
If you are running ArchLinux or a derivative like Manjaro, you'll need to run `sudo sysctl -w kernel.unprivileged_userns_clone=1` before installing nix.

120
Cargo.lock generated
View File

@ -771,6 +771,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "dissimilar"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632"
[[package]]
name = "distance"
version = "0.4.0"
@ -838,6 +844,19 @@ dependencies = [
"regex",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -864,6 +883,16 @@ dependencies = [
"str-buf",
]
[[package]]
name = "expect-test"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3"
dependencies = [
"dissimilar",
"once_cell",
]
[[package]]
name = "fd-lock"
version = "3.0.13"
@ -1151,6 +1180,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.27"
@ -1327,6 +1362,17 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "is-terminal"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [
"hermit-abi 0.3.3",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "itertools"
version = "0.9.0"
@ -1922,7 +1968,7 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger",
"env_logger 0.8.4",
"log",
"rand",
]
@ -2150,6 +2196,7 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"indoc",
"regex",
"roc_build",
"roc_cli",
"roc_repl_cli",
@ -2273,6 +2320,7 @@ dependencies = [
"roc_collections",
"roc_command_utils",
"roc_constrain",
"roc_debug_flags",
"roc_error_macros",
"roc_gen_dev",
"roc_gen_llvm",
@ -2649,6 +2697,10 @@ name = "roc_lang_srv"
version = "0.0.1"
dependencies = [
"bumpalo",
"env_logger 0.10.2",
"expect-test",
"indoc",
"log",
"parking_lot",
"roc_can",
"roc_collections",
@ -4546,6 +4598,15 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
@ -4576,6 +4637,21 @@ dependencies = [
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
@ -4588,6 +4664,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
@ -4600,6 +4682,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
@ -4612,6 +4700,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
@ -4624,6 +4718,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
@ -4636,6 +4736,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
@ -4648,6 +4754,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
@ -4660,6 +4772,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winreg"
version = "0.50.0"

View File

@ -1,6 +1,6 @@
VERSION 0.6
FROM rust:1.71.1-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
FROM rust:1.72.1-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
@ -32,7 +32,7 @@ install-zig-llvm:
RUN apt -y install libpolly-16-dev # required by llvm-sys crate
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
RUN apt -y install libssl-dev
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
RUN wget https://rustwasm.github.io/wasm-pack/installer/init.sh -O init.sh && sh init.sh
# sccache
RUN cargo install sccache --locked
RUN sccache -V
@ -53,7 +53,7 @@ build-nightly-release:
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
# version.txt is used by the CLI: roc --version
RUN ./ci/write_version.sh
RUN RUSTFLAGS=$RUSTFLAGS cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
RUN RUSTFLAGS=$RUSTFLAGS cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
RUN ./ci/package_release.sh $RELEASE_FOLDER_NAME
RUN ls
SAVE ARTIFACT ./$RELEASE_FOLDER_NAME.tar.gz AS LOCAL $RELEASE_FOLDER_NAME.tar.gz

View File

@ -33,6 +33,7 @@ If you would like your company to become a corporate sponsor of Roc's developmen
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:
* [Steven Chen](https://github.com/megakilo)
* [Drew Lazzeri](https://github.com/asteroidb612)
* [Alex Binaei](https://github.com/mrmizz)
* [Jono Mallanyk](https://github.com/jonomallanyk)

View File

@ -3,7 +3,6 @@ use data_encoding::HEXUPPER;
use is_executable::IsExecutable;
use regex::Regex;
use ring::digest::{Context, Digest, SHA256};
use std::fs::File;
use std::io::Read;
use std::{
collections::{HashMap, HashSet, VecDeque},
@ -22,7 +21,7 @@ fn main() {
delete_old_bench_results();
if optional_args.check_executables_changed {
println!("Doing a test run to verify benchmarks are working correctly and generate executables.");
println!("\nDoing a test run to verify benchmarks are working correctly and generate executables.\n");
std::env::set_var("BENCH_DRY_RUN", "1");
@ -79,6 +78,7 @@ fn finish(all_regressed_benches: HashSet<String>, nr_repeat_benchmarks: usize) {
// returns all benchmarks that have regressed
fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
delete_old_bench_results();
do_benchmark("main");
let mut all_regressed_benches = do_benchmark("branch");
@ -91,6 +91,7 @@ fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
for _ in 1..nr_repeat_benchmarks {
delete_old_bench_results();
do_benchmark("main");
let regressed_benches = do_benchmark("branch");
@ -110,17 +111,25 @@ fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
// returns Vec with names of regressed benchmarks
fn do_benchmark(branch_name: &'static str) -> HashSet<String> {
let mut cmd_child = Command::new(format!(
"./bench-folder-{}/target/release/deps/time_bench",
branch_name
))
.args(&["--bench", "--noplot"])
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.unwrap_or_else(|_| panic!("Failed to benchmark {}.", branch_name));
let mut bench_cmd =
Command::new(format!(
"./bench-folder-{}/target/release/deps/time_bench",
branch_name
));
let stdout = cmd_child.stdout.as_mut().unwrap();
let bench_cmd_w_args =
bench_cmd.args(&["--bench", "--noplot"]);
let bench_cmd_as_str = format!("{bench_cmd_w_args:?}");
let mut bench_cmd_child =
bench_cmd_w_args
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.unwrap_or_else(|_| panic!("Failed to benchmark {}.", branch_name));
let stdout = bench_cmd_child.stdout.as_mut().unwrap();
let stdout_reader = BufReader::new(stdout);
let stdout_lines = stdout_reader.lines();
@ -147,6 +156,18 @@ fn do_benchmark(branch_name: &'static str) -> HashSet<String> {
println!(">>bench {:?}: {:?}", branch_name, line_str);
}
let exit_status = bench_cmd_child.wait().expect("Failed to wait on cmd_child");
if !exit_status.success() {
panic!(
"Error: time-bench execution failed with exit code {}.\n\
See output above for error info.\n\
Command was:\n\t{}",
exit_status,
bench_cmd_as_str
);
}
regressed_benches
}
@ -190,20 +211,21 @@ fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest, io::Error> {
}
fn sha_file(file_path: &Path) -> Result<String, io::Error> {
// Debug info is dependent on the dir in which executable was created,
// so we need to strip that to be able to compare binaries.
let no_debug_info_file_path = file_path.to_str().unwrap().to_string() + ("_no_debug_info");
std::fs::copy(file_path, &no_debug_info_file_path)?;
// only checking disassembly because of #6386
let disassembly_output = Command::new("objdump")
.args(["-d", file_path.to_str().unwrap()])
.output()
.expect("failed to execute objdump");
let strip_output = Command::new("strip")
.args(["--strip-debug", &no_debug_info_file_path])
.output()
.expect("failed to execute process");
assert!(disassembly_output.status.success());
assert!(strip_output.status.success());
let mut reader = BufReader::new(disassembly_output.stdout.as_slice());
// the first line contains the path, we want to skip it
let mut _discard_lines = String::new();
reader.read_line(&mut _discard_lines)?;
reader.read_line(&mut _discard_lines)?;
let no_debug_info_file = File::open(no_debug_info_file_path)?;
let reader = BufReader::new(no_debug_info_file);
let digest = sha256_digest(reader)?;
Ok(HEXUPPER.encode(digest.as_ref()))
@ -250,6 +272,7 @@ fn check_if_bench_executables_changed() -> bool {
let main_bench_hashes = calc_hashes_for_folder(&main_benches_path_str);
let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join("");
let branch_bench_hashes = calc_hashes_for_folder(&branch_benches_path_str);
if main_bench_hashes.keys().len() == branch_bench_hashes.keys().len() {

View File

@ -5,19 +5,19 @@ set -euxo pipefail
# this makes the binaries a lot smaller
strip ./target/release-with-lto/roc
strip ./target/release-with-lto/roc_ls
strip ./target/release-with-lto/roc_language_server
# to be able to delete "target" later
cp target/release-with-lto/roc ./roc
cp target/release-with-lto/roc_ls ./roc_lang_server
cp target/release-with-lto/roc_language_server ./roc_language_server
# delete unnecessary files and folders
git clean -fdx --exclude roc --exclude roc_lang_server
git clean -fdx --exclude roc --exclude roc_language_server
mkdir $1
mv roc roc_lang_server LICENSE LEGAL_DETAILS $1
mv roc roc_language_server LICENSE LEGAL_DETAILS $1
mkdir $1/examples
mv examples/helloWorld.roc examples/platform-switching examples/cli $1/examples

View File

@ -7,7 +7,7 @@ use std::time::Duration;
fn roc_repl_session() -> Result<PtyReplSession, Error> {
let roc_repl = PtyReplSession {
echo_on: false,
prompt: "\u{1b}[0K\u{1b}[34\u{1b}[0m ".to_string(),
prompt: "\u{1b}[0K\u{1b}[1;36\u{1b}[0m ".to_string(),
pty_session: spawn("./roc repl", Some(7000))?,
quit_command: None,
};
@ -23,8 +23,8 @@ fn main() -> Result<(), Error> {
thread::sleep(Duration::from_secs(1));
match repl.exp_regex(r".*2\u{1b}\[35m : \u{1b}\[0mNum *.*") {
Ok((a, b)) => {
match repl.exp_regex(r".*2\u{1b}\[1;32m : \u{1b}\[0mNum *.*") { // 2 : Num
Ok(_) => {
println!("Expected output received.");
return Ok(());
}

View File

@ -1,19 +1,12 @@
# Running the benchmarks
Install cargo criterion:
If you're not using nix, install cargo criterion:
```sh
cargo install cargo-criterion
```
To prevent stack overflow on the `CFold` benchmark:
```sh
ulimit -s unlimited
```
In the `cli` folder execute:
In the `crates/cli` folder execute:
```sh
cargo criterion

View File

@ -68,6 +68,7 @@ pub const FLAG_STDIN: &str = "stdin";
pub const FLAG_STDOUT: &str = "stdout";
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
pub const FLAG_OUTPUT: &str = "output";
pub const FLAG_FUZZ: &str = "fuzz";
pub const ROC_FILE: &str = "ROC_FILE";
pub const ROC_DIR: &str = "ROC_DIR";
pub const GLUE_DIR: &str = "GLUE_DIR";
@ -139,6 +140,12 @@ pub fn build_app() -> Command {
.value_parser(value_parser!(u32))
.required(false);
let flag_fuzz = Arg::new(FLAG_FUZZ)
.long(FLAG_FUZZ)
.help("Instrument the roc binary for fuzzing with roc-fuzz")
.action(ArgAction::SetTrue)
.required(false);
let roc_file_to_run = Arg::new(ROC_FILE)
.help("The .roc file of an app to run")
.value_parser(value_parser!(PathBuf))
@ -175,6 +182,7 @@ pub fn build_app() -> Command {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
.arg(flag_fuzz.clone())
.arg(flag_wasm_stack_size_kb)
.arg(
Arg::new(FLAG_TARGET)
@ -225,6 +233,7 @@ pub fn build_app() -> Command {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
.arg(flag_fuzz.clone())
.arg(
Arg::new(ROC_FILE)
.help("The .roc file for the main module")
@ -248,6 +257,7 @@ pub fn build_app() -> Command {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
.arg(flag_fuzz.clone())
.arg(roc_file_to_run.clone())
.arg(args_for_app.clone().last(true))
)
@ -262,11 +272,12 @@ pub fn build_app() -> Command {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_prebuilt.clone())
.arg(flag_fuzz.clone())
.arg(roc_file_to_run.clone())
.arg(args_for_app.clone().last(true))
)
.subcommand(Command::new(CMD_FORMAT)
.about("Format a .roc file using standard Roc formatting")
.about("Format a .roc file or the .roc files contained in a directory using standard\nRoc formatting")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
@ -294,6 +305,7 @@ pub fn build_app() -> Command {
.action(ArgAction::SetTrue)
.required(false),
)
.after_help("If DIRECTORY_OR_FILES is omitted, the .roc files in the current working\ndirectory are formatted.")
)
.subcommand(Command::new(CMD_VERSION)
.about(concatcp!("Print the Roc compilers version, which is currently ", VERSION)))
@ -392,6 +404,7 @@ pub fn build_app() -> Command {
.arg(flag_time)
.arg(flag_linker)
.arg(flag_prebuilt)
.arg(flag_fuzz)
.arg(roc_file_to_run)
.arg(args_for_app.trailing_var_arg(true))
}
@ -427,6 +440,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
use roc_build::program::report_problems_monomorphized;
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadMonomorphizedError};
use roc_packaging::cache;
use roc_reporting::report::ANSI_STYLE_CODES;
use roc_target::TargetInfo;
let start_time = Instant::now();
@ -528,7 +542,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let mut writer = std::io::stdout();
let (failed, passed) = roc_repl_expect::run::run_toplevel_expects(
let (failed_count, passed_count) = roc_repl_expect::run::run_toplevel_expects(
&mut writer,
roc_reporting::report::RenderTarget::ColorTerminal,
arena,
@ -542,7 +556,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let total_time = start_time.elapsed();
if failed == 0 && passed == 0 {
if failed_count == 0 && passed_count == 0 {
// TODO print this in a more nicely formatted way!
println!("No expectations were found.");
@ -553,18 +567,20 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
// running tests altogether!
Ok(2)
} else {
let failed_color = if failed == 0 {
32 // green
let failed_color = if failed_count == 0 {
ANSI_STYLE_CODES.green
} else {
31 // red
ANSI_STYLE_CODES.red
};
let passed_color = ANSI_STYLE_CODES.green;
println!(
"\n\x1B[{failed_color}m{failed}\x1B[39m failed and \x1B[32m{passed}\x1B[39m passed in {} ms.\n",
"\n{failed_color}{failed_count}\x1B[39m failed and {passed_color}{passed_count}\x1B[39m passed in {} ms.\n",
total_time.as_millis(),
);
Ok((failed > 0) as i32)
Ok((failed_count > 0) as i32)
}
}
@ -747,6 +763,11 @@ pub fn build(
(cross_compile && !targeting_wasm)
};
let fuzz = matches.get_flag(FLAG_FUZZ);
if fuzz && !matches!(code_gen_backend, CodeGenBackend::Llvm(_)) {
user_error!("Cannot instrument binary for fuzzing while using a dev backend.");
}
let wasm_dev_stack_bytes: Option<u32> = matches
.try_get_one::<u32>(FLAG_WASM_STACK_SIZE_KB)
.ok()
@ -763,6 +784,7 @@ pub fn build(
opt_level,
emit_debug_info,
emit_llvm_ir,
fuzz,
};
let load_config = standard_load_config(&triple, build_ordering, threading);

View File

@ -210,33 +210,7 @@ fn main() -> io::Result<()> {
threading,
) {
Ok((problems, total_time)) => {
println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",
if problems.errors == 0 {
32 // green
} else {
33 // yellow
},
problems.errors,
if problems.errors == 1 {
"error"
} else {
"errors"
},
if problems.warnings == 0 {
32 // green
} else {
33 // yellow
},
problems.warnings,
if problems.warnings == 1 {
"warning"
} else {
"warnings"
},
total_time.as_millis(),
);
problems.print_to_stdout(total_time);
Ok(problems.exit_code())
}
@ -287,6 +261,7 @@ fn main() -> io::Result<()> {
values.push(os_string.to_owned());
}
}
None if from_stdin || to_stdout => {}
None => {
let mut os_string_values: Vec<OsString> = Vec::new();

View File

@ -11,12 +11,13 @@ extern crate roc_module;
mod cli_run {
use cli_utils::helpers::{
extract_valgrind_errors, file_path_from_root, fixture_file, fixtures_dir, has_error,
known_bad_file, run_cmd, run_roc, run_with_valgrind, strip_colors, Out, ValgrindError,
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat,
};
use const_format::concatcp;
use indoc::indoc;
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_FORMAT, CMD_RUN, CMD_TEST};
use roc_reporting::report::strip_colors;
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial;
use std::iter;
@ -323,11 +324,12 @@ mod cli_run {
}
// when you want to run `roc test` to execute `expect`s, perhaps on a library rather than an application.
fn test_roc_expect(dir_name: &str, roc_filename: &str) {
let path = file_path_from_root(dir_name, roc_filename);
let out = run_roc([CMD_TEST, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
// not currently used
// fn test_roc_expect(dir_name: &str, roc_filename: &str) {
// let path = file_path_from_root(dir_name, roc_filename);
// let out = run_roc([CMD_TEST, path.to_str().unwrap()], &[], &[]);
// assert!(out.status.success());
// }
// when you don't need args, stdin or extra_env
fn test_roc_app_slim(
@ -552,9 +554,11 @@ mod cli_run {
&[],
indoc!(
r#"
EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc
This expectation failed:
19 expect words == []
28 expect words == []
^^^^^^^^^^^
When it failed, these variables had these values:
@ -562,12 +566,12 @@ mod cli_run {
words : List Str
words = ["this", "will", "for", "sure", "be", "a", "large", "string", "so", "when", "we", "split", "it", "it", "will", "use", "seamless", "slices", "which", "affect", "printing"]
[<ignored for tests>:22] x = 42
[<ignored for tests>:23] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests>:24] "this is line 24" = "this is line 24"
[<ignored for tests>:13] x = "abc"
[<ignored for tests>:13] x = 10
[<ignored for tests>:13] x = (A (B C))
[<ignored for tests>:31] x = 42
[<ignored for tests>:33] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests>:35] "this is line 24" = "this is line 24"
[<ignored for tests>:21] x = "abc"
[<ignored for tests>:21] x = 10
[<ignored for tests>:21] x = (A (B C))
Program finished!
"#
),
@ -583,20 +587,46 @@ mod cli_run {
&[],
indoc!(
r#"
EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc
This expectation failed:
6> expect
7> a = 1
8> b = 2
9>
10> a == b
9 expect a == 2
^^^^^^
When it failed, these variables had these values:
a : Num *
a = 1
b : Num *
EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc
This expectation failed:
10 expect a == 3
^^^^^^
When it failed, these variables had these values:
a : Num *
a = 1
EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc
This expectation failed:
14> expect
15> a = makeA
16> b = 2i64
17>
18> a == b
When it failed, these variables had these values:
a : Int Signed64
a = 1
b : I64
b = 2
@ -871,10 +901,10 @@ mod cli_run {
&[],
indoc!(
r#"
This roc file can print it's own source code. The source is:
This roc file can print its own source code. The source is:
app "ingested-file"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [
pf.Stdout,
"ingested-file.roc" as ownCode : Str,
@ -882,7 +912,7 @@ mod cli_run {
provides [main] to pf
main =
Stdout.line "\nThis roc file can print it's own source code. The source is:\n\n$(ownCode)"
Stdout.line "\nThis roc file can print its own source code. The source is:\n\n$(ownCode)"
"#
),
@ -912,9 +942,9 @@ mod cli_run {
#[cfg_attr(windows, ignore)]
fn parse_movies_csv() {
test_roc_app_slim(
"examples/parser/examples",
"examples/parser",
"parse-movies-csv.roc",
"Parse success!\n",
"2 movies were found:\n\nThe movie 'Airplane!' was released in 1980 and stars Robert Hays and Julie Hagerty\nThe movie 'Caddyshack' was released in 1980 and stars Chevy Chase, Rodney Dangerfield, Ted Knight, Michael O'Keefe and Bill Murray\n\nParse success!\n\n",
UseValgrind::No,
)
}
@ -924,19 +954,13 @@ mod cli_run {
#[cfg_attr(windows, ignore)]
fn parse_letter_counts() {
test_roc_app_slim(
"examples/parser/examples",
"examples/parser",
"letter-counts.roc",
"I counted 7 letter A's!\n",
UseValgrind::No,
)
}
#[test]
#[cfg_attr(windows, ignore)]
fn parse_http() {
test_roc_expect("examples/parser/package", "ParserHttp.roc")
}
#[test]
#[cfg_attr(windows, ignore)]
fn inspect_logging() {
@ -976,13 +1000,13 @@ mod cli_run {
// TODO fix QuicksortApp and then remove this!
match roc_filename {
"QuicksortApp.roc" => {
"quicksortApp.roc" => {
eprintln!(
"WARNING: skipping testing benchmark {roc_filename} because the test is broken right now!"
);
return;
}
"TestAStar.roc" => {
"testAStar.roc" => {
if cfg!(feature = "wasm32-cli-run") {
eprintln!(
"WARNING: skipping testing benchmark {roc_filename} because it currently does not work on wasm32 due to dictionaries."
@ -1137,20 +1161,20 @@ mod cli_run {
#[test]
#[cfg_attr(windows, ignore)]
fn nqueens() {
test_benchmark("NQueens.roc", &["6"], "4\n", UseValgrind::Yes)
test_benchmark("nQueens.roc", &["6"], "4\n", UseValgrind::Yes)
}
#[test]
#[cfg_attr(windows, ignore)]
fn cfold() {
test_benchmark("CFold.roc", &["3"], "11 & 11\n", UseValgrind::Yes)
test_benchmark("cFold.roc", &["3"], "11 & 11\n", UseValgrind::Yes)
}
#[test]
#[cfg_attr(windows, ignore)]
fn deriv() {
test_benchmark(
"Deriv.roc",
"deriv.roc",
&["2"],
"1 count: 6\n2 count: 22\n",
UseValgrind::Yes,
@ -1160,14 +1184,14 @@ mod cli_run {
#[test]
#[cfg_attr(windows, ignore)]
fn rbtree_ck() {
test_benchmark("RBTreeCk.roc", &["100"], "10\n", UseValgrind::Yes)
test_benchmark("rBTreeCk.roc", &["100"], "10\n", UseValgrind::Yes)
}
#[test]
#[cfg_attr(windows, ignore)]
fn rbtree_insert() {
test_benchmark(
"RBTreeInsert.roc",
"rBTreeInsert.roc",
&[],
"Node Black 0 {} Empty Empty\n",
UseValgrind::Yes,
@ -1179,25 +1203,25 @@ mod cli_run {
#[test]
fn rbtree_del() {
test_benchmark(
"RBTreeDel.roc",
"rBTreeDel.roc",
&["420"],
&[],
"30\n",
true
UseValgrind::Yes,
)
}*/
}
*/
#[test]
#[cfg_attr(windows, ignore)]
fn astar() {
test_benchmark("TestAStar.roc", &[], "True\n", UseValgrind::No)
test_benchmark("testAStar.roc", &[], "True\n", UseValgrind::No)
}
#[test]
#[cfg_attr(windows, ignore)]
fn base64() {
test_benchmark(
"TestBase64.roc",
"testBase64.roc",
&[],
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
UseValgrind::Yes,
@ -1207,19 +1231,19 @@ mod cli_run {
#[test]
#[cfg_attr(windows, ignore)]
fn closure() {
test_benchmark("Closure.roc", &[], "", UseValgrind::No)
test_benchmark("closure.roc", &[], "", UseValgrind::No)
}
#[test]
#[cfg_attr(windows, ignore)]
fn issue2279() {
test_benchmark("Issue2279.roc", &[], "Hello, world!\n", UseValgrind::Yes)
test_benchmark("issue2279.roc", &[], "Hello, world!\n", UseValgrind::Yes)
}
#[test]
fn quicksort_app() {
test_benchmark(
"QuicksortApp.roc",
"quicksortApp.roc",
&[],
"todo put the correct quicksort answer here",
UseValgrind::Yes,
@ -1333,7 +1357,7 @@ mod cli_run {
&[],
indoc!(
r#"
TYPE MISMATCH tests/known_bad/TypeError.roc
TYPE MISMATCH in tests/known_bad/TypeError.roc
Something is off with the body of the main definition:
@ -1362,6 +1386,29 @@ mod cli_run {
);
}
#[test]
fn known_type_error_with_long_path() {
check_compile_error(
&known_bad_file("UnusedImportButWithALongFileNameForTesting.roc"),
&[],
indoc!(
r#"
UNUSED IMPORT in ...nown_bad/UnusedImportButWithALongFileNameForTesting.roc
Nothing from Symbol is used in this module.
3 imports [Symbol.{ Ident }]
^^^^^^^^^^^^^^^^
Since Symbol isn't used, you don't need to import it.
0 errors and 1 warning found in <ignored for test> ms."#
),
);
}
#[test]
fn exposed_not_defined() {
check_compile_error(
@ -1369,7 +1416,7 @@ mod cli_run {
&[],
indoc!(
r#"
MISSING DEFINITION tests/known_bad/ExposedNotDefined.roc
MISSING DEFINITION in tests/known_bad/ExposedNotDefined.roc
bar is listed as exposed, but it isn't defined in this module.
@ -1390,7 +1437,7 @@ mod cli_run {
&[],
indoc!(
r#"
UNUSED IMPORT tests/known_bad/UnusedImport.roc
UNUSED IMPORT in tests/known_bad/UnusedImport.roc
Nothing from Symbol is used in this module.
@ -1413,7 +1460,7 @@ mod cli_run {
&[],
indoc!(
r#"
UNKNOWN GENERATES FUNCTION tests/known_bad/UnknownGeneratesWith.roc
UNKNOWN GENERATES FUNCTION in tests/known_bad/UnknownGeneratesWith.roc
I don't know how to generate the foobar function.

View File

@ -1,11 +0,0 @@
app
*.o
*.dSYM
dynhost
libapp.so
metadata
preprocessedhost
packages-test
multi-dep-str/multi-dep-str
multi-dep-thunk/multi-dep-thunk

View File

@ -1 +0,0 @@
Main

View File

@ -1 +0,0 @@
Main

View File

@ -3,4 +3,4 @@ app "packages-test"
imports [json.JsonParser, csv.Csv]
provides [main] to pf
main = "Hello, World! \(JsonParser.example) \(Csv.example)"
main = "Hello, World! $(JsonParser.example) $(Csv.example)"

View File

@ -0,0 +1,7 @@
interface UnusedImportButWithALongFileNameForTesting
exposes [plainText, emText]
imports [Symbol.{ Ident }]
plainText = \str -> PlainText str
emText = \str -> EmText str

View File

@ -1,12 +0,0 @@
*.dSYM
libhost.a
libapp.so
dynhost
preprocessedhost
metadata
expects/expects
benchmarks/rbtree-ck
benchmarks/rbtree-insert
benchmarks/test-astar
benchmarks/test-base64

View File

@ -1,2 +0,0 @@
fibonacci
quicksort

View File

@ -8,7 +8,7 @@ quicksort = \originalList ->
quicksortHelp originalList 0 (n - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp : List (Num a), U64, U64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -19,7 +19,7 @@ quicksortHelp = \list, low, high ->
else
list
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition : U64, U64, List (Num a) -> [Pair U64 (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -30,7 +30,7 @@ partition = \low, high, initialList ->
Err _ ->
Pair low initialList
partitionHelp : Nat, Nat, List (Num c), Nat, Num c -> [Pair Nat (List (Num c))]
partitionHelp : U64, U64, List (Num c), U64, Num c -> [Pair U64 (List (Num c))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -45,7 +45,7 @@ partitionHelp = \i, j, list, high, pivot ->
else
Pair i list
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->

View File

@ -1,12 +0,0 @@
cfold
closure
deriv
issue2279
nqueens
quicksortapp
RBTreeCk
RBTreeDel
RBTreeInsert
TestAStar
TestBase64
*.wasm

View File

@ -4,10 +4,10 @@ fromBytes : List U8 -> Result Str DecodeProblem
fromBytes = \bytes ->
Bytes.Decode.decode bytes (decodeBase64 (List.len bytes))
decodeBase64 : Nat -> ByteDecoder Str
decodeBase64 : U64 -> ByteDecoder Str
decodeBase64 = \width -> Bytes.Decode.loop loopHelp { remaining: width, string: "" }
loopHelp : { remaining : Nat, string : Str } -> ByteDecoder (Bytes.Decode.Step { remaining : Nat, string : Str } Str)
loopHelp : { remaining : U64, string : Str } -> ByteDecoder (Bytes.Decode.Step { remaining : U64, string : Str } Str)
loopHelp = \{ remaining, string } ->
if remaining >= 3 then
x, y, z <- Bytes.Decode.map3 Bytes.Decode.u8 Bytes.Decode.u8 Bytes.Decode.u8

View File

@ -18,7 +18,7 @@ encodeChunks = \bytes ->
List.walk bytes { output: [], accum: None } folder
|> encodeResidual
coerce : Nat, a -> a
coerce : U64, a -> a
coerce = \_, x -> x
# folder : { output : List ByteEncoder, accum : State }, U8 -> { output : List ByteEncoder, accum : State }

View File

@ -1,6 +1,6 @@
interface Bytes.Decode exposes [ByteDecoder, decode, map, map2, u8, loop, Step, succeed, DecodeProblem, after, map3] imports []
State : { bytes : List U8, cursor : Nat }
State : { bytes : List U8, cursor : U64 }
DecodeProblem : [OutOfBytes]

View File

@ -2,7 +2,7 @@ interface Bytes.Encode exposes [ByteEncoder, sequence, u8, u16, bytes, empty, en
Endianness : [BE, LE]
ByteEncoder : [Signed8 I8, Unsigned8 U8, Signed16 Endianness I16, Unsigned16 Endianness U16, Sequence Nat (List ByteEncoder), Bytes (List U8)]
ByteEncoder : [Signed8 I8, Unsigned8 U8, Signed16 Endianness I16, Unsigned16 Endianness U16, Sequence U64 (List ByteEncoder), Bytes (List U8)]
u8 : U8 -> ByteEncoder
u8 = \value -> Unsigned8 value
@ -24,7 +24,7 @@ sequence : List ByteEncoder -> ByteEncoder
sequence = \encoders ->
Sequence (getWidths encoders 0) encoders
getWidth : ByteEncoder -> Nat
getWidth : ByteEncoder -> U64
getWidth = \encoder ->
when encoder is
Signed8 _ -> 1
@ -40,7 +40,7 @@ getWidth = \encoder ->
Sequence w _ -> w
Bytes bs -> List.len bs
getWidths : List ByteEncoder, Nat -> Nat
getWidths : List ByteEncoder, U64 -> U64
getWidths = \encoders, initial ->
List.walk encoders initial \accum, encoder -> accum + getWidth encoder
@ -51,7 +51,7 @@ encode = \encoder ->
encodeHelp encoder 0 output
|> .output
encodeHelp : ByteEncoder, Nat, List U8 -> { output : List U8, offset : Nat }
encodeHelp : ByteEncoder, U64, List U8 -> { output : List U8, offset : U64 }
encodeHelp = \encoder, offset, output ->
when encoder is
Unsigned8 value ->

View File

@ -10,7 +10,7 @@ show = \list ->
|> List.map Num.toStr
|> Str.joinWith ", "
"[\(content)]"
"[$(content)]"
sortBy : List a, (a -> Num *) -> List a
sortBy = \list, toComparable ->
@ -24,7 +24,7 @@ sortWith = \list, order ->
quicksortHelp list order 0 (n - 1)
quicksortHelp : List a, Order a, Nat, Nat -> List a
quicksortHelp : List a, Order a, U64, U64 -> List a
quicksortHelp = \list, order, low, high ->
if low < high then
when partition low high list order is
@ -35,7 +35,7 @@ quicksortHelp = \list, order, low, high ->
else
list
partition : Nat, Nat, List a, Order a -> [Pair Nat (List a)]
partition : U64, U64, List a, Order a -> [Pair U64 (List a)]
partition = \low, high, initialList, order ->
when List.get initialList high is
Ok pivot ->
@ -46,7 +46,7 @@ partition = \low, high, initialList, order ->
Err _ ->
Pair low initialList
partitionHelp : Nat, Nat, List c, Order c, Nat, c -> [Pair Nat (List c)]
partitionHelp : U64, U64, List c, Order c, U64, c -> [Pair U64 (List c)]
partitionHelp = \i, j, list, order, high, pivot ->
if j < high then
when List.get list j is
@ -63,7 +63,7 @@ partitionHelp = \i, j, list, order, high, pivot ->
else
Pair i list
swap : Nat, Nat, List a -> List a
swap : U64, U64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->

View File

@ -15,7 +15,7 @@ closure1 = \_ ->
Task.succeed (foo toUnitBorrowed "a long string such that it's malloced")
|> Task.map \_ -> {}
toUnitBorrowed = \x -> Str.countGraphemes x
toUnitBorrowed = \x -> Str.countUtf8Bytes x
foo = \f, x -> f x

View File

@ -248,4 +248,4 @@ del = \t, k ->
rebalanceLeft cx lx ky vy ry
Delmin (Del ry Bool.false) ky vy ->
Del (Node cx lx ky vy ry) Bool.false
Del (Node cx lx ky vy ry) Bool.false

View File

@ -26,7 +26,7 @@ showRBTree = \tree, showKey, showValue ->
sL = nodeInParens left showKey showValue
sR = nodeInParens right showKey showValue
"Node \(sColor) \(sKey) \(sValue) \(sL) \(sR)"
"Node $(sColor) $(sKey) $(sValue) $(sL) $(sR)"
nodeInParens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
nodeInParens = \tree, showKey, showValue ->
@ -37,7 +37,7 @@ nodeInParens = \tree, showKey, showValue ->
Node _ _ _ _ _ ->
inner = showRBTree tree showKey showValue
"(\(inner))"
"($(inner))"
showColor : NodeColor -> Str
showColor = \color ->

View File

@ -14,7 +14,7 @@ main =
#
# _ ->
# ns = Num.toStr n
# Task.putLine "No test \(ns)"
# Task.putLine "No test $(ns)"
showBool : Bool -> Str
showBool = \b ->
if

View File

@ -3,14 +3,23 @@ app "expects-test"
imports []
provides [main] to pf
expect
makeA =
a = 1
b = 2
expect a == 2
expect a == 3
a
expect
a = makeA
b = 2i64
a == b
polyDbg = \x ->
dbg x
x
main =
@ -20,10 +29,12 @@ main =
x = 42
dbg x
dbg "Fjoer en ferdjer frieten oan dyn geve lea"
dbg "this is line 24"
r = {x : polyDbg "abc", y: polyDbg 10u8, z : polyDbg (A (B C))}
r = { x: polyDbg "abc", y: polyDbg 10u8, z: polyDbg (A (B C)) }
when r is
_ -> "Program finished!\n"

File diff suppressed because one or more lines are too long

View File

@ -98,22 +98,6 @@ pub fn path_to_binary(binary_name: &str) -> PathBuf {
path
}
pub fn strip_colors(str: &str) -> String {
use roc_reporting::report::ANSI_STYLE_CODES;
str.replace(ANSI_STYLE_CODES.red, "")
.replace(ANSI_STYLE_CODES.green, "")
.replace(ANSI_STYLE_CODES.yellow, "")
.replace(ANSI_STYLE_CODES.blue, "")
.replace(ANSI_STYLE_CODES.magenta, "")
.replace(ANSI_STYLE_CODES.cyan, "")
.replace(ANSI_STYLE_CODES.white, "")
.replace(ANSI_STYLE_CODES.bold, "")
.replace(ANSI_STYLE_CODES.underline, "")
.replace(ANSI_STYLE_CODES.reset, "")
.replace(ANSI_STYLE_CODES.color_reset, "")
}
pub fn run_roc_with_stdin<I, S>(args: I, stdin_vals: &[&str]) -> Out
where
I: IntoIterator<Item = S>,
@ -255,7 +239,12 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|_| panic!("failed to execute cmd `{cmd_name}` in CLI test"));
.unwrap_or_else(|err| {
panic!(
"Encountered error:\n\t{:?}\nWhile executing cmd:\n\t{:?}",
err, cmd_str
)
});
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
@ -269,7 +258,7 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
let output = child
.wait_with_output()
.unwrap_or_else(|_| panic!("failed to execute cmd `{cmd_name}` in CLI test"));
.unwrap_or_else(|_| panic!("Failed to execute cmd:\n\t`{:?}`", cmd_str));
Out {
cmd_str,

View File

@ -1121,7 +1121,7 @@ fn lowlevel_spec<'a>(
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
ListLen => {
ListLenUsize | ListLenU64 => {
// TODO should this touch the heap cell?
// just dream up a unit value
builder.add_make_tuple(block, &[])
@ -1173,6 +1173,16 @@ fn lowlevel_spec<'a>(
_ => unreachable!(),
}
}
ListClone => {
let list = env.symbols[&arguments[0]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
with_new_heap_cell(builder, block, bag)
}
ListSwap => {
let list = env.symbols[&arguments[0]];
@ -1220,7 +1230,7 @@ fn lowlevel_spec<'a>(
builder.add_make_tuple(block, &[cell, bag])
}
StrFromUtf8Range => {
StrFromUtf8 => {
let list = env.symbols[&arguments[0]];
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;

View File

@ -12,6 +12,7 @@ roc_bitcode = { path = "../builtins/bitcode" }
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_constrain = { path = "../constrain" }
roc_debug_flags = { path = "../debug_flags" }
roc_error_macros = { path = "../../error_macros" }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_gen_llvm = { path = "../gen_llvm" }

View File

@ -1,6 +1,7 @@
use crate::target::{arch_str, target_zig_str};
use libloading::{Error, Library};
use roc_command_utils::{cargo, clang, rustup, zig};
use roc_debug_flags;
use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
@ -537,7 +538,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly-2023-05-28", "cargo"]);
cmd.args(["run", "nightly-2023-07-09", "cargo"]);
cmd
} else {
@ -613,7 +614,8 @@ pub fn rebuild_host(
// Clean up c_host.o
if c_host_dest.exists() {
std::fs::remove_file(c_host_dest).unwrap();
// there can be a race condition on this file cleanup
let _ = std::fs::remove_file(c_host_dest);
}
}
} else if rust_host_src.exists() {
@ -848,6 +850,17 @@ fn strs_to_path(strs: &[&str]) -> PathBuf {
strs.iter().collect()
}
fn extra_link_flags() -> Vec<String> {
match env::var("ROC_LINK_FLAGS") {
Ok(flags) => {
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
flags
}
Err(_) => "".to_string(),
}.split_whitespace().map(|x| x.to_owned()).collect()
}
fn link_linux(
target: &Triple,
output_path: PathBuf,
@ -1037,6 +1050,7 @@ fn link_linux(
.args(&base_args)
.args(["-dynamic-linker", ld_linux])
.args(input_paths)
.args(extra_link_flags())
// ld.lld requires this argument, and does not accept --arch
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
.args([
@ -1054,6 +1068,7 @@ fn link_linux(
"-o",
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
]);
debug_print_command(&command);
let output = command.spawn()?;
@ -1108,7 +1123,8 @@ fn link_macos(
"-macos_version_min",
&get_macos_version(),
])
.args(input_paths);
.args(input_paths)
.args(extra_link_flags());
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
if Path::new(sdk_path).exists() {
@ -1116,18 +1132,6 @@ fn link_macos(
ld_command.arg(format!("-L{sdk_path}/swift"));
};
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
Ok(flags) => {
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
flags
}
Err(_) => "".to_string(),
};
for roc_link_flag in roc_link_flags.split_whitespace() {
ld_command.arg(roc_link_flag);
}
ld_command.args([
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496392274
// for discussion and further references
@ -1162,14 +1166,18 @@ fn link_macos(
output_path.to_str().unwrap(), // app
]);
debug_print_command(&ld_command);
let mut ld_child = ld_command.spawn()?;
match target.architecture {
Architecture::Aarch64(_) => {
ld_child.wait()?;
let codesign_child = Command::new("codesign")
.args(["-s", "-", output_path.to_str().unwrap()])
.spawn()?;
let mut codesign_cmd = Command::new("codesign");
codesign_cmd.args(["-s", "-", output_path.to_str().unwrap()]);
debug_print_command(&codesign_cmd);
let codesign_child = codesign_cmd.spawn()?;
Ok((codesign_child, output_path))
}
@ -1178,8 +1186,11 @@ fn link_macos(
}
fn get_macos_version() -> String {
let cmd_stdout = Command::new("sw_vers")
.arg("-productVersion")
let mut cmd = Command::new("sw_vers");
cmd.arg("-productVersion");
debug_print_command(&cmd);
let cmd_stdout = cmd
.output()
.expect("Failed to execute command 'sw_vers -productVersion'")
.stdout;
@ -1221,6 +1232,7 @@ fn link_wasm32(
"-fstrip",
"-O",
"ReleaseSmall",
"-rdynamic",
// useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll",
])
@ -1382,15 +1394,11 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
}
fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_counter: usize) {
let mut command_string = std::ffi::OsString::new();
command_string.push(command.get_program());
for arg in command.get_args() {
command_string.push(" ");
command_string.push(arg);
}
let cmd_str = command_string.to_str().unwrap();
let command_string = stringify_command(&command);
let cmd_str = &command_string;
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
print_command_str(cmd_str);
});
let cmd_output = command.output().unwrap();
let max_flaky_fail_count = 10;
@ -1428,3 +1436,41 @@ fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_count
}
}
}
/// Stringify a command for printing
/// e.g. `HOME=~ zig build-exe foo.zig -o foo`
fn stringify_command(cmd: &Command) -> String {
let mut command_string = std::ffi::OsString::new();
for (name, opt_val) in cmd.get_envs() {
command_string.push(name);
command_string.push("=");
if let Some(val) = opt_val {
command_string.push(val);
} else {
command_string.push("''");
}
command_string.push(" ");
}
command_string.push(cmd.get_program());
for arg in cmd.get_args() {
command_string.push(" ");
command_string.push(arg);
}
String::from(command_string.to_str().unwrap())
}
#[cfg(debug_assertions)]
fn print_command_str(s: &str) {
println!("\nRoc build command:\n{}\n", s);
}
fn debug_print_command(_cmd: &Command) {
// This debug macro is compiled out in release mode, so the argument is unused
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
print_command_str(&stringify_command(_cmd));
});
}

View File

@ -86,6 +86,7 @@ pub struct CodeGenOptions {
pub opt_level: OptLevel,
pub emit_debug_info: bool,
pub emit_llvm_ir: bool,
pub fuzz: bool,
}
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
@ -103,6 +104,7 @@ pub fn gen_from_mono_module<'a>(
let path = roc_file_path;
let debug = code_gen_options.emit_debug_info;
let emit_llvm_ir = code_gen_options.emit_llvm_ir;
let fuzz = code_gen_options.fuzz;
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
@ -131,6 +133,7 @@ pub fn gen_from_mono_module<'a>(
backend_mode,
debug,
emit_llvm_ir,
fuzz,
),
}
}
@ -148,6 +151,7 @@ fn gen_from_mono_module_llvm<'a>(
backend_mode: LlvmBackendMode,
emit_debug_info: bool,
emit_llvm_ir: bool,
fuzz: bool,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
@ -284,7 +288,8 @@ fn gen_from_mono_module_llvm<'a>(
// annotate the LLVM IR output with debug info
// so errors are reported with the line number of the LLVM source
let memory_buffer = if cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok() {
let gen_sanitizers = cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok();
let memory_buffer = if fuzz || gen_sanitizers {
let dir = tempfile::tempdir().unwrap();
let dir = dir.into_path();
@ -301,33 +306,27 @@ fn gen_from_mono_module_llvm<'a>(
let mut passes = vec![];
let mut extra_args = vec![];
let mut unrecognized = vec![];
for sanitizer in std::env::var("ROC_SANITIZERS")
.unwrap()
.split(',')
.map(|x| x.trim())
{
match sanitizer {
"address" => passes.push("asan-module"),
"memory" => passes.push("msan-module"),
"thread" => passes.push("tsan-module"),
"cargo-fuzz" => {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=3",
"-sanitizer-coverage-prune-blocks=0",
"-sanitizer-coverage-inline-8bit-counters",
"-sanitizer-coverage-pc-table",
]);
if fuzz {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=4",
"-sanitizer-coverage-inline-8bit-counters",
"-sanitizer-coverage-pc-table",
"-sanitizer-coverage-trace-compares",
]);
}
if gen_sanitizers {
for sanitizer in std::env::var("ROC_SANITIZERS")
.unwrap()
.split(',')
.map(|x| x.trim())
{
match sanitizer {
"address" => passes.push("asan-module"),
"memory" => passes.push("msan-module"),
"thread" => passes.push("tsan-module"),
x => unrecognized.push(x.to_owned()),
}
"afl.rs" => {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=3",
"-sanitizer-coverage-prune-blocks=0",
"-sanitizer-coverage-trace-pc-guard",
]);
}
x => unrecognized.push(x.to_owned()),
}
}
if !unrecognized.is_empty() {
@ -802,7 +801,7 @@ fn build_loaded_file<'a>(
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
};
let mut output_exe_path = match out_path {
let output_exe_path = match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
@ -830,12 +829,22 @@ fn build_loaded_file<'a>(
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_executable_extension(&path.join(filename), operating_system)
with_output_extension(
&path.join(filename),
operating_system,
linking_strategy,
link_type,
)
} else {
path.to_path_buf()
}
}
None => with_executable_extension(&app_module_path, operating_system),
None => with_output_extension(
&app_module_path,
operating_system,
linking_strategy,
link_type,
),
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
@ -994,7 +1003,6 @@ fn build_loaded_file<'a>(
}
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
// Just copy the object file to the output folder.
output_exe_path.set_extension(operating_system.object_file_ext());
std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
}
(LinkingStrategy::Legacy, _) => {
@ -1282,6 +1290,7 @@ pub fn build_str_test<'a>(
opt_level: OptLevel::Normal,
emit_debug_info: false,
emit_llvm_ir: false,
fuzz: false,
};
let emit_timings = false;
@ -1324,6 +1333,17 @@ pub fn build_str_test<'a>(
)
}
fn with_executable_extension(path: &Path, os: OperatingSystem) -> PathBuf {
path.with_extension(os.executable_file_ext().unwrap_or_default())
fn with_output_extension(
path: &Path,
os: OperatingSystem,
linking_strategy: LinkingStrategy,
link_type: LinkType,
) -> PathBuf {
match (linking_strategy, link_type) {
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
// Additive linking and no linking both output the object file type.
path.with_extension(os.object_file_ext())
}
_ => path.with_extension(os.executable_file_ext().unwrap_or_default()),
}
}

View File

@ -1 +0,0 @@
builtins.ll

View File

@ -6,7 +6,7 @@ Builtins are the functions and modules that are implicitly imported into every m
Edit the appropriate `roc/*.roc` file with your new implementation. All normal rules for writing Roc code apply. Be sure to add a declaration, definition, some documentation and add it to the exposes list it in the module head.
Next, look towards the bottom of the `compiler/module/src/symbol.rs` file. Inside the `define_builtins!` macro, there is a list for each of the builtin modules and the function or value names it contains. Add a new entry to the appropriate list for your new function.
Next, look towards the bottom of the `compiler/module/src/symbol.rs` file. Inside the `define_builtins!` macro, there is a list for each of the builtin modules and the function or value names it contains. Add a new entry to the appropriate list for your new function.
For each of the builtin modules, there is a file in `compiler/test_gen/src/` like `gen_num.rs`, `gen_str.rs` etc. Add new tests for the module you are changing to the appropriate file here. You can look at the existing test cases for examples and inspiration.
@ -22,14 +22,14 @@ Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a tri
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Nat -> elem` in LLVM
- ..writing `List elem, Nat -> Result elem [OutOfBounds]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Nat` index exists.
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, U64 -> elem` in LLVM
- ..writing `List elem, U64 -> Result elem [OutOfBounds]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `U64` index exists.
### can/src/builtins.rs
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
Lets look at `List.repeat : elem, Nat -> List elem`, which is more straight-forward, and points directly to its lower level implementation:
Lets look at `List.repeat : elem, U64 -> List elem`, which is more straightforward, and points directly to its lower level implementation:
```rust
fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
@ -106,7 +106,7 @@ fn atan() {
But replace `Num.atan` and the type signature with the new builtin.
### test_gen/test/*.rs
### test_gen/test/\*.rs
In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like:
@ -123,5 +123,5 @@ But replace `Num.atan`, the return value, and the return type with your new buil
When implementing a new builtin, it is often easy to copy and paste the implementation for an existing builtin. This can take you quite far since many builtins are very similar, but it also risks forgetting to change one small part of what you copy and pasted and losing a lot of time later on when you cant figure out why things don't work. So, speaking from experience, even if you are copying an existing builtin, try and implement it manually without copying and pasting. Two recent instances of this (as of September 7th, 2020):
- `List.keepIf` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walkBackwards` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.
- `List.keepIf` did not work for a long time because in builtins its `LowLevel` was `ListMap`. This was because I copy and pasted the `List.map` implementation in `builtins.rs
- `List.walkBackwards` had mysterious memory bugs for a little while because in `unique.rs` its return type was `list_type(flex(b))` instead of `flex(b)` since it was copy and pasted from `List.keepIf`.

View File

@ -1,5 +0,0 @@
zig-out
zig-cache
src/zig-cache
benchmark/zig-cache
dec

View File

@ -24,6 +24,9 @@ pub const RocDec = extern struct {
pub const one_point_zero_i128: i128 = math.pow(i128, 10, RocDec.decimal_places);
pub const one_point_zero: RocDec = .{ .num = one_point_zero_i128 };
pub const two_point_zero: RocDec = RocDec.add(RocDec.one_point_zero, RocDec.one_point_zero);
pub const zero_point_five: RocDec = RocDec.div(RocDec.one_point_zero, RocDec.two_point_zero);
pub fn fromU64(num: u64) RocDec {
return .{ .num = num * one_point_zero_i128 };
}
@ -340,6 +343,77 @@ pub const RocDec = extern struct {
}
}
fn trunc(self: RocDec) RocDec {
return RocDec.sub(self, self.fract());
}
fn fract(self: RocDec) RocDec {
const sign = std.math.sign(self.num);
const digits = @mod(sign * self.num, RocDec.one_point_zero.num);
return RocDec{ .num = sign * digits };
}
// Returns the nearest integer to self. If a value is half-way between two integers, round away from 0.0.
fn round(arg1: RocDec) RocDec {
// this rounds towards zero
const tmp = arg1.trunc();
const sign = std.math.sign(arg1.num);
const abs_fract = sign * arg1.fract().num;
if (abs_fract >= RocDec.zero_point_five.num) {
return RocDec.add(tmp, RocDec{ .num = sign * RocDec.one_point_zero.num });
} else {
return tmp;
}
}
// Returns the largest integer less than or equal to itself
fn floor(arg1: RocDec) RocDec {
const tmp = arg1.trunc();
if (arg1.num < 0 and arg1.fract().num != 0) {
return RocDec.sub(tmp, RocDec.one_point_zero);
} else {
return tmp;
}
}
// Returns the smallest integer greater than or equal to itself
fn ceiling(arg1: RocDec) RocDec {
const tmp = arg1.trunc();
if (arg1.num > 0 and arg1.fract().num != 0) {
return RocDec.add(tmp, RocDec.one_point_zero);
} else {
return tmp;
}
}
fn powInt(base: RocDec, exponent: i128) RocDec {
if (exponent == 0) {
return RocDec.one_point_zero;
} else if (exponent > 0) {
if (@mod(exponent, 2) == 0) {
const half_power = RocDec.powInt(base, exponent >> 1); // `>> 1` == `/ 2`
return RocDec.mul(half_power, half_power);
} else {
return RocDec.mul(base, RocDec.powInt(base, exponent - 1));
}
} else {
return RocDec.div(RocDec.one_point_zero, RocDec.powInt(base, -exponent));
}
}
fn pow(base: RocDec, exponent: RocDec) RocDec {
if (exponent.trunc().num == exponent.num) {
return base.powInt(@divTrunc(exponent.num, RocDec.one_point_zero_i128));
} else {
return fromF64(std.math.pow(f64, base.toF64(), exponent.toF64())).?;
}
}
pub fn mul(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.mulWithOverflow(self, other);
@ -1195,6 +1269,153 @@ test "log: 1" {
try expectEqual(RocDec.fromU64(0), RocDec.log(RocDec.fromU64(1)));
}
test "fract: 0" {
var roc_str = RocStr.init("0", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.fract());
}
test "fract: 1" {
var roc_str = RocStr.init("1", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.fract());
}
test "fract: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
}
test "fract: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.fract());
}
test "fract: .45" {
var roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
}
test "fract: -0.00045" {
const dec: RocDec = .{ .num = -450000000000000 };
const res = dec.fract();
try expectEqual(dec.num, res.num);
}
test "trunc: 0" {
var roc_str = RocStr.init("0", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
}
test "trunc: 1" {
var roc_str = RocStr.init("1", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.trunc());
}
test "trunc: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.trunc());
}
test "trunc: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.trunc());
}
test "trunc: .45" {
var roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
}
test "trunc: -0.00045" {
const dec: RocDec = .{ .num = -450000000000000 };
const res = dec.trunc();
try expectEqual(RocDec{ .num = 0 }, res);
}
test "round: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.round());
}
test "round: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.round());
}
test "round: 0.5" {
var roc_str = RocStr.init("0.5", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.round());
}
test "round: -0.5" {
var roc_str = RocStr.init("-0.5", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = -1000000000000000000 }, dec.round());
}
test "powInt: 3.1 ^ 0" {
var roc_str = RocStr.init("3.1", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec.one_point_zero, dec.powInt(0));
}
test "powInt: 3.1 ^ 1" {
var roc_str = RocStr.init("3.1", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, dec.powInt(1));
}
test "powInt: 2 ^ 2" {
var roc_str = RocStr.init("4", 1);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.two_point_zero.powInt(2));
}
test "powInt: 0.5 ^ 2" {
var roc_str = RocStr.init("0.25", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.zero_point_five.powInt(2));
}
test "pow: 0.5 ^ 2.0" {
var roc_str = RocStr.init("0.25", 4);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.zero_point_five.pow(RocDec.two_point_zero));
}
// exports
pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
@ -1295,6 +1516,10 @@ pub fn logC(arg: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.log, .{arg}).num;
}
pub fn powC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.pow, .{ arg1, arg2 }).num;
}
pub fn sinC(arg: RocDec) callconv(.C) i128 {
return @call(.always_inline, RocDec.sin, .{arg}).num;
}
@ -1342,3 +1567,30 @@ pub fn mulOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
pub fn mulSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
return @call(.always_inline, RocDec.mulSaturated, .{ arg1, arg2 });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.round().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportFloor(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.floor().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCeiling(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: RocDec) callconv(.C) T {
return @as(T, @intCast(@divFloor(input.ceiling().num, RocDec.one_point_zero_i128)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

File diff suppressed because it is too large Load Diff

View File

@ -472,7 +472,7 @@ pub fn listMap4(
}
pub fn listWithCapacity(
capacity: usize,
capacity: u64,
alignment: u32,
element_width: usize,
) callconv(.C) RocList {
@ -482,16 +482,22 @@ pub fn listWithCapacity(
pub fn listReserve(
list: RocList,
alignment: u32,
spare: usize,
spare: u64,
element_width: usize,
update_mode: UpdateMode,
) callconv(.C) RocList {
const old_length = list.len();
if ((update_mode == .InPlace or list.isUnique()) and list.getCapacity() >= list.len() + spare) {
const original_len = list.len();
const cap = @as(u64, @intCast(list.getCapacity()));
const desired_cap = @as(u64, @intCast(original_len)) +| spare;
if ((update_mode == .InPlace or list.isUnique()) and cap >= desired_cap) {
return list;
} else {
var output = list.reallocate(alignment, old_length + spare, element_width);
output.length = old_length;
// Make sure on 32-bit targets we don't accidentally wrap when we cast our U64 desired capacity to U32.
const reserve_size: u64 = @min(desired_cap, @as(u64, @intCast(std.math.maxInt(usize))));
var output = list.reallocate(alignment, @as(usize, @intCast(reserve_size)), element_width);
output.length = original_len;
return output;
}
}
@ -577,13 +583,13 @@ pub fn listSwap(
list: RocList,
alignment: u32,
element_width: usize,
index_1: usize,
index_2: usize,
index_1: u64,
index_2: u64,
update_mode: UpdateMode,
) callconv(.C) RocList {
const size = list.len();
const size = @as(u64, @intCast(list.len()));
if (index_1 == index_2 or index_1 >= size or index_2 >= size) {
// Either index out of bounds so we just return
// Either one index was out of bounds, or both indices were the same; just return
return list;
}
@ -596,7 +602,11 @@ pub fn listSwap(
};
const source_ptr = @as([*]u8, @ptrCast(newList.bytes));
swapElements(source_ptr, element_width, index_1, index_2);
swapElements(source_ptr, element_width, @as(usize,
// We already verified that both indices are less than the stored list length,
// which is usize, so casting them to usize will definitely be lossless.
@intCast(index_1)), @as(usize, @intCast(index_2)));
return newList;
}
@ -605,12 +615,12 @@ pub fn listSublist(
list: RocList,
alignment: u32,
element_width: usize,
start: usize,
len: usize,
start_u64: u64,
len_u64: u64,
dec: Dec,
) callconv(.C) RocList {
const size = list.len();
if (len == 0 or start >= size) {
if (size == 0 or start_u64 >= @as(u64, @intCast(size))) {
// Decrement the reference counts of all elements.
if (list.bytes) |source_ptr| {
var i: usize = 0;
@ -629,9 +639,26 @@ pub fn listSublist(
}
if (list.bytes) |source_ptr| {
const keep_len = @min(len, size - start);
// This cast is lossless because we would have early-returned already
// if `start_u64` were greater than `size`, and `size` fits in usize.
const start: usize = @intCast(start_u64);
const drop_start_len = start;
const drop_end_len = size - (start + keep_len);
// (size - start) can't overflow because we would have early-returned already
// if `start` were greater than `size`.
const size_minus_start = size - start;
// This outer cast to usize is lossless. size, start, and size_minus_start all fit in usize,
// and @min guarantees that if `len_u64` gets returned, it's because it was smaller
// than something that fit in usize.
const keep_len = @as(usize, @intCast(@min(len_u64, @as(u64, @intCast(size_minus_start)))));
// This can't overflow because if len > size_minus_start,
// then keep_len == size_minus_start and this will be 0.
// Alternatively, if len <= size_minus_start, then keep_len will
// be equal to len, meaning keep_len <= size_minus_start too,
// which in turn means this won't overflow.
const drop_end_len = size_minus_start - keep_len;
// Decrement the reference counts of elements before `start`.
var i: usize = 0;
@ -671,28 +698,33 @@ pub fn listDropAt(
list: RocList,
alignment: u32,
element_width: usize,
drop_index: usize,
drop_index_u64: u64,
dec: Dec,
) callconv(.C) RocList {
const size = list.len();
const size_u64 = @as(u64, @intCast(size));
// If droping the first or last element, return a seamless slice.
// For simplicity, do this by calling listSublist.
// In the future, we can test if it is faster to manually inline the important parts here.
if (drop_index == 0) {
if (drop_index_u64 == 0) {
return listSublist(list, alignment, element_width, 1, size -| 1, dec);
} else if (drop_index == size -| 1) {
} else if (drop_index_u64 == size_u64 - 1) { // It's fine if (size - 1) wraps on size == 0 here,
// because if size is 0 then it's always fine for this branch to be taken; no
// matter what drop_index was, we're size == 0, so empty list will always be returned.
return listSublist(list, alignment, element_width, 0, size -| 1, dec);
}
if (list.bytes) |source_ptr| {
if (drop_index >= size) {
if (drop_index_u64 >= size_u64) {
return list;
}
if (drop_index < size) {
const element = source_ptr + drop_index * element_width;
dec(element);
}
// This cast must be lossless, because we would have just early-returned if drop_index
// were >= than `size`, and we know `size` fits in usize.
const drop_index: usize = @intCast(drop_index_u64);
const element = source_ptr + drop_index * element_width;
dec(element);
// NOTE
// we need to return an empty list explicitly,
@ -906,7 +938,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
pub fn listReplaceInPlace(
list: RocList,
index: usize,
index: u64,
element: Opaque,
element_width: usize,
out_element: ?[*]u8,
@ -916,14 +948,15 @@ pub fn listReplaceInPlace(
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListReplace input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listReplaceInPlaceHelp(list, index, element, element_width, out_element);
// because inserting into an empty list is always out of bounds,
// and it's always safe to cast index to usize.
return listReplaceInPlaceHelp(list, @as(usize, @intCast(index)), element, element_width, out_element);
}
pub fn listReplace(
list: RocList,
alignment: u32,
index: usize,
index: u64,
element: Opaque,
element_width: usize,
out_element: ?[*]u8,
@ -933,8 +966,9 @@ pub fn listReplace(
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListReplace input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element);
// because inserting into an empty list is always out of bounds,
// and it's always safe to cast index to usize.
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), @as(usize, @intCast(index)), element, element_width, out_element);
}
inline fn listReplaceInPlaceHelp(
@ -962,6 +996,14 @@ pub fn listIsUnique(
return list.isEmpty() or list.isUnique();
}
pub fn listClone(
list: RocList,
alignment: u32,
element_width: usize,
) callconv(.C) RocList {
return list.makeUnique(alignment, element_width);
}
pub fn listCapacity(
list: RocList,
) callconv(.C) usize {

View File

@ -36,6 +36,7 @@ comptime {
exportDecFn(dec.fromStr, "from_str");
exportDecFn(dec.fromU64C, "from_u64");
exportDecFn(dec.logC, "log");
exportDecFn(dec.powC, "pow");
exportDecFn(dec.mulC, "mul_with_overflow");
exportDecFn(dec.mulOrPanicC, "mul_or_panic");
exportDecFn(dec.mulSaturatedC, "mul_saturated");
@ -52,6 +53,10 @@ comptime {
inline for (INTEGERS) |T| {
dec.exportFromInt(T, ROC_BUILTINS ++ ".dec.from_int.");
dec.exportRound(T, ROC_BUILTINS ++ ".dec.round.");
dec.exportFloor(T, ROC_BUILTINS ++ ".dec.floor.");
dec.exportCeiling(T, ROC_BUILTINS ++ ".dec.ceiling.");
}
}
@ -75,6 +80,7 @@ comptime {
exportListFn(list.listReplaceInPlace, "replace_in_place");
exportListFn(list.listSwap, "swap");
exportListFn(list.listIsUnique, "is_unique");
exportListFn(list.listClone, "clone");
exportListFn(list.listCapacity, "capacity");
exportListFn(list.listAllocationPtr, "allocation_ptr");
exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
@ -89,11 +95,6 @@ const FLOATS = [_]type{ f32, f64 };
const NUMBERS = INTEGERS ++ FLOATS;
comptime {
exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32");
exportNumFn(num.bytesToU64C, "bytes_to_u64");
exportNumFn(num.bytesToU128C, "bytes_to_u128");
exportNumFn(num.shiftRightZeroFillI128, "shift_right_zero_fill.i128");
exportNumFn(num.shiftRightZeroFillU128, "shift_right_zero_fill.u128");
@ -110,19 +111,6 @@ comptime {
exportNumFn(num.greaterThanU128, "greater_than.u128");
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
exportNumFn(num.compareI128, "compare.i128");
exportNumFn(num.compareU128, "compare.u128");
exportNumFn(num.lessThanI128, "less_than.i128");
exportNumFn(num.lessThanOrEqualI128, "less_than_or_equal.i128");
exportNumFn(num.greaterThanI128, "greater_than.i128");
exportNumFn(num.greaterThanOrEqualI128, "greater_than_or_equal.i128");
exportNumFn(num.lessThanU128, "less_than.u128");
exportNumFn(num.lessThanOrEqualU128, "less_than_or_equal.u128");
exportNumFn(num.greaterThanU128, "greater_than.u128");
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
inline for (INTEGERS, 0..) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
@ -134,6 +122,9 @@ comptime {
num.exportCeiling(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f32.");
num.exportCeiling(f64, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f64.");
num.exportNumToFloatCast(T, f32, ROC_BUILTINS ++ "." ++ NUM ++ ".num_to_float_cast_f32.");
num.exportNumToFloatCast(T, f64, ROC_BUILTINS ++ "." ++ NUM ++ ".num_to_float_cast_f64.");
num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow.");
num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic.");
num.exportAddSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_saturated.");
@ -190,34 +181,28 @@ comptime {
const str = @import("str.zig");
comptime {
exportStrFn(str.init, "init");
exportStrFn(str.strToScalarsC, "to_scalars");
exportStrFn(str.strSplit, "str_split");
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
exportStrFn(str.isEmpty, "is_empty");
exportStrFn(str.getCapacity, "capacity");
exportStrFn(str.startsWith, "starts_with");
exportStrFn(str.startsWithScalar, "starts_with_scalar");
exportStrFn(str.endsWith, "ends_with");
exportStrFn(str.strConcatC, "concat");
exportStrFn(str.strJoinWithC, "joinWith");
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strEqual, "equal");
exportStrFn(str.substringUnsafe, "substring_unsafe");
exportStrFn(str.getUnsafe, "get_unsafe");
exportStrFn(str.reserve, "reserve");
exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe");
exportStrFn(str.appendScalar, "append_scalar");
exportStrFn(str.substringUnsafeC, "substring_unsafe");
exportStrFn(str.getUnsafeC, "get_unsafe");
exportStrFn(str.reserveC, "reserve");
exportStrFn(str.strToUtf8C, "to_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.repeatC, "repeat");
exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimStart, "trim_start");
exportStrFn(str.strTrimEnd, "trim_end");
exportStrFn(str.strCloneTo, "clone_to");
exportStrFn(str.withCapacity, "with_capacity");
exportStrFn(str.strGraphemes, "graphemes");
exportStrFn(str.withCapacityC, "with_capacity");
exportStrFn(str.strAllocationPtr, "allocation_ptr");
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
@ -264,6 +249,9 @@ comptime {
if (builtin.target.cpu.arch == .aarch64) {
@export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
} else if (builtin.os.tag == .windows) {
@export(__roc_force_setjmp_windows, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp_windows, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
}
}
@ -279,14 +267,103 @@ pub extern fn _longjmp([*c]c_int, c_int) noreturn;
pub extern fn sigsetjmp([*c]c_int, c_int) c_int;
pub extern fn siglongjmp([*c]c_int, c_int) noreturn;
pub extern fn longjmperror() void;
// Zig won't expose the externs (and hence link correctly) unless we force them to be used.
fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int {
return setjmp(it);
}
fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
longjmp(a0, a1);
}
pub extern fn windows_setjmp([*c]c_int) c_int;
pub extern fn windows_longjmp([*c]c_int, c_int) noreturn;
fn __roc_force_setjmp_windows(it: [*c]c_int) callconv(.C) c_int {
return windows_setjmp(it);
}
fn __roc_force_longjmp_windows(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
windows_longjmp(a0, a1);
}
comptime {
if (builtin.os.tag == .windows) {
asm (
\\.global windows_longjmp;
\\windows_longjmp:
\\ movq 0x00(%rcx), %rdx
\\ movq 0x08(%rcx), %rbx
\\ # note 0x10 is not used yet!
\\ movq 0x18(%rcx), %rbp
\\ movq 0x20(%rcx), %rsi
\\ movq 0x28(%rcx), %rdi
\\ movq 0x30(%rcx), %r12
\\ movq 0x38(%rcx), %r13
\\ movq 0x40(%rcx), %r14
\\ movq 0x48(%rcx), %r15
\\
\\ # restore stack pointer
\\ movq 0x10(%rcx), %rsp
\\
\\ # load jmp address
\\ movq 0x50(%rcx), %r8
\\
\\ # set up return value
\\ movq %rbx, %rax
\\
\\ movdqu 0x60(%rcx), %xmm6
\\ movdqu 0x70(%rcx), %xmm7
\\ movdqu 0x80(%rcx), %xmm8
\\ movdqu 0x90(%rcx), %xmm9
\\ movdqu 0xa0(%rcx), %xmm10
\\ movdqu 0xb0(%rcx), %xmm11
\\ movdqu 0xc0(%rcx), %xmm12
\\ movdqu 0xd0(%rcx), %xmm13
\\ movdqu 0xe0(%rcx), %xmm14
\\ movdqu 0xf0(%rcx), %xmm15
\\
\\ jmp *%r8
\\
\\.global windows_setjmp;
\\windows_setjmp:
\\ movq %rdx, 0x00(%rcx)
\\ movq %rbx, 0x08(%rcx)
\\ # note 0x10 is not used yet!
\\ movq %rbp, 0x18(%rcx)
\\ movq %rsi, 0x20(%rcx)
\\ movq %rdi, 0x28(%rcx)
\\ movq %r12, 0x30(%rcx)
\\ movq %r13, 0x38(%rcx)
\\ movq %r14, 0x40(%rcx)
\\ movq %r15, 0x48(%rcx)
\\
\\ # the stack location right after the windows_setjmp call
\\ leaq 0x08(%rsp), %r8
\\ movq %r8, 0x10(%rcx)
\\
\\ movq (%rsp), %r8
\\ movq %r8, 0x50(%rcx)
\\
\\ movdqu %xmm6, 0x60(%rcx)
\\ movdqu %xmm7, 0x70(%rcx)
\\ movdqu %xmm8, 0x80(%rcx)
\\ movdqu %xmm9, 0x90(%rcx)
\\ movdqu %xmm10, 0xa0(%rcx)
\\ movdqu %xmm11, 0xb0(%rcx)
\\ movdqu %xmm12, 0xc0(%rcx)
\\ movdqu %xmm13, 0xd0(%rcx)
\\ movdqu %xmm14, 0xe0(%rcx)
\\ movdqu %xmm15, 0xf0(%rcx)
\\
\\ xorl %eax, %eax
\\ ret
\\
);
}
}
// Export helpers - Must be run inside a comptime
fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void {
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong });

View File

@ -86,6 +86,15 @@ pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportNumToFloatCast(comptime T: type, comptime F: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(x: T) callconv(.C) F {
return @floatFromInt(x);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportPow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(base: T, exp: T) callconv(.C) T {
@ -274,42 +283,6 @@ pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comp
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
}
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
return @call(.always_inline, bytesToU16, .{ arg, position });
}
fn bytesToU16(arg: RocList, position: usize) u16 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u16, @bitCast([_]u8{ bytes[position], bytes[position + 1] }));
}
pub fn bytesToU32C(arg: RocList, position: usize) callconv(.C) u32 {
return @call(.always_inline, bytesToU32, .{ arg, position });
}
fn bytesToU32(arg: RocList, position: usize) u32 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u32, @bitCast([_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }));
}
pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 {
return @call(.always_inline, bytesToU64, .{ arg, position });
}
fn bytesToU64(arg: RocList, position: usize) u64 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u64, @bitCast([_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] }));
}
pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 {
return @call(.always_inline, bytesToU128, .{ arg, position });
}
fn bytesToU128(arg: RocList, position: usize) u128 {
const bytes = @as([*]const u8, @ptrCast(arg.bytes));
return @as(u128, @bitCast([_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] }));
}
fn isMultipleOf(comptime T: type, lhs: T, rhs: T) bool {
if (rhs == 0 or rhs == -1) {
// lhs is a multiple of rhs iff

View File

@ -1,6 +1,5 @@
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const grapheme = @import("helpers/grapheme.zig");
const UpdateMode = utils.UpdateMode;
const std = @import("std");
const mem = std.mem;
@ -552,242 +551,6 @@ pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
return string.len();
}
// Str.toScalars
pub fn strToScalarsC(str: RocStr) callconv(.C) RocList {
return @call(.always_inline, strToScalars, .{str});
}
fn strToScalars(string: RocStr) callconv(.C) RocList {
const len = string.len();
if (len == 0) {
return RocList.empty();
}
var capacity = len;
if (!string.isSmallStr()) {
capacity = string.getCapacity();
}
// For purposes of preallocation, assume the number of code points is the same
// as the number of bytes. This might be longer than necessary, but definitely
// should not require a second allocation.
var answer = RocList.allocate(@alignOf(u32), capacity, @sizeOf(u32));
// `orelse unreachable` is fine here, because we already did an early
// return to verify the string was nonempty.
var answer_elems = answer.elements(u32) orelse unreachable;
var src_index: usize = 0;
var answer_index: usize = 0;
while (src_index < len) {
src_index += writeNextScalar(string, src_index, answer_elems, answer_index);
answer_index += 1;
}
answer.length = answer_index;
return answer;
}
// Given a non-empty RocStr, and a src_index byte index into that string,
// and a destination [*]u32, and an index into that destination,
// Parses the next scalar value out of the string (at the given byte index),
// writes it into the destination, and returns the number of bytes parsed.
inline fn writeNextScalar(non_empty_string: RocStr, src_index: usize, dest: [*]u32, dest_index: usize) usize {
const utf8_byte = non_empty_string.getUnchecked(src_index);
// How UTF-8 bytes work:
// https://docs.teradata.com/r/Teradata-Database-International-Character-Set-Support/June-2017/Client-Character-Set-Options/UTF8-Client-Character-Set-Support/UTF8-Multibyte-Sequences
if (utf8_byte <= 127) {
// It's an ASCII character. Copy it over directly.
dest[dest_index] = @as(u32, @intCast(utf8_byte));
return 1;
} else if (utf8_byte >> 5 == 0b0000_0110) {
// Its three high order bits are 110, so this is a two-byte sequence.
// Example:
// utf-8: 1100 1111 1011 0001
// code pt: 0000 0011 1111 0001 (decimal: 1009)
// Discard the first byte's high order bits of 110.
var code_pt = @as(u32, @intCast(utf8_byte & 0b0001_1111));
// Discard the second byte's high order bits of 10.
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
dest[dest_index] = code_pt;
return 2;
} else if (utf8_byte >> 4 == 0b0000_1110) {
// Its four high order bits are 1110, so this is a three-byte sequence.
// Discard the first byte's high order bits of 1110.
var code_pt = @as(u32, @intCast(utf8_byte & 0b0000_1111));
// Discard the second byte's high order bits of 10.
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
// Discard the third byte's high order bits of 10 (same as second byte).
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 2) & 0b0011_1111;
dest[dest_index] = code_pt;
return 3;
} else {
// This must be a four-byte sequence, so the five high order bits should be 11110.
// Discard the first byte's high order bits of 11110.
var code_pt = @as(u32, @intCast(utf8_byte & 0b0000_0111));
// Discard the second byte's high order bits of 10.
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
// Discard the third byte's high order bits of 10 (same as second byte).
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 2) & 0b0011_1111;
// Discard the fourth byte's high order bits of 10 (same as second and third).
code_pt <<= 6;
code_pt |= non_empty_string.getUnchecked(src_index + 3) & 0b0011_1111;
dest[dest_index] = code_pt;
return 4;
}
}
test "strToScalars: empty string" {
const str = RocStr.fromSlice("");
defer RocStr.decref(str);
const expected = RocList.empty();
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One ASCII char" {
const str = RocStr.fromSlice("R");
defer RocStr.decref(str);
const expected_array = [_]u32{82};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple ASCII chars" {
const str = RocStr.fromSlice("Roc!");
defer RocStr.decref(str);
const expected_array = [_]u32{ 82, 111, 99, 33 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 2-byte UTF-8 character" {
const str = RocStr.fromSlice("é");
defer RocStr.decref(str);
const expected_array = [_]u32{233};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 2-byte UTF-8 characters" {
const str = RocStr.fromSlice("Cäfés");
defer RocStr.decref(str);
const expected_array = [_]u32{ 67, 228, 102, 233, 115 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 3-byte UTF-8 character" {
const str = RocStr.fromSlice("");
defer RocStr.decref(str);
const expected_array = [_]u32{40527};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 3-byte UTF-8 characters" {
const str = RocStr.fromSlice("鹏很有趣");
defer RocStr.decref(str);
const expected_array = [_]u32{ 40527, 24456, 26377, 36259 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 4-byte UTF-8 character" {
// from https://design215.com/toolbox/utf8-4byte-characters.php
const str = RocStr.fromSlice("𒀀");
defer RocStr.decref(str);
const expected_array = [_]u32{73728};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 4-byte UTF-8 characters" {
// from https://design215.com/toolbox/utf8-4byte-characters.php
const str = RocStr.fromSlice("𒀀𒀁");
defer RocStr.decref(str);
const expected_array = [_]u32{ 73728, 73729 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
// Str.fromInt
pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
@ -1371,127 +1134,8 @@ test "countSegments: overlapping delimiter 2" {
try expectEqual(segments_count, 3);
}
// Str.countGraphemeClusters
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
if (string.isEmpty()) {
return 0;
}
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr();
var bytes = bytes_ptr[0..bytes_len];
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
var count: usize = 0;
var grapheme_break_state: ?grapheme.BoundClass = null;
var grapheme_break_state_ptr = &grapheme_break_state;
var opt_last_codepoint: ?u21 = null;
while (iter.nextCodepoint()) |cur_codepoint| {
if (opt_last_codepoint) |last_codepoint| {
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, grapheme_break_state_ptr);
if (did_break) {
count += 1;
grapheme_break_state = null;
}
}
opt_last_codepoint = cur_codepoint;
}
// If there are no breaks, but the str is not empty, then there
// must be a single grapheme
if (bytes_len != 0) {
count += 1;
}
return count;
}
// Str.graphemes
pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList {
var break_state: ?grapheme.BoundClass = null;
var opt_last_codepoint: ?u21 = null;
var index: usize = 0;
var last_codepoint_len: u8 = 0;
const alloc_ptr = @intFromPtr(roc_str.getAllocationPtr()) >> 1;
const init_fn = if (roc_str.isSmallStr())
&initFromSmallStr
else
&initFromBigStr;
var result = RocList.allocate(@alignOf(RocStr), countGraphemeClusters(roc_str), @sizeOf(RocStr));
const graphemes = result.elements(RocStr) orelse return result;
var slice = roc_str.asSlice();
var iter = (unicode.Utf8View.init(slice) catch unreachable).iterator();
while (iter.nextCodepoint()) |cur_codepoint| {
const cur_codepoint_len = unicode.utf8CodepointSequenceLength(cur_codepoint) catch unreachable;
if (opt_last_codepoint) |last_codepoint| {
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, &break_state);
if (did_break) {
graphemes[index] = init_fn(@constCast(slice.ptr), last_codepoint_len, alloc_ptr);
slice = slice[last_codepoint_len..];
index += 1;
break_state = null;
last_codepoint_len = 0;
}
}
last_codepoint_len += cur_codepoint_len;
opt_last_codepoint = cur_codepoint;
}
// Append last grapheme
graphemes[index] = init_fn(@constCast(slice.ptr), slice.len, alloc_ptr);
if (!roc_str.isSmallStr()) {
// Correct refcount for all of the splits made.
roc_str.incref(index + 1);
}
return result;
}
// these test both countGraphemeClusters() and strGraphemes()
fn graphemesTest(input: []const u8, expected: []const []const u8) !void {
const rocstr = RocStr.fromSlice(input);
defer rocstr.decref();
const count = countGraphemeClusters(rocstr);
try expectEqual(expected.len, count);
const graphemes = strGraphemes(rocstr);
defer graphemes.decref(@sizeOf(u8));
if (input.len == 0) return; // empty string
const elems = graphemes.elements(RocStr) orelse unreachable;
for (expected, 0..) |g, i| {
try std.testing.expectEqualStrings(g, elems[i].asSlice());
}
}
test "graphemes: empty string" {
try graphemesTest("", &.{});
}
test "graphemes: ascii characters" {
try graphemesTest("abcd", &.{ "a", "b", "c", "d" });
}
test "graphemes: utf8 characters" {
try graphemesTest("ãxā", &.{ "ã", "x", "ā" });
}
test "graphemes: emojis" {
try graphemesTest("🤔🤔🤔", &.{ "🤔", "🤔", "🤔" });
}
test "graphemes: emojis and ut8 characters" {
try graphemesTest("🤔å🤔¥🤔ç", &.{ "🤔", "å", "🤔", "¥", "🤔", "ç" });
}
test "graphemes: emojis, ut8, and ascii characters" {
try graphemesTest("6🤔å🤔e¥🤔çpp", &.{ "6", "🤔", "å", "🤔", "e", "¥", "🤔", "ç", "p", "p" });
}
pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
return string.len();
pub fn countUtf8Bytes(string: RocStr) callconv(.C) u64 {
return @intCast(string.len());
}
pub fn isEmpty(string: RocStr) callconv(.C) bool {
@ -1502,7 +1146,14 @@ pub fn getCapacity(string: RocStr) callconv(.C) usize {
return string.getCapacity();
}
pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr {
pub fn substringUnsafeC(string: RocStr, start_u64: u64, length_u64: u64) callconv(.C) RocStr {
const start: usize = @intCast(start_u64);
const length: usize = @intCast(length_u64);
return substringUnsafe(string, start, length);
}
fn substringUnsafe(string: RocStr, start: usize, length: usize) RocStr {
if (string.isSmallStr()) {
if (start == 0) {
var output = string;
@ -1534,8 +1185,8 @@ pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C)
return RocStr.empty();
}
pub fn getUnsafe(string: RocStr, index: usize) callconv(.C) u8 {
return string.getUnchecked(index);
pub fn getUnsafeC(string: RocStr, index: u64) callconv(.C) u8 {
return string.getUnchecked(@intCast(index));
}
test "substringUnsafe: start" {
@ -1598,7 +1249,8 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
}
// Str.repeat
pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
pub fn repeatC(string: RocStr, count_u64: u64) callconv(.C) RocStr {
const count: usize = @intCast(count_u64);
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr();
@ -1614,44 +1266,6 @@ pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
return ret_string;
}
// Str.startsWithScalar
pub fn startsWithScalar(string: RocStr, prefix: u32) callconv(.C) bool {
const len = string.len();
if (len == 0) {
return false;
}
// Write this (non-empty) string's first scalar into `first_scalar`
var first_scalar: [1]u32 = undefined;
_ = writeNextScalar(string, 0, &first_scalar, 0);
// Return whether `first_scalar` equals `prefix`
return @as(*u32, @ptrCast(&first_scalar)).* == prefix;
}
test "startsWithScalar: empty string" {
const whole = RocStr.empty();
const prefix: u32 = 'x';
try expect(!startsWithScalar(whole, prefix));
}
test "startsWithScalar: ascii char" {
const whole = RocStr.fromSlice("foobar");
const prefix: u32 = 'f';
try expect(startsWithScalar(whole, prefix));
}
test "startsWithScalar: emoji" {
const yes = RocStr.fromSlice("💖foobar");
const no = RocStr.fromSlice("foobar");
const prefix: u32 = '💖';
try expect(startsWithScalar(yes, prefix));
try expect(!startsWithScalar(no, prefix));
}
test "startsWith: foo starts with fo" {
const foo = RocStr.fromSlice("foo");
const fo = RocStr.fromSlice("fo");
@ -1891,29 +1505,25 @@ inline fn strToBytes(arg: RocStr) RocList {
}
const FromUtf8Result = extern struct {
byte_index: usize,
byte_index: u64,
string: RocStr,
is_ok: bool,
problem_code: Utf8ByteProblem,
};
const CountAndStart = extern struct {
count: usize,
start: usize,
};
pub fn fromUtf8RangeC(
pub fn fromUtf8C(
list: RocList,
start: usize,
count: usize,
update_mode: UpdateMode,
) callconv(.C) FromUtf8Result {
return fromUtf8Range(list, start, count, update_mode);
return fromUtf8(list, update_mode);
}
pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: UpdateMode) FromUtf8Result {
if (arg.len() == 0 or count == 0) {
arg.decref(RocStr.alignment);
pub fn fromUtf8(
list: RocList,
update_mode: UpdateMode,
) FromUtf8Result {
if (list.len() == 0) {
list.decref(1); // Alignment 1 for List U8
return FromUtf8Result{
.is_ok = true,
.string = RocStr.empty(),
@ -1921,11 +1531,11 @@ pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: Upda
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
}
const bytes = @as([*]const u8, @ptrCast(arg.bytes))[start .. start + count];
const bytes = @as([*]const u8, @ptrCast(list.bytes))[0..list.len()];
if (isValidUnicode(bytes)) {
// Make a seamless slice of the input.
const string = RocStr.fromSubListUnsafe(arg, start, count, update_mode);
const string = RocStr.fromSubListUnsafe(list, 0, list.len(), update_mode);
return FromUtf8Result{
.is_ok = true,
.string = string,
@ -1933,25 +1543,25 @@ pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: Upda
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
} else {
const temp = errorToProblem(@as([*]u8, @ptrCast(arg.bytes)), arg.length);
const temp = errorToProblem(bytes);
// decref the list
arg.decref(RocStr.alignment);
list.decref(1); // Alignment 1 for List U8
return FromUtf8Result{
.is_ok = false,
.string = RocStr.empty(),
.byte_index = temp.index,
.byte_index = @intCast(temp.index),
.problem_code = temp.problem,
};
}
}
fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: Utf8ByteProblem } {
fn errorToProblem(bytes: []const u8) struct { index: usize, problem: Utf8ByteProblem } {
const len = bytes.len;
var index: usize = 0;
while (index < length) {
const nextNumBytes = numberOfNextCodepointBytes(bytes, length, index) catch |err| {
while (index < len) {
const nextNumBytes = numberOfNextCodepointBytes(bytes, index) catch |err| {
switch (err) {
error.UnexpectedEof => {
return .{ .index = index, .problem = Utf8ByteProblem.UnexpectedEndOfSequence };
@ -2025,13 +1635,13 @@ const Utf8DecodeError = error{
// Essentially unicode.utf8ValidateSlice -> https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L156
// but only for the next codepoint from the index. Then we return the number of bytes of that codepoint.
// TODO: we only ever use the values 0-4, so can we use smaller int than `usize`?
pub fn numberOfNextCodepointBytes(ptr: [*]u8, len: usize, index: usize) Utf8DecodeError!usize {
const codepoint_len = try unicode.utf8ByteSequenceLength(ptr[index]);
pub fn numberOfNextCodepointBytes(bytes: []const u8, index: usize) Utf8DecodeError!usize {
const codepoint_len = try unicode.utf8ByteSequenceLength(bytes[index]);
const codepoint_end_index = index + codepoint_len;
if (codepoint_end_index > len) {
if (codepoint_end_index > bytes.len) {
return error.UnexpectedEof;
}
_ = try unicode.utf8Decode(ptr[index..codepoint_end_index]);
_ = try unicode.utf8Decode(bytes[index..codepoint_end_index]);
return codepoint_end_index - index;
}
@ -2047,11 +1657,11 @@ pub const Utf8ByteProblem = enum(u8) {
};
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
return fromUtf8Range(RocList{ .bytes = bytes, .length = length, .capacity_or_alloc_ptr = length }, 0, length, .Immutable);
return fromUtf8(RocList{ .bytes = bytes, .length = length, .capacity_or_alloc_ptr = length }, .Immutable);
}
fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8Range(str, 0, str.len(), .Immutable);
return fromUtf8(str, .Immutable);
}
fn expectOk(result: FromUtf8Result) !void {
@ -2068,7 +1678,7 @@ fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
}
fn toErrUtf8ByteResponse(index: usize, problem: Utf8ByteProblem) FromUtf8Result {
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = index, .problem_code = problem };
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = @as(u64, @intCast(index)), .problem_code = problem };
}
// NOTE on memory: the validate function consumes a RC token of the input. Since
@ -2130,7 +1740,7 @@ fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8Byt
const str_ptr = @as([*]u8, @ptrCast(list.bytes));
const len = list.length;
try expectError(err, numberOfNextCodepointBytes(str_ptr, len, index));
try expectError(err, numberOfNextCodepointBytes(str_ptr[0..len], index));
try expectEqual(toErrUtf8ByteResponse(index, problem), validateUtf8Bytes(str_ptr, len));
}
@ -2761,80 +2371,13 @@ test "capacity: big string" {
try expect(data.getCapacity() >= data_bytes.len);
}
pub fn appendScalar(string: RocStr, scalar_u32: u32) callconv(.C) RocStr {
const scalar = @as(u21, @intCast(scalar_u32));
const width = std.unicode.utf8CodepointSequenceLength(scalar) catch unreachable;
var output = string.reallocate(string.len() + width);
var slice = output.asSliceWithCapacityMut();
_ = std.unicode.utf8Encode(scalar, slice[string.len() .. string.len() + width]) catch unreachable;
return output;
pub fn reserveC(string: RocStr, spare_u64: u64) callconv(.C) RocStr {
return reserve(string, @intCast(spare_u64));
}
test "appendScalar: small A" {
const A: []const u8 = "A";
const data_bytes = "hello";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, A[0]);
defer actual.decref();
const expected_bytes = "helloA";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
test "appendScalar: small 😀" {
const data_bytes = "hello";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, 0x1F600);
defer actual.decref();
const expected_bytes = "hello😀";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
test "appendScalar: big A" {
const A: []const u8 = "A";
const data_bytes = "a string so large that it must be heap-allocated";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, A[0]);
defer actual.decref();
const expected_bytes = "a string so large that it must be heap-allocatedA";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
test "appendScalar: big 😀" {
const data_bytes = "a string so large that it must be heap-allocated";
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, 0x1F600);
defer actual.decref();
const expected_bytes = "a string so large that it must be heap-allocated😀";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.decref();
try expect(actual.eq(expected));
}
pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
fn reserve(string: RocStr, spare: usize) RocStr {
const old_length = string.len();
if (string.getCapacity() >= old_length + spare) {
return string;
} else {
@ -2844,32 +2387,12 @@ pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
}
}
pub fn withCapacity(capacity: usize) callconv(.C) RocStr {
var str = RocStr.allocate(capacity);
pub fn withCapacityC(capacity: u64) callconv(.C) RocStr {
var str = RocStr.allocate(@intCast(capacity));
str.setLen(0);
return str;
}
pub fn getScalarUnsafe(string: RocStr, index: usize) callconv(.C) extern struct { bytesParsed: usize, scalar: u32 } {
const slice = string.asSlice();
const bytesParsed = @as(usize, @intCast(std.unicode.utf8ByteSequenceLength(slice[index]) catch unreachable));
const scalar = std.unicode.utf8Decode(slice[index .. index + bytesParsed]) catch unreachable;
return .{ .bytesParsed = bytesParsed, .scalar = @as(u32, @intCast(scalar)) };
}
test "getScalarUnsafe" {
const data_bytes = "A";
var data = RocStr.init(data_bytes, data_bytes.len);
const result = getScalarUnsafe(data, 0);
const expected = try std.unicode.utf8Decode("A");
try expectEqual(result.scalar, @as(u32, @intCast(expected)));
try expectEqual(result.bytesParsed, 1);
}
pub fn strCloneTo(
string: RocStr,
ptr: [*]u8,

View File

@ -44,7 +44,6 @@ interface Decode
I32,
I64,
I128,
Nat,
F32,
F64,
Dec,
@ -113,7 +112,7 @@ DecoderFormatting implements
## index passed to `stepElem` is 0-indexed.
##
## `finalizer` should produce the tuple value from the decoded `state`.
tuple : state, (state, Nat -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
tuple : state, (state, U64 -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
## Build a custom [Decoder] function. For example the implementation of
## `decodeBool` could be defined as follows;

View File

@ -34,7 +34,7 @@ interface Dict
Result.{ Result },
List,
Str,
Num.{ Nat, U64, F32, U32, U8, I8 },
Num.{ U64, F32, U32, U8, I8 },
Hash.{ Hasher, Hash },
Inspect.{ Inspect, Inspector, InspectFormatter },
]
@ -151,26 +151,25 @@ empty = \{} ->
## Return a dictionary with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Dict * *
withCapacity : U64 -> Dict * *
withCapacity = \requested ->
empty {}
|> reserve requested
## Enlarge the dictionary for at least capacity additional elements
reserve : Dict k v, Nat -> Dict k v
reserve : Dict k v, U64 -> Dict k v
reserve = \@Dict { buckets, data, maxBucketCapacity: originalMaxBucketCapacity, maxLoadFactor, shifts }, requested ->
currentSize = List.len data
requestedSize = currentSize + requested
size = Num.min (Num.toU64 requestedSize) maxSize
requestedSize = Num.addWrap currentSize requested
size = Num.min requestedSize maxSize
requestedShifts = calcShiftsForSize size maxLoadFactor
if (List.isEmpty buckets) || requestedShifts > shifts then
(buckets0, maxBucketCapacity) = allocBucketsFromShift requestedShifts maxLoadFactor
buckets1 = fillBucketsFromData buckets0 data requestedShifts
sizeNat = Num.toNat size
@Dict {
buckets: buckets1,
data: List.reserve data (Num.subSaturated sizeNat currentSize),
data: List.reserve data (Num.subSaturated size currentSize),
maxBucketCapacity,
maxLoadFactor,
shifts: requestedShifts,
@ -186,7 +185,7 @@ releaseExcessCapacity = \@Dict { buckets, data, maxBucketCapacity: originalMaxBu
size = List.len data
# NOTE: If we want, we technically could increase the load factor here to potentially minimize size more.
minShifts = calcShiftsForSize (Num.toU64 size) maxLoadFactor
minShifts = calcShiftsForSize size maxLoadFactor
if minShifts < shifts then
(buckets0, maxBucketCapacity) = allocBucketsFromShift minShifts maxLoadFactor
buckets1 = fillBucketsFromData buckets0 data minShifts
@ -208,9 +207,9 @@ releaseExcessCapacity = \@Dict { buckets, data, maxBucketCapacity: originalMaxBu
##
## capacityOfDict = Dict.capacity foodDict
## ```
capacity : Dict * * -> Nat
capacity : Dict * * -> U64
capacity = \@Dict { maxBucketCapacity } ->
Num.toNat maxBucketCapacity
maxBucketCapacity
## Returns a dictionary containing the key and value provided as input.
## ```
@ -251,7 +250,7 @@ fromList = \data ->
## |> Dict.len
## |> Bool.isEq 3
## ```
len : Dict * * -> Nat
len : Dict * * -> U64
len = \@Dict { data } ->
List.len data
@ -372,14 +371,14 @@ keepIf : Dict k v, ((k, v) -> Bool) -> Dict k v
keepIf = \dict, predicate ->
keepIfHelp dict predicate 0 (Dict.len dict)
keepIfHelp : Dict k v, ((k, v) -> Bool), Nat, Nat -> Dict k v
keepIfHelp : Dict k v, ((k, v) -> Bool), U64, U64 -> Dict k v
keepIfHelp = \@Dict dict, predicate, index, length ->
if index < length then
(key, value) = listGetUnsafe dict.data index
if predicate (key, value) then
keepIfHelp (@Dict dict) predicate (index + 1) length
keepIfHelp (@Dict dict) predicate (index |> Num.addWrap 1) length
else
keepIfHelp (Dict.remove (@Dict dict) key) predicate index (length - 1)
keepIfHelp (Dict.remove (@Dict dict) key) predicate index (length |> Num.subWrap 1)
else
@Dict dict
@ -450,13 +449,13 @@ insert = \dict, key, value ->
insertHelper buckets data bucketIndex distAndFingerprint key value maxBucketCapacity maxLoadFactor shifts
insertHelper : List Bucket, List (k, v), Nat, U32, k, v, U64, F32, U8 -> Dict k v
insertHelper : List Bucket, List (k, v), U64, U32, k, v, U64, F32, U8 -> Dict k v
insertHelper = \buckets0, data0, bucketIndex0, distAndFingerprint0, key, value, maxBucketCapacity, maxLoadFactor, shifts ->
loaded = listGetUnsafe buckets0 (Num.toNat bucketIndex0)
loaded = listGetUnsafe buckets0 bucketIndex0
if distAndFingerprint0 == loaded.distAndFingerprint then
(foundKey, _) = listGetUnsafe data0 (Num.toNat loaded.dataIndex)
(foundKey, _) = listGetUnsafe data0 (Num.toU64 loaded.dataIndex)
if foundKey == key then
data1 = List.set data0 (Num.toNat loaded.dataIndex) (key, value)
data1 = List.set data0 (Num.toU64 loaded.dataIndex) (key, value)
@Dict { buckets: buckets0, data: data1, maxBucketCapacity, maxLoadFactor, shifts }
else
bucketIndex1 = nextBucketIndex bucketIndex0 (List.len buckets0)
@ -464,7 +463,7 @@ insertHelper = \buckets0, data0, bucketIndex0, distAndFingerprint0, key, value,
insertHelper buckets0 data0 bucketIndex1 distAndFingerprint1 key value maxBucketCapacity maxLoadFactor shifts
else if distAndFingerprint0 > loaded.distAndFingerprint then
data1 = List.append data0 (key, value)
dataIndex = (List.len data1) - 1
dataIndex = (List.len data1) |> Num.subWrap 1
buckets1 = placeAndShiftUp buckets0 { distAndFingerprint: distAndFingerprint0, dataIndex: Num.toU32 dataIndex } bucketIndex0
@Dict { buckets: buckets1, data: data1, maxBucketCapacity, maxLoadFactor, shifts }
else
@ -487,7 +486,7 @@ remove = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
(bucketIndex0, distAndFingerprint0) = nextWhileLess buckets key shifts
(bucketIndex1, distAndFingerprint1) = removeHelper buckets bucketIndex0 distAndFingerprint0 data key
bucket = listGetUnsafe buckets (Num.toNat bucketIndex1)
bucket = listGetUnsafe buckets bucketIndex1
if distAndFingerprint1 != bucket.distAndFingerprint then
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
else
@ -495,10 +494,11 @@ remove = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
else
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
removeHelper : List Bucket, U64, U32, List (k, *), k -> (U64, U32) where k implements Eq
removeHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
bucket = listGetUnsafe buckets (Num.toNat bucketIndex)
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, _) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, _) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
(bucketIndex, distAndFingerprint)
else
@ -529,7 +529,7 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
when alter (Present value) is
Present newValue ->
bucket = listGetUnsafe buckets bucketIndex
newData = List.set data (Num.toNat bucket.dataIndex) (key, newValue)
newData = List.set data (Num.toU64 bucket.dataIndex) (key, newValue)
@Dict { buckets, data: newData, maxBucketCapacity, maxLoadFactor, shifts }
Missing ->
@ -538,7 +538,7 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
Err KeyNotFound ->
when alter Missing is
Present newValue ->
if List.len data >= (Num.toNat maxBucketCapacity) then
if List.len data >= maxBucketCapacity then
# Need to reallocate let regular insert handle that.
insert (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key newValue
else
@ -721,20 +721,20 @@ emptyBucket = { distAndFingerprint: 0, dataIndex: 0 }
distInc = Num.shiftLeftBy 1u32 8 # skip 1 byte fingerprint
fingerprintMask = Num.subWrap distInc 1 # mask for 1 byte of fingerprint
defaultMaxLoadFactor = 0.8
initialShifts = 64 - 3 # 2^(64-shifts) number of buckets
initialShifts = 64 |> Num.subWrap 3 # 2^(64-shifts) number of buckets
maxSize = Num.shiftLeftBy 1u64 32
maxBucketCount = maxSize
incrementDist = \distAndFingerprint ->
distAndFingerprint + distInc
Num.addWrap distAndFingerprint distInc
incrementDistN = \distAndFingerprint, n ->
distAndFingerprint + (n * distInc)
Num.addWrap distAndFingerprint (Num.mulWrap n distInc)
decrementDist = \distAndFingerprint ->
distAndFingerprint - distInc
distAndFingerprint |> Num.subWrap distInc
find : Dict k v, k -> { bucketIndex : Nat, result : Result v [KeyNotFound] }
find : Dict k v, k -> { bucketIndex : U64, result : Result v [KeyNotFound] }
find = \@Dict { buckets, data, shifts }, key ->
hash = hashKey key
distAndFingerprint = distAndFingerprintFromHash hash
@ -749,13 +749,13 @@ find = \@Dict { buckets, data, shifts }, key ->
findManualUnrolls = 2
findFirstUnroll : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
findFirstUnroll : List Bucket, U64, U32, List (k, v), k -> { bucketIndex : U64, result : Result v [KeyNotFound] } where k implements Eq
findFirstUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
# TODO: once we have short circuit evaluation, use it here and other similar locations in this file.
# Avoid the nested if with else block inconvenience.
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, value) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, value) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
{ bucketIndex, result: Ok value }
else
@ -763,11 +763,11 @@ findFirstUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
else
findSecondUnroll buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint) data key
findSecondUnroll : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
findSecondUnroll : List Bucket, U64, U32, List (k, v), k -> { bucketIndex : U64, result : Result v [KeyNotFound] } where k implements Eq
findSecondUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, value) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, value) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
{ bucketIndex, result: Ok value }
else
@ -775,11 +775,11 @@ findSecondUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
else
findHelper buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint) data key
findHelper : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
findHelper : List Bucket, U64, U32, List (k, v), k -> { bucketIndex : U64, result : Result v [KeyNotFound] } where k implements Eq
findHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
bucket = listGetUnsafe buckets bucketIndex
if distAndFingerprint == bucket.distAndFingerprint then
(foundKey, value) = listGetUnsafe data (Num.toNat bucket.dataIndex)
(foundKey, value) = listGetUnsafe data (Num.toU64 bucket.dataIndex)
if foundKey == key then
{ bucketIndex, result: Ok value }
else
@ -789,18 +789,19 @@ findHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
else
findHelper buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint) data key
removeBucket : Dict k v, Nat -> Dict k v
removeBucket : Dict k v, U64 -> Dict k v
removeBucket = \@Dict { buckets: buckets0, data: data0, maxBucketCapacity, maxLoadFactor, shifts }, bucketIndex0 ->
{ dataIndex: dataIndexToRemove } = listGetUnsafe buckets0 bucketIndex0
dataIndexToRemove = (listGetUnsafe buckets0 bucketIndex0).dataIndex
dataIndexToRemoveU64 = Num.toU64 dataIndexToRemove
(buckets1, bucketIndex1) = removeBucketHelper buckets0 bucketIndex0
buckets2 = List.set buckets1 bucketIndex1 emptyBucket
lastDataIndex = List.len data0 - 1
if (Num.toNat dataIndexToRemove) != lastDataIndex then
lastDataIndex = List.len data0 |> Num.subWrap 1
if dataIndexToRemoveU64 != lastDataIndex then
# Swap removed item to the end
data1 = List.swap data0 (Num.toNat dataIndexToRemove) lastDataIndex
(key, _) = listGetUnsafe data1 (Num.toNat dataIndexToRemove)
data1 = List.swap data0 dataIndexToRemoveU64 lastDataIndex
(key, _) = listGetUnsafe data1 dataIndexToRemoveU64
# Update the data index of the new value.
hash = hashKey key
@ -824,7 +825,7 @@ removeBucket = \@Dict { buckets: buckets0, data: data0, maxBucketCapacity, maxLo
shifts,
}
scanForIndex : List Bucket, Nat, U32 -> Nat
scanForIndex : List Bucket, U64, U32 -> U64
scanForIndex = \buckets, bucketIndex, dataIndex ->
bucket = listGetUnsafe buckets bucketIndex
if bucket.dataIndex != dataIndex then
@ -832,12 +833,12 @@ scanForIndex = \buckets, bucketIndex, dataIndex ->
else
bucketIndex
removeBucketHelper : List Bucket, Nat -> (List Bucket, Nat)
removeBucketHelper : List Bucket, U64 -> (List Bucket, U64)
removeBucketHelper = \buckets, bucketIndex ->
nextIndex = nextBucketIndex bucketIndex (List.len buckets)
nextBucket = listGetUnsafe buckets nextIndex
# shift down until either empty or an element with correct spot is found
if nextBucket.distAndFingerprint >= distInc * 2 then
if nextBucket.distAndFingerprint >= Num.mulWrap distInc 2 then
List.set buckets bucketIndex { nextBucket & distAndFingerprint: decrementDist nextBucket.distAndFingerprint }
|> removeBucketHelper nextIndex
else
@ -846,7 +847,7 @@ removeBucketHelper = \buckets, bucketIndex ->
increaseSize : Dict k v -> Dict k v
increaseSize = \@Dict { data, maxBucketCapacity, maxLoadFactor, shifts } ->
if maxBucketCapacity != maxBucketCount then
newShifts = shifts - 1
newShifts = shifts |> Num.subWrap 1
(buckets0, newMaxBucketCapacity) = allocBucketsFromShift newShifts maxLoadFactor
buckets1 = fillBucketsFromData buckets0 data newShifts
@Dict {
@ -857,21 +858,21 @@ increaseSize = \@Dict { data, maxBucketCapacity, maxLoadFactor, shifts } ->
shifts: newShifts,
}
else
crash "Dict hit limit of \(Num.toStr maxBucketCount) elements. Unable to grow more."
crash "Dict hit limit of $(Num.toStr maxBucketCount) elements. Unable to grow more."
allocBucketsFromShift : U8, F32 -> (List Bucket, U64)
allocBucketsFromShift = \shifts, maxLoadFactor ->
bucketCount = calcNumBuckets shifts
if bucketCount == maxBucketCount then
# reached the maximum, make sure we can use each bucket
(List.repeat emptyBucket (Num.toNat maxBucketCount), maxBucketCount)
(List.repeat emptyBucket maxBucketCount, maxBucketCount)
else
maxBucketCapacity =
bucketCount
|> Num.toF32
|> Num.mul maxLoadFactor
|> Num.floor
(List.repeat emptyBucket (Num.toNat bucketCount), maxBucketCapacity)
(List.repeat emptyBucket bucketCount, maxBucketCapacity)
calcShiftsForSize : U64, F32 -> U8
calcShiftsForSize = \size, maxLoadFactor ->
@ -885,13 +886,13 @@ calcShiftsForSizeHelper = \shifts, size, maxLoadFactor ->
|> Num.mul maxLoadFactor
|> Num.floor
if shifts > 0 && maxBucketCapacity < size then
calcShiftsForSizeHelper (shifts - 1) size maxLoadFactor
calcShiftsForSizeHelper (shifts |> Num.subWrap 1) size maxLoadFactor
else
shifts
calcNumBuckets = \shifts ->
Num.min
(Num.shiftLeftBy 1 (64 - shifts))
(Num.shiftLeftBy 1 (64 |> Num.subWrap shifts))
maxBucketCount
fillBucketsFromData = \buckets0, data, shifts ->
@ -899,7 +900,7 @@ fillBucketsFromData = \buckets0, data, shifts ->
(bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts
placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex
nextWhileLess : List Bucket, k, U8 -> (Nat, U32) where k implements Hash & Eq
nextWhileLess : List Bucket, k, U8 -> (U64, U32) where k implements Hash & Eq
nextWhileLess = \buckets, key, shifts ->
hash = hashKey key
distAndFingerprint = distAndFingerprintFromHash hash
@ -908,22 +909,22 @@ nextWhileLess = \buckets, key, shifts ->
nextWhileLessHelper buckets bucketIndex distAndFingerprint
nextWhileLessHelper = \buckets, bucketIndex, distAndFingerprint ->
loaded = listGetUnsafe buckets (Num.toNat bucketIndex)
loaded = listGetUnsafe buckets bucketIndex
if distAndFingerprint < loaded.distAndFingerprint then
nextWhileLessHelper buckets (nextBucketIndex bucketIndex (List.len buckets)) (incrementDist distAndFingerprint)
else
(bucketIndex, distAndFingerprint)
placeAndShiftUp = \buckets0, bucket, bucketIndex ->
loaded = listGetUnsafe buckets0 (Num.toNat bucketIndex)
loaded = listGetUnsafe buckets0 bucketIndex
if loaded.distAndFingerprint != 0 then
buckets1 = List.set buckets0 (Num.toNat bucketIndex) bucket
buckets1 = List.set buckets0 bucketIndex bucket
placeAndShiftUp
buckets1
{ loaded & distAndFingerprint: incrementDist loaded.distAndFingerprint }
(nextBucketIndex bucketIndex (List.len buckets1))
else
List.set buckets0 (Num.toNat bucketIndex) bucket
List.set buckets0 bucketIndex bucket
nextBucketIndex = \bucketIndex, maxBuckets ->
# I just ported this impl directly.
@ -947,11 +948,10 @@ distAndFingerprintFromHash = \hash ->
|> Num.bitwiseAnd fingerprintMask
|> Num.bitwiseOr distInc
bucketIndexFromHash : U64, U8 -> Nat
bucketIndexFromHash : U64, U8 -> U64
bucketIndexFromHash = \hash, shifts ->
hash
|> Num.shiftRightZfBy shifts
|> Num.toNat
expect
val =
@ -1185,13 +1185,6 @@ expect
|> len
|> Bool.isEq 0
# Makes sure a Dict with Nat keys works
expect
empty {}
|> insert 7nat "Testing"
|> get 7
|> Bool.isEq (Ok "Testing")
# All BadKey's hash to the same location.
# This is needed to test some robinhood logic.
BadKey := U64 implements [
@ -1225,7 +1218,7 @@ expect
acc, k <- List.walk badKeys (Dict.empty {})
Dict.update acc k \val ->
when val is
Present p -> Present (p + 1)
Present p -> Present (p |> Num.addWrap 1)
Missing -> Present 0
allInsertedCorrectly =
@ -1236,7 +1229,7 @@ expect
# Note, there are a number of places we should probably use set and replace unsafe.
# unsafe primitive that does not perform a bounds check
listGetUnsafe : List a, Nat -> a
listGetUnsafe : List a, U64 -> a
# We have decided not to expose the standard roc hashing algorithm.
# This is to avoid external dependence and the need for versioning.
@ -1368,9 +1361,9 @@ addBytes = \@LowLevelHasher { initializedSeed, state }, list ->
else
hashBytesHelper48 initializedSeed initializedSeed initializedSeed list 0 length
combineState (@LowLevelHasher { initializedSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length: Num.toU64 length }
combineState (@LowLevelHasher { initializedSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length }
hashBytesHelper48 : U64, U64, U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
hashBytesHelper48 : U64, U64, U64, List U8, U64, U64 -> { a : U64, b : U64, seed : U64 }
hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
newSee1 = wymix (Num.bitwiseXor (wyr8 list (Num.addWrap index 16)) wyp2) (Num.bitwiseXor (wyr8 list (Num.addWrap index 24)) see1)
@ -1389,7 +1382,7 @@ hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
{ a: wyr8 list (Num.subWrap newRemaining 16 |> Num.addWrap newIndex), b: wyr8 list (Num.subWrap newRemaining 8 |> Num.addWrap newIndex), seed: finalSeed }
hashBytesHelper16 : U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
hashBytesHelper16 : U64, List U8, U64, U64 -> { a : U64, b : U64, seed : U64 }
hashBytesHelper16 = \seed, list, index, remaining ->
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
newRemaining = Num.subWrap remaining 16
@ -1417,7 +1410,7 @@ wymix = \a, b ->
wymum : U64, U64 -> { lower : U64, upper : U64 }
wymum = \a, b ->
r = Num.toU128 a * Num.toU128 b
r = Num.mulWrap (Num.toU128 a) (Num.toU128 b)
lower = Num.toU64 r
upper = Num.shiftRightZfBy r 64 |> Num.toU64
@ -1426,7 +1419,7 @@ wymum = \a, b ->
{ lower, upper }
# Get the next 8 bytes as a U64
wyr8 : List U8, Nat -> U64
wyr8 : List U8, U64 -> U64
wyr8 = \list, index ->
# With seamless slices and Num.fromBytes, this should be possible to make faster and nicer.
# It would also deal with the fact that on big endian systems we want to invert the order here.
@ -1447,7 +1440,7 @@ wyr8 = \list, index ->
Num.bitwiseOr (Num.bitwiseOr a b) (Num.bitwiseOr c d)
# Get the next 4 bytes as a U64 with some shifting.
wyr4 : List U8, Nat -> U64
wyr4 : List U8, U64 -> U64
wyr4 = \list, index ->
p1 = listGetUnsafe list index |> Num.toU64
p2 = listGetUnsafe list (Num.addWrap index 1) |> Num.toU64
@ -1460,7 +1453,7 @@ wyr4 = \list, index ->
# Get the next K bytes with some shifting.
# K must be 3 or less.
wyr3 : List U8, Nat, Nat -> U64
wyr3 : List U8, U64, U64 -> U64
wyr3 = \list, index, k ->
# ((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1]
p1 = listGetUnsafe list index |> Num.toU64

View File

@ -73,14 +73,46 @@ EncoderFormatting implements
tuple : List (Encoder fmt) -> Encoder fmt where fmt implements EncoderFormatting
tag : Str, List (Encoder fmt) -> Encoder fmt where fmt implements EncoderFormatting
## Creates a custom encoder from a given function.
##
## ```
## expect
## # Appends the byte 42
## customEncoder = Encode.custom (\bytes, _fmt -> List.append bytes 42)
##
## actual = Encode.appendWith [] customEncoder Core.json
## expected = [42] # Expected result is a list with a single byte, 42
##
## actual == expected
## ```
custom : (List U8, fmt -> List U8) -> Encoder fmt where fmt implements EncoderFormatting
custom = \encoder -> @Encoder encoder
appendWith : List U8, Encoder fmt, fmt -> List U8 where fmt implements EncoderFormatting
appendWith = \lst, @Encoder doEncoding, fmt -> doEncoding lst fmt
## Appends the encoded representation of a value to an existing list of bytes.
##
## ```
## expect
## actual = Encode.append [] { foo: 43 } Core.json
## expected = Str.toUtf8 """{"foo":43}"""
##
## actual == expected
## ```
append : List U8, val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
append = \lst, val, fmt -> appendWith lst (toEncoder val) fmt
## Encodes a value to a list of bytes (`List U8`) according to the specified format.
##
## ```
## expect
## fooRec = { foo: 42 }
##
## actual = Encode.toBytes fooRec Core.json
## expected = Str.toUtf8 """{"foo":42}"""
##
## actual == expected
## ```
toBytes : val, fmt -> List U8 where val implements Encoding, fmt implements EncoderFormatting
toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt

View File

@ -15,17 +15,16 @@ interface Hash
hashI32,
hashI64,
hashI128,
hashNat,
hashDec,
complete,
hashStrBytes,
hashList,
hashUnordered,
] imports [
Bool.{ Bool, isEq },
Bool.{ Bool },
List,
Str,
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat, Dec },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Dec },
]
## A value that can be hashed.
@ -98,21 +97,6 @@ hashI64 = \hasher, n -> addU64 hasher (Num.toU64 n)
hashI128 : a, I128 -> a where a implements Hasher
hashI128 = \hasher, n -> addU128 hasher (Num.toU128 n)
## Adds a single Nat to a hasher.
hashNat : a, Nat -> a where a implements Hasher
hashNat = \hasher, n ->
isPlatform32bit =
x : Nat
x = 0xffff_ffff
y = Num.addWrap x 1
y == 0
if isPlatform32bit then
addU32 hasher (Num.toU32 n)
else
addU64 hasher (Num.toU64 n)
## LOWLEVEL get the i128 representation of a Dec.
i128OfDec : Dec -> I128

View File

@ -27,7 +27,6 @@ interface Inspect
i64,
u128,
i128,
nat,
f32,
f64,
dec,
@ -38,7 +37,7 @@ interface Inspect
]
imports [
Bool.{ Bool },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec, Nat },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
List,
Str,
]
@ -77,7 +76,6 @@ InspectFormatter implements
i64 : I64 -> Inspector f where f implements InspectFormatter
u128 : U128 -> Inspector f where f implements InspectFormatter
i128 : I128 -> Inspector f where f implements InspectFormatter
nat : Nat -> Inspector f where f implements InspectFormatter
f32 : F32 -> Inspector f where f implements InspectFormatter
f64 : F64 -> Inspector f where f implements InspectFormatter
dec : Dec -> Inspector f where f implements InspectFormatter
@ -131,7 +129,6 @@ DbgFormatter := { data : Str }
i64: dbgI64,
u128: dbgU128,
i128: dbgI128,
nat: dbgNat,
f32: dbgF32,
f64: dbgF64,
dec: dbgDec,
@ -326,11 +323,6 @@ dbgI128 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
dbgNat : Nat -> Inspector DbgFormatter
dbgNat = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)
dbgF32 : F32 -> Inspector DbgFormatter
dbgF32 = \num ->
f0 <- custom

View File

@ -74,7 +74,7 @@ interface List
imports [
Bool.{ Bool, Eq },
Result.{ Result },
Num.{ Nat, Num, Int },
Num.{ U64, Num, Int },
]
## ## Types
@ -91,14 +91,14 @@ interface List
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > The theoretical maximum length for a list created in Roc is `Num.maxI32` on 32-bit systems
## > and `Num.maxI64` on 64-bit systems. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field, a `capacity : Nat`
## Under the hood, a list is a record containing a `len : U64` field, a `capacity : U64`
## field, and a pointer to a reference count and a flat array of bytes.
##
## ## Shared Lists
@ -227,7 +227,7 @@ isEmpty = \list ->
# unsafe primitive that does not perform a bounds check
# but will cause a reference count increment on the value it got out of the list
getUnsafe : List a, Nat -> a
getUnsafe : List a, U64 -> a
## Returns an element from a list at the given index.
##
@ -236,7 +236,7 @@ getUnsafe : List a, Nat -> a
## expect List.get [100, 200, 300] 1 == Ok 200
## expect List.get [100, 200, 300] 5 == Err OutOfBounds
## ```
get : List a, Nat -> Result a [OutOfBounds]
get : List a, U64 -> Result a [OutOfBounds]
get = \list, index ->
if index < List.len list then
Ok (List.getUnsafe list index)
@ -245,9 +245,9 @@ get = \list, index ->
# unsafe primitive that does not perform a bounds check
# but will cause a reference count increment on the value it got out of the list
replaceUnsafe : List a, Nat, a -> { list : List a, value : a }
replaceUnsafe : List a, U64, a -> { list : List a, value : a }
replace : List a, Nat, a -> { list : List a, value : a }
replace : List a, U64, a -> { list : List a, value : a }
replace = \list, index, newValue ->
if index < List.len list then
List.replaceUnsafe list index newValue
@ -262,7 +262,7 @@ replace = \list, index, newValue ->
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List a, Nat, a -> List a
set : List a, U64, a -> List a
set = \list, index, value ->
(List.replace list index value).list
@ -275,7 +275,7 @@ set = \list, index, value ->
##
## To replace the element at a given index, instead of updating based on the current value,
## see [List.set] and [List.replace]
update : List a, Nat, (a -> a) -> List a
update : List a, U64, (a -> a) -> List a
update = \list, index, func ->
when List.get list index is
Err OutOfBounds -> list
@ -285,7 +285,7 @@ update = \list, index, func ->
# Update one element in bounds
expect
list : List Nat
list : List U64
list = [1, 2, 3]
got = update list 1 (\x -> x + 42)
want = [1, 44, 3]
@ -293,14 +293,14 @@ expect
# Update out of bounds
expect
list : List Nat
list : List U64
list = [1, 2, 3]
got = update list 5 (\x -> x + 42)
got == list
# Update chain
expect
list : List Nat
list : List U64
list = [1, 2, 3]
got =
list
@ -374,13 +374,13 @@ prependIfOk = \list, result ->
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List * -> Nat
len : List * -> U64
## Create a list with space for at least capacity elements
withCapacity : Nat -> List *
withCapacity : U64 -> List *
## Enlarge the list for at least capacity additional elements
reserve : List a, Nat -> List a
reserve : List a, U64 -> List a
## Shrink the memory footprint of a list such that it's capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.
@ -418,11 +418,11 @@ single : a -> List a
single = \x -> [x]
## Returns a list with the given length, where every element is the given value.
repeat : a, Nat -> List a
repeat : a, U64 -> List a
repeat = \value, count ->
repeatHelp value count (List.withCapacity count)
repeatHelp : a, Nat, List a -> List a
repeatHelp : a, U64, List a -> List a
repeatHelp = \value, count, accum ->
if count > 0 then
repeatHelp value (Num.subWrap count 1) (List.appendUnsafe accum value)
@ -435,7 +435,8 @@ repeatHelp = \value, count, accum ->
## ```
reverse : List a -> List a
reverse = \list ->
reverseHelp list 0 (Num.subSaturated (List.len list) 1)
end = List.len list |> Num.subSaturated 1
reverseHelp (List.clone list) 0 end
reverseHelp = \list, left, right ->
if left < right then
@ -443,6 +444,9 @@ reverseHelp = \list, left, right ->
else
list
# Ensures that the list in unique (will re-use if already unique)
clone : List a -> List a
## Join the given lists together into one list.
## ```
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
@ -497,7 +501,7 @@ walk = \list, init, func ->
walkHelp list init func 0 (List.len list)
## internal helper
walkHelp : List elem, s, (s, elem -> s), Nat, Nat -> s
walkHelp : List elem, s, (s, elem -> s), U64, U64 -> s
walkHelp = \list, state, f, index, length ->
if index < length then
nextState = f state (List.getUnsafe list index)
@ -507,12 +511,12 @@ walkHelp = \list, state, f, index, length ->
state
## Like [walk], but at each step the function also receives the index of the current element.
walkWithIndex : List elem, state, (state, elem, Nat -> state) -> state
walkWithIndex : List elem, state, (state, elem, U64 -> state) -> state
walkWithIndex = \list, init, func ->
walkWithIndexHelp list init func 0 (List.len list)
## internal helper
walkWithIndexHelp : List elem, s, (s, elem, Nat -> s), Nat, Nat -> s
walkWithIndexHelp : List elem, s, (s, elem, U64 -> s), U64, U64 -> s
walkWithIndexHelp = \list, state, f, index, length ->
if index < length then
nextState = f state (List.getUnsafe list index) index
@ -522,14 +526,14 @@ walkWithIndexHelp = \list, state, f, index, length ->
state
## Like [walkUntil], but at each step the function also receives the index of the current element.
walkWithIndexUntil : List elem, state, (state, elem, Nat -> [Continue state, Break state]) -> state
walkWithIndexUntil : List elem, state, (state, elem, U64 -> [Continue state, Break state]) -> state
walkWithIndexUntil = \list, state, f ->
when walkWithIndexUntilHelp list state f 0 (List.len list) is
Continue new -> new
Break new -> new
## internal helper
walkWithIndexUntilHelp : List elem, s, (s, elem, Nat -> [Continue s, Break b]), Nat, Nat -> [Continue s, Break b]
walkWithIndexUntilHelp : List elem, s, (s, elem, U64 -> [Continue s, Break b]), U64, U64 -> [Continue s, Break b]
walkWithIndexUntilHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) index is
@ -547,7 +551,7 @@ walkBackwards = \list, state, func ->
walkBackwardsHelp list state func (len list)
## internal helper
walkBackwardsHelp : List elem, state, (state, elem -> state), Nat -> state
walkBackwardsHelp : List elem, state, (state, elem -> state), U64 -> state
walkBackwardsHelp = \list, state, f, indexPlusOne ->
if indexPlusOne == 0 then
state
@ -582,7 +586,7 @@ walkBackwardsUntil = \list, initial, func ->
Break new -> new
## Walks to the end of the list from a specified starting index
walkFrom : List elem, Nat, state, (state, elem -> state) -> state
walkFrom : List elem, U64, state, (state, elem -> state) -> state
walkFrom = \list, index, state, func ->
step : _, _ -> [Continue _, Break []]
step = \currentState, element -> Continue (func currentState element)
@ -591,7 +595,7 @@ walkFrom = \list, index, state, func ->
Continue new -> new
## A combination of [List.walkFrom] and [List.walkUntil]
walkFromUntil : List elem, Nat, state, (state, elem -> [Continue state, Break state]) -> state
walkFromUntil : List elem, U64, state, (state, elem -> [Continue state, Break state]) -> state
walkFromUntil = \list, index, state, func ->
when List.iterHelp list state func index (List.len list) is
Continue new -> new
@ -660,7 +664,7 @@ keepIf = \list, predicate ->
keepIfHelp list predicate 0 0 length
keepIfHelp : List a, (a -> Bool), Nat, Nat, Nat -> List a
keepIfHelp : List a, (a -> Bool), U64, U64, U64 -> List a
keepIfHelp = \list, predicate, kept, index, length ->
if index < length then
if predicate (List.getUnsafe list index) then
@ -689,7 +693,7 @@ dropIf = \list, predicate ->
## expect List.countIf [1, -2, -3] Num.isNegative == 2
## expect List.countIf [1, 2, 3] (\num -> num > 1 ) == 2
## ```
countIf : List a, (a -> Bool) -> Nat
countIf : List a, (a -> Bool) -> U64
countIf = \list, predicate ->
walkState = \state, elem ->
if predicate elem then
@ -771,7 +775,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## ```
## expect List.mapWithIndex [10, 20, 30] (\num, index -> num + index) == [10, 21, 32]
## ```
mapWithIndex : List a, (a, Nat -> b) -> List b
mapWithIndex : List a, (a, U64 -> b) -> List b
mapWithIndex = \src, func ->
length = len src
dest = withCapacity length
@ -779,7 +783,7 @@ mapWithIndex = \src, func ->
mapWithIndexHelp src dest func 0 length
# Internal helper
mapWithIndexHelp : List a, List b, (a, Nat -> b), Nat, Nat -> List b
mapWithIndexHelp : List a, List b, (a, U64 -> b), U64, U64 -> List b
mapWithIndexHelp = \src, dest, func, index, length ->
if index < length then
elem = getUnsafe src index
@ -954,7 +958,7 @@ sortAsc = \list -> List.sortWith list Num.compare
sortDesc : List (Num a) -> List (Num a)
sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a)
swap : List a, Nat, Nat -> List a
swap : List a, U64, U64 -> List a
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List a -> Result a [ListWasEmpty]
@ -979,7 +983,7 @@ first = \list ->
##
## To split the list into two lists, use `List.split`.
##
takeFirst : List elem, Nat -> List elem
takeFirst : List elem, U64 -> List elem
takeFirst = \list, outputLength ->
List.sublist list { start: 0, len: outputLength }
@ -999,19 +1003,19 @@ takeFirst = \list, outputLength ->
##
## To split the list into two lists, use `List.split`.
##
takeLast : List elem, Nat -> List elem
takeLast : List elem, U64 -> List elem
takeLast = \list, outputLength ->
List.sublist list { start: Num.subSaturated (List.len list) outputLength, len: outputLength }
## Drops n elements from the beginning of the list.
dropFirst : List elem, Nat -> List elem
dropFirst : List elem, U64 -> List elem
dropFirst = \list, n ->
remaining = Num.subSaturated (List.len list) n
List.takeLast list remaining
## Drops n elements from the end of the list.
dropLast : List elem, Nat -> List elem
dropLast : List elem, U64 -> List elem
dropLast = \list, n ->
remaining = Num.subSaturated (List.len list) n
@ -1022,7 +1026,7 @@ dropLast = \list, n ->
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
dropAt : List elem, U64 -> List elem
min : List (Num a) -> Result (Num a) [ListWasEmpty]
min = \list ->
@ -1097,7 +1101,7 @@ findLast = \list, pred ->
## Returns the index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findFirstIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]
findFirstIndex : List elem, (elem -> Bool) -> Result U64 [NotFound]
findFirstIndex = \list, matcher ->
foundIndex = List.iterate list 0 \index, elem ->
if matcher elem then
@ -1112,7 +1116,7 @@ findFirstIndex = \list, matcher ->
## Returns the last index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findLastIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]
findLastIndex : List elem, (elem -> Bool) -> Result U64 [NotFound]
findLastIndex = \list, matches ->
foundIndex = List.iterateBackwards list (List.len list) \prevIndex, elem ->
answer = Num.subWrap prevIndex 1
@ -1141,12 +1145,12 @@ findLastIndex = \list, matches ->
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
sublist : List elem, { start : U64, len : U64 } -> List elem
sublist = \list, config ->
sublistLowlevel list config.start config.len
## low-level slicing operation that does no bounds checking
sublistLowlevel : List elem, Nat, Nat -> List elem
sublistLowlevel : List elem, U64, U64 -> List elem
## Intersperses `sep` between the elements of `list`
## ```
@ -1198,7 +1202,7 @@ endsWith = \list, suffix ->
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before : List elem, others : List elem }
split : List elem, U64 -> { before : List elem, others : List elem }
split = \elements, userSplitIndex ->
length = List.len elements
splitIndex = if length > userSplitIndex then userSplitIndex else length
@ -1243,7 +1247,7 @@ splitLast = \list, delimiter ->
## size. The last chunk will be shorter if the list does not evenly divide by the
## chunk size. If the provided list is empty or if the chunk size is 0 then the
## result is an empty list.
chunksOf : List a, Nat -> List (List a)
chunksOf : List a, U64 -> List (List a)
chunksOf = \list, chunkSize ->
if chunkSize == 0 || List.isEmpty list then
[]
@ -1251,7 +1255,7 @@ chunksOf = \list, chunkSize ->
chunkCapacity = Num.divCeil (List.len list) chunkSize
chunksOfHelp list chunkSize (List.withCapacity chunkCapacity)
chunksOfHelp : List a, Nat, List (List a) -> List (List a)
chunksOfHelp : List a, U64, List (List a) -> List (List a)
chunksOfHelp = \listRest, chunkSize, chunks ->
if List.isEmpty listRest then
chunks
@ -1284,7 +1288,7 @@ walkTry = \list, init, func ->
walkTryHelp list init func 0 (List.len list)
## internal helper
walkTryHelp : List elem, state, (state, elem -> Result state err), Nat, Nat -> Result state err
walkTryHelp : List elem, state, (state, elem -> Result state err), U64, U64 -> Result state err
walkTryHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) is
@ -1299,7 +1303,7 @@ iterate = \list, init, func ->
iterHelp list init func 0 (List.len list)
## internal helper
iterHelp : List elem, s, (s, elem -> [Continue s, Break b]), Nat, Nat -> [Continue s, Break b]
iterHelp : List elem, s, (s, elem -> [Continue s, Break b]), U64, U64 -> [Continue s, Break b]
iterHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) is
@ -1315,7 +1319,7 @@ iterateBackwards = \list, init, func ->
iterBackwardsHelp list init func (List.len list)
## internal helper
iterBackwardsHelp : List elem, s, (s, elem -> [Continue s, Break b]), Nat -> [Continue s, Break b]
iterBackwardsHelp : List elem, s, (s, elem -> [Continue s, Break b]), U64 -> [Continue s, Break b]
iterBackwardsHelp = \list, state, f, prevIndex ->
if prevIndex > 0 then
index = Num.subWrap prevIndex 1

View File

@ -25,11 +25,9 @@ interface Num
Unsigned32,
Unsigned16,
Unsigned8,
Nat,
Dec,
F64,
F32,
Natural,
Decimal,
Binary32,
Binary64,
@ -48,6 +46,7 @@ interface Num
isLte,
isGt,
isGte,
isApproxEq,
sin,
cos,
tan,
@ -97,10 +96,6 @@ interface Num
mulSaturated,
mulChecked,
intCast,
bytesToU16,
bytesToU32,
bytesToU64,
bytesToU128,
divCeil,
divCeilChecked,
divTrunc,
@ -151,8 +146,6 @@ interface Num
toU64Checked,
toU128,
toU128Checked,
toNat,
toNatChecked,
toF32,
toF32Checked,
toF64,
@ -192,9 +185,9 @@ interface Num
## a more specific type based on how they're used.
##
## For example, in `(1 + List.len myList)`, the `1` has the type `Num *` at first,
## but because `List.len` returns a `Nat`, the `1` ends up changing from
## `Num *` to the more specific `Nat`, and the expression as a whole
## ends up having the type `Nat`.
## but because `List.len` returns a `U64`, the `1` ends up changing from
## `Num *` to the more specific `U64`, and the expression as a whole
## ends up having the type `U64`.
##
## Sometimes number literals don't become more specific. For example,
## the `Num.toStr` function has the type `Num * -> Str`. This means that
@ -216,7 +209,6 @@ interface Num
## * `215u8` is a `215` value of type [U8]
## * `76.4f32` is a `76.4` value of type [F32]
## * `123.45dec` is a `123.45` value of type [Dec]
## * `12345nat` is a `12345` value of type [Nat]
##
## In practice, these are rarely needed. It's most common to write
## number literals without any suffix.
@ -326,16 +318,6 @@ Num range := range
## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
##
## Roc also has one variable-size integer type: [Nat]. 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] is the same as [U64]. When compiling for a
## 32-bit system, it's the same as [U32].
##
## A common use for [Nat] is to store the length ("len" for short) of a
## collection like a [List]. 64-bit systems can represent longer
## lists in memory than 32-bit systems, which is why the length of a list
## is represented as a [Nat] in Roc.
##
## If any operation would result in an [Int] that is either too big
## or too small to fit in that range (e.g. calling `Num.maxI32 + 1`),
## then the operation will *overflow*. When an overflow occurs, the program will crash.
@ -425,8 +407,6 @@ Unsigned32 := []
Unsigned16 := []
Unsigned8 := []
Natural := []
Integer range := range
I128 : Num (Integer Signed128)
@ -443,18 +423,6 @@ U32 : Num (Integer Unsigned32)
U16 : Num (Integer Unsigned16)
U8 : Num (Integer Unsigned8)
## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented
## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer
## on 32-bit systems, and so on.
##
## This system-specific size makes it useful for certain data structure
## functions like [List.len], because the number of elements many data structures
## can hold is also system-specific. For example, the maximum number of elements
## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and
## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a
## good fit for [List.len] regardless of system.
Nat : Num (Integer Natural)
Decimal := []
Binary64 := []
Binary32 := []
@ -574,51 +542,6 @@ tau = 2 * pi
toStr : Num * -> Str
intCast : Int a -> Int b
bytesToU16Lowlevel : List U8, Nat -> U16
bytesToU32Lowlevel : List U8, Nat -> U32
bytesToU64Lowlevel : List U8, Nat -> U64
bytesToU128Lowlevel : List U8, Nat -> U128
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
bytesToU16 = \bytes, index ->
# we need at least 1 more byte
offset = 1
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU16Lowlevel bytes index)
else
Err OutOfBounds
bytesToU32 : List U8, Nat -> Result U32 [OutOfBounds]
bytesToU32 = \bytes, index ->
# we need at least 3 more bytes
offset = 3
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU32Lowlevel bytes index)
else
Err OutOfBounds
bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds]
bytesToU64 = \bytes, index ->
# we need at least 7 more bytes
offset = 7
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU64Lowlevel bytes index)
else
Err OutOfBounds
bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds]
bytesToU128 = \bytes, index ->
# we need at least 15 more bytes
offset = 15
if Num.addSaturated index offset < List.len bytes then
Ok (bytesToU128Lowlevel bytes index)
else
Err OutOfBounds
compare : Num a, Num a -> [LT, EQ, GT]
## Returns `Bool.true` if the first number is less than the second.
@ -661,6 +584,22 @@ isLte : Num a, Num a -> Bool
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
isGte : Num a, Num a -> Bool
## Returns `Bool.true` if the first number and second number are within a specific threshold
##
## A specific relative and absolute tolerance can be provided to change the threshold
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
isApproxEq : Frac a, Frac a, { rtol ? Frac a, atol ? Frac a } -> Bool
isApproxEq = \value, refValue, { rtol ? 0.00001, atol ? 0.00000001 } -> value
<= refValue
&& value
>= refValue
|| Num.absDiff value refValue
<= atol
+ rtol
* Num.abs refValue
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
isZero : Num a -> Bool
@ -985,13 +924,21 @@ divCeilChecked = \a, b ->
## Num.divTrunc 8 -3
## ```
divTrunc : Int a, Int a -> Int a
divTrunc = \a, b ->
if Num.isZero b then
crash "Integer division by 0!"
else
Num.divTruncUnchecked a b
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
divTruncChecked = \a, b ->
if Num.isZero b then
Err DivByZero
else
Ok (Num.divTrunc a b)
Ok (Num.divTruncUnchecked a b)
## traps (hardware fault) when given zero as the second argument.
divTruncUnchecked : Int a, Int a -> Int a
## Obtains the remainder (truncating modulo) from the division of two integers.
##
@ -1006,13 +953,21 @@ divTruncChecked = \a, b ->
## Num.rem -8 -3
## ```
rem : Int a, Int a -> Int a
rem = \a, b ->
if Num.isZero b then
crash "Integer division by 0!"
else
Num.remUnchecked a b
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
remChecked = \a, b ->
if Num.isZero b then
Err DivByZero
else
Ok (Num.rem a b)
Ok (Num.remUnchecked a b)
## traps (hardware fault) when given zero as the second argument.
remUnchecked : Int a, Int a -> Int a
isMultipleOf : Int a, Int a -> Bool
@ -1430,22 +1385,6 @@ toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Converts an [Int] to a [Nat]. If the given number doesn't fit in [Nat], it will be truncated!
## Since [Nat] has a different maximum number depending on the system you're building
## for, this may give a different answer on different systems.
##
## For example, on a 32-bit system, calling `Num.toNat 9_000_000_000` on a 32-bit
## system will return `Num.maxU32` instead of 9 billion, because 9 billion is
## higher than `Num.maxU32` and will not fit in a [Nat] on a 32-bit system.
##
## However, calling `Num.toNat 9_000_000_000` on a 64-bit system will return
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
## hold up to `Num.maxU64`, and 9_000_000_000 is lower than `Num.maxU64`.
##
## To convert a [Frac] to a [Nat], first call either `Num.round`, `Num.ceil`, or `Num.floor`
## on it, then call this on the resulting [Int].
toNat : Int * -> Nat
## Converts a [Num] to an [F32]. If the given number can't be precisely represented in an [F32],
## the returned number may be different from the given number.
toF32 : Num * -> F32
@ -1467,6 +1406,5 @@ toU16Checked : Int * -> Result U16 [OutOfBounds]
toU32Checked : Int * -> Result U32 [OutOfBounds]
toU64Checked : Int * -> Result U64 [OutOfBounds]
toU128Checked : Int * -> Result U128 [OutOfBounds]
toNatChecked : Int * -> Result Nat [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]

View File

@ -84,8 +84,8 @@ try = \result, transform ->
## function on the value the `Err` holds. Then returns that new result. If the
## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
## ```
## Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
## Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
## Result.onErr (Ok 10) \errorNum -> Str.toU64 errorNum
## Result.onErr (Err "42") \errorNum -> Str.toU64 errorNum
## ```
onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
onErr = \result, transform ->

View File

@ -28,7 +28,7 @@ interface Set
List,
Bool.{ Bool, Eq },
Dict.{ Dict },
Num.{ Nat },
Num.{ U64 },
Hash.{ Hash, Hasher },
Inspect.{ Inspect, Inspector, InspectFormatter },
]
@ -80,12 +80,12 @@ empty = \{} -> @Set (Dict.empty {})
## Return a set with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Set *
withCapacity : U64 -> Set *
withCapacity = \cap ->
@Set (Dict.withCapacity cap)
## Enlarge the set for at least capacity additional elements
reserve : Set k, Nat -> Set k
reserve : Set k, U64 -> Set k
reserve = \@Set dict, requested ->
@Set (Dict.reserve dict requested)
@ -152,7 +152,7 @@ expect
##
## expect countValues == 3
## ```
len : Set * -> Nat
len : Set * -> U64
len = \@Set dict ->
Dict.len dict
@ -164,7 +164,7 @@ len = \@Set dict ->
##
## capacityOfSet = Set.capacity foodSet
## ```
capacity : Set * -> Nat
capacity : Set * -> U64
capacity = \@Set dict ->
Dict.capacity dict
@ -462,22 +462,22 @@ expect
x == fromList (toList x)
expect
orderOne : Set Nat
orderOne : Set U64
orderOne =
single 1
|> insert 2
orderTwo : Set Nat
orderTwo : Set U64
orderTwo =
single 2
|> insert 1
wrapperOne : Set (Set Nat)
wrapperOne : Set (Set U64)
wrapperOne =
single orderOne
|> insert orderTwo
wrapperTwo : Set (Set Nat)
wrapperTwo : Set (Set U64)
wrapperTwo =
single orderTwo
|> insert orderOne

View File

@ -1,90 +1,331 @@
## Roc strings are sequences of text values. This module includes functions for combining strings,
## as well as breaking them up into smaller units—most commonly [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## (referred to in this module's documentation as "graphemes" rather than "characters" for clarity;
## "characters" can mean very different things in different languages).
## Strings represent text. For example, `"Hi!"` is a string.
##
## This module focuses on graphemes (as opposed to, say, Unicode code points or LATIN-1 bytes)
## because graphemes avoid common classes of bugs. Breaking strings up using code points often
## leads to bugs around things like emoji, where multiple code points combine to form to a
## single rendered glyph. Graphemes avoid these bugs by treating multi-code-point things like
## emojis as indivisible units.
## This guide starts at a high level and works down to the in-memory representation of strings and their [performance characteristics](#performance). For reasons that will be explained later in this guide, some string operations are in the `Str` module while others (notably [capitalization](#capitalization), [code points](#code-points), [graphemes](#graphemes), and sorting) are in separate packages. There's also a list of recommendations for [when to use code points, graphemes, and UTF-8](#when-to-use).
##
## Because graphemes can have variable length (there's no upper limit on how many code points one
## grapheme can represent), it takes linear time to count the number of graphemes in a string,
## and also linear time to find an individual grapheme within a string by its position (or "index")
## among the string's other graphemes. The only way to get constant-time access to these is in a way
## that can result in bugs if the string contains multi-code-point things like emojis, which is why
## this module does not offer those.
## ## Syntax
##
## The most common way to represent strings is using quotation marks:
##
## ## Working with Unicode strings in Roc
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
## ```
## "Roc!"
## "鹏"
## "🕊"
## "Hello, World!"
## ```
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling `Str.countGraphemes` on it:
## Using this syntax, the whole string must go on one line. You can write multiline strings using triple quotes:
##
## ```
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
## text =
## """
## In memory, this string will not have any spaces
## at its start. That's because the first line
## starts at the same indentation level as the
## opening quotation mark. Actually, none of these
## lines will be indented.
##
## However, this line will be indented!
## """
## ```
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
##
## ### Escape sequences
## In triple-quoted strings, both the opening and closing `"""` must be at the same indentation level. Lines in the string begin at that indentation level; the spaces that indent the multiline string itself are not considered content.
##
## ### Interpolation
##
## *String interpolation* is syntax for inserting a string into another string.
##
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
## ```
## "I took the one less traveled by,\nAnd that has made all the difference."
## name = "Sam"
##
## "Hi, my name is $(name)!"
## ```
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u(0A)` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `\u` escape sequences are always followed by a hexadecimal number inside `(` and `)`
## like this.
## This will evaluate to the string `"Hi, my name is Sam!"`
##
## As another example, `"R\u(6F)c"` is the same string as `"Roc"`, because
## `"\u(6F)"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u(F6)c"` as an alternative way to get the string `"Röc"\.
## You can put any expression you like inside the parentheses, as long as it's all on one line:
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
## ```
## name = "Lee"
## city = "Roctown"
## greeting = "Hello there, \(name)! Welcome to \(city)."
## colors = ["red", "green", "blue"]
##
## "The colors are $(colors |> Str.joinWith ", ")!"
## ```
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
##
## Interpolation can be used in multiline strings, but the part inside the parentheses must still be on one line.
##
## ### Escapes
##
## There are a few special escape sequences in strings:
##
## * `\n` becomes a [newline](https://en.wikipedia.org/wiki/Newline)
## * `\r` becomes a [carriage return](https://en.wikipedia.org/wiki/Carriage_return#Computers)
## * `\t` becomes a [tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\"` becomes a normal `"` (this lets you write `"` inside a single-line string)
## * `\\` becomes a normal `\` (this lets you write `\` without it being treated as an escape)
## * `\$` becomes a normal `$` (this lets you write `$` followed by `(` without it being treated as [interpolation](#interpolation))
##
## These work in both single-line and multiline strings. We'll also discuss another escape later, for inserting [Unicode code points](#code-points) into a string.
##
## ### Single quote syntax
##
## Try putting `'👩'` into `roc repl`. You should see this:
##
## ```
## » '👩'
##
## 128105 : Int *
## ```
##
## The single-quote `'` syntax lets you represent a Unicode code point (discussed in the next section) in source code, in a way that renders as the actual text it represents rather than as a number literal. This lets you see what it looks like in the source code rather than looking at a number.
##
## At runtime, the single-quoted value will be treated the same as an ordinary number literal—in other words, `'👩'` is syntax sugar for writing `128105`. You can verify this in `roc repl`:
##
## ```
## » '👩' == 128105
##
## Bool.true : Bool
## ```
##
## Double quotes (`"`), on the other hand, are not type-compatible with integers—not only because strings can be empty (`""` is valid, but `''` is not) but also because there may be more than one code point involved in any given string!
##
## There are also some special escape sequences in single-quote strings:
##
## * `\n` becomes a [newline](https://en.wikipedia.org/wiki/Newline)
## * `\r` becomes a [carriage return](https://en.wikipedia.org/wiki/Carriage_return#Computers)
## * `\t` becomes a [tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\'` becomes a normal `'` (this lets you write `'` inside a single-quote string)
## * `\\` becomes a normal `\` (this lets you write `\` without it being treated as an escape)
##
## Most often this single-quote syntax is used when writing parsers; most Roc programs never use it at all.
##
## ## Unicode
##
## Roc strings represent text using [Unicode](https://unicode.org) This guide will provide only a basic overview of Unicode (the [Unicode glossary](http://www.unicode.org/glossary/) has over 500 entries in it), but it will include the most relevant differences between these concepts:
##
## * Code points
## * Graphemes
## * UTF-8
##
## It will also explain why some operations are included in Roc's builtin [Str](https://www.roc-lang.org/builtins/Str)
## module, and why others are in separate packages like [roc-lang/unicode](https://github.com/roc-lang/unicode).
##
## ### Graphemes
##
## Let's start with the following string:
##
## `"👩‍👩‍👦‍👦"`
##
## Some might call this a "character." After all, in a monospace font, it looks to be about the same width as the letter "A" or the punctuation mark "!"—both of which are commonly called "characters." Unfortunately, the term "character" in programming has changed meanings many times across the years and across programming languages, and today it's become a major source of confusion.
##
## Unicode uses the less ambiguous term [*grapheme*](https://www.unicode.org/glossary/#grapheme), which it defines as a "user-perceived character" (as opposed to one of the several historical ways the term "character" has been used in programming) or, alternatively, "A minimally distinctive unit of writing in the context of a particular writing system."
##
## By Unicode's definition, each of the following is an individual grapheme:
##
## * `a`
## * `鹏`
## * `👩‍👩‍👦‍👦`
##
## Note that although *grapheme* is less ambiguous than *character*, its definition is still open to interpretation. To address this, Unicode has formally specified [text segmentation rules](https://www.unicode.org/reports/tr29/) which define grapheme boundaries in precise technical terms. We won't get into those rules here, but since they can change with new Unicode releases, functions for working with graphemes are in the [roc-lang/unicode](https://github.com/roc-lang/unicode) package rather than in the builtin [`Str`](https://www.roc-lang.org/builtins/Str) module. This allows them to be updated without being blocked on a new release of the Roc language.
##
## ### Code Points
##
## Every Unicode text value can be broken down into [Unicode code points](http://www.unicode.org/glossary/#code_point), which are integers between `0` and `285_212_438` that describe components of the text. In memory, every Roc string is a sequence of these integers stored in a format called UTF-8, which will be discussed [later](#utf8).
##
## The string `"👩‍👩‍👦‍👦"` happens to be made up of these code points:
##
## ```
## [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## ```
##
## From this we can see that:
##
## - One grapheme can be made up of multiple code points. In fact, there is no upper limit on how many code points can go into a single grapheme! (Some programming languages use the term "character" to refer to individual code points; this can be confusing for graphemes like 👩‍👩‍👦‍👦 because it visually looks like "one character" but no single code point can represent it.)
## - Sometimes code points repeat within an individual grapheme. Here, 128105 repeats twice, as does 128102, and there's an 8205 in between each of the other code points.
##
## ### Combining Code Points
##
## The reason every other code point in 👩‍👩‍👦‍👦 is 8205 is that code point 8205 joins together other code points. This emoji, known as ["Family: Woman, Woman, Boy, Boy"](https://emojipedia.org/family-woman-woman-boy-boy), is made by combining several emoji using [zero-width joiners](https://emojipedia.org/zero-width-joiner)—which are represented by code point 8205 in memory, and which have no visual repesentation on their own.
##
## Here are those code points again, this time with comments about what they represent:
##
## ```
## [128105] # "👩"
## [8205] # (joiner)
## [128105] # "👩"
## [8205] # (joiner)
## [128102] # "👦"
## [8205] # (joiner)
## [128102] # "👦"
## ```
##
## One way to read this is "woman emoji joined to woman emoji joined to boy emoji joined to boy emoji." Without the joins, it would be:
##
## ```
## "👩👩👦👦"
## ```
##
## With the joins, however, it is instead:
##
## ```
## "👩‍👩‍👦‍👦"
## ```
##
## Even though 👩‍👩‍👦‍👦 is visually smaller when rendered, it takes up almost twice as much memory as 👩👩👦👦 does! That's because it has all the same code points, plus the zero-width joiners in between them.
##
## ### String equality and normalization
##
## Besides emoji like 👩‍👩‍👦‍👦, another classic example of multiple code points being combined to render as one grapheme has to do with accent marks. Try putting these two strings into `roc repl`:
##
## ```
## "caf\u(e9)"
## "cafe\u(301)"
## ```
##
## The `\u(e9)` syntax is a way of inserting code points into string literals. In this case, it's the same as inserting the hexadecimal number `0xe9` as a code point onto the end of the string `"caf"`. Since Unicode code point `0xe9` happens to be `é`, the string `"caf\u(e9)"` ends up being identical in memory to the string `"café"`.
##
## We can verify this too:
##
## ```
## » "caf\u(e9)" == "café"
##
## Bool.true : Bool
## ```
##
## As it turns out, `"cafe\u(301)"` is another way to represent the same word. The Unicode code point 0x301 represents a ["combining acute accent"](https://unicodeplus.com/U+0301)—which essentially means that it will add an accent mark to whatever came before it. In this case, since `"cafe\u(301)"` has an `e` before the `"\u(301)"`, that `e` ends up with an accent mark on it and becomes `é`.
##
## Although these two strings get rendered identically to one another, they are different in memory because their code points are different! We can also confirm this in `roc repl`:
##
## ```
## » "caf\u(e9)" == "cafe\u(301)"
##
## Bool.false : Bool
## ```
##
## As you can imagine, this can be a source of bugs. Not only are they considered unequal, they also hash differently, meaning `"caf\u(e9)"` and `"cafe\u(301)"` can both be separate entries in the same [`Set`](https://www.roc-lang.org/builtins/Set).
##
## One way to prevent problems like these is to perform [Unicode normalization](https://www.unicode.org/reports/tr15/), a process which converts conceptually equivalent strings (like `"caf\u(e9)"` and `"cafe\u(301)"`) into one canonical in-memory representation. This makes equality checks on them pass, among other benefits.
##
## It would be technically possible for Roc to perform string normalization automatically on every equality check. Unfortunately, although some programs might want to treat `"caf\u(e9)"` and `"cafe\u(301)"` as equivalent, for other programs it might actually be important to be able to tell them apart. If these equality checks always passed, then there would be no way to tell them apart!
##
## As such, normalization must be performed explicitly when desired. Like graphemes, Unicode normalization rules can change with new releases of Unicode. As such, these functions are in separate packages instead of builtins (normalization is planned to be in [roc-lang/unicode](https://github.com/roc-lang/unicode) in the future, but it has not yet been implemented) so that updates to these functions based on new Unicode releases can happen without waiting on new releases of the Roc language.
##
## ### Capitalization
##
## We've already seen two examples of Unicode definitions that can change with new Unicode releases: graphemes and normalization. Another is capitalization; these rules can change with new Unicode releases (most often in the form of additions of new languages, but breaking changes to capitalization rules for existing languages are also possible), and so they are not included in builtin [`Str`](https://www.roc-lang.org/builtins/Str).
##
## This might seem particularly surprising, since capitalization functions are commonly included in standard libraries. However, it turns out that "capitalizing an arbitrary string" is impossible to do correctly without additional information.
##
## For example, what is the capitalized version of this string?
##
## ```
## "i"
## ```
##
## * In English, the correct answer is `"I"`.
## * In Turkish, the correct answer is `"İ"`.
##
## Similarly, the correct lowercased version of the string `"I"` is `"i"` in English and `"ı"` in Turkish.
##
## Turkish is not the only language to use this [dotless i](https://en.wikipedia.org/wiki/Dotless_I), and it's an example of how a function which capitalizes strings cannot give correct answers without the additional information of which language's capitalization rules should be used.
##
## Many languages defer to the operating system's [localization](https://en.wikipedia.org/wiki/Internationalization_and_localization) settings for this information. In that design, calling a program's capitalization function with an input string of `"i"` might give an answer of `"I"` on one machine and `"İ"` on a different machine, even though it was the same program running on both systems. Naturally, this can cause bugs—but more than that, writing tests to prevent bugs like this usually requires extra complexity compared to writing ordinary tests.
##
## In general, Roc programs should give the same answers for the same inputs even when run on different machines. There are exceptions to this (e.g. a program running out of system resources on one machine, while being able to make more progress on a machine that has more resources), but operating system's language localization is not among them.
##
## For these reasons, capitalization functions are not in [`Str`](https://www.roc-lang.org/builtins/Str). There is a planned `roc-lang` package to handle use cases like capitalization and sorting—sorting can also vary by language as well as by things like country—but implementation work has not yet started on this package.
##
## ### UTF-8
##
## Earlier, we discussed how Unicode code points can be described as [`U32`](https://www.roc-lang.org/builtins/Num#U32) integers. However, many common code points are very low integers, and can fit into a `U8` instead of needing an entire `U32` to represent them in memory. UTF-8 takes advantage of this, using a variable-width encoding to represent code points in 1-4 bytes, which saves a lot of memory in the typical case—especially compared to [UTF-16](https://en.wikipedia.org/wiki/UTF-16), which always uses at least 2 bytes to represent each code point, or [UTF-32](https://en.wikipedia.org/wiki/UTF-32), which always uses the maximum 4 bytes.
##
## This guide won't cover all the details of UTF-8, but the basic idea is this:
##
## - If a code point is 127 or lower, UTF-8 stores it in 1 byte.
## - If it's between 128 and 2047, UTF-8 stores it in 2 bytes.
## - If it's between 2048 and 65535, UTF-8 stores it in 3 bytes.
## - If it's higher than that, UTF-8 stores it in 4 bytes.
##
## The specific [UTF-8 encoding](https://en.wikipedia.org/wiki/UTF-8#Encoding) of these bytes involves using 1 to 5 bits of each byte for metadata about multi-byte sequences.
##
## A valuable feature of UTF-8 is that it is backwards-compatible with the [ASCII](https://en.wikipedia.org/wiki/ASCII) encoding that was widely used for many years. ASCII existed before Unicode did, and only used the integers 0 to 127 to represent its equivalent of code points. The Unicode code points 0 to 127 represent the same semantic information as ASCII, (e.g. the number 64 represents the letter "A" in both ASCII and in Unicode), and since UTF-8 represents code points 0 to 127 using one byte, all valid ASCII strings can be successfully parsed as UTF-8 without any need for conversion.
##
## Since many textual computer encodings—including [CSV](https://en.wikipedia.org/wiki/CSV), [XML](https://en.wikipedia.org/wiki/XML), and [JSON](https://en.wikipedia.org/wiki/JSON)—do not use any code points above 127 for their delimiters, it is often possible to write parsers for these formats using only `Str` functions which present UTF-8 as raw `U8` sequences, such as [`Str.walkUtf8`](https://www.roc-lang.org/builtins/Str#walkUtf8) and [`Str.toUtf8`](https://www.roc-lang.org/builtins/Str#toUtf8). In the typical case where they do not to need to parse out individual Unicode code points, they can get everything they need from `Str` UTF-8 functions without needing to depend on other packages.
##
## ### When to use code points, graphemes, and UTF-8
##
## Deciding when to use code points, graphemes, and UTF-8 can be nonobvious to say the least!
##
## The way Roc organizes the `Str` module and supporting packages is designed to help answer this question. Every situation is different, but the following rules of thumb are typical:
##
## * Most often, using `Str` values along with helper functions like [`split`](https://www.roc-lang.org/builtins/Str#split), [`joinWith`](https://www.roc-lang.org/builtins/Str#joinWith), and so on, is the best option.
## * If you are specifically implementing a parser, working in UTF-8 bytes is usually the best option. So functions like [`walkUtf8`](https://www.roc-lang.org/builtins/Str#walkUtf8), [toUtf8](https://www.roc-lang.org/builtins/Str#toUtf8), and so on. (Note that single-quote literals produce number literals, so ASCII-range literals like `'a'` gives an integer literal that works with a UTF-8 `U8`.)
## * If you are implementing a Unicode library like [roc-lang/unicode](https://github.com/roc-lang/unicode), working in terms of code points will be unavoidable. Aside from basic readability considerations like `\u(...)` in string literals, if you have the option to avoid working in terms of code points, it is almost always correct to avoid them.
## * If it seems like a good idea to split a string into "characters" (graphemes), you should definitely stop and reconsider whether this is really the best design. Almost always, doing this is some combination of more error-prone or slower (usually both) than doing something else that does not require taking graphemes into consideration.
##
## For this reason (among others), grapheme functions live in [roc-lang/unicode](https://github.com/roc-lang/unicode) rather than in [`Str`](https://www.roc-lang.org/builtins/Str). They are more niche than they seem, so they should not be reached for all the time!
##
## ## Performance
##
## This section deals with how Roc strings are represented in memory, and their performance characteristics.
##
## A normal heap-allocated roc `Str` is represented on the stack as:
## - A "capacity" unsigned integer, which respresents how many bytes are allocated on the heap to hold the string's contents.
## - A "length" unsigned integer, which rerepresents how many of the "capacity" bytes are actually in use. (A `Str` can have more bytes allocated on the heap than are actually in use.)
## - The memory address of the first byte in the string's actual contents.
##
## Each of these three fields is the same size: 64 bits on a 64-bit system, and 32 bits on a 32-bit system. The actual contents of the string are stored in one contiguous sequence of bytes, encoded as UTF-8, often on the heap but sometimes elsewhere—more on this later. Empty strings do not have heap allocations, so an empty `Str` on a 64-bit system still takes up 24 bytes on the stack (due to its three 64-bit fields).
##
## ### Reference counting and opportunistic mutation
##
## Like lists, dictionaries, and sets, Roc strings are automatically reference-counted and can benefit from opportunistic in-place mutation. The reference count is stored on the heap immediately before the first byte of the string's contents, and it has the same size as a memory address. This means it can count so high that it's impossible to write a Roc program which overflows a reference count, because having that many simultaneous references (each of which is a memory address) would have exhausted the operating system's address space first.
##
## When the string's reference count is 1, functions like [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) and [`Str.replaceEach`](https://www.roc-lang.org/builtins/Str#replaceEach) mutate the string in-place rather than allocating a new string. This preserves semantic immutability because it is unobservable in terms of the operation's output; if the reference count is 1, it means that memory would have otherwise been deallocated immediately anyway, and it's more efficient to reuse it instead of deallocating it and then immediately making a new allocation.
##
## The contents of statically-known strings (today that means string literals) are stored in the readonly section of the binary, so they do not need heap allocations or reference counts. They are not eligible for in-place mutation, since mutating the readonly section of the binary would cause an operating system [access violation](https://en.wikipedia.org/wiki/Segmentation_fault).
##
## ### Small String Optimization
##
## Roc uses a "small string optimization" when representing certain strings in memory.
##
## If you have a sufficiently long string, then on a 64-bit system it will be represented on the stack using 24 bytes, and on a 32-bit system it will take 12 bytes—plus however many bytes are in the string itself—on the heap. However, if there is a string shorter than either of these stack sizes (so, a string of up to 23 bytes on a 64-bit system, and up to 11 bytes on a 32-bit system), then that string will be stored entirely on the stack rather than having a separate heap allocation at all.
##
## This can be much more memory-efficient! However, `List` does not have this optimization (it has some runtime cost, and in the case of `List` it's not anticipated to come up nearly as often), which means when converting a small string to `List U8` it can result in a heap allocation.
##
## Note that this optimization is based entirely on how many UTF-8 bytes the string takes up in memory. It doesn't matter how many [graphemes](#graphemes), [code points](#code-points) or anything else it has; the only factor that determines whether a particular string is eligible for the small string optimization is the number of UTF-8 bytes it takes up in memory!
##
## ### Seamless Slices
##
## Try putting this into `roc repl`:
##
## ```
## » "foo/bar/baz" |> Str.split "/"
##
## ["foo", "bar", "baz"] : List Str
## ```
##
## All of these strings are small enough that the [small string optimization](#small) will apply, so none of them will be allocated on the heap.
##
## Now let's suppose they were long enough that this optimization no longer applied:
##
## ```
## » "a much, much, much, much/longer/string compared to the last one!" |> Str.split "/"
##
## ["a much, much, much, much", "longer", "string compared to the last one!"] : List Str
## ```
##
## Here, the only strings small enough for the small string optimization are `"/"` and `"longer"`. They will be allocated on the stack.
##
## The first and last strings in the returned list `"a much, much, much, much"` and `"string compared to the last one!"` will not be allocated on the heap either. Instead, they will be *seamless slices*, which means they will share memory with the original input string.
##
## * `"a much, much, much, much"` will share the first 24 bytes of the original string.
## * `"string compared to the last one!"` will share the last 32 bytes of the original string.
##
## All of these strings are semantically immutable, so sharing these bytes is an implementation detail that should only affect performance. By design, there is no way at either compile time or runtime to tell whether a string is a seamless slice. This allows the optimization's behavior to change in the future without affecting Roc programs' semantic behavior.
##
## Seamless slices create additional references to the original string, which make it ineligible for opportunistic mutation (along with the slices themselves; slices are never eligible for mutation), and which also make it take longer before the original string can be deallocated. A case where this might be noticeable in terms of performance would be:
## 1. A function takes a very large string as an argument and returns a much smaller slice into that string.
## 2. The smaller slice is used for a long time in the program, whereas the much larger original string stops being used.
## 3. In this situation, it might have been better for total program memory usage (although not necessarily overall performance) if the original large string could have been deallocated sooner, even at the expense of having to copy the smaller string into a new allocation instead of reusing the bytes with a seamless slice.
##
## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)).
##
## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice.
interface Str
exposes [
Utf8Problem,
@ -94,12 +335,9 @@ interface Str
joinWith,
split,
repeat,
countGraphemes,
countUtf8Bytes,
startsWithScalar,
toUtf8,
fromUtf8,
fromUtf8Range,
startsWith,
endsWith,
trim,
@ -108,7 +346,6 @@ interface Str
toDec,
toF64,
toF32,
toNat,
toU128,
toI128,
toU64,
@ -119,7 +356,6 @@ interface Str
toI16,
toU8,
toI8,
toScalars,
replaceEach,
replaceFirst,
replaceLast,
@ -129,19 +365,15 @@ interface Str
walkUtf8WithIndex,
reserve,
releaseExcessCapacity,
appendScalar,
walkScalars,
walkScalarsUntil,
withCapacity,
withPrefix,
graphemes,
contains,
]
imports [
Bool.{ Bool, Eq },
Result.{ Result },
List,
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
Num.{ Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
]
Utf8ByteProblem : [
@ -153,7 +385,7 @@ Utf8ByteProblem : [
EncodesSurrogateHalf,
]
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
Utf8Problem : { byteIndex : U64, problem : Utf8ByteProblem }
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
## ```
@ -194,7 +426,7 @@ concat : Str, Str -> Str
## the cost of using more memory than is necessary.
##
## For more details on how the performance optimization works, see [Str.reserve].
withCapacity : Nat -> Str
withCapacity : U64 -> Str
## Increase a string's capacity by at least the given number of additional bytes.
##
@ -252,7 +484,7 @@ withCapacity : Nat -> Str
## reallocations but will also waste a lot of memory!
##
## If you plan to use [Str.reserve] on an empty string, it's generally better to use [Str.withCapacity] instead.
reserve : Str, Nat -> Str
reserve : Str, U64 -> Str
## Combines a [List] of strings into a single string, with a separator
## string in between each.
@ -265,8 +497,7 @@ joinWith : List Str, Str -> Str
## Split a string around a separator.
##
## Passing `""` for the separator is not useful;
## it returns the original string wrapped in a [List]. To split a string
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
## it returns the original string wrapped in a [List].
## ```
## expect Str.split "1,2,3" "," == ["1","2","3"]
## expect Str.split "1,2,3" "" == ["1,2,3"]
@ -283,79 +514,7 @@ split : Str, Str -> List Str
## expect Str.repeat "" 10 == ""
## expect Str.repeat "anything" 0 == ""
## ```
repeat : Str, Nat -> Str
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Note that the number of extended grapheme clusters can be different from the number
## of visual glyphs rendered! Consider the following examples:
## ```
## expect Str.countGraphemes "Roc" == 3
## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4
## expect Str.countGraphemes "🕊" == 1
## ```
## Note that "👩‍👩‍👦‍👦" takes up 4 graphemes (even though visually it appears as a single
## glyph) because under the hood it's represented using an emoji modifier sequence.
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
## using a single Unicode code point.
countGraphemes : Str -> Nat
## Split a string into its constituent graphemes.
##
## This function breaks a string into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103),
## returning them as a list of strings. This is useful for working with text that
## contains complex characters, such as emojis.
##
## Examples:
## ```
## expect Str.graphemes "Roc" == ["R", "o", "c"]
## expect Str.graphemes "नमस्ते" == ["न", "म", "स्", "ते"]
## expect Str.graphemes "👩‍👩‍👦‍👦" == ["👩‍", "👩‍", "👦‍", "👦"]
## ```
##
## Note that the "👩‍👩‍👦‍👦" example consists of 4 grapheme clusters, although it visually
## appears as a single glyph. This is because it uses an emoji modifier sequence.
graphemes : Str -> List Str
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], returns [Bool.true]. Otherwise returns [Bool.false].
##
## If the given string is empty, or if the given [U32] is not a valid
## code point, returns [Bool.false].
## ```
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
## expect !Str.startsWithScalar "" 40527
## ```
##
## ## Performance Details
##
## This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
## This will not work for graphemes which take up multiple code points, however;
## `Str.startsWithScalar '👩‍👩‍👦‍👦'` would be a compiler error because 👩‍👩‍👦‍👦 takes up
## multiple code points and cannot be represented as a single [U32].
## You'd need to use `Str.startsWithScalar "🕊"` instead.
startsWithScalar : Str, U32 -> Bool
## Returns a [List] of the [Unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
## in the given string.
##
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
## ```
## expect Str.toScalars "Roc" == [82, 111, 99]
## expect Str.toScalars "鹏" == [40527]
## expect Str.toScalars "சி" == [2970, 3007]
## expect Str.toScalars "🐦" == [128038]
## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
## expect Str.toScalars "" == []
## ```
toScalars : Str -> List U32
repeat : Str, U64 -> Str
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
@ -379,9 +538,9 @@ toUtf8 : Str -> List U8
## expect Str.fromUtf8 [] == Ok ""
## expect Str.fromUtf8 [255] |> Result.isErr
## ```
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
result = fromUtf8Lowlevel bytes
if result.cIsOk then
Ok result.bString
@ -394,37 +553,14 @@ expect (Str.fromUtf8 [240, 159, 144, 166]) == Ok "🐦"
expect (Str.fromUtf8 []) == Ok ""
expect (Str.fromUtf8 [255]) |> Result.isErr
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str]
## ```
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
## ```
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]
fromUtf8Range = \bytes, config ->
if Num.addSaturated config.start config.count <= List.len bytes then
result = fromUtf8RangeLowlevel bytes config.start config.count
if result.cIsOk then
Ok result.bString
else
Err (BadUtf8 result.dProblemCode result.aByteIndex)
else
Err OutOfBounds
expect (Str.fromUtf8Range [72, 105, 80, 103] { start: 0, count: 2 }) == Ok "Hi"
expect (Str.fromUtf8Range [233, 185, 143, 224, 174, 154, 224, 174, 191] { start: 3, count: 3 }) == Ok "ச"
expect (Str.fromUtf8Range [240, 159, 144, 166] { start: 0, count: 4 }) == Ok "🐦"
expect (Str.fromUtf8Range [] { start: 0, count: 0 }) == Ok ""
expect (Str.fromUtf8Range [72, 105, 80, 103] { start: 2, count: 3 }) |> Result.isErr
FromUtf8Result : {
aByteIndex : Nat,
aByteIndex : U64,
bString : Str,
cIsOk : Bool,
dProblemCode : Utf8ByteProblem,
}
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
fromUtf8Lowlevel : List U8 -> FromUtf8Result
## Check if the given [Str] starts with a value.
## ```
@ -489,25 +625,6 @@ toF64 = \string -> strToNumHelp string
toF32 : Str -> Result F32 [InvalidNumStr]
toF32 = \string -> strToNumHelp string
## Convert a [Str] to a [Nat]. If the given number doesn't fit in [Nat], it will be [truncated](https://www.ualberta.ca/computing-science/media-library/teaching-resources/java/truncation-rounding.html).
## [Nat] has a different maximum number depending on the system you're building
## for, so this may give a different answer on different systems.
##
## For example, on a 32-bit system, `Num.maxNat` will return the same answer as
## `Num.maxU32`. This means that calling `Str.toNat "9_000_000_000"` on a 32-bit
## system will return `Num.maxU32` instead of 9 billion, because 9 billion is
## larger than `Num.maxU32` and will not fit in a [Nat] on a 32-bit system.
##
## Calling `Str.toNat "9_000_000_000"` on a 64-bit system will return
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
## hold up to `Num.maxU64`, and 9_000_000_000 is smaller than `Num.maxU64`.
## ```
## expect Str.toNat "9_000_000_000" == Ok 9000000000
## expect Str.toNat "not a number" == Err InvalidNumStr
## ```
toNat : Str -> Result Nat [InvalidNumStr]
toNat = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
## 340 undecillion). It can be specified with a u128 suffix.
@ -627,16 +744,16 @@ toI8 : Str -> Result I8 [InvalidNumStr]
toI8 = \string -> strToNumHelp string
## Get the byte at the given index, without performing a bounds check.
getUnsafe : Str, Nat -> U8
getUnsafe : Str, U64 -> U8
## Gives the number of bytes in a [Str] value.
## ```
## expect Str.countUtf8Bytes "Hello World" == 11
## ```
countUtf8Bytes : Str -> Nat
countUtf8Bytes : Str -> U64
## string slice that does not do bounds checking or utf-8 verification
substringUnsafe : Str, Nat, Nat -> Str
substringUnsafe : Str, U64, U64 -> Str
## Returns the given [Str] with each occurrence of a substring replaced.
## If the substring is not found, returns the original string.
@ -683,7 +800,7 @@ replaceFirst : Str, Str, Str -> Str
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
"\(before)\(flower)\(after)"
"$(before)$(flower)$(after)"
Err NotFound -> haystack
@ -701,7 +818,7 @@ replaceLast : Str, Str, Str -> Str
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
Ok { before, after } ->
"\(before)\(flower)\(after)"
"$(before)$(flower)$(after)"
Err NotFound -> haystack
@ -744,7 +861,7 @@ expect splitFirst "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
# splitFirst when needle is haystack
expect splitFirst "foo" "foo" == Ok { before: "", after: "" }
firstMatch : Str, Str -> [Some Nat, None]
firstMatch : Str, Str -> [Some U64, None]
firstMatch = \haystack, needle ->
haystackLength = Str.countUtf8Bytes haystack
needleLength = Str.countUtf8Bytes needle
@ -752,7 +869,7 @@ firstMatch = \haystack, needle ->
firstMatchHelp haystack needle 0 lastPossible
firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None]
firstMatchHelp : Str, Str, U64, U64 -> [Some U64, None]
firstMatchHelp = \haystack, needle, index, lastPossible ->
if index <= lastPossible then
if matchesAt haystack index needle then
@ -795,7 +912,7 @@ expect Str.splitLast "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
# splitLast when needle is haystack
expect Str.splitLast "foo" "foo" == Ok { before: "", after: "" }
lastMatch : Str, Str -> [Some Nat, None]
lastMatch : Str, Str -> [Some U64, None]
lastMatch = \haystack, needle ->
haystackLength = Str.countUtf8Bytes haystack
needleLength = Str.countUtf8Bytes needle
@ -803,7 +920,7 @@ lastMatch = \haystack, needle ->
lastMatchHelp haystack needle lastPossibleIndex
lastMatchHelp : Str, Str, Nat -> [Some Nat, None]
lastMatchHelp : Str, Str, U64 -> [Some U64, None]
lastMatchHelp = \haystack, needle, index ->
if matchesAt haystack index needle then
Some index
@ -817,7 +934,7 @@ lastMatchHelp = \haystack, needle, index ->
min = \x, y -> if x < y then x else y
matchesAt : Str, Nat, Str -> Bool
matchesAt : Str, U64, Str -> Bool
matchesAt = \haystack, haystackIndex, needle ->
haystackLength = Str.countUtf8Bytes haystack
needleLength = Str.countUtf8Bytes needle
@ -858,15 +975,15 @@ matchesAtHelp = \state ->
## state for each byte. The index for that byte in the string is provided
## to the update function.
## ```
## f : List U8, U8, Nat -> List U8
## f : List U8, U8, U64 -> List U8
## f = \state, byte, _ -> List.append state byte
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
## ```
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
walkUtf8WithIndex : Str, state, (state, U8, U64 -> state) -> state
walkUtf8WithIndex = \string, state, step ->
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
walkUtf8WithIndexHelp : Str, state, (state, U8, Nat -> state), Nat, Nat -> state
walkUtf8WithIndexHelp : Str, state, (state, U8, U64 -> state), U64, U64 -> state
walkUtf8WithIndexHelp = \string, state, step, index, length ->
if index < length then
byte = Str.getUnsafe string index
@ -890,7 +1007,7 @@ walkUtf8 : Str, state, (state, U8 -> state) -> state
walkUtf8 = \str, initial, step ->
walkUtf8Help str initial step 0 (Str.countUtf8Bytes str)
walkUtf8Help : Str, state, (state, U8 -> state), Nat, Nat -> state
walkUtf8Help : Str, state, (state, U8 -> state), U64, U64 -> state
walkUtf8Help = \str, state, step, index, length ->
if index < length then
byte = Str.getUnsafe str index
@ -907,80 +1024,6 @@ expect (walkUtf8 "鹏" [] List.append) == [233, 185, 143]
## Note: This will also convert seamless slices to regular lists.
releaseExcessCapacity : Str -> Str
## is UB when the scalar is invalid
appendScalarUnsafe : Str, U32 -> Str
## Append a [U32] scalar to the given string. If the given scalar is not a valid
## unicode value, it returns [Err InvalidScalar].
## ```
## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
## ```
appendScalar : Str, U32 -> Result Str [InvalidScalar]
appendScalar = \string, scalar ->
if isValidScalar scalar then
Ok (appendScalarUnsafe string scalar)
else
Err InvalidScalar
isValidScalar : U32 -> Bool
isValidScalar = \scalar ->
scalar <= 0xD7FF || (scalar >= 0xE000 && scalar <= 0x10FFFF)
getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
## ```
## f : List U32, U32 -> List U32
## f = \state, scalar -> List.append state scalar
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
## ```
walkScalars : Str, state, (state, U32 -> state) -> state
walkScalars = \string, init, step ->
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
walkScalarsHelp : Str, state, (state, U32 -> state), Nat, Nat -> state
walkScalarsHelp = \string, state, step, index, length ->
if index < length then
{ scalar, bytesParsed } = getScalarUnsafe string index
newState = step state scalar
walkScalarsHelp string newState step (Num.addWrap index bytesParsed) length
else
state
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
## ```
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
## f = \state, scalar ->
## check = 66
## if scalar == check then
## Break [check]
## else
## Continue (List.append state scalar)
## expect Str.walkScalarsUntil "ABC" [] f == [66]
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
## ```
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
walkScalarsUntil = \string, init, step ->
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
walkScalarsUntilHelp : Str, state, (state, U32 -> [Break state, Continue state]), Nat, Nat -> state
walkScalarsUntilHelp = \string, state, step, index, length ->
if index < length then
{ scalar, bytesParsed } = getScalarUnsafe string index
when step state scalar is
Continue newState ->
walkScalarsUntilHelp string newState step (Num.addWrap index bytesParsed) length
Break newState ->
newState
else
state
strToNum : Str -> { berrorcode : U8, aresult : Num * }
strToNumHelp : Str -> Result (Num a) [InvalidNumStr]

View File

@ -34,7 +34,6 @@ interface TotallyNotJson
I128,
F32,
F64,
Nat,
Dec,
},
Bool.{ Bool, Eq },
@ -43,7 +42,7 @@ interface TotallyNotJson
## An opaque type with the `EncoderFormatting` and
## `DecoderFormatting` abilities.
Json := { fieldNameMapping : FieldNameMapping }
Json := {}
implements [
EncoderFormatting {
u8: encodeU8,
@ -89,21 +88,11 @@ Json := { fieldNameMapping : FieldNameMapping }
]
## Returns a JSON `Encoder` and `Decoder`
json = @Json { fieldNameMapping: Default }
json = @Json {}
## Returns a JSON `Encoder` and `Decoder` with configuration options
jsonWithOptions = \{ fieldNameMapping ? Default } ->
@Json { fieldNameMapping }
## Mapping between Roc record fields and JSON object names
FieldNameMapping : [
Default, # no transformation
SnakeCase, # snake_case
PascalCase, # PascalCase
KebabCase, # kabab-case
CamelCase, # camelCase
Custom (Str -> Str), # provide a custom formatting
]
jsonWithOptions = \{} ->
@Json {}
# TODO encode as JSON numbers as base 10 decimal digits
# e.g. the REPL `Num.toStr 12e42f64` gives
@ -171,14 +160,6 @@ encodeBool = \b ->
else
List.concat bytes (Str.toUtf8 "false")
# Test encode boolean
expect
input = [Bool.true, Bool.false]
actual = Encode.toBytes input json
expected = Str.toUtf8 "[true,false]"
actual == expected
encodeString = \str ->
Encode.custom \bytes, @Json {} ->
List.concat bytes (encodeStrBytes str)
@ -248,38 +229,10 @@ escapedByteToJson = \b ->
0x09 -> [0x5c, 'r'] # U+0009 Tab
_ -> [b]
expect escapedByteToJson '\n' == ['\\', 'n']
expect escapedByteToJson '\\' == ['\\', '\\']
expect escapedByteToJson '"' == ['\\', '"']
# Test encode small string
expect
input = "G'day"
actual = Encode.toBytes input json
expected = Str.toUtf8 "\"G'day\""
actual == expected
# Test encode large string
expect
input = "the quick brown fox jumps over the lazy dog"
actual = Encode.toBytes input json
expected = Str.toUtf8 "\"the quick brown fox jumps over the lazy dog\""
actual == expected
# Test encode with escapes e.g. "\r" encodes to "\\r"
expect
input = "the quick brown fox jumps over the lazy doga\r\nbc\\\"xz"
actual = Encode.toBytes input json
expected = Str.toUtf8 "\"the quick brown fox jumps over the lazy doga\\r\\nbc\\\\\\\"xz\""
actual == expected
encodeList = \lst, encodeElem ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
writeList = \{ buffer, elemsLeft }, elem ->
bufferWithElem = appendWith buffer (encodeElem elem) (@Json { fieldNameMapping })
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
bufferWithSuffix =
if elemsLeft > 1 then
List.append bufferWithElem (Num.toU8 ',')
@ -293,27 +246,16 @@ encodeList = \lst, encodeElem ->
List.append withList (Num.toU8 ']')
# Test encode list of floats
expect
input : List F64
input = [-1, 0.00001, 1e12, 2.0e-2, 0.0003, 43]
actual = Encode.toBytes input json
expected = Str.toUtf8 "[-1,0.00001,1000000000000,0.02,0.0003,43]"
actual == expected
encodeRecord = \fields ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
writeRecord = \{ buffer, fieldsLeft }, { key, value } ->
fieldName = toObjectNameUsingMap key fieldNameMapping
fieldName = key
bufferWithKeyValue =
List.append buffer (Num.toU8 '"')
|> List.concat (Str.toUtf8 fieldName)
|> List.append (Num.toU8 '"')
|> List.append (Num.toU8 ':') # Note we need to encode using the json config here
|> appendWith value (@Json { fieldNameMapping })
|> appendWith value (@Json {})
bufferWithSuffix =
if fieldsLeft > 1 then
@ -328,52 +270,11 @@ encodeRecord = \fields ->
List.append bytesWithRecord (Num.toU8 '}')
# Test encode for a record with two strings ignoring whitespace
expect
input = { fruitCount: 2, ownerName: "Farmer Joe" }
encoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"FruitCount\":2,\"OwnerName\":\"Farmer Joe\"}"
actual == expected
# Test encode of record with an array of strings and a boolean field
expect
input = { fruitFlavours: ["Apples", "Bananas", "Pears"], isFresh: Bool.true }
encoder = jsonWithOptions { fieldNameMapping: KebabCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"fruit-flavours\":[\"Apples\",\"Bananas\",\"Pears\"],\"is-fresh\":true}"
actual == expected
# Test encode of record with a string and number field
expect
input = { firstSegment: "ab", secondSegment: 10u8 }
encoder = jsonWithOptions { fieldNameMapping: SnakeCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"first_segment\":\"ab\",\"second_segment\":10}"
actual == expected
# Test encode of record of a record
expect
input = { outer: { inner: "a" }, other: { one: "b", two: 10u8 } }
encoder = jsonWithOptions { fieldNameMapping: Custom toYellingCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"OTHER\":{\"ONE\":\"b\",\"TWO\":10},\"OUTER\":{\"INNER\":\"a\"}}"
actual == expected
toYellingCase = \str ->
Str.graphemes str
|> List.map toUppercase
|> Str.joinWith ""
encodeTuple = \elems ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
bufferWithElem =
appendWith buffer elemEncoder (@Json { fieldNameMapping })
appendWith buffer elemEncoder (@Json {})
bufferWithSuffix =
if elemsLeft > 1 then
@ -387,20 +288,11 @@ encodeTuple = \elems ->
{ buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
List.append bytesWithRecord (Num.toU8 ']')
# Test encode of tuple
expect
input = ("The Answer is", 42)
actual = Encode.toBytes input json
expected = Str.toUtf8 "[\"The Answer is\",42]"
actual == expected
encodeTag = \name, payload ->
Encode.custom \bytes, @Json { fieldNameMapping } ->
Encode.custom \bytes, @Json {} ->
# Idea: encode `A v1 v2` as `{"A": [v1, v2]}`
writePayload = \{ buffer, itemsLeft }, encoder ->
bufferWithValue = appendWith buffer encoder (@Json { fieldNameMapping })
bufferWithValue = appendWith buffer encoder (@Json {})
bufferWithSuffix =
if itemsLeft > 1 then
List.append bufferWithValue (Num.toU8 ',')
@ -422,15 +314,6 @@ encodeTag = \name, payload ->
List.append bytesWithPayload (Num.toU8 ']')
|> List.append (Num.toU8 '}')
# Test encode of tag
expect
input = TheAnswer "is" 42
encoder = jsonWithOptions { fieldNameMapping: KebabCase }
actual = Encode.toBytes input encoder
expected = Str.toUtf8 "{\"TheAnswer\":[\"is\",42]}"
actual == expected
decodeU8 = Decode.custom \bytes, @Json {} ->
{ taken, rest } = takeJsonNumber bytes
@ -794,21 +677,21 @@ numberHelp = \state, byte ->
NumberState : [
Start,
Minus Nat,
Zero Nat,
Integer Nat,
FractionA Nat,
FractionB Nat,
ExponentA Nat,
ExponentB Nat,
ExponentC Nat,
Minus U64,
Zero U64,
Integer U64,
FractionA U64,
FractionB U64,
ExponentA U64,
ExponentB U64,
ExponentC U64,
Invalid,
Finish Nat,
Finish U64,
]
# TODO confirm if we would like to be able to decode
# "340282366920938463463374607431768211455" which is MAX U128 and 39 bytes
maxBytes : Nat
maxBytes : U64
maxBytes = 21 # Max bytes in a double precision float
isDigit0to9 : U8 -> Bool
@ -1010,13 +893,13 @@ stringHelp = \state, byte ->
StringState : [
Start,
Chars Nat,
Escaped Nat,
UnicodeA Nat,
UnicodeB Nat,
UnicodeC Nat,
UnicodeD Nat,
Finish Nat,
Chars U64,
Escaped U64,
UnicodeA U64,
UnicodeB U64,
UnicodeC U64,
UnicodeD U64,
Finish U64,
InvalidNumber,
]
@ -1292,14 +1175,14 @@ expect
actual == expected
ArrayOpeningState : [
BeforeOpeningBracket Nat,
AfterOpeningBracket Nat,
BeforeOpeningBracket U64,
AfterOpeningBracket U64,
]
ArrayClosingState : [
BeforeNextElemOrClosingBracket Nat,
BeforeNextElement Nat,
AfterClosingBracket Nat,
BeforeNextElemOrClosingBracket U64,
BeforeNextElement U64,
AfterClosingBracket U64,
]
# Test decoding an empty array
@ -1334,7 +1217,7 @@ expect
# JSON OBJECTS -----------------------------------------------------------------
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json { fieldNameMapping } ->
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json {} ->
# Recursively build up record from object field:value pairs
decodeFields = \recordState, bytesBeforeField ->
@ -1361,8 +1244,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
# Decode the json value
{ val: updatedRecord, rest: bytesAfterValue } <-
(
fieldName =
fromObjectNameUsingMap objectName fieldNameMapping
fieldName = objectName
# Retrieve value decoder for the current field
when stepField recordState fieldName is
@ -1375,7 +1257,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
Keep valueDecoder ->
# Decode the value using the decoder from the recordState
# Note we need to pass json config options recursively here
Decode.decodeWith valueBytes valueDecoder (@Json { fieldNameMapping })
Decode.decodeWith valueBytes valueDecoder (@Json {})
)
|> tryDecode
@ -1434,337 +1316,13 @@ objectHelp = \state, byte ->
_ -> Break InvalidObject
ObjectState : [
BeforeOpeningBrace Nat,
AfterOpeningBrace Nat,
ObjectFieldNameStart Nat,
BeforeColon Nat,
AfterColon Nat,
AfterObjectValue Nat,
AfterComma Nat,
AfterClosingBrace Nat,
BeforeOpeningBrace U64,
AfterOpeningBrace U64,
ObjectFieldNameStart U64,
BeforeColon U64,
AfterColon U64,
AfterObjectValue U64,
AfterComma U64,
AfterClosingBrace U64,
InvalidObject,
]
# Test decode of record with two strings ignoring whitespace
expect
input = Str.toUtf8 " {\n\"FruitCount\"\t:2\n, \"OwnerName\": \"Farmer Joe\" } "
decoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { fruitCount: 2, ownerName: "Farmer Joe" }
actual.result == expected
# Test decode of record with an array of strings and a boolean field
expect
input = Str.toUtf8 "{\"fruit-flavours\": [\"Apples\",\"Bananas\",\"Pears\"], \"is-fresh\": true }"
decoder = jsonWithOptions { fieldNameMapping: KebabCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { fruitFlavours: ["Apples", "Bananas", "Pears"], isFresh: Bool.true }
actual.result == expected
# Test decode of record with a string and number field
expect
input = Str.toUtf8 "{\"first_segment\":\"ab\",\"second_segment\":10}"
decoder = jsonWithOptions { fieldNameMapping: SnakeCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { firstSegment: "ab", secondSegment: 10u8 }
actual.result == expected
# Test decode of record of a record
expect
input = Str.toUtf8 "{\"OUTER\":{\"INNER\":\"a\"},\"OTHER\":{\"ONE\":\"b\",\"TWO\":10}}"
decoder = jsonWithOptions { fieldNameMapping: Custom fromYellingCase }
actual = Decode.fromBytesPartial input decoder
expected = Ok { outer: { inner: "a" }, other: { one: "b", two: 10u8 } }
actual.result == expected
fromYellingCase = \str ->
Str.graphemes str
|> List.map toLowercase
|> Str.joinWith ""
expect fromYellingCase "YELLING" == "yelling"
# Complex example from IETF RFC 8259 (2017)
complexExampleJson = Str.toUtf8 "{\"Image\":{\"Animated\":false,\"Height\":600,\"Ids\":[116,943,234,38793],\"Thumbnail\":{\"Height\":125,\"Url\":\"http:\\/\\/www.example.com\\/image\\/481989943\",\"Width\":100},\"Title\":\"View from 15th Floor\",\"Width\":800}}"
complexExampleRecord = {
image: {
width: 800,
height: 600,
title: "View from 15th Floor",
thumbnail: {
url: "http://www.example.com/image/481989943",
height: 125,
width: 100,
},
animated: Bool.false,
ids: [116, 943, 234, 38793],
},
}
# Test decode of Complex Example
expect
input = complexExampleJson
decoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Decode.fromBytes input decoder
expected = Ok complexExampleRecord
actual == expected
# Test encode of Complex Example
expect
input = complexExampleRecord
encoder = jsonWithOptions { fieldNameMapping: PascalCase }
actual = Encode.toBytes input encoder
expected = complexExampleJson
actual == expected
fromObjectNameUsingMap : Str, FieldNameMapping -> Str
fromObjectNameUsingMap = \objectName, fieldNameMapping ->
when fieldNameMapping is
Default -> objectName
SnakeCase -> fromSnakeCase objectName
PascalCase -> fromPascalCase objectName
KebabCase -> fromKebabCase objectName
CamelCase -> fromCamelCase objectName
Custom transformation -> transformation objectName
toObjectNameUsingMap : Str, FieldNameMapping -> Str
toObjectNameUsingMap = \fieldName, fieldNameMapping ->
when fieldNameMapping is
Default -> fieldName
SnakeCase -> toSnakeCase fieldName
PascalCase -> toPascalCase fieldName
KebabCase -> toKebabCase fieldName
CamelCase -> toCamelCase fieldName
Custom transformation -> transformation fieldName
# Convert a `snake_case` JSON Object name to a Roc Field name
fromSnakeCase = \str ->
snakeToCamel str
# Convert a `PascalCase` JSON Object name to a Roc Field name
fromPascalCase = \str ->
pascalToCamel str
# Convert a `kabab-case` JSON Object name to a Roc Field name
fromKebabCase = \str ->
kebabToCamel str
# Convert a `camelCase` JSON Object name to a Roc Field name
fromCamelCase = \str ->
# Nothing to change as Roc field names are camelCase by default
str
# Convert a `camelCase` Roc Field name to a `snake_case` JSON Object name
toSnakeCase = \str ->
camelToSnake str
# Convert a `camelCase` Roc Field name to a `PascalCase` JSON Object name
toPascalCase = \str ->
camelToPascal str
# Convert a `camelCase` Roc Field name to a `kabab-case` JSON Object name
toKebabCase = \str ->
camelToKebeb str
# Convert a `camelCase` Roc Field name to a `camelCase` JSON Object name
toCamelCase = \str ->
# Nothing to change as Roc field names are camelCase by default
str
snakeToCamel : Str -> Str
snakeToCamel = \str ->
segments = Str.split str "_"
when segments is
[first, ..] ->
segments
|> List.dropFirst 1
|> List.map uppercaseFirst
|> List.prepend first
|> Str.joinWith ""
_ -> str
expect snakeToCamel "snake_case_string" == "snakeCaseString"
pascalToCamel : Str -> Str
pascalToCamel = \str ->
segments = Str.graphemes str
when segments is
[a, ..] ->
first = toLowercase a
rest = List.dropFirst segments 1
Str.joinWith (List.prepend rest first) ""
_ -> str
expect pascalToCamel "PascalCaseString" == "pascalCaseString"
kebabToCamel : Str -> Str
kebabToCamel = \str ->
segments = Str.split str "-"
when segments is
[first, ..] ->
segments
|> List.dropFirst 1
|> List.map uppercaseFirst
|> List.prepend first
|> Str.joinWith ""
_ -> str
expect kebabToCamel "kebab-case-string" == "kebabCaseString"
camelToPascal : Str -> Str
camelToPascal = \str ->
segments = Str.graphemes str
when segments is
[a, ..] ->
first = toUppercase a
rest = List.dropFirst segments 1
Str.joinWith (List.prepend rest first) ""
_ -> str
expect camelToPascal "someCaseString" == "SomeCaseString"
camelToKebeb : Str -> Str
camelToKebeb = \str ->
rest = Str.graphemes str
taken = List.withCapacity (List.len rest)
camelToKebabHelp { taken, rest }
|> .taken
|> Str.joinWith ""
camelToKebabHelp : { taken : List Str, rest : List Str } -> { taken : List Str, rest : List Str }
camelToKebabHelp = \{ taken, rest } ->
when rest is
[] -> { taken, rest }
[a, ..] if isUpperCase a ->
camelToKebabHelp {
taken: List.concat taken ["-", toLowercase a],
rest: List.dropFirst rest 1,
}
[a, ..] ->
camelToKebabHelp {
taken: List.append taken a,
rest: List.dropFirst rest 1,
}
expect camelToKebeb "someCaseString" == "some-case-string"
camelToSnake : Str -> Str
camelToSnake = \str ->
rest = Str.graphemes str
taken = List.withCapacity (List.len rest)
camelToSnakeHelp { taken, rest }
|> .taken
|> Str.joinWith ""
camelToSnakeHelp : { taken : List Str, rest : List Str } -> { taken : List Str, rest : List Str }
camelToSnakeHelp = \{ taken, rest } ->
when rest is
[] -> { taken, rest }
[a, ..] if isUpperCase a ->
camelToSnakeHelp {
taken: List.concat taken ["_", toLowercase a],
rest: List.dropFirst rest 1,
}
[a, ..] ->
camelToSnakeHelp {
taken: List.append taken a,
rest: List.dropFirst rest 1,
}
expect camelToSnake "someCaseString" == "some_case_string"
uppercaseFirst : Str -> Str
uppercaseFirst = \str ->
segments = Str.graphemes str
when segments is
[a, ..] ->
first = toUppercase a
rest = List.dropFirst segments 1
Str.joinWith (List.prepend rest first) ""
_ -> str
toUppercase : Str -> Str
toUppercase = \str ->
when str is
"a" -> "A"
"b" -> "B"
"c" -> "C"
"d" -> "D"
"e" -> "E"
"f" -> "F"
"g" -> "G"
"h" -> "H"
"i" -> "I"
"j" -> "J"
"k" -> "K"
"l" -> "L"
"m" -> "M"
"n" -> "N"
"o" -> "O"
"p" -> "P"
"q" -> "Q"
"r" -> "R"
"s" -> "S"
"t" -> "T"
"u" -> "U"
"v" -> "V"
"w" -> "W"
"x" -> "X"
"y" -> "Y"
"z" -> "Z"
_ -> str
toLowercase : Str -> Str
toLowercase = \str ->
when str is
"A" -> "a"
"B" -> "b"
"C" -> "c"
"D" -> "d"
"E" -> "e"
"F" -> "f"
"G" -> "g"
"H" -> "h"
"I" -> "i"
"J" -> "j"
"K" -> "k"
"L" -> "l"
"M" -> "m"
"N" -> "n"
"O" -> "o"
"P" -> "p"
"Q" -> "q"
"R" -> "r"
"S" -> "s"
"T" -> "t"
"U" -> "u"
"V" -> "v"
"W" -> "w"
"X" -> "x"
"Y" -> "y"
"Z" -> "z"
_ -> str
isUpperCase : Str -> Bool
isUpperCase = \str ->
when str is
"A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" -> Bool.true
_ -> Bool.false

View File

@ -130,10 +130,10 @@ impl IntWidth {
// according to https://reviews.llvm.org/D28990#655487
//
// however, rust does not always think that this is true
// Our alignmets here are correct, but they will not match rust/zig/llvm until they update to llvm version 18.
match target_info.architecture {
Architecture::X86_64 => 16,
Architecture::Aarch64 | Architecture::Aarch32 | Architecture::Wasm32 => 16,
Architecture::X86_32 => 8,
Architecture::X86_64 | Architecture::Aarch64 | Architecture::X86_32 => 16,
Architecture::Aarch32 | Architecture::Wasm32 => 8,
}
}
}
@ -292,6 +292,10 @@ pub const NUM_FLOOR_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_
pub const NUM_FLOOR_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_f64");
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
pub const INT_TO_FLOAT_CAST_F32: IntrinsicName =
int_intrinsic!("roc_builtins.num.num_to_float_cast_f32");
pub const INT_TO_FLOAT_CAST_F64: IntrinsicName =
int_intrinsic!("roc_builtins.num.num_to_float_cast_f64");
pub const NUM_ADD_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_or_panic");
pub const NUM_ADD_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_saturated");
@ -331,23 +335,15 @@ pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
pub const NUM_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64";
pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith";
pub const STR_SPLIT: &str = "roc_builtins.str.str_split";
pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars";
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes";
pub const STR_IS_EMPTY: &str = "roc_builtins.str.is_empty";
pub const STR_CAPACITY: &str = "roc_builtins.str.capacity";
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int");
@ -358,18 +354,15 @@ pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_SUBSTRING_UNSAFE: &str = "roc_builtins.str.substring_unsafe";
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const STR_TRIM_START: &str = "roc_builtins.str.trim_start";
pub const STR_TRIM_END: &str = "roc_builtins.str.trim_end";
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to";
pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity";
pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes";
pub const STR_ALLOCATION_PTR: &str = "roc_builtins.str.allocation_ptr";
pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity";
@ -386,6 +379,7 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_REPLACE: &str = "roc_builtins.list.replace";
pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const LIST_CLONE: &str = "roc_builtins.list.clone";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe";
pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
@ -409,6 +403,7 @@ pub const DEC_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.dec.from_in
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_FROM_U64: &str = "roc_builtins.dec.from_u64";
pub const DEC_LOG: &str = "roc_builtins.dec.log";
pub const DEC_POW: &str = "roc_builtins.dec.pow";
pub const DEC_MUL_OR_PANIC: &str = "roc_builtins.dec.mul_or_panic";
pub const DEC_MUL_SATURATED: &str = "roc_builtins.dec.mul_saturated";
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
@ -421,6 +416,9 @@ pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
pub const DEC_TAN: &str = "roc_builtins.dec.tan";
pub const DEC_TO_I128: &str = "roc_builtins.dec.to_i128";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
pub const DEC_ROUND: IntrinsicName = int_intrinsic!("roc_builtins.dec.round");
pub const DEC_FLOOR: IntrinsicName = int_intrinsic!("roc_builtins.dec.floor");
pub const DEC_CEILING: IntrinsicName = int_intrinsic!("roc_builtins.dec.ceiling");
pub const UTILS_DBG_IMPL: &str = "roc_builtins.utils.dbg_impl";
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
@ -445,6 +443,9 @@ pub const NOTIFY_PARENT_EXPECT: &str = "roc_builtins.utils.notify_parent_expect"
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";
pub const UTILS_WINDOWS_SETJMP: &str = "windows_setjmp";
pub const UTILS_WINDOWS_LONGJMP: &str = "windows_longjmp";
#[derive(Debug, Default)]
pub struct IntToIntrinsicName {
pub options: [IntrinsicName; 10],

View File

@ -33,7 +33,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U32 => Some(lowlevel_1(Symbol::NUM_TO_U32, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_U64 => Some(lowlevel_1(Symbol::NUM_TO_U64, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_U128 => Some(lowlevel_1(Symbol::NUM_TO_U128, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_NAT => Some(lowlevel_1(Symbol::NUM_TO_NAT, LowLevel::NumIntCast, var_store)),
Symbol::NUM_INT_CAST => Some(lowlevel_1(Symbol::NUM_INT_CAST, LowLevel::NumIntCast, var_store)),
@ -50,7 +49,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U32_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U64_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U128_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U128_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_NAT_CHECKED => Some(to_num_checked(Symbol::NUM_TO_NAT_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)),
Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)),
@ -115,33 +113,28 @@ map_symbol_to_lowlevel_and_arity! {
StrJoinWith; STR_JOIN_WITH; 2,
StrIsEmpty; STR_IS_EMPTY; 1,
StrStartsWith; STR_STARTS_WITH; 2,
StrStartsWithScalar; STR_STARTS_WITH_SCALAR; 2,
StrEndsWith; STR_ENDS_WITH; 2,
StrSplit; STR_SPLIT; 2,
StrCountGraphemes; STR_COUNT_GRAPHEMES; 1,
StrCountUtf8Bytes; STR_COUNT_UTF8_BYTES; 1,
StrFromUtf8Range; STR_FROM_UTF8_RANGE_LOWLEVEL; 3,
StrFromUtf8; STR_FROM_UTF8_LOWLEVEL; 1,
StrToUtf8; STR_TO_UTF8; 1,
StrRepeat; STR_REPEAT; 2,
StrTrim; STR_TRIM; 1,
StrTrimStart; STR_TRIM_START; 1,
StrTrimEnd; STR_TRIM_END; 1,
StrToScalars; STR_TO_SCALARS; 1,
StrGetUnsafe; STR_GET_UNSAFE; 2,
StrSubstringUnsafe; STR_SUBSTRING_UNSAFE; 3,
StrReserve; STR_RESERVE; 2,
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
StrToNum; STR_TO_NUM; 1,
StrGetCapacity; STR_CAPACITY; 1,
StrWithCapacity; STR_WITH_CAPACITY; 1,
StrGraphemes; STR_GRAPHEMES; 1,
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
ListLen; LIST_LEN; 1,
ListLenUsize; LIST_LEN_USIZE; 1,
ListLenU64; LIST_LEN_U64; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1,
ListReserve; LIST_RESERVE; 2,
ListIsUnique; LIST_IS_UNIQUE; 1,
ListClone; LIST_CLONE; 1,
ListAppendUnsafe; LIST_APPEND_UNSAFE; 2,
ListPrepend; LIST_PREPEND; 2,
ListGetUnsafe; LIST_GET_UNSAFE; 2,
@ -178,9 +171,9 @@ map_symbol_to_lowlevel_and_arity! {
NumLte; NUM_LTE; 2,
NumCompare; NUM_COMPARE; 2,
NumDivFrac; NUM_DIV_FRAC; 2,
NumDivTruncUnchecked; NUM_DIV_TRUNC; 2,
NumDivTruncUnchecked; NUM_DIV_TRUNC_UNCHECKED; 2,
NumDivCeilUnchecked; NUM_DIV_CEIL; 2,
NumRemUnchecked; NUM_REM; 2,
NumRemUnchecked; NUM_REM_UNCHECKED; 2,
NumIsMultipleOf; NUM_IS_MULTIPLE_OF; 2,
NumAbs; NUM_ABS; 1,
NumNeg; NUM_NEG; 1,
@ -201,10 +194,6 @@ map_symbol_to_lowlevel_and_arity! {
NumAtan; NUM_ATAN; 1,
NumAcos; NUM_ACOS; 1,
NumAsin; NUM_ASIN; 1,
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2,
NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2,
NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2,
NumBitwiseAnd; NUM_BITWISE_AND; 2,
NumBitwiseXor; NUM_BITWISE_XOR; 2,
NumBitwiseOr; NUM_BITWISE_OR; 2,
@ -231,7 +220,7 @@ map_symbol_to_lowlevel_and_arity! {
/// Some builtins cannot be constructed in code gen alone, and need to be defined
/// as separate Roc defs. For example, List.get has this type:
///
/// List.get : List elem, Nat -> Result elem [OutOfBounds]*
/// List.get : List elem, U64 -> Result elem [OutOfBounds]*
///
/// Because this returns an open tag union for its Err type, it's not possible
/// for code gen to return a hardcoded value for OutOfBounds. For example,

View File

@ -1597,7 +1597,8 @@ fn canonicalize_closure_body<'a>(
enum MultiPatternVariables {
OnePattern,
MultiPattern {
bound_occurrences: VecMap<Symbol, (Region, u8)>,
num_patterns: usize,
bound_occurrences: VecMap<Symbol, (Region, usize)>,
},
}
@ -1606,7 +1607,8 @@ impl MultiPatternVariables {
fn new(num_patterns: usize) -> Self {
if num_patterns > 1 {
Self::MultiPattern {
bound_occurrences: VecMap::with_capacity(2),
num_patterns,
bound_occurrences: VecMap::with_capacity(num_patterns),
}
} else {
Self::OnePattern
@ -1617,7 +1619,9 @@ impl MultiPatternVariables {
fn add_pattern(&mut self, pattern: &Loc<Pattern>) {
match self {
MultiPatternVariables::OnePattern => {}
MultiPatternVariables::MultiPattern { bound_occurrences } => {
MultiPatternVariables::MultiPattern {
bound_occurrences, ..
} => {
for (sym, region) in BindingsFromPattern::new(pattern) {
if !bound_occurrences.contains_key(&sym) {
bound_occurrences.insert(sym, (region, 0));
@ -1630,15 +1634,18 @@ impl MultiPatternVariables {
#[inline(always)]
fn get_unbound(self) -> impl Iterator<Item = (Symbol, Region)> {
let bound_occurrences = match self {
MultiPatternVariables::OnePattern => Default::default(),
MultiPatternVariables::MultiPattern { bound_occurrences } => bound_occurrences,
let (bound_occurrences, num_patterns) = match self {
MultiPatternVariables::OnePattern => (Default::default(), 1),
MultiPatternVariables::MultiPattern {
bound_occurrences,
num_patterns,
} => (bound_occurrences, num_patterns),
};
bound_occurrences
.into_iter()
.filter_map(|(sym, (region, occurs))| {
if occurs == 1 {
.filter_map(move |(sym, (region, occurs))| {
if occurs != num_patterns {
Some((sym, region))
} else {
None
@ -2600,10 +2607,38 @@ fn flatten_str_lines<'a>(
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
use StrSegment::*;
let n = segments.len();
let mut iter = segments.into_iter().rev();
let mut loc_expr = match iter.next() {
Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)),
Some(Interpolation(loc_expr)) => loc_expr,
Some(Interpolation(loc_expr)) => {
if n == 1 {
// We concat with the empty string to ensure a type error when loc_expr is not a string
let empty_string = Loc::at(Region::zero(), Expr::Str("".into()));
let fn_expr = Loc::at(
Region::zero(),
Expr::Var(Symbol::STR_CONCAT, var_store.fresh()),
);
let expr = Expr::Call(
Box::new((
var_store.fresh(),
fn_expr,
var_store.fresh(),
var_store.fresh(),
)),
vec![
(var_store.fresh(), empty_string),
(var_store.fresh(), loc_expr),
],
CalledVia::StringInterpolation,
);
Loc::at(Region::zero(), expr)
} else {
loc_expr
}
}
None => {
// No segments? Empty string!

View File

@ -1051,14 +1051,13 @@ fn fix_values_captured_in_closure_expr(
debug_assert!(!captures.is_empty());
captured_symbols.extend(captures);
captured_symbols.swap_remove(i);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
added_captures = true;
} else {
i += 1;
}
// Always jump one, because the current element either does not have captures or
// is now one of the newly-added captures, which we don't need to check.
i += 1;
}
if added_captures {
// Re-sort, since we've added new captures.

View File

@ -198,7 +198,6 @@ fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
"i32", ParsedWidth::Int(IntLitWidth::I32)
"i64", ParsedWidth::Int(IntLitWidth::I64)
"i128", ParsedWidth::Int(IntLitWidth::I128)
"nat", ParsedWidth::Int(IntLitWidth::Nat)
"dec", ParsedWidth::Float(FloatWidth::Dec)
"f32", ParsedWidth::Float(FloatWidth::F32)
"f64", ParsedWidth::Float(FloatWidth::F64)

View File

@ -594,7 +594,7 @@ pub fn desugar_expr<'a>(
// line_info is an option so that we can lazily calculate it.
// That way it there are no `dbg` statements, we never pay the cast of scanning the source an extra time.
if matches!(line_info, None) {
if line_info.is_none() {
*line_info = Some(LineInfo::new(src));
}
let line_col = line_info.as_ref().unwrap().convert_pos(region.start());

View File

@ -13,7 +13,7 @@ use crate::{
},
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
#[derive(Clone)]
pub enum DeclarationInfo<'a> {
Value {
loc_symbol: Loc<Symbol>,
@ -164,7 +164,7 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
}
}
fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
use DeclarationInfo::*;
match decl {

View File

@ -1,17 +0,0 @@
/node_modules
/.pnp
.pnp.js
/coverage
/build
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

Some files were not shown because too many files have changed in this diff Show More