mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-11 05:34:11 +03:00
Merge remote-tracking branch 'origin/main' into more-dollars
This commit is contained in:
commit
6f84e24fa5
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -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
|
||||
|
51
.github/workflows/basic_cli_build_release.yml
vendored
51
.github/workflows/basic_cli_build_release.yml
vendored
@ -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
|
||||
|
||||
|
8
.github/workflows/basic_cli_test_arm64.yml
vendored
8
.github/workflows/basic_cli_test_arm64.yml
vendored
@ -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
|
||||
|
@ -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: |
|
||||
|
4
.github/workflows/benchmarks.yml
vendored
4
.github/workflows/benchmarks.yml
vendored
@ -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
|
||||
|
||||
|
39
.github/workflows/ci_manager.yml
vendored
39
.github/workflows/ci_manager.yml
vendored
@ -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 :)"
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
||||
|
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@ -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
|
||||
|
||||
|
44
.github/workflows/macos_x86_64.yml
vendored
44
.github/workflows/macos_x86_64.yml
vendored
@ -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
|
||||
|
2
.github/workflows/markdown_link_check.yml
vendored
2
.github/workflows/markdown_link_check.yml
vendored
@ -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'
|
||||
|
16
.github/workflows/nightly_linux_arm64.yml
vendored
16
.github/workflows/nightly_linux_arm64.yml
vendored
@ -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
|
||||
|
20
.github/workflows/nightly_linux_x86_64.yml
vendored
20
.github/workflows/nightly_linux_x86_64.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
103
.github/workflows/nightly_macos_x86_64.yml
vendored
103
.github/workflows/nightly_macos_x86_64.yml
vendored
@ -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
|
||||
|
10
.github/workflows/nightly_old_linux_arm64.yml
vendored
10
.github/workflows/nightly_old_linux_arm64.yml
vendored
@ -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
|
||||
|
14
.github/workflows/nightly_old_linux_x86_64.yml
vendored
14
.github/workflows/nightly_old_linux_x86_64.yml
vendored
@ -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
|
||||
|
6
.github/workflows/nix_linux_arm64_cargo.yml
vendored
6
.github/workflows/nix_linux_arm64_cargo.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
6
.github/workflows/nix_linux_x86_64.yml
vendored
6
.github/workflows/nix_linux_x86_64.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
2
.github/workflows/nix_macos_x86_64.yml
vendored
2
.github/workflows/nix_macos_x86_64.yml
vendored
@ -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
|
||||
|
||||
|
2
.github/workflows/spellcheck.yml
vendored
2
.github/workflows/spellcheck.yml
vendored
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
8
.github/workflows/test_nightly_many_os.yml
vendored
8
.github/workflows/test_nightly_many_os.yml
vendored
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
6
.github/workflows/ubuntu_x86_64.yml
vendored
6
.github/workflows/ubuntu_x86_64.yml
vendored
@ -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
|
||||
|
||||
|
6
.github/workflows/windows_release_build.yml
vendored
6
.github/workflows/windows_release_build.yml
vendored
@ -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: |
|
||||
|
19
.github/workflows/windows_tests.yml
vendored
19
.github/workflows/windows_tests.yml
vendored
@ -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
37
.gitignore
vendored
@ -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/
|
||||
|
@ -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
120
Cargo.lock
generated
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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}[34m»\u{1b}[0m ".to_string(),
|
||||
prompt: "\u{1b}[0K\u{1b}[1;36m»\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(());
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 compiler’s 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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
11
crates/cli/tests/fixtures/.gitignore
vendored
11
crates/cli/tests/fixtures/.gitignore
vendored
@ -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
|
@ -1 +0,0 @@
|
||||
Main
|
@ -1 +0,0 @@
|
||||
Main
|
2
crates/cli/tests/fixtures/packages/app.roc
vendored
2
crates/cli/tests/fixtures/packages/app.roc
vendored
@ -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)"
|
||||
|
@ -0,0 +1,7 @@
|
||||
interface UnusedImportButWithALongFileNameForTesting
|
||||
exposes [plainText, emText]
|
||||
imports [Symbol.{ Ident }]
|
||||
|
||||
plainText = \str -> PlainText str
|
||||
|
||||
emText = \str -> EmText str
|
12
crates/cli_testing_examples/.gitignore
vendored
12
crates/cli_testing_examples/.gitignore
vendored
@ -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
|
@ -1,2 +0,0 @@
|
||||
fibonacci
|
||||
quicksort
|
@ -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) ->
|
||||
|
@ -1,12 +0,0 @@
|
||||
cfold
|
||||
closure
|
||||
deriv
|
||||
issue2279
|
||||
nqueens
|
||||
quicksortapp
|
||||
RBTreeCk
|
||||
RBTreeDel
|
||||
RBTreeInsert
|
||||
TestAStar
|
||||
TestBase64
|
||||
*.wasm
|
@ -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
|
||||
|
@ -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 }
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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 ->
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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 ->
|
@ -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
|
@ -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
@ -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,
|
||||
|
@ -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)?;
|
||||
|
@ -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" }
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
1
crates/compiler/builtins/.gitignore
vendored
1
crates/compiler/builtins/.gitignore
vendored
@ -1 +0,0 @@
|
||||
builtins.ll
|
@ -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`.
|
||||
|
5
crates/compiler/builtins/bitcode/.gitignore
vendored
5
crates/compiler/builtins/bitcode/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
zig-out
|
||||
zig-cache
|
||||
src/zig-cache
|
||||
benchmark/zig-cache
|
||||
dec
|
@ -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
@ -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 {
|
||||
|
@ -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 });
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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 ->
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
|
@ -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,
|
||||
|
@ -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!
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
|
@ -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 {
|
||||
|
17
crates/compiler/checkmate/www/.gitignore
vendored
17
crates/compiler/checkmate/www/.gitignore
vendored
@ -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
Loading…
Reference in New Issue
Block a user