Merge branch 'main' of github.com:roc-lang/roc into tutorial_updates

This commit is contained in:
Anton-4 2023-03-13 19:46:05 +01:00
commit fe066e5567
No known key found for this signature in database
GPG key ID: 0971D718C0A9B937
304 changed files with 14284 additions and 20427 deletions

View file

@ -6,14 +6,21 @@ test-gen-llvm-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --f
[target.wasm32-unknown-unknown] [target.wasm32-unknown-unknown]
# Rust compiler flags for minimum-sized .wasm binary in the web REPL # Rust compiler flags for minimum-sized .wasm binary in the web REPL
# opt-level=s Optimizations should focus more on size than speed # opt-level=s Optimizations should focus more on size than speed
# lto=fat Spend extra effort on link-time optimization across crates # lto=fat Spend extra effort on link-time optimization across crates
rustflags = ["-Copt-level=s", "-Clto=fat"] # embed-bitcode=yes Turn back on lto since it is no longer default
rustflags = ["-Copt-level=s", "-Clto=fat", "-Cembed-bitcode=yes"]
[target.'cfg(not(target = "wasm32-unknown-unknown"))'] [target.'cfg(not(target = "wasm32-unknown-unknown"))']
# Sets the avx, avx2, sse2 and sse4.2 target-features correctly based on your CPU. # Sets the avx, avx2, sse2 and sse4.2 target-features correctly based on your CPU.
rustflags = ["-Ctarget-cpu=native"] rustflags = ["-Ctarget-cpu=native"]
# TODO: there is probably a more proper solution to this.
# We are pulling in roc_alloc and friends due to using roc_std.
# They ared defined in roc_glue, but windows linking breaks before we get there.
[target.'cfg(target_os = "windows")']
rustflags = ["-Clink-args=/FORCE:UNRESOLVED"]
[env] [env]
# Gives us the path of the workspace root for use in cargo tests without having # Gives us the path of the workspace root for use in cargo tests without having
# to compute it per-package. # to compute it per-package.

View file

@ -16,8 +16,8 @@ jobs:
- name: create version.txt - name: create version.txt
run: ./ci/write_version.sh run: ./ci/write_version.sh
- name: build release - name: build release with lto
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower. # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: get commit SHA - name: get commit SHA
@ -41,6 +41,10 @@ jobs:
DATE: ${{ env.DATE }} DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }} SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_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: Make nightly release tar archive - name: Make nightly release tar archive
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }} run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}

View file

@ -36,7 +36,11 @@ jobs:
run: ./ci/write_version.sh run: ./ci/write_version.sh
- name: build nightly release - name: build nightly release
run: cargo build --locked --release run: cargo build --locked --profile=release-with-lto
# this makes the roc binary a lot smaller
- name: strip debug info
run: strip ./target/release-with-lto/roc
- name: package release - name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }} run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}

View file

@ -22,7 +22,7 @@ jobs:
# this issue may be caused by using older versions of XCode # this issue may be caused by using older versions of XCode
- name: build release - name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower. # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: get commit SHA - name: get commit SHA
@ -36,6 +36,10 @@ jobs:
DATE: ${{ env.DATE }} DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }} SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV 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 - name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }} run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}

View file

@ -19,13 +19,7 @@ jobs:
with: with:
clean: "true" clean: "true"
- uses: cachix/install-nix-action@v15 - uses: cachix/install-nix-action@v20
# to cache nix packages
- uses: cachix/cachix-action@v10
with:
name: enigmaticsunrise
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: execute cli_run tests only, the full tests take too long but are run nightly - name: execute cli_run tests only, the full tests take too long but are run nightly
run: nix develop -c cargo test --locked --release -p roc_cli run: nix develop -c cargo test --locked --release -p roc_cli

9
.gitignore vendored
View file

@ -72,4 +72,11 @@ roc_linux_x86_64.tar.gz
result result
# tutorial # tutorial
www/src/roc-tutorial www/src/roc-tutorial
# Only keep Cargo.lock dependencies for the main compiler.
# Examples and test only crates should be fine to be unlocked.
# This remove unneccessary lock file versioning.
# 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

1858
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,44 +1,40 @@
[workspace] [workspace]
members = [ members = [
"crates/compiler/*", "crates/compiler/*",
"crates/vendor/*", "crates/vendor/*",
"crates/glue", "crates/glue",
"crates/editor", "crates/editor",
"crates/ast", "crates/ast",
"crates/cli", "crates/cli",
"crates/code_markup", "crates/cli_utils",
"crates/highlight", "crates/code_markup",
"crates/error_macros", "crates/highlight",
"crates/reporting", "crates/error_macros",
"crates/packaging", "crates/reporting",
"crates/repl_cli", "crates/packaging",
"crates/repl_eval", "crates/repl_cli",
"crates/repl_test", "crates/repl_eval",
"crates/repl_wasm", "crates/repl_test",
"crates/repl_expect", "crates/repl_wasm",
"crates/test_utils", "crates/repl_expect",
"crates/valgrind", "crates/roc_std",
"crates/tracing", "crates/test_utils",
"crates/utils", "crates/valgrind",
"crates/docs", "crates/tracing",
"crates/docs_cli", "crates/utils/*",
"crates/linker", "crates/docs",
"crates/wasi-libc-sys", "crates/docs_cli",
"crates/wasm_module", "crates/linker",
"crates/wasm_interp", "crates/wasi-libc-sys",
"crates/wasm_module",
"crates/wasm_interp",
] ]
exclude = [ exclude = [
"ci/benchmarks/bench-runner", "ci/benchmarks/bench-runner",
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those. # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
"crates/cli_testing_examples", "crates/cli_testing_examples",
"examples", "examples",
# Ignore building these normally. They are only imported by tests.
# The tests will still correctly build them.
"crates/cli_utils",
"crates/compiler/test_mono_macros",
"crates/compiler/str",
# `cargo build` would cause roc_std to be built with default features which errors on windows
"crates/roc_std",
] ]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` - # Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more. # see www/build.sh for more.
@ -47,6 +43,13 @@ exclude = [
# workspace, and without `resolver = "2"` here, you can't use `-p` like this. # workspace, and without `resolver = "2"` here, you can't use `-p` like this.
resolver = "2" resolver = "2"
[workspace.package]
authors = ["The Roc Contributors"]
edition = "2021"
license = "UPL-1.0"
repository = "https://github.com/roc-lang/roc"
version = "0.0.1"
[workspace.dependencies] [workspace.dependencies]
# NOTE: roc-lang/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: roc-lang/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #
@ -65,72 +68,120 @@ resolver = "2"
# commit of TheDan64/inkwell, push a new tag which points to the latest commit, # commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken. # This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-15", features = [ "llvm13-0" ] } inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-15", features = ["llvm13-0"] }
arrayvec = "0.7.2" arrayvec = "0.7.2" # update roc_std/Cargo.toml on change
base64-url = "1.4.13"
bincode = "1.3.3" bincode = "1.3.3"
bitflags = "1.3.2"
bitvec = "1.0.1" bitvec = "1.0.1"
bumpalo = { version = "3.11.1", features = ["collections"] } blake3 = "1.3.3"
capstone = "0.11.0" brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] } bumpalo = { version = "3.12.0", features = ["collections"] }
const_format = { version = "0.2.23", features = ["const_generics"] } bytemuck = { version = "1.13.1", features = ["derive"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]} capstone = { version = "0.11.0", default-features = false }
cgmath = "0.18.0"
clap = { version = "3.2.23", default-features = false, features = ["std", "color", "suggestions"] }
colored = "2.0.0"
confy = { git = 'https://github.com/rust-cli/confy', features = ["yaml_conf"], default-features = false }
console_error_panic_hook = "0.1.7"
const_format = { version = "0.2.30", features = ["const_generics"] }
copypasta = "0.8.2"
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"] }
criterion-perf-events = { git = "https://github.com/Anton-4/criterion-perf-events" }
crossbeam = "0.8.2" crossbeam = "0.8.2"
dircpy = "0.3.14"
distance = "0.4.0" distance = "0.4.0"
encode_unicode = "1.0.0" encode_unicode = "1.0.0"
errno = "0.2.8" errno = "0.3.0"
flate2 = "1.0.25"
fnv = "1.0.7" fnv = "1.0.7"
fs_extra = "1.2.0" fs_extra = "1.3.0"
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] } futures = "0.3.26"
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } glyph_brush = "0.7.7"
im = "15.0.0" hashbrown = { version = "0.13.2", features = ["bumpalo"] }
im-rc = "15.0.0" iced-x86 = { version = "1.18.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
indoc = "1.0.7" im = "15.1.0"
insta = "1.20.0" im-rc = "15.1.0"
indexmap = "1.9.2"
indoc = "1.0.9"
insta = "1.28.0"
js-sys = "0.3.61"
lazy_static = "1.4.0" lazy_static = "1.4.0"
libc = "0.2.135" libc = "0.2.139" # update roc_std/Cargo.toml on change
libloading = "0.7.1" libfuzzer-sys = "0.4"
libloading = "0.7.4"
log = "0.4.17"
mach_object = "0.1" mach_object = "0.1"
maplit = "1.0.2" maplit = "1.0.2"
memmap2 = "0.5.7" memmap2 = "0.5.10"
mimalloc = { version = "0.1.26", default-features = false } mimalloc = { version = "0.1.34", default-features = false }
packed_struct = "0.10.0" nonempty = "0.8.1"
page_size = "0.4.2" object = { version = "0.30.3", features = ["read", "write"] }
packed_struct = "0.10.1"
page_size = "0.5.0"
palette = "0.6.1"
parking_lot = "0.12" parking_lot = "0.12"
peg = "0.8.1" peg = "0.8.1"
pretty_assertions = "1.3.0" perfcnt = "0.8.0"
quickcheck = "1.0.3" pest = "2.5.6"
quickcheck_macros = "1.0.0" pest_derive = "2.5.6"
regex = "1.5.5" pretty_assertions = "1.3.0" # update roc_std/Cargo.toml on change
rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"} proc-macro2 = "1.0.51"
rustyline-derive = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"} proptest = "1.1.0"
serde = { version = "1.0.144", features = ["derive"] } pulldown-cmark = { version = "0.9.2", default-features = false }
signal-hook = "0.3.14" quickcheck = "1.0.3" # update roc_std/Cargo.toml on change
snafu = { version = "0.7.1", features = ["backtraces"] } quickcheck_macros = "1.0.0" # update roc_std/Cargo.toml on change
static_assertions = "1.1.0" quote = "1.0.23"
rand = "0.8.5"
regex = "1.7.1"
remove_dir_all = "0.8.1"
reqwest = { version = "0.11.14", default-features = false, features = ["blocking", "rustls-tls"] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available!
rlimit = "0.9.1"
rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change
serde-xml-rs = "0.6.0"
serde_json = "1.0.94" # update roc_std/Cargo.toml on change
serial_test = "1.0.0"
signal-hook = "0.3.15"
snafu = { version = "0.7.4", features = ["backtraces"] }
static_assertions = "1.1.0" # update roc_std/Cargo.toml on change
strip-ansi-escapes = "0.1.1" strip-ansi-escapes = "0.1.1"
strum = { version = "0.24.1", features = ["derive"] } strum = { version = "0.24.1", features = ["derive"] }
target-lexicon = "0.12.3" strum_macros = "0.24.3"
tempfile = "3.2.0" syn = { version = "1.0.109", features = ["full", "extra-traits"] }
unicode-segmentation = "1.10.0" tar = "0.4.38"
target-lexicon = "0.12.6"
tempfile = "=3.2.0"
threadpool = "1.8.1"
tracing = { version = "0.1.37", features = ["release_max_level_off"] }
tracing-appender = "0.2.2"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
unicode-segmentation = "1.10.1"
uuid = { version = "1.3.0", features = ["v4"] }
walkdir = "2.3.2" walkdir = "2.3.2"
wasm-bindgen = "0.2.84"
wasm-bindgen-futures = "0.4.34"
wgpu = "0.12.0"
wgpu_glyph = "0.16.0"
winapi = { version = "0.3.9", features = ["memoryapi"] }
winit = "0.26.1"
wyhash = "0.5.0" wyhash = "0.5.0"
# TODO: Deal with the update of object to 0.27.
# It looks like it breaks linking the generated objects.
# Probably just need to specify an extra field that used to be implicit or something.
object = { version = "0.29.0", features = ["read", "write"] }
# Optimizations based on https://deterministic.space/high-performance-rust.html # Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release] [profile.release]
lto = "thin"
codegen-units = 1 codegen-units = 1
# debug = true # enable when profiling # debug = true # enable when profiling
[profile.bench] [profile.bench]
lto = "thin"
codegen-units = 1 codegen-units = 1
lto = "thin"
[profile.release-with-debug] [profile.release-with-debug]
inherits = "release"
debug = true debug = true
inherits = "release"
[profile.release-with-lto]
lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
inherits = "release"

View file

@ -12,12 +12,24 @@ If you'd like to get involved in contributing to the language, the Zulip chat is
## Sponsors ## Sponsors
We are very grateful to our sponsors [NoRedInk](https://www.noredink.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en). We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en).
[<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>](https://www.noredink.com/) [<img src="https://user-images.githubusercontent.com/1094080/223597445-81755626-a080-4299-a38c-3c92e7548489.png" height="60" alt="Vendr logo"/>](https://www.vendr.com)
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://www.rwx.com/rwx_banner.svg" height="60" alt="rwx logo"/>](https://www.rwx.com) [<img src="https://www.rwx.com/rwx_banner.svg" height="60" alt="rwx logo"/>](https://www.rwx.com)
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://user-images.githubusercontent.com/1094080/183123052-856815b1-8cc9-410a-83b0-589f03613188.svg" height="60" alt="tweede golf logo"/>](https://tweedegolf.nl/en) [<img src="https://user-images.githubusercontent.com/1094080/183123052-856815b1-8cc9-410a-83b0-589f03613188.svg" height="60" alt="tweede golf logo"/>](https://tweedegolf.nl/en)
If you or your employer would like to sponsor Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)!
We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
* [Christopher Dolan](https://github.com/cdolan)
* [Nick Gravgaard](https://github.com/nickgravgaard)
* [Aaron White](https://github.com/aaronwhite)
* [Zeljko Nesic](https://github.com/popara)
* [Shritesh Bhattarai](https://github.com/shritesh)
* [Richard Feldman](https://github.com/rtfeldman)
* [Ayaz Hafiz](https://github.com/ayazhafiz)
Thank you all so much for helping Roc progress!

View file

@ -1,422 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bench-runner"
version = "0.0.1"
dependencies = [
"clap",
"data-encoding",
"is_executable",
"regex",
"ring",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cc"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "data-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "is_executable"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
dependencies = [
"winapi",
]
[[package]]
name = "js-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
[[package]]
name = "web-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "3.1.15", features = ["derive"] } clap = { version = "3.1.15", features = ["derive"] }
regex = "1.5.5" data-encoding = "2.3.2"
is_executable = "1.0.1" is_executable = "1.0.1"
regex = "1.5.5"
ring = "0.16.20" ring = "0.16.20"
data-encoding = "2.3.2"

View file

@ -3,7 +3,7 @@
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail set -euxo pipefail
cp target/release/roc ./roc # to be able to delete "target" later cp target/release-with-lto/roc ./roc # to be able to delete "target" later
# delete unnecessary files and folders # delete unnecessary files and folders
git clean -fdx --exclude roc git clean -fdx --exclude roc

View file

@ -60,7 +60,6 @@ The compiler includes the following sub-crates;
- `roc_serialize` provides helpers for serializing and deserializing to/from bytes. - `roc_serialize` provides helpers for serializing and deserializing to/from bytes.
- `roc_solve` The entry point of Roc's [type inference](https://en.wikipedia.org/wiki/Type_inference) system. Implements type inference and specialization of abilities. - `roc_solve` The entry point of Roc's [type inference](https://en.wikipedia.org/wiki/Type_inference) system. Implements type inference and specialization of abilities.
- `roc_solve_problem` provides types to describe problems that can occur during solving. - `roc_solve_problem` provides types to describe problems that can occur during solving.
- `roc_str` provides `Roc` styled collection [reference counting](https://en.wikipedia.org/wiki/Reference_counting). See [README.md](./compiler/str/README.md) for more information.
- `test_derive` Tests Roc's auto-derivers. - `test_derive` Tests Roc's auto-derivers.
- `test_gen` contains all of Roc's [code generation](https://en.wikipedia.org/wiki/Code_generation_(compiler)) tests. See [README.md](./compiler/test_gen/README.md) for more information. - `test_gen` contains all of Roc's [code generation](https://en.wikipedia.org/wiki/Code_generation_(compiler)) tests. See [README.md](./compiler/test_gen/README.md) for more information.
- `test_mono` Tests Roc's generation of the mono intermediate representation. - `test_mono` Tests Roc's generation of the mono intermediate representation.

View file

@ -1,39 +1,39 @@
[package] [package]
name = "roc_ast" name = "roc_ast"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file." description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_builtins = { path = "../compiler/builtins"} roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" } roc_can = { path = "../compiler/can" }
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_region = { path = "../compiler/region" } roc_error_macros = { path = "../error_macros" }
roc_load = { path = "../compiler/load" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_packaging = { path = "../packaging" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_problem = { path = "../compiler/problem" } roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" } roc_region = { path = "../compiler/region" }
roc_unify = { path = "../compiler/unify"}
roc_solve = { path = "../compiler/solve"}
roc_load = { path = "../compiler/load" }
roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" }
roc_packaging = { path = "../packaging" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../reporting" }
roc_solve = { path = "../compiler/solve" }
roc_target = { path = "../compiler/roc_target" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify" }
ven_graph = { path = "../vendor/pathfinding" } ven_graph = { path = "../vendor/pathfinding" }
arrayvec.workspace = true arrayvec.workspace = true
bumpalo.workspace = true bumpalo.workspace = true
libc.workspace = true
page_size.workspace = true page_size.workspace = true
snafu.workspace = true snafu.workspace = true
libc.workspace = true
[dev-dependencies] [dev-dependencies]
indoc.workspace = true indoc.workspace = true
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["memoryapi"]} winapi.workspace = true

View file

@ -1,87 +1,81 @@
[package] [package]
name = "roc_cli" name = "roc_cli"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "The Roc binary that brings together all functionality in the Roc toolset." description = "The Roc binary that brings together all functionality in the Roc toolset."
default-run = "roc" default-run = "roc"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[[bin]] [[bin]]
bench = false
name = "roc" name = "roc"
path = "src/main.rs" path = "src/main.rs"
test = false test = false
bench = false
[features] [features]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"] default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
i386-cli-run = ["target-x86"] i386-cli-run = ["target-x86"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
editor = ["roc_editor"] editor = ["roc_editor"]
run-wasm32 = ["roc_wasm_interp"] run-wasm32 = ["roc_wasm_interp"]
# Compiling for a different target than the current machine can cause linker errors. # Compiling for a different target than the current machine can cause linker errors.
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"] target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"] target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"] target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
target-all = [ target-all = ["target-aarch64", "target-arm", "target-x86", "target-x86_64", "target-wasm32"]
"target-aarch64",
"target-arm",
"target-x86",
"target-x86_64",
"target-wasm32"
]
sanitizers = ["roc_build/sanitizers"] sanitizers = ["roc_build/sanitizers"]
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_build = { path = "../compiler/build" }
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" } roc_can = { path = "../compiler/can" }
roc_collections = { path = "../compiler/collections" }
roc_docs = { path = "../docs" } roc_docs = { path = "../docs" }
roc_editor = { path = "../editor", optional = true }
roc_error_macros = { path = "../error_macros" }
roc_fmt = { path = "../compiler/fmt" }
roc_gen_llvm = { path = "../compiler/gen_llvm" }
roc_glue = { path = "../glue" } roc_glue = { path = "../glue" }
roc_linker = { path = "../linker" }
roc_load = { path = "../compiler/load" }
roc_module = { path = "../compiler/module" }
roc_mono = { path = "../compiler/mono" }
roc_packaging = { path = "../packaging" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" }
roc_builtins = { path = "../compiler/builtins" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_build = { path = "../compiler/build" }
roc_fmt = { path = "../compiler/fmt" }
roc_target = { path = "../compiler/roc_target" }
roc_packaging = { path = "../packaging" }
roc_reporting = { path = "../reporting" }
roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true } roc_repl_cli = { path = "../repl_cli", optional = true }
roc_reporting = { path = "../reporting" }
roc_target = { path = "../compiler/roc_target" }
roc_tracing = { path = "../tracing" } roc_tracing = { path = "../tracing" }
roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_wasm_interp = { path = "../wasm_interp", optional = true } roc_wasm_interp = { path = "../wasm_interp", optional = true }
ven_pretty = { path = "../vendor/pretty" } ven_pretty = { path = "../vendor/pretty" }
indoc.workspace = true bumpalo.workspace = true
clap.workspace = true clap.workspace = true
const_format.workspace = true const_format.workspace = true
mimalloc.workspace = true
bumpalo.workspace = true
libc.workspace = true
errno.workspace = true errno.workspace = true
indoc.workspace = true
inkwell.workspace = true
libc.workspace = true
libloading.workspace = true
mimalloc.workspace = true
signal-hook.workspace = true
strum.workspace = true
target-lexicon.workspace = true target-lexicon.workspace = true
tempfile.workspace = true tempfile.workspace = true
strum.workspace = true
libloading.workspace = true
signal-hook.workspace = true
inkwell.workspace = true
# for now, uses unix/libc functions that windows does not support # for now, uses unix/libc functions that windows does not support
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
@ -89,14 +83,15 @@ roc_repl_expect = { path = "../repl_expect" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.3.0"
roc_test_utils = { path = "../test_utils" }
roc_utils = { path = "../utils" }
indoc = "1.0.7"
serial_test = "0.9.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" } cli_utils = { path = "../cli_utils" }
parking_lot = "0.12" roc_test_utils = { path = "../test_utils" }
roc_command_utils = { path = "../utils/command" }
criterion.workspace = true
indoc.workspace = true
parking_lot.workspace = true
pretty_assertions.workspace = true
serial_test.workspace = true
[[bench]] [[bench]]
name = "time_bench" name = "time_bench"

View file

@ -1,628 +0,0 @@
use bumpalo::Bump;
use roc_build::{
link::{
legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename,
rebuild_host, LinkType, LinkingStrategy,
},
program::{self, CodeGenBackend, CodeGenOptions},
};
use roc_builtins::bitcode;
use roc_load::{
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, Threading,
};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_reporting::{
cli::Problems,
report::{RenderTarget, DEFAULT_PALETTE},
};
use roc_target::TargetInfo;
use std::{
path::Path,
time::{Duration, Instant},
};
use std::{path::PathBuf, thread::JoinHandle};
use target_lexicon::Triple;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
use std::fmt::Write;
writeln!(
buf,
" {:9.3} ms {}",
duration.as_secs_f64() * 1000.0,
label,
)
.unwrap()
}
pub struct BuiltFile<'a> {
pub binary_path: PathBuf,
pub problems: Problems,
pub total_time: Duration,
pub expect_metadata: ExpectMetadata<'a>,
}
pub enum BuildOrdering {
/// Run up through typechecking first; continue building iff that is successful.
BuildIfChecks,
/// Always build the Roc binary, even if there are type errors.
AlwaysBuild,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BuildFileError<'a> {
LoadingProblem(LoadingProblem<'a>),
ErrorModule {
module: LoadedModule,
total_time: Duration,
},
}
impl<'a> BuildFileError<'a> {
fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self {
match error {
LoadMonomorphizedError::LoadingProblem(problem) => {
BuildFileError::LoadingProblem(problem)
}
LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule {
module,
total_time: compilation_start.elapsed(),
},
}
}
}
pub fn standard_load_config(
target: &Triple,
order: BuildOrdering,
threading: Threading,
) -> LoadConfig {
let target_info = TargetInfo::from(target);
let exec_mode = match order {
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
LoadConfig {
target_info,
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
exec_mode,
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
// Step 1: compile the app and generate the .o file
let loaded =
roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
build_loaded_file(
arena,
target,
app_module_path,
code_gen_options,
emit_timings,
link_type,
linking_strategy,
prebuilt_requested,
wasm_dev_stack_bytes,
loaded,
compilation_start,
)
}
#[allow(clippy::too_many_arguments)]
fn build_loaded_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
wasm_dev_stack_bytes: Option<u32>,
loaded: roc_load::MonomorphizedModule<'a>,
compilation_start: Instant,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let operating_system = roc_target::OperatingSystem::from(target.operating_system);
let platform_main_roc = match &loaded.entry_point {
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
_ => unreachable!(),
};
// the preprocessed host is stored beside the platform's main.roc
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
if let roc_target::OperatingSystem::Wasi = operating_system {
// when compiling a wasm application, we implicitly assume here that the host is in zig
// and has a file called "host.zig"
platform_main_roc.with_file_name("host.zig")
} else {
platform_main_roc.with_file_name(legacy_host_filename(target).unwrap())
}
} else {
platform_main_roc.with_file_name(preprocessed_host_filename(target).unwrap())
};
// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform=true CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let cwd = app_module_path.parent().unwrap();
let mut output_exe_path = cwd.join(&*loaded.output_path);
if let Some(extension) = operating_system.executable_file_ext() {
output_exe_path.set_extension(extension);
}
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_platform_prebuilt {
if !preprocessed_host_path.exists() {
invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
std::process::exit(1);
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
None
} else {
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get prebuilt platforms from there.
let exposed_values = loaded
.exposed_to_host
.values
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect();
let exposed_closure_types = loaded
.exposed_to_host
.closure_types
.iter()
.map(|x| {
format!(
"{}_{}",
x.module_string(&loaded.interns),
x.as_str(&loaded.interns)
)
})
.collect();
let join_handle = spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
platform_main_roc.clone(),
preprocessed_host_path.clone(),
output_exe_path.clone(),
target,
exposed_values,
exposed_closure_types,
);
Some(join_handle)
};
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
use std::fmt::Write;
write!(buf, "{}", module_timing).unwrap();
if it.peek().is_some() {
buf.push('\n');
}
}
// This only needs to be mutable for report_problems. This can't be done
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
let problems = program::report_problems_monomorphized(&mut loaded);
let loaded = loaded;
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
}
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
} else {
Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
}
} else {
None
};
let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
code_gen_options,
&preprocessed_host_path,
wasm_dev_stack_bytes,
);
buf.push('\n');
buf.push_str(" ");
buf.push_str("Code Generation");
buf.push('\n');
report_timing(
buf,
"Generate Assembly from Mono IR",
code_gen_timing.code_gen,
);
let compilation_end = compilation_start.elapsed();
let size = roc_app_bytes.len();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
}
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
}
// Step 2: link the prebuilt platform and compiled app
let link_start = Instant::now();
match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => {
roc_linker::link_preprocessed_host(
target,
&platform_main_roc,
&roc_app_bytes,
&output_exe_path,
);
}
(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, _) => {
let app_o_file = tempfile::Builder::new()
.prefix("roc_app")
.suffix(&format!(".{}", operating_system.object_file_ext()))
.tempfile()
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
let app_o_file = app_o_file.path();
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let builtins_host_tempfile =
bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
// the host has been compiled into a .o or .obj file
inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
}
if matches!(code_gen_options.backend, program::CodeGenBackend::Assembly) {
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
}
let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
// Extend the lifetime of the tempfile so it doesn't get dropped
// (and thus deleted) before the child process is done using it!
let _ = builtins_host_tempfile;
if !exit_status.success() {
todo!(
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
exit_status.code()
);
}
}
}
let linking_time = link_start.elapsed();
if emit_timings {
println!("Finished linking in {} ms\n", linking_time.as_millis());
}
let total_time = compilation_start.elapsed();
Ok(BuiltFile {
binary_path: output_exe_path,
problems,
total_time,
expect_metadata,
})
}
fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
let prefix = match prebuilt_requested {
true => "Because I was run with --prebuilt-platform=true, ",
false => "",
};
eprintln!(
indoc::indoc!(
r#"
{}I was expecting this file to exist:
{}
However, it was not there!
If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
"#
),
prefix,
preprocessed_host_path.to_string_lossy(),
);
}
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread(
opt_level: OptLevel,
linking_strategy: LinkingStrategy,
platform_main_roc: PathBuf,
preprocessed_host_path: PathBuf,
output_exe_path: PathBuf,
target: &Triple,
exported_symbols: Vec<String>,
exported_closure_types: Vec<String>,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
let rebuild_host_start = Instant::now();
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
None,
);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
roc_linker::build_and_preprocess_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
preprocessed_host_path.as_path(),
exported_symbols,
exported_closure_types,
);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
LinkingStrategy::Legacy => {
rebuild_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
None,
);
}
}
rebuild_host_start.elapsed().as_millis()
})
}
#[allow(clippy::too_many_arguments)]
pub fn check_file<'a>(
arena: &'a Bump,
roc_file_path: PathBuf,
emit_timings: bool,
roc_cache_dir: RocCacheDir<'_>,
threading: Threading,
) -> Result<(Problems, Duration), LoadingProblem<'a>> {
let compilation_start = Instant::now();
// only used for generating errors. We don't do code generation, so hardcoding should be fine
// we need monomorphization for when exhaustiveness checking
let target_info = TargetInfo::default_x86_64();
// Step 1: compile the app and generate the .o file
let load_config = LoadConfig {
target_info,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
exec_mode: ExecutionMode::Check,
};
let mut loaded =
roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
let compilation_end = compilation_start.elapsed();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
}
Ok((
program::report_problems_typechecked(&mut loaded),
compilation_end,
))
}
pub fn build_str_test<'a>(
arena: &'a Bump,
app_module_path: &Path,
app_module_source: &'a str,
assume_prebuild: bool,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let triple = target_lexicon::Triple::host();
let code_gen_options = CodeGenOptions {
backend: CodeGenBackend::Llvm,
opt_level: OptLevel::Normal,
emit_debug_info: false,
};
let emit_timings = false;
let link_type = LinkType::Executable;
let linking_strategy = LinkingStrategy::Surgical;
let wasm_dev_stack_bytes = None;
let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
let build_ordering = BuildOrdering::AlwaysBuild;
let threading = Threading::AtMost(2);
let load_config = standard_load_config(&triple, build_ordering, threading);
let compilation_start = std::time::Instant::now();
// Step 1: compile the app and generate the .o file
let loaded = roc_load::load_and_monomorphize_from_str(
arena,
PathBuf::from("valgrind_test.roc"),
app_module_source,
app_module_path.to_path_buf(),
roc_cache_dir,
load_config,
)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
build_loaded_file(
arena,
&triple,
app_module_path.to_path_buf(),
code_gen_options,
emit_timings,
link_type,
linking_strategy,
assume_prebuild,
wasm_dev_stack_bytes,
loaded,
compilation_start,
)
}

View file

@ -3,11 +3,12 @@
#[macro_use] #[macro_use]
extern crate const_format; extern crate const_format;
use build::BuiltFile;
use bumpalo::Bump; use bumpalo::Bump;
use clap::{Arg, ArgMatches, Command, ValueSource}; use clap::{Arg, ArgMatches, Command, ValueSource};
use roc_build::link::{LinkType, LinkingStrategy}; use roc_build::link::{LinkType, LinkingStrategy};
use roc_build::program::{CodeGenBackend, CodeGenOptions}; use roc_build::program::{
standard_load_config, BuildFileError, BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions,
};
use roc_error_macros::{internal_error, user_error}; use roc_error_macros::{internal_error, user_error};
use roc_load::{ExpectMetadata, LoadingProblem, Threading}; use roc_load::{ExpectMetadata, LoadingProblem, Threading};
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
@ -29,12 +30,9 @@ use target_lexicon::{
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
use tempfile::TempDir; use tempfile::TempDir;
pub mod build;
mod format; mod format;
pub use format::format; pub use format::format;
use crate::build::{standard_load_config, BuildFileError, BuildOrdering};
const DEFAULT_ROC_FILENAME: &str = "main.roc"; const DEFAULT_ROC_FILENAME: &str = "main.roc";
pub const CMD_BUILD: &str = "build"; pub const CMD_BUILD: &str = "build";
@ -332,6 +330,7 @@ pub fn build_app<'a>() -> Command<'a> {
Arg::new(DIRECTORY_OR_FILES) Arg::new(DIRECTORY_OR_FILES)
.multiple_values(true) .multiple_values(true)
.required(false) .required(false)
.allow_invalid_utf8(true)
.help("(optional) The directory or files to open on launch"), .help("(optional) The directory or files to open on launch"),
), ),
) )
@ -520,7 +519,7 @@ pub fn build(
roc_cache_dir: RocCacheDir<'_>, roc_cache_dir: RocCacheDir<'_>,
link_type: LinkType, link_type: LinkType,
) -> io::Result<i32> { ) -> io::Result<i32> {
use build::build_file; use roc_build::program::build_file;
use BuildConfig::*; use BuildConfig::*;
let filename = matches.value_of_os(ROC_FILE).unwrap(); let filename = matches.value_of_os(ROC_FILE).unwrap();

View file

@ -1,6 +1,6 @@
//! The `roc` binary that brings together all functionality in the Roc toolset. //! The `roc` binary that brings together all functionality in the Roc toolset.
use roc_build::link::LinkType; use roc_build::link::LinkType;
use roc_cli::build::check_file; use roc_build::program::check_file;
use roc_cli::{ use roc_cli::{
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV, build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV,
CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST,

View file

@ -533,6 +533,7 @@ mod cli_run {
} }
#[test] #[test]
#[serial(zig_platform)]
#[cfg_attr(windows, ignore)] #[cfg_attr(windows, ignore)]
fn platform_switching_zig() { fn platform_switching_zig() {
test_roc_app_slim( test_roc_app_slim(
@ -676,6 +677,7 @@ mod cli_run {
} }
#[cfg_attr(windows, ignore)] // flaky error; issue #5024 #[cfg_attr(windows, ignore)] // flaky error; issue #5024
#[serial(breakout)]
#[test] #[test]
fn breakout() { fn breakout() {
test_roc_app_slim( test_roc_app_slim(
@ -688,6 +690,7 @@ mod cli_run {
} }
#[test] #[test]
#[serial(breakout)]
fn breakout_hello_gui() { fn breakout_hello_gui() {
test_roc_app_slim( test_roc_app_slim(
"examples/gui/breakout", "examples/gui/breakout",
@ -862,6 +865,7 @@ mod cli_run {
#[test] #[test]
#[serial(parser_package)] #[serial(parser_package)]
#[serial(zig_platform)]
#[cfg_attr(windows, ignore)] #[cfg_attr(windows, ignore)]
fn parse_movies_csv() { fn parse_movies_csv() {
test_roc_app_slim( test_roc_app_slim(

View file

@ -10,13 +10,22 @@ mod editor_launch_test {
use cli_utils::helpers::build_roc_bin_cached; use cli_utils::helpers::build_roc_bin_cached;
use roc_cli::CMD_EDIT; use roc_cli::CMD_EDIT;
use roc_utils::root_dir; use roc_command_utils::root_dir;
use std::io::Read; use std::io::Read;
// ignored because we don't want to bring up the editor window during regular tests, only on specific CI machines #[ignore = "we don't want to bring up the editor window during regular tests, only on specific CI machines"]
#[ignore]
#[test] #[test]
fn launch() { fn launch_test() {
launch(None);
// with a file arg
launch(Some("roc-projects/new-roc-project-1/main.roc"));
// with a folder arg
launch(Some("roc-projects/new-roc-project-1"));
}
fn launch(arg_path_str_opt: Option<&str>) {
let root_dir = root_dir(); let root_dir = root_dir();
// The editor expects to be run from the root of the repo, so it can find the cli-platform to init a new project folder. // The editor expects to be run from the root of the repo, so it can find the cli-platform to init a new project folder.
@ -25,8 +34,14 @@ mod editor_launch_test {
let roc_binary_path = build_roc_bin_cached(); let roc_binary_path = build_roc_bin_cached();
let mut cmd_args = vec![CMD_EDIT];
if let Some(arg_path_str) = arg_path_str_opt {
cmd_args.push(arg_path_str)
}
let mut roc_process = Command::new(roc_binary_path) let mut roc_process = Command::new(roc_binary_path)
.arg(CMD_EDIT) .args(cmd_args)
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.expect("Failed to start editor from cli."); .expect("Failed to start editor from cli.");

View file

@ -1,27 +1,25 @@
[package] [package]
name = "cli_utils" name = "cli_utils"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "Provides shared code for cli tests and benchmarks." description = "Provides shared code for cli tests and benchmarks."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_reporting = { path = "../reporting" }
roc_load = { path = "../compiler/load" } roc_load = { path = "../compiler/load" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_utils = { path = "../utils" } roc_reporting = { path = "../reporting" }
roc_command_utils = { path = "../utils/command" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo.workspace = true
criterion = { git = "https://github.com/Anton-4/criterion.rs"} criterion.workspace = true
serde = { version = "1.0.130", features = ["derive"] } serde-xml-rs.workspace = true
serde-xml-rs = "0.5.1" serde.workspace = true
strip-ansi-escapes = "0.1.1" tempfile.workspace = true
tempfile = "3.2.0"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
rlimit = "0.6.2" rlimit.workspace = true

View file

@ -4,9 +4,7 @@ extern crate roc_load;
extern crate roc_module; extern crate roc_module;
extern crate tempfile; extern crate tempfile;
use roc_utils::cargo; use roc_command_utils::{cargo, pretty_command_string, root_dir};
use roc_utils::pretty_command_string;
use roc_utils::root_dir;
use serde::Deserialize; use serde::Deserialize;
use serde_xml_rs::from_str; use serde_xml_rs::from_str;
use std::env; use std::env;
@ -387,7 +385,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
// Descend into examples/{dir_name} // Descend into examples/{dir_name}
path.push("crates"); path.push("crates");
path.push("cli_testing_examples"); path.push("cli_testing_examples");
path.extend(dir_name.split("/")); // Make slashes cross-target path.extend(dir_name.split('/')); // Make slashes cross-target
path path
} }
@ -396,7 +394,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
pub fn dir_path_from_root(dir_name: &str) -> PathBuf { pub fn dir_path_from_root(dir_name: &str) -> PathBuf {
let mut path = root_dir(); let mut path = root_dir();
path.extend(dir_name.split("/")); // Make slashes cross-target path.extend(dir_name.split('/')); // Make slashes cross-target
path path
} }
@ -419,7 +417,7 @@ pub fn fixtures_dir(dir_name: &str) -> PathBuf {
path.push("cli"); path.push("cli");
path.push("tests"); path.push("tests");
path.push("fixtures"); path.push("fixtures");
path.extend(dir_name.split("/")); // Make slashes cross-target path.extend(dir_name.split('/')); // Make slashes cross-target
path path
} }

View file

@ -1,16 +1,19 @@
[package] [package]
name = "roc_code_markup" name = "roc_code_markup"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Our own markup language for Roc code. Used by the editor and the docs." description = "Our own markup language for Roc code. Used by the editor and the docs."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_ast = { path = "../ast" } roc_ast = { path = "../ast" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_utils = { path = "../utils" } roc_error_utils = { path = "../utils/error" }
serde = { version = "1.0.144", features = ["derive"] }
palette = "0.6.1" palette.workspace = true
snafu = { version = "0.7.1", features = ["backtraces"] }
bumpalo = { version = "3.11.1", features = ["collections"] } bumpalo.workspace = true
serde.workspace = true
snafu.workspace = true

View file

@ -16,7 +16,7 @@ use roc_ast::{
lang::{core::ast::ASTNodeId, env::Env}, lang::{core::ast::ASTNodeId, env::Env},
mem_pool::pool_str::PoolStr, mem_pool::pool_str::PoolStr,
}; };
use roc_utils::{index_of, slice_get}; use roc_error_utils::{index_of, slice_get};
use std::fmt; use std::fmt;
use std::fmt::Write; use std::fmt::Write;

View file

@ -1,4 +1,4 @@
use roc_utils::util_error::UtilError; use roc_error_utils::UtilError;
use snafu::{Backtrace, NoneError, ResultExt, Snafu}; use snafu::{Backtrace, NoneError, ResultExt, Snafu};
use crate::slow_pool::MarkNodeId; use crate::slow_pool::MarkNodeId;

View file

@ -1,15 +1,17 @@
[package] [package]
authors = ["The Roc Contributors"]
edition = "2021"
license = "UPL-1.0"
name = "roc_alias_analysis" name = "roc_alias_analysis"
version = "0.0.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
morphic_lib = {path = "../../vendor/morphic_lib"} morphic_lib = { path = "../../vendor/morphic_lib" }
roc_collections = {path = "../collections"} roc_collections = { path = "../collections" }
roc_error_macros = {path = "../../error_macros"} roc_debug_flags = { path = "../debug_flags" }
roc_module = {path = "../module"} roc_error_macros = { path = "../../error_macros" }
roc_mono = {path = "../mono"} roc_module = { path = "../module" }
roc_debug_flags = {path = "../debug_flags"} roc_mono = { path = "../mono" }
bumpalo.workspace = true bumpalo.workspace = true

View file

@ -188,20 +188,28 @@ where
let func_name = FuncName(&bytes); let func_name = FuncName(&bytes);
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts { if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
for (_, (symbol, top_level, layout)) in aliases { for (_, hels) in aliases {
match layout { match hels.raw_function_layout {
RawFunctionLayout::Function(_, _, _) => { RawFunctionLayout::Function(_, _, _) => {
let it = top_level.arguments.iter().copied(); let it = hels.proc_layout.arguments.iter().copied();
let bytes = let bytes = func_name_bytes_help(
func_name_bytes_help(*symbol, it, Niche::NONE, top_level.result); hels.symbol,
it,
Niche::NONE,
hels.proc_layout.result,
);
host_exposed_functions.push((bytes, top_level.arguments)); host_exposed_functions.push((bytes, hels.proc_layout.arguments));
} }
RawFunctionLayout::ZeroArgumentThunk(_) => { RawFunctionLayout::ZeroArgumentThunk(_) => {
let bytes = let bytes = func_name_bytes_help(
func_name_bytes_help(*symbol, [], Niche::NONE, top_level.result); hels.symbol,
[],
Niche::NONE,
hels.proc_layout.result,
);
host_exposed_functions.push((bytes, top_level.arguments)); host_exposed_functions.push((bytes, hels.proc_layout.arguments));
} }
} }
} }

View file

@ -1,11 +1,12 @@
[package] [package]
name = "arena-pool" name = "arena-pool"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "An implementation of an arena allocator designed for the compiler's workloads." description = "An implementation of an arena allocator designed for the compiler's workloads."
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }

View file

@ -1,52 +1,55 @@
[package] [package]
name = "roc_build" name = "roc_build"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Responsible for coordinating building and linking of a Roc app with its host." description = "Responsible for coordinating building and linking of a Roc app with its host."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_bitcode = { path = "../builtins/bitcode" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_parse = { path = "../parse" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" } roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" } roc_error_macros = { path = "../../error_macros" }
roc_solve_problem = { path = "../solve_problem" } roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_target = { path = "../roc_target" }
roc_gen_llvm = { path = "../gen_llvm" } roc_gen_llvm = { path = "../gen_llvm" }
roc_gen_wasm = { path = "../gen_wasm" } roc_gen_wasm = { path = "../gen_wasm" }
roc_gen_dev = { path = "../gen_dev", default-features = false } roc_linker = { path = "../../linker" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_packaging = { path = "../../packaging" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" } roc_solve_problem = { path = "../solve_problem" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
roc_utils = { path = "../../utils" } roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
roc_command_utils = { path = "../../utils/command" }
wasi_libc_sys = { path = "../../wasi-libc-sys" } wasi_libc_sys = { path = "../../wasi-libc-sys" }
const_format.workspace = true
bumpalo.workspace = true bumpalo.workspace = true
libloading.workspace = true indoc.workspace = true
tempfile.workspace = true
target-lexicon.workspace = true
inkwell.workspace = true inkwell.workspace = true
libloading.workspace = true
target-lexicon.workspace = true
tempfile.workspace = true
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.85" serde_json.workspace = true
[features] [features]
target-arm = []
target-aarch64 = ["roc_gen_dev/target-aarch64"] target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-arm = []
target-wasm32 = []
target-x86 = [] target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"] target-x86_64 = ["roc_gen_dev/target-x86_64"]
target-wasm32 = []
# This is used to enable fuzzing and sanitizers. # This is used to enable fuzzing and sanitizers.
# Example use is describe here: https://github.com/bhansconnect/roc-fuzz # Example use is describe here: https://github.com/bhansconnect/roc-fuzz

View file

@ -1,11 +1,8 @@
use crate::target::{arch_str, target_zig_str}; use crate::target::{arch_str, target_zig_str};
use const_format::concatcp;
use libloading::{Error, Library}; use libloading::{Error, Library};
use roc_builtins::bitcode; use roc_command_utils::{cargo, clang, get_lib_path, rustup, zig};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_utils::{cargo, clang, zig};
use roc_utils::{get_lib_path, rustup};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::DirEntry; use std::fs::DirEntry;
use std::io; use std::io;
@ -15,13 +12,7 @@ use std::{env, fs};
use target_lexicon::{Architecture, OperatingSystem, Triple}; use target_lexicon::{Architecture, OperatingSystem, Triple};
use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH}; use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub use roc_linker::LinkType;
pub enum LinkType {
// These numbers correspond to the --lib and --no-link flags
Executable = 0,
Dylib = 1,
None = 2,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkingStrategy { pub enum LinkingStrategy {
@ -61,134 +52,15 @@ pub fn link(
} }
} }
const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future)
const WASM_TARGET_STR: &str = "wasm32";
const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64";
const LINUX_ARM64_TARGET_STR: &str = "linux-arm64";
const MACOS_ARM64_TARGET_STR: &str = "macos-arm64";
const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64";
const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64";
const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32";
const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64";
pub const fn preprocessed_host_filename(target: &Triple) -> Option<&'static str> {
// Don't try to split this match off in a different function, it will not work with concatcp
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => Some(concatcp!(WASM_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::X86_64,
..
} => Some(concatcp!(
LINUX_X86_64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!(LINUX_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!(MACOS_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::X86_64,
..
} => Some(concatcp!(
MACOS_X86_64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_64,
..
} => Some(concatcp!(
WINDOWS_X86_64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_32(_),
..
} => Some(concatcp!(
WINDOWS_X86_32_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!(
WIDNOWS_ARM64_TARGET_STR,
'.',
PRECOMPILED_HOST_EXT
)),
_ => None,
}
}
pub fn get_target_triple_str(target: &Triple) -> Option<&'static str> {
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => Some(WASM_TARGET_STR),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::X86_64,
..
} => Some(LINUX_X86_64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::Aarch64(_),
..
} => Some(LINUX_ARM64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::Aarch64(_),
..
} => Some(MACOS_ARM64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::X86_64,
..
} => Some(MACOS_X86_64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_64,
..
} => Some(WINDOWS_X86_64_TARGET_STR),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_32(_),
..
} => Some(WINDOWS_X86_32_TARGET_STR),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::Aarch64(_),
..
} => Some(WIDNOWS_ARM64_TARGET_STR),
_ => None,
}
}
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj" /// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
pub fn legacy_host_filename(target: &Triple) -> Option<String> { pub fn legacy_host_filename(target: &Triple) -> Option<String> {
let os = roc_target::OperatingSystem::from(target.operating_system); let os = roc_target::OperatingSystem::from(target.operating_system);
let ext = os.object_file_ext(); let ext = os.object_file_ext();
Some(preprocessed_host_filename(target)?.replace(PRECOMPILED_HOST_EXT, ext)) Some(
roc_linker::preprocessed_host_filename(target)?
.replace(roc_linker::PRECOMPILED_HOST_EXT, ext),
)
} }
fn find_zig_str_path() -> PathBuf { fn find_zig_str_path() -> PathBuf {
@ -682,7 +554,7 @@ pub fn rebuild_host(
let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string()); let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string());
let builtins_host_tempfile = let builtins_host_tempfile =
bitcode::host_tempfile().expect("failed to write host builtins object to tempfile"); roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
if zig_host_src.exists() { if zig_host_src.exists() {
// Compile host.zig // Compile host.zig
@ -1561,8 +1433,8 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
(but seems to be an unofficial API) (but seems to be an unofficial API)
*/ */
let builtins_host_tempfile = let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile()
bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile"); .expect("failed to write host builtins object to tempfile");
let mut zig_cmd = zig(); let mut zig_cmd = zig();
let args = &[ let args = &[

View file

@ -1,13 +1,29 @@
use crate::link::{
legacy_host_filename, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
};
use bumpalo::Bump;
use inkwell::memory_buffer::MemoryBuffer; use inkwell::memory_buffer::MemoryBuffer;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode}; use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule}; use roc_load::{
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, MonomorphizedModule, Threading,
};
use roc_mono::ir::{OptLevel, SingleEntryPoint}; use roc_mono::ir::{OptLevel, SingleEntryPoint};
use roc_reporting::cli::{report_problems, Problems}; use roc_packaging::cache::RocCacheDir;
use roc_reporting::{
cli::{report_problems, Problems},
report::{RenderTarget, DEFAULT_PALETTE},
};
use roc_target::TargetInfo;
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::{
use std::time::{Duration, Instant}; path::{Path, PathBuf},
thread::JoinHandle,
time::{Duration, Instant},
};
use target_lexicon::Triple;
#[cfg(feature = "target-wasm32")] #[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
@ -579,3 +595,627 @@ fn gen_from_mono_module_dev_assembly<'a>(
}, },
) )
} }
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
use std::fmt::Write;
writeln!(
buf,
" {:9.3} ms {}",
duration.as_secs_f64() * 1000.0,
label,
)
.unwrap()
}
pub struct BuiltFile<'a> {
pub binary_path: PathBuf,
pub problems: Problems,
pub total_time: Duration,
pub expect_metadata: ExpectMetadata<'a>,
}
pub enum BuildOrdering {
/// Run up through typechecking first; continue building iff that is successful.
BuildIfChecks,
/// Always build the Roc binary, even if there are type errors.
AlwaysBuild,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BuildFileError<'a> {
LoadingProblem(LoadingProblem<'a>),
ErrorModule {
module: LoadedModule,
total_time: Duration,
},
}
impl<'a> BuildFileError<'a> {
fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self {
match error {
LoadMonomorphizedError::LoadingProblem(problem) => {
BuildFileError::LoadingProblem(problem)
}
LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule {
module,
total_time: compilation_start.elapsed(),
},
}
}
}
pub fn standard_load_config(
target: &Triple,
order: BuildOrdering,
threading: Threading,
) -> LoadConfig {
let target_info = TargetInfo::from(target);
let exec_mode = match order {
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
LoadConfig {
target_info,
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
exec_mode,
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
// Step 1: compile the app and generate the .o file
let loaded =
roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
build_loaded_file(
arena,
target,
app_module_path,
code_gen_options,
emit_timings,
link_type,
linking_strategy,
prebuilt_requested,
wasm_dev_stack_bytes,
loaded,
compilation_start,
)
}
#[allow(clippy::too_many_arguments)]
fn build_loaded_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
wasm_dev_stack_bytes: Option<u32>,
loaded: roc_load::MonomorphizedModule<'a>,
compilation_start: Instant,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let operating_system = roc_target::OperatingSystem::from(target.operating_system);
let platform_main_roc = match &loaded.entry_point {
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
_ => unreachable!(),
};
// the preprocessed host is stored beside the platform's main.roc
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
if let roc_target::OperatingSystem::Wasi = operating_system {
// when compiling a wasm application, we implicitly assume here that the host is in zig
// and has a file called "host.zig"
platform_main_roc.with_file_name("host.zig")
} else {
platform_main_roc.with_file_name(legacy_host_filename(target).unwrap())
}
} else {
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
};
// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform=true CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let cwd = app_module_path.parent().unwrap();
let mut output_exe_path = cwd.join(&*loaded.output_path);
if let Some(extension) = operating_system.executable_file_ext() {
output_exe_path.set_extension(extension);
}
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_platform_prebuilt {
if !preprocessed_host_path.exists() {
invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
std::process::exit(1);
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
None
} else {
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get prebuilt platforms from there.
let exposed_values = loaded
.exposed_to_host
.values
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect();
let exposed_closure_types = loaded
.exposed_to_host
.closure_types
.iter()
.map(|x| {
format!(
"{}_{}",
x.module_string(&loaded.interns),
x.as_str(&loaded.interns)
)
})
.collect();
let join_handle = spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
platform_main_roc.clone(),
preprocessed_host_path.clone(),
output_exe_path.clone(),
target,
exposed_values,
exposed_closure_types,
);
Some(join_handle)
};
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
use std::fmt::Write;
write!(buf, "{}", module_timing).unwrap();
if it.peek().is_some() {
buf.push('\n');
}
}
// This only needs to be mutable for report_problems. This can't be done
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
let problems = report_problems_monomorphized(&mut loaded);
let loaded = loaded;
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
}
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
} else {
Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
}
} else {
None
};
let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
code_gen_options,
&preprocessed_host_path,
wasm_dev_stack_bytes,
);
buf.push('\n');
buf.push_str(" ");
buf.push_str("Code Generation");
buf.push('\n');
report_timing(
buf,
"Generate Assembly from Mono IR",
code_gen_timing.code_gen,
);
let compilation_end = compilation_start.elapsed();
let size = roc_app_bytes.len();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
}
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
}
// Step 2: link the prebuilt platform and compiled app
let link_start = Instant::now();
match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => {
roc_linker::link_preprocessed_host(
target,
&platform_main_roc,
&roc_app_bytes,
&output_exe_path,
);
}
(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, _) => {
let app_o_file = tempfile::Builder::new()
.prefix("roc_app")
.suffix(&format!(".{}", operating_system.object_file_ext()))
.tempfile()
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
let app_o_file = app_o_file.path();
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let builtins_host_tempfile = roc_bitcode::host_tempfile()
.expect("failed to write host builtins object to tempfile");
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
// the host has been compiled into a .o or .obj file
inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
}
if matches!(code_gen_options.backend, CodeGenBackend::Assembly) {
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
}
let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
// Extend the lifetime of the tempfile so it doesn't get dropped
// (and thus deleted) before the child process is done using it!
let _ = builtins_host_tempfile;
if !exit_status.success() {
todo!(
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
exit_status.code()
);
}
}
}
let linking_time = link_start.elapsed();
if emit_timings {
println!("Finished linking in {} ms\n", linking_time.as_millis());
}
let total_time = compilation_start.elapsed();
Ok(BuiltFile {
binary_path: output_exe_path,
problems,
total_time,
expect_metadata,
})
}
fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
let prefix = match prebuilt_requested {
true => "Because I was run with --prebuilt-platform=true, ",
false => "",
};
eprintln!(
indoc::indoc!(
r#"
{}I was expecting this file to exist:
{}
However, it was not there!
If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
"#
),
prefix,
preprocessed_host_path.to_string_lossy(),
);
}
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread(
opt_level: OptLevel,
linking_strategy: LinkingStrategy,
platform_main_roc: PathBuf,
preprocessed_host_path: PathBuf,
output_exe_path: PathBuf,
target: &Triple,
exported_symbols: Vec<String>,
exported_closure_types: Vec<String>,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
let rebuild_host_start = Instant::now();
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
None,
);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
build_and_preprocess_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
preprocessed_host_path.as_path(),
exported_symbols,
exported_closure_types,
);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
LinkingStrategy::Legacy => {
rebuild_host(
opt_level,
&thread_local_target,
platform_main_roc.as_path(),
None,
);
}
}
rebuild_host_start.elapsed().as_millis()
})
}
pub fn build_and_preprocess_host(
opt_level: OptLevel,
target: &Triple,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
exposed_to_host: Vec<String>,
exported_closure_types: Vec<String>,
) {
let (stub_lib, stub_dll_symbols) = roc_linker::generate_stub_lib_from_loaded(
target,
platform_main_roc,
exposed_to_host,
exported_closure_types,
);
rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
roc_linker::preprocess_host(
target,
platform_main_roc,
preprocessed_host_path,
&stub_lib,
&stub_dll_symbols,
)
}
#[allow(clippy::too_many_arguments)]
pub fn check_file<'a>(
arena: &'a Bump,
roc_file_path: PathBuf,
emit_timings: bool,
roc_cache_dir: RocCacheDir<'_>,
threading: Threading,
) -> Result<(Problems, Duration), LoadingProblem<'a>> {
let compilation_start = Instant::now();
// only used for generating errors. We don't do code generation, so hardcoding should be fine
// we need monomorphization for when exhaustiveness checking
let target_info = TargetInfo::default_x86_64();
// Step 1: compile the app and generate the .o file
let load_config = LoadConfig {
target_info,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
exec_mode: ExecutionMode::Check,
};
let mut loaded =
roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
let compilation_end = compilation_start.elapsed();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
}
Ok((report_problems_typechecked(&mut loaded), compilation_end))
}
pub fn build_str_test<'a>(
arena: &'a Bump,
app_module_path: &Path,
app_module_source: &'a str,
assume_prebuild: bool,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let triple = target_lexicon::Triple::host();
let code_gen_options = CodeGenOptions {
backend: CodeGenBackend::Llvm,
opt_level: OptLevel::Normal,
emit_debug_info: false,
};
let emit_timings = false;
let link_type = LinkType::Executable;
let linking_strategy = LinkingStrategy::Surgical;
let wasm_dev_stack_bytes = None;
let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
let build_ordering = BuildOrdering::AlwaysBuild;
let threading = Threading::AtMost(2);
let load_config = standard_load_config(&triple, build_ordering, threading);
let compilation_start = std::time::Instant::now();
// Step 1: compile the app and generate the .o file
let loaded = roc_load::load_and_monomorphize_from_str(
arena,
PathBuf::from("valgrind_test.roc"),
app_module_source,
app_module_path.to_path_buf(),
roc_cache_dir,
load_config,
)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
build_loaded_file(
arena,
&triple,
app_module_path.to_path_buf(),
code_gen_options,
emit_timings,
link_type,
linking_strategy,
assume_prebuild,
wasm_dev_stack_bytes,
loaded,
compilation_start,
)
}

View file

@ -1,23 +1,17 @@
[package] [package]
name = "roc_builtins" name = "roc_builtins"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides the Roc functions and modules that are implicitly imported into every module." description = "Provides the Roc functions and modules that are implicitly imported into every module."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_utils = { path = "../../utils" }
tempfile.workspace = true tempfile.workspace = true
[build-dependencies]
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.3"
roc_utils = { path = "../../utils" }
[target.'cfg(target_os = "macos")'.build-dependencies]
tempfile.workspace = true

View file

@ -0,0 +1,19 @@
[package]
name = "roc_bitcode"
description = "Compiles the zig bitcode to `.o` for builtins"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
tempfile.workspace = true
[build-dependencies]
roc_command_utils = { path = "../../../utils/command" }
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.3"
[target.'cfg(target_os = "macos")'.build-dependencies]
tempfile.workspace = true

View file

@ -0,0 +1,16 @@
[package]
name = "roc_bitcode_bc"
description = "Compiles the zig bitcode to `.bc` for llvm"
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[build-dependencies]
roc_command_utils = { path = "../../../../utils/command" }
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.3"
[target.'cfg(target_os = "macos")'.build-dependencies]
tempfile.workspace = true

View file

@ -0,0 +1,154 @@
use roc_command_utils::{pretty_command_string, zig};
use std::fs;
use std::io;
use std::path::Path;
use std::str;
use std::{env, path::PathBuf, process::Command};
#[cfg(target_os = "macos")]
use tempfile::tempdir;
/// To debug the zig code with debug prints, we need to disable the wasm code gen
const DEBUG: bool = false;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// "." is relative to where "build.rs" is
// dunce can be removed once ziglang/zig#5109 is fixed
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap().join("..");
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache");
#[cfg(target_os = "macos")]
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
// LLVM .bc FILES
generate_bc_file(&bitcode_path, "ir", "builtins-host");
if !DEBUG {
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
}
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
generate_bc_file(
&bitcode_path,
"ir-windows-x86_64",
"builtins-windows-x86_64",
);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;
println!(
"cargo:rerun-if-changed={}",
path.to_str().expect("Failed to convert path to str")
);
})
.unwrap();
#[cfg(target_os = "macos")]
zig_cache_dir
.close()
.expect("Failed to delete temp dir zig_cache_dir.");
}
fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
let mut ll_path = bitcode_path.join(file_name);
ll_path.set_extension("ll");
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
println!("Compiling host ir to: {}", dest_ir_host);
let mut bc_path = bitcode_path.join(file_name);
bc_path.set_extension("bc");
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
let _ = fs::remove_dir_all("./zig-cache");
let mut zig_cmd = zig();
zig_cmd
.current_dir(bitcode_path)
.args(["build", zig_object, "-Drelease=true"]);
run_command(zig_cmd, 0);
}
pub fn get_lib_dir() -> PathBuf {
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
// So we just need to add "/bitcode" to that.
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
// create dir if it does not exist
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
dir
}
fn run_command(mut command: Command, flaky_fail_counter: usize) {
let command_str = pretty_command_string(&command);
let command_str = command_str.to_string_lossy();
let output_result = command.output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
// Flaky test errors that only occur sometimes on MacOS ci server.
if error_str.contains("FileNotFound")
|| error_str.contains("unable to save cached ZIR code")
|| error_str.contains("LLVM failed to emit asm")
{
if flaky_fail_counter == 10 {
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
} else {
run_command(command, flaky_fail_counter + 1)
}
} else if error_str
.contains("lld-link: error: failed to write the output file: Permission denied")
{
panic!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str);
} else {
panic!("{} failed with:\n\n {}\n", command_str, error_str);
}
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
if !path_buf.ends_with("zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else {
let path = path_buf.as_path();
match path.extension() {
Some(osstr) if osstr == "zig" => {
cb(path);
}
_ => {}
}
}
}
}
Ok(())
}

View file

@ -0,0 +1 @@

View file

@ -1,11 +1,9 @@
use roc_utils::zig; use roc_command_utils::{pretty_command_string, zig};
use std::env;
use std::fs; use std::fs;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::str; use std::str;
use std::{env, path::PathBuf, process::Command};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use tempfile::tempdir; use tempfile::tempdir;
@ -18,8 +16,7 @@ fn main() {
// "." is relative to where "build.rs" is // "." is relative to where "build.rs" is
// dunce can be removed once ziglang/zig#5109 is fixed // dunce can be removed once ziglang/zig#5109 is fixed
let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap(); let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
// workaround for github.com/ziglang/zig/issues/9711 // workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -27,22 +24,6 @@ fn main() {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str()); std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
// LLVM .bc FILES
generate_bc_file(&bitcode_path, "ir", "builtins-host");
if !DEBUG {
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
}
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
generate_bc_file(
&bitcode_path,
"ir-windows-x86_64",
"builtins-windows-x86_64",
);
// OBJECT FILES // OBJECT FILES
#[cfg(windows)] #[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj"; const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
@ -107,38 +88,13 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
} }
} }
fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
let mut ll_path = bitcode_path.join(file_name);
ll_path.set_extension("ll");
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
println!("Compiling host ir to: {}", dest_ir_host);
let mut bc_path = bitcode_path.join(file_name);
bc_path.set_extension("bc");
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
let _ = fs::remove_dir_all("./bitcode/zig-cache");
let mut zig_cmd = zig();
zig_cmd
.current_dir(bitcode_path)
.args(["build", zig_object, "-Drelease=true"]);
run_command(zig_cmd, 0);
}
pub fn get_lib_dir() -> PathBuf { pub fn get_lib_dir() -> PathBuf {
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`. // Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
// So we just need to add "/bitcode" to that. // So we just need to add "/bitcode" to that.
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bitcode"); let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
// create dir if it does not exist // create dir if it does not exist
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/bitcode dir."); fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
dir dir
} }
@ -192,7 +148,7 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
} }
fn run_command(mut command: Command, flaky_fail_counter: usize) { fn run_command(mut command: Command, flaky_fail_counter: usize) {
let command_str = roc_utils::pretty_command_string(&command); let command_str = pretty_command_string(&command);
let command_str = command_str.to_string_lossy(); let command_str = command_str.to_string_lossy();
let output_result = command.output(); let output_result = command.output();

View file

@ -0,0 +1,65 @@
use tempfile::NamedTempFile;
const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-wasm32.o"));
// TODO: in the future, we should use Zig's cross-compilation to generate and store these
// for all targets, so that we can do cross-compilation!
#[cfg(unix)]
const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-host.o"));
#[cfg(windows)]
const HOST_WINDOWS: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/builtins-windows-x86_64.obj"));
pub fn host_wasm_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".wasm")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WASM)?;
Ok(tempfile)
}
#[cfg(unix)]
fn host_unix_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".o")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_UNIX)?;
Ok(tempfile)
}
#[cfg(windows)]
fn host_windows_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".obj")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WINDOWS)?;
Ok(tempfile)
}
pub fn host_tempfile() -> std::io::Result<NamedTempFile> {
#[cfg(unix)]
{
host_unix_tempfile()
}
#[cfg(windows)]
{
host_windows_tempfile()
}
#[cfg(not(any(windows, unix)))]
{
unreachable!()
}
}

View file

@ -67,6 +67,8 @@ const NUMBERS = INTEGERS ++ FLOATS;
comptime { comptime {
exportNumFn(num.bytesToU16C, "bytes_to_u16"); exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32"); exportNumFn(num.bytesToU32C, "bytes_to_u32");
exportNumFn(num.bytesToU64C, "bytes_to_u64");
exportNumFn(num.bytesToU128C, "bytes_to_u128");
inline for (INTEGERS) |T, i| { inline for (INTEGERS) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
@ -86,6 +88,10 @@ comptime {
num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow."); num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic."); num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic.");
num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated."); num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated.");
num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits.");
num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits.");
num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits.");
} }
inline for (INTEGERS) |FROM| { inline for (INTEGERS) |FROM| {

View file

@ -236,6 +236,24 @@ fn bytesToU32(arg: RocList, position: usize) u32 {
return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] });
} }
pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 {
return @call(.{ .modifier = always_inline }, bytesToU64, .{ arg, position });
}
fn bytesToU64(arg: RocList, position: usize) u64 {
const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u64, [_]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(.{ .modifier = always_inline }, bytesToU128, .{ arg, position });
}
fn bytesToU128(arg: RocList, position: usize) u128 {
const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u128, [_]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 addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) { fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
.Int => { .Int => {
@ -460,3 +478,30 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @clz(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @ctz(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @popCount(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

View file

@ -2159,125 +2159,45 @@ test "isWhitespace" {
pub fn strTrim(input_string: RocStr) callconv(.C) RocStr { pub fn strTrim(input_string: RocStr) callconv(.C) RocStr {
var string = input_string; var string = input_string;
if (!string.isEmpty()) { if (string.isEmpty()) {
const bytes_ptr = string.asU8ptrMut(); string.deinit();
return RocStr.empty();
const leading_bytes = countLeadingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == leading_bytes) {
string.deinit();
return RocStr.empty();
}
const trailing_bytes = countTrailingWhitespaceBytes(string);
const new_len = original_len - leading_bytes - trailing_bytes;
if (string.isSmallStr() or !string.isRefcountOne()) {
// consume the input string; this will not free the
// bytes because the string is small or shared
const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len);
string.decref();
return result;
} else {
// nonempty, large, and unique: shift everything over in-place if necessary.
// Note: must use memmove over memcpy, because the bytes definitely overlap!
if (leading_bytes > 0) {
// Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on:
// https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115
// Copyright Andrew Kelley, MIT licensed.
const src = bytes_ptr + leading_bytes;
var index: usize = 0;
while (index != new_len) : (index += 1) {
bytes_ptr[index] = src[index];
}
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
} }
return RocStr.empty(); const bytes_ptr = string.asU8ptrMut();
}
pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr { const leading_bytes = countLeadingWhitespaceBytes(string);
if (string.str_bytes) |bytes_ptr| { const original_len = string.len();
const leading_bytes = countLeadingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == leading_bytes) { if (original_len == leading_bytes) {
string.deinit(); string.deinit();
return RocStr.empty(); return RocStr.empty();
}
const new_len = original_len - leading_bytes;
if (string.isSmallStr() or !string.isRefcountOne()) {
// if the trimmed string fits in a small string,
// make the result a small string and decref the original string
const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len);
string.decref();
return result;
} else {
// nonempty, large, and unique: shift everything over in-place if necessary.
// Note: must use memmove over memcpy, because the bytes definitely overlap!
if (leading_bytes > 0) {
// Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on:
// https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115
// Copyright Andrew Kelley, MIT licensed.
const src = bytes_ptr + leading_bytes;
var index: usize = 0;
while (index != new_len) : (index += 1) {
bytes_ptr[index] = src[index];
}
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
} }
return RocStr.empty(); const trailing_bytes = countTrailingWhitespaceBytes(string);
} const new_len = original_len - leading_bytes - trailing_bytes;
pub fn strTrimRight(string: RocStr) callconv(.C) RocStr { if (string.isSmallStr() or !string.isRefcountOne()) {
if (string.str_bytes) |bytes_ptr| { // consume the input string; this will not free the
const trailing_bytes = countTrailingWhitespaceBytes(string); // bytes because the string is small or shared
const original_len = string.len(); const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len);
if (original_len == trailing_bytes) { string.decref();
string.deinit();
return RocStr.empty();
}
const new_len = original_len - trailing_bytes; return result;
} else {
// nonempty, large, and unique: shift everything over in-place if necessary.
// Note: must use memmove over memcpy, because the bytes definitely overlap!
if (leading_bytes > 0) {
// Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on:
// https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115
// Copyright Andrew Kelley, MIT licensed.
const src = bytes_ptr + leading_bytes;
var index: usize = 0;
if (string.isSmallStr() or !string.isRefcountOne()) { while (index != new_len) : (index += 1) {
const result = RocStr.init(string.asU8ptr(), new_len); bytes_ptr[index] = src[index];
}
string.decref();
return result;
}
// nonempty, large, and unique:
var i: usize = 0;
while (i < new_len) : (i += 1) {
const dest = bytes_ptr + i;
const source = dest;
@memcpy(dest, source, 1);
} }
var new_string = string; var new_string = string;
@ -2285,8 +2205,99 @@ pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
return new_string; return new_string;
} }
}
return RocStr.empty(); pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
var string = input_string;
if (string.isEmpty()) {
string.deinit();
return RocStr.empty();
}
const bytes_ptr = string.asU8ptrMut();
const leading_bytes = countLeadingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == leading_bytes) {
string.deinit();
return RocStr.empty();
}
const new_len = original_len - leading_bytes;
if (string.isSmallStr() or !string.isRefcountOne()) {
// if the trimmed string fits in a small string,
// make the result a small string and decref the original string
const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len);
string.decref();
return result;
} else {
// nonempty, large, and unique: shift everything over in-place if necessary.
// Note: must use memmove over memcpy, because the bytes definitely overlap!
if (leading_bytes > 0) {
// Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on:
// https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115
// Copyright Andrew Kelley, MIT licensed.
const src = bytes_ptr + leading_bytes;
var index: usize = 0;
while (index != new_len) : (index += 1) {
bytes_ptr[index] = src[index];
}
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
}
pub fn strTrimRight(input_string: RocStr) callconv(.C) RocStr {
var string = input_string;
if (string.isEmpty()) {
string.deinit();
return RocStr.empty();
}
const bytes_ptr = string.asU8ptrMut();
const trailing_bytes = countTrailingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == trailing_bytes) {
string.deinit();
return RocStr.empty();
}
const new_len = original_len - trailing_bytes;
if (string.isSmallStr() or !string.isRefcountOne()) {
const result = RocStr.init(string.asU8ptr(), new_len);
string.decref();
return result;
}
// nonempty, large, and unique:
var i: usize = 0;
while (i < new_len) : (i += 1) {
const dest = bytes_ptr + i;
const source = dest;
@memcpy(dest, source, 1);
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
} }
fn countLeadingWhitespaceBytes(string: RocStr) usize { fn countLeadingWhitespaceBytes(string: RocStr) usize {

View file

@ -1,64 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
mod bitcode {
use roc_builtins_bitcode::{count_segments_, str_split_};
#[test]
fn count_segments() {
assert_eq!(
count_segments_((&"hello there").as_bytes(), (&"hello").as_bytes()),
2
);
assert_eq!(
count_segments_((&"a\nb\nc").as_bytes(), (&"\n").as_bytes()),
3
);
assert_eq!(
count_segments_((&"str").as_bytes(), (&"delimiter").as_bytes()),
1
);
}
#[test]
fn str_split() {
fn splits_to(string: &str, delimiter: &str, expectation: &[&[u8]]) {
assert_eq!(
str_split_(
&mut [(&"").as_bytes()].repeat(expectation.len()),
&string.as_bytes(),
&delimiter.as_bytes()
),
expectation
);
}
splits_to(
"a!b!c",
"!",
&[(&"a").as_bytes(), (&"b").as_bytes(), (&"c").as_bytes()],
);
splits_to(
"a!?b!?c!?",
"!?",
&[
(&"a").as_bytes(),
(&"b").as_bytes(),
(&"c").as_bytes(),
(&"").as_bytes(),
],
);
splits_to("abc", "!", &[(&"abc").as_bytes()]);
splits_to(
"tttttghittttt",
"ttttt",
&[(&"").as_bytes(), (&"ghi").as_bytes(), (&"").as_bytes()],
);
splits_to("def", "!!!!!!", &[(&"def").as_bytes()]);
}
}

View file

@ -49,23 +49,25 @@ false = @Bool False
## gate. The infix operator `&&` can also be used as shorthand for ## gate. The infix operator `&&` can also be used as shorthand for
## `Bool.and`. ## `Bool.and`.
## ##
## expect (Bool.and Bool.true Bool.true) == Bool.true ## ```
## expect (Bool.true && Bool.true) == Bool.true ## expect (Bool.and Bool.true Bool.true) == Bool.true
## expect (Bool.false && Bool.true) == Bool.false ## expect (Bool.true && Bool.true) == Bool.true
## expect (Bool.true && Bool.false) == Bool.false ## expect (Bool.false && Bool.true) == Bool.false
## expect (Bool.false && Bool.false) == Bool.false ## expect (Bool.true && Bool.false) == Bool.false
## expect (Bool.false && Bool.false) == Bool.false
## ```
## ##
## **Performance Note** that in Roc the `&&` and `||` work the same way as any ## **Performance Note** that in Roc the `&&` and `||` work the same way as any
## other function. However, in some languages `&&` and `||` are special-cased. ## other function. However, in some languages `&&` and `||` are special-cased.
## In these languages the compiler will skip evaluating the expression after the ## In these languages the compiler will skip evaluating the expression after the
## first operator under certain circumstances. For example an expression like ## first operator under certain circumstances. For example an expression like
## `enablePets && likesDogs user` would compile to. ## `enablePets && likesDogs user` would compile to.
## ## ```
## if enablePets then ## if enablePets then
## likesDogs user ## likesDogs user
## else ## else
## Bool.false ## Bool.false
## ## ```
## Roc does not do this because conditionals like `if` and `when` have a ## Roc does not do this because conditionals like `if` and `when` have a
## performance cost. Calling a function can sometimes be faster across the board ## performance cost. Calling a function can sometimes be faster across the board
## than doing an `if` to decide whether to skip calling it. ## than doing an `if` to decide whether to skip calling it.
@ -74,13 +76,13 @@ and : Bool, Bool -> Bool
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to ## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate. ## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
## The infix operator `||` can also be used as shorthand for `Bool.or`. ## The infix operator `||` can also be used as shorthand for `Bool.or`.
## ## ```
## expect (Bool.or Bool.false Bool.true) == Bool.true ## expect (Bool.or Bool.false Bool.true) == Bool.true
## expect (Bool.true || Bool.true) == Bool.true ## expect (Bool.true || Bool.true) == Bool.true
## expect (Bool.false || Bool.true) == Bool.true ## expect (Bool.false || Bool.true) == Bool.true
## expect (Bool.true || Bool.false) == Bool.true ## expect (Bool.true || Bool.false) == Bool.true
## expect (Bool.false || Bool.false) == Bool.false ## expect (Bool.false || Bool.false) == Bool.false
## ## ```
## **Performance Note** that in Roc the `&&` and `||` work the same way as any ## **Performance Note** that in Roc the `&&` and `||` work the same way as any
## other functions. However, in some languages `&&` and `||` are special-cased. ## other functions. However, in some languages `&&` and `||` are special-cased.
## Refer to the note in `Bool.and` for more detail. ## Refer to the note in `Bool.and` for more detail.
@ -89,9 +91,10 @@ or : Bool, Bool -> Bool
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is ## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation) ## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
## gate. The operator `!` can also be used as shorthand for `Bool.not`. ## gate. The operator `!` can also be used as shorthand for `Bool.not`.
## ## ```
## expect (Bool.not Bool.false) == Bool.true ## expect (Bool.not Bool.false) == Bool.true
## expect (!Bool.false) == Bool.true ## expect (!Bool.false) == Bool.true
## ```
not : Bool -> Bool not : Bool -> Bool
## This will call the function `Bool.isEq` on the inputs, and then `Bool.not` ## This will call the function `Bool.isEq` on the inputs, and then `Bool.not`
@ -101,10 +104,11 @@ not : Bool -> Bool
## ##
## **Note** that `isNotEq` does not accept arguments whose types contain ## **Note** that `isNotEq` does not accept arguments whose types contain
## functions. ## functions.
## ## ```
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true ## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
## expect (Bool.false != Bool.false) == Bool.false ## expect (Bool.false != Bool.false) == Bool.false
## expect "Apples" != "Oranges" ## expect "Apples" != "Oranges"
## ```
isNotEq : a, a -> Bool | a has Eq isNotEq : a, a -> Bool | a has Eq
isNotEq = \a, b -> structuralNotEq a b isNotEq = \a, b -> structuralNotEq a b

View file

@ -6,13 +6,15 @@ interface Box
## the value from the stack to the heap. This may provide a performance ## the value from the stack to the heap. This may provide a performance
## optimization for advanced use cases with large values. A platform may require ## optimization for advanced use cases with large values. A platform may require
## that some values are boxed. ## that some values are boxed.
## ## ```
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster" ## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## ```
box : a -> Box a box : a -> Box a
## Returns a boxed value. ## Returns a boxed value.
## ## ```
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster" ## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
## ```
unbox : Box a -> a unbox : Box a -> a
# # we'd need reset/reuse for box for this to be efficient # # we'd need reset/reuse for box for this to be efficient

View file

@ -45,15 +45,15 @@ interface Dict
## ##
## Here's an example of a dictionary which uses a city's name as the key, and ## Here's an example of a dictionary which uses a city's name as the key, and
## its population as the associated value. ## its population as the associated value.
## ## ```
## populationByCity = ## populationByCity =
## Dict.empty {} ## Dict.empty {}
## |> Dict.insert "London" 8_961_989 ## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797 ## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895 ## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941 ## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680 ## |> Dict.insert "Amsterdam" 872_680
## ## ```
## ### Accessing keys or values ## ### Accessing keys or values
## ##
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or ## We can use [Dict.keys] and [Dict.values] functions to get only the keys or
@ -66,13 +66,13 @@ interface Dict
## ### Removing ## ### Removing
## ##
## We can remove an element from the dictionary, like so: ## We can remove an element from the dictionary, like so:
## ## ```
## populationByCity ## populationByCity
## |> Dict.remove "Philadelphia" ## |> Dict.remove "Philadelphia"
## |> Dict.keys ## |> Dict.keys
## == ## ==
## ["London", "Amsterdam", "Shanghai", "Delhi"] ## ["London", "Amsterdam", "Shanghai", "Delhi"]
## ## ```
## Notice that the order has changed. Philadelphia was not only removed from the ## Notice that the order has changed. Philadelphia was not only removed from the
## list, but Amsterdam - the last entry we inserted - has been moved into the ## list, but Amsterdam - the last entry we inserted - has been moved into the
## spot where Philadelphia was previously. This is exactly what [Dict.remove] ## spot where Philadelphia was previously. This is exactly what [Dict.remove]
@ -125,36 +125,39 @@ withCapacity = \_ ->
empty {} empty {}
## Returns a dictionary containing the key and value provided as input. ## Returns a dictionary containing the key and value provided as input.
## ## ```
## expect ## expect
## Dict.single "A" "B" ## Dict.single "A" "B"
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B") ## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
## ```
single : k, v -> Dict k v | k has Hash & Eq single : k, v -> Dict k v | k has Hash & Eq
single = \k, v -> single = \k, v ->
insert (empty {}) k v insert (empty {}) k v
## Returns dictionary with the keys and values specified by the input [List]. ## Returns dictionary with the keys and values specified by the input [List].
## ## ```
## expect ## expect
## Dict.single 1 "One" ## Dict.single 1 "One"
## |> Dict.insert 2 "Two" ## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three" ## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four" ## |> Dict.insert 4 "Four"
## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]) ## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
## ```
fromList : List (T k v) -> Dict k v | k has Hash & Eq fromList : List (T k v) -> Dict k v | k has Hash & Eq
fromList = \data -> fromList = \data ->
# TODO: make this efficient. Should just set data and then set all indicies in the hashmap. # TODO: make this efficient. Should just set data and then set all indicies in the hashmap.
List.walk data (empty {}) (\dict, T k v -> insert dict k v) List.walk data (empty {}) (\dict, T k v -> insert dict k v)
## Returns the number of values in the dictionary. ## Returns the number of values in the dictionary.
## ## ```
## expect ## expect
## Dict.empty {} ## Dict.empty {}
## |> Dict.insert "One" "A Song" ## |> Dict.insert "One" "A Song"
## |> Dict.insert "Two" "Candy Canes" ## |> Dict.insert "Two" "Candy Canes"
## |> Dict.insert "Three" "Boughs of Holly" ## |> Dict.insert "Three" "Boughs of Holly"
## |> Dict.len ## |> Dict.len
## |> Bool.isEq 3 ## |> Bool.isEq 3
## ```
len : Dict k v -> Nat | k has Hash & Eq len : Dict k v -> Nat | k has Hash & Eq
len = \@Dict { size } -> len = \@Dict { size } ->
size size
@ -180,13 +183,14 @@ clear = \@Dict { metadata, dataIndices, data } ->
## Iterate through the keys and values in the dictionary and call the provided ## Iterate through the keys and values in the dictionary and call the provided
## function with signature `state, k, v -> state` for each value, with an ## function with signature `state, k, v -> state` for each value, with an
## initial `state` value provided for the first call. ## initial `state` value provided for the first call.
## ## ```
## expect ## expect
## Dict.empty {} ## Dict.empty {}
## |> Dict.insert "Apples" 12 ## |> Dict.insert "Apples" 12
## |> Dict.insert "Orange" 24 ## |> Dict.insert "Orange" 24
## |> Dict.walk 0 (\count, _, qty -> count + qty) ## |> Dict.walk 0 (\count, _, qty -> count + qty)
## |> Bool.isEq 36 ## |> Bool.isEq 36
## ```
walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq
walk = \@Dict { data }, initialState, transform -> walk = \@Dict { data }, initialState, transform ->
List.walk data initialState (\state, T k v -> transform state k v) List.walk data initialState (\state, T k v -> transform state k v)
@ -208,14 +212,15 @@ walkUntil = \@Dict { data }, initialState, transform ->
## Get the value for a given key. If there is a value for the specified key it ## Get the value for a given key. If there is a value for the specified key it
## will return [Ok value], otherwise return [Err KeyNotFound]. ## will return [Ok value], otherwise return [Err KeyNotFound].
## ```
## dictionary =
## Dict.empty {}
## |> Dict.insert 1 "Apple"
## |> Dict.insert 2 "Orange"
## ##
## dictionary = ## expect Dict.get dictionary 1 == Ok "Apple"
## Dict.empty {} ## expect Dict.get dictionary 2000 == Err KeyNotFound
## |> Dict.insert 1 "Apple" ## ```
## |> Dict.insert 2 "Orange"
##
## expect Dict.get dictionary 1 == Ok "Apple"
## expect Dict.get dictionary 2000 == Err KeyNotFound
get : Dict k v, k -> Result v [KeyNotFound] | k has Hash & Eq get : Dict k v, k -> Result v [KeyNotFound] | k has Hash & Eq
get = \@Dict { metadata, dataIndices, data }, key -> get = \@Dict { metadata, dataIndices, data }, key ->
hashKey = hashKey =
@ -237,12 +242,13 @@ get = \@Dict { metadata, dataIndices, data }, key ->
Err KeyNotFound Err KeyNotFound
## Check if the dictionary has a value for a specified key. ## Check if the dictionary has a value for a specified key.
## ## ```
## expect ## expect
## Dict.empty {} ## Dict.empty {}
## |> Dict.insert 1234 "5678" ## |> Dict.insert 1234 "5678"
## |> Dict.contains 1234 ## |> Dict.contains 1234
## |> Bool.isEq Bool.true ## |> Bool.isEq Bool.true
## ```
contains : Dict k v, k -> Bool | k has Hash & Eq contains : Dict k v, k -> Bool | k has Hash & Eq
contains = \@Dict { metadata, dataIndices, data }, key -> contains = \@Dict { metadata, dataIndices, data }, key ->
hashKey = hashKey =
@ -261,12 +267,13 @@ contains = \@Dict { metadata, dataIndices, data }, key ->
Bool.false Bool.false
## Insert a value into the dictionary at a specified key. ## Insert a value into the dictionary at a specified key.
## ## ```
## expect ## expect
## Dict.empty {} ## Dict.empty {}
## |> Dict.insert "Apples" 12 ## |> Dict.insert "Apples" 12
## |> Dict.get "Apples" ## |> Dict.get "Apples"
## |> Bool.isEq (Ok 12) ## |> Bool.isEq (Ok 12)
## ```
insert : Dict k v, k, v -> Dict k v | k has Hash & Eq insert : Dict k v, k, v -> Dict k v | k has Hash & Eq
insert = \@Dict { metadata, dataIndices, data, size }, key, value -> insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
hashKey = hashKey =
@ -305,13 +312,14 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
insertNotFoundHelper rehashedDict key value h1Key h2Key insertNotFoundHelper rehashedDict key value h1Key h2Key
## Remove a value from the dictionary for a specified key. ## Remove a value from the dictionary for a specified key.
## ## ```
## expect ## expect
## Dict.empty {} ## Dict.empty {}
## |> Dict.insert "Some" "Value" ## |> Dict.insert "Some" "Value"
## |> Dict.remove "Some" ## |> Dict.remove "Some"
## |> Dict.len ## |> Dict.len
## |> Bool.isEq 0 ## |> Bool.isEq 0
## ```
remove : Dict k v, k -> Dict k v | k has Hash & Eq remove : Dict k v, k -> Dict k v | k has Hash & Eq
remove = \@Dict { metadata, dataIndices, data, size }, key -> remove = \@Dict { metadata, dataIndices, data, size }, key ->
# TODO: change this from swap remove to tombstone and test is performance is still good. # TODO: change this from swap remove to tombstone and test is performance is still good.
@ -345,16 +353,17 @@ remove = \@Dict { metadata, dataIndices, data, size }, key ->
## performance optimisation for the use case of providing a default when a value ## performance optimisation for the use case of providing a default when a value
## is missing. This is more efficient than doing both a `Dict.get` and then a ## is missing. This is more efficient than doing both a `Dict.get` and then a
## `Dict.insert` call, and supports being piped. ## `Dict.insert` call, and supports being piped.
## ```
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
## alterValue = \possibleValue ->
## when possibleValue is
## Missing -> Present Bool.false
## Present value -> if value then Missing else Present Bool.true
## ##
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing] ## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
## alterValue = \possibleValue -> ## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
## when possibleValue is ## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
## Missing -> Present Bool.false ## ```
## Present value -> if value then Missing else Present Bool.true
##
## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Hash & Eq update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Hash & Eq
update = \dict, key, alter -> update = \dict, key, alter ->
# TODO: look into optimizing by merging substeps and reducing lookups. # TODO: look into optimizing by merging substeps and reducing lookups.
@ -369,42 +378,45 @@ update = \dict, key, alter ->
## Returns the keys and values of a dictionary as a [List]. ## Returns the keys and values of a dictionary as a [List].
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead. ## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
## ## ```
## expect ## expect
## Dict.single 1 "One" ## Dict.single 1 "One"
## |> Dict.insert 2 "Two" ## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three" ## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four" ## |> Dict.insert 4 "Four"
## |> Dict.toList ## |> Dict.toList
## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"] ## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
## ```
toList : Dict k v -> List (T k v) | k has Hash & Eq toList : Dict k v -> List (T k v) | k has Hash & Eq
toList = \@Dict { data } -> toList = \@Dict { data } ->
data data
## Returns the keys of a dictionary as a [List]. ## Returns the keys of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead. ## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
## ## ```
## expect ## expect
## Dict.single 1 "One" ## Dict.single 1 "One"
## |> Dict.insert 2 "Two" ## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three" ## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four" ## |> Dict.insert 4 "Four"
## |> Dict.keys ## |> Dict.keys
## |> Bool.isEq [1,2,3,4] ## |> Bool.isEq [1,2,3,4]
## ```
keys : Dict k v -> List k | k has Hash & Eq keys : Dict k v -> List k | k has Hash & Eq
keys = \@Dict { data } -> keys = \@Dict { data } ->
List.map data (\T k _ -> k) List.map data (\T k _ -> k)
## Returns the values of a dictionary as a [List]. ## Returns the values of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead. ## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
## ## ```
## expect ## expect
## Dict.single 1 "One" ## Dict.single 1 "One"
## |> Dict.insert 2 "Two" ## |> Dict.insert 2 "Two"
## |> Dict.insert 3 "Three" ## |> Dict.insert 3 "Three"
## |> Dict.insert 4 "Four" ## |> Dict.insert 4 "Four"
## |> Dict.values ## |> Dict.values
## |> Bool.isEq ["One","Two","Three","Four"] ## |> Bool.isEq ["One","Two","Three","Four"]
## ```
values : Dict k v -> List v | k has Hash & Eq values : Dict k v -> List v | k has Hash & Eq
values = \@Dict { data } -> values = \@Dict { data } ->
List.map data (\T _ v -> v) List.map data (\T _ v -> v)
@ -414,24 +426,25 @@ values = \@Dict { data } ->
## both dictionaries will be combined. Note that where there are pairs ## both dictionaries will be combined. Note that where there are pairs
## with the same key, the value contained in the second input will be ## with the same key, the value contained in the second input will be
## retained, and the value in the first input will be removed. ## retained, and the value in the first input will be removed.
## ```
## first =
## Dict.single 1 "Not Me"
## |> Dict.insert 2 "And Me"
## ##
## first = ## second =
## Dict.single 1 "Not Me" ## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me" ## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
## ##
## second = ## expected =
## Dict.single 1 "Keep Me" ## Dict.single 1 "Keep Me"
## |> Dict.insert 3 "Me Too" ## |> Dict.insert 2 "And Me"
## |> Dict.insert 4 "And Also Me" ## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
## ##
## expected = ## expect
## Dict.single 1 "Keep Me" ## Dict.insertAll first second == expected
## |> Dict.insert 2 "And Me" ## ```
## |> Dict.insert 3 "Me Too"
## |> Dict.insert 4 "And Also Me"
##
## expect
## Dict.insertAll first second == expected
insertAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq insertAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
insertAll = \xs, ys -> insertAll = \xs, ys ->
walk ys xs insert walk ys xs insert
@ -441,18 +454,19 @@ insertAll = \xs, ys ->
## that are in both dictionaries. Note that where there are pairs with ## that are in both dictionaries. Note that where there are pairs with
## the same key, the value contained in the first input will be retained, ## the same key, the value contained in the first input will be retained,
## and the value in the second input will be removed. ## and the value in the second input will be removed.
## ```
## first =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## ##
## first = ## second =
## Dict.single 1 "Keep Me" ## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me" ## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "But Not Me"
## |> Dict.insert 4 "Or Me"
## ##
## second = ## expect Dict.keepShared first second == first
## Dict.single 1 "Keep Me" ## ```
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "But Not Me"
## |> Dict.insert 4 "Or Me"
##
## expect Dict.keepShared first second == first
keepShared : Dict k v, Dict k v -> Dict k v | k has Hash & Eq keepShared : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
keepShared = \xs, ys -> keepShared = \xs, ys ->
walk walk
@ -469,21 +483,22 @@ keepShared = \xs, ys ->
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement) ## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
## of the values. This means that we will be left with only those pairs that ## of the values. This means that we will be left with only those pairs that
## are in the first dictionary and whose keys are not in the second. ## are in the first dictionary and whose keys are not in the second.
## ```
## first =
## Dict.single 1 "Keep Me"
## |> Dict.insert 2 "And Me"
## |> Dict.insert 3 "Remove Me"
## ##
## first = ## second =
## Dict.single 1 "Keep Me" ## Dict.single 3 "Remove Me"
## |> Dict.insert 2 "And Me" ## |> Dict.insert 4 "I do nothing..."
## |> Dict.insert 3 "Remove Me"
## ##
## second = ## expected =
## Dict.single 3 "Remove Me" ## Dict.single 1 "Keep Me"
## |> Dict.insert 4 "I do nothing..." ## |> Dict.insert 2 "And Me"
## ##
## expected = ## expect Dict.removeAll first second == expected
## Dict.single 1 "Keep Me" ## ```
## |> Dict.insert 2 "And Me"
##
## expect Dict.removeAll first second == expected
removeAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq removeAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
removeAll = \xs, ys -> removeAll = \xs, ys ->
walk ys xs (\state, k, _ -> remove state k) walk ys xs (\state, k, _ -> remove state k)

View file

@ -191,16 +191,42 @@ encodeTag = \name, payload ->
List.append bytesWithPayload (Num.toU8 ']') List.append bytesWithPayload (Num.toU8 ']')
|> List.append (Num.toU8 '}') |> List.append (Num.toU8 '}')
isEscapeSequence : U8, U8 -> Bool
isEscapeSequence = \a, b ->
when P a b is
P '\\' 'b' -> Bool.true # Backspace
P '\\' 'f' -> Bool.true # Form feed
P '\\' 'n' -> Bool.true # Newline
P '\\' 'r' -> Bool.true # Carriage return
P '\\' 't' -> Bool.true # Tab
P '\\' '"' -> Bool.true # Double quote
P '\\' '\\' -> Bool.true # Backslash
_ -> Bool.false
takeWhile = \list, predicate -> takeWhile = \list, predicate ->
helper = \{ taken, rest } -> helper = \{ taken, rest } ->
when List.first rest is when rest is
Ok elem -> [a, b, ..] ->
if predicate elem then if isEscapeSequence a b then
helper { taken: List.append taken elem, rest: List.split rest 1 |> .others } helper {
taken: taken |> List.append a |> List.append b,
rest: List.drop rest 2,
}
else if predicate a then
helper {
taken: List.append taken a,
rest: List.dropFirst rest,
}
else else
{ taken, rest } { taken, rest }
Err _ -> { taken, rest } [a, ..] if predicate a ->
helper {
taken: List.append taken a,
rest: List.dropFirst rest,
}
_ -> { taken, rest }
helper { taken: [], rest: list } helper { taken: [], rest: list }
@ -341,7 +367,6 @@ jsonString = \bytes ->
if if
before == ['"'] before == ['"']
then then
# TODO: handle escape sequences
{ taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"' { taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"'
when Str.fromUtf8 strSequence is when Str.fromUtf8 strSequence is
@ -358,42 +383,30 @@ decodeString = Decode.custom \bytes, @Json {} ->
jsonString bytes jsonString bytes
decodeList = \decodeElem -> Decode.custom \bytes, @Json {} -> decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
decodeElems = \chunk, accum -> decodeElems = \chunk, accum ->
when Decode.decodeWith chunk decodeElem (@Json {}) is when Decode.decodeWith chunk decodeElem (@Json {}) is
{ result, rest } -> { result, rest } ->
when result is when result is
Ok val ->
# TODO: handle spaces before ','
{ before: afterElem, others } = List.split rest 1
if
afterElem == [',']
then
decodeElems others (List.append accum val)
else
Done (List.append accum val) rest
Err e -> Errored e rest Err e -> Errored e rest
Ok val ->
restWithoutWhitespace = eatWhitespace rest
when restWithoutWhitespace is
[',', ..] -> decodeElems (eatWhitespace (List.dropFirst restWithoutWhitespace)) (List.append accum val)
_ -> Done (List.append accum val) restWithoutWhitespace
{ before, others: afterStartingBrace } = List.split bytes 1 when bytes is
['[', ']'] -> { result: Ok [], rest: List.drop bytes 2 }
['[', ..] ->
when decodeElems (eatWhitespace (List.dropFirst bytes)) [] is
Errored e rest -> { result: Err e, rest }
Done vals rest ->
when rest is
[']', ..] -> { result: Ok vals, rest: List.dropFirst rest }
_ -> { result: Err TooShort, rest }
if _ ->
before == ['['] { result: Err TooShort, rest: bytes }
then
# TODO: empty lists
when decodeElems afterStartingBrace [] is
Errored e rest -> { result: Err e, rest }
Done vals rest ->
{ before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1
if
maybeEndingBrace == [']']
then
{ result: Ok vals, rest: afterEndingBrace }
else
{ result: Err TooShort, rest }
else
{ result: Err TooShort, rest: bytes }
parseExactChar : List U8, U8 -> DecodeResult {} parseExactChar : List U8, U8 -> DecodeResult {}
parseExactChar = \bytes, char -> parseExactChar = \bytes, char ->
@ -463,3 +476,82 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
when finalizer endStateResult is when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterRecordBytes } Ok val -> { result: Ok val, rest: afterRecordBytes }
Err e -> { result: Err e, rest: afterRecordBytes } Err e -> { result: Err e, rest: afterRecordBytes }
# Helper to eat leading Json whitespace characters
eatWhitespace = \input ->
when input is
[' ', ..] -> eatWhitespace (List.dropFirst input)
['\n', ..] -> eatWhitespace (List.dropFirst input)
['\r', ..] -> eatWhitespace (List.dropFirst input)
['\t', ..] -> eatWhitespace (List.dropFirst input)
_ -> input
# Test eating Json whitespace
expect
input = Str.toUtf8 " \n\r\tabc"
actual = eatWhitespace input
expected = Str.toUtf8 "abc"
actual == expected
# Test json string decoding with escapes
expect
input = Str.toUtf8 "\"a\r\nbc\\\"xz\""
expected = Ok "a\r\nbc\\\"xz"
actual = Decode.fromBytes input fromUtf8
actual == expected
# Test json string encoding with escapes
expect
input = "a\r\nbc\\\"xz"
expected = Str.toUtf8 "\"a\r\nbc\\\"xz\""
actual = Encode.toBytes input toUtf8
actual == expected
# Test json array decode empty list
expect
input = Str.toUtf8 "[ ]"
expected = []
actual : List U8
actual = Decode.fromBytes input fromUtf8 |> Result.withDefault []
actual == expected
# Test json array decoding into integers
expect
input = Str.toUtf8 "[ 1,\n2,\t3]"
expected = [1, 2, 3]
actual : List U8
actual = Decode.fromBytes input fromUtf8 |> Result.withDefault []
actual == expected
# Test json array decoding into strings ignoring whitespace around values
expect
input = Str.toUtf8 "[\r\"one\" ,\t\"two\"\n,\n\"3\"\t]"
expected = ["one", "two", "3"]
actual : List Str
actual =
Decode.fromBytes input fromUtf8
|> Result.onErr handleJsonDecodeError
|> Result.withDefault []
actual == expected
# Helper for tests to handle Json decoding errors
handleJsonDecodeError = \err ->
when err is
Leftover bytes ->
when Str.fromUtf8 bytes is
Ok bs -> crash "ERROR: bytes left \(bs)"
Err _ ->
ls =
bytes
|> List.map Num.toStr
|> Str.joinWith ","
crash "ERROR: bytes left \(ls)"
TooShort -> crash "ERROR: input too short"

View file

@ -73,11 +73,11 @@ interface List
## Types ## Types
## A sequential list of values. ## A sequential list of values.
## ## ```
## >>> [1, 2, 3] # a list of numbers ## [1, 2, 3] # a list of numbers
## >>> ["a", "b", "c"] # a list of strings ## ["a", "b", "c"] # a list of strings
## >>> [[1.1], [], [2.2, 3.3]] # a list of lists of numbers ## [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
## ## ```
## The maximum size of a [List] is limited by the amount of heap memory available ## The maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to ## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) ## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
@ -105,11 +105,11 @@ interface List
## will be immediately freed. ## will be immediately freed.
## ##
## Let's look at an example. ## Let's look at an example.
## ```
## ratings = [5, 4, 3]
## ##
## ratings = [5, 4, 3] ## { foo: ratings, bar: ratings }
## ## ```
## { foo: ratings, bar: ratings }
##
## The first line binds the name `ratings` to the list `[5, 4, 3]`. The list ## The first line binds the name `ratings` to the list `[5, 4, 3]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it. ## begins with a refcount of 1, because so far only `ratings` is referencing it.
## ##
@ -118,14 +118,14 @@ interface List
## refcount getting incremented from 1 to 3. ## refcount getting incremented from 1 to 3.
## ##
## Let's turn this example into a function. ## Let's turn this example into a function.
## ```
## getRatings = \first ->
## ratings = [first, 4, 3]
## ##
## getRatings = \first -> ## { foo: ratings, bar: ratings }
## ratings = [first, 4, 3]
##
## { foo: ratings, bar: ratings }
##
## getRatings 5
## ##
## getRatings 5
## ```
## At the end of the `getRatings` function, when the record gets returned, ## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer ## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the ## accessible. (Trying to reference `ratings` outside the scope of the
@ -140,23 +140,23 @@ interface List
## list, and that list has a refcount of 2. ## list, and that list has a refcount of 2.
## ##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: ## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
## ```
## getRatings = \first ->
## ratings = [first, 4, 3]
## ##
## getRatings = \first -> ## { foo: ratings, bar: ratings }
## ratings = [first, 4, 3]
##
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
## ##
## (getRatings 5).bar
## ```
## Now, when this expression returns, only the `bar` field of the record will ## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing ## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back ## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it. ## where it started: there is only 1 reference to it.
## ##
## Finally let's suppose the final line were changed to this: ## Finally let's suppose the final line were changed to this:
## ## ```
## List.first (getRatings 5).bar ## List.first (getRatings 5).bar
## ## ```
## This call to [List.first] means that even the list in the `bar` field has become ## This call to [List.first] means that even the list in the `bar` field has become
## inaccessible. As such, this line will cause the list's refcount to get ## inaccessible. As such, this line will cause the list's refcount to get
## decremented all the way to 0. At that point, nothing is referencing the list ## decremented all the way to 0. At that point, nothing is referencing the list
@ -167,25 +167,25 @@ interface List
## and then with a list of lists, to see how they differ. ## and then with a list of lists, to see how they differ.
## ##
## Here's the example using a list of numbers. ## Here's the example using a list of numbers.
## ```
## nums = [1, 2, 3, 4, 5, 6, 7]
## ##
## nums = [1, 2, 3, 4, 5, 6, 7] ## first = List.first nums
## ## last = List.last nums
## first = List.first nums
## last = List.last nums
##
## first
## ##
## first
## ```
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`. ## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
## ##
## Here's the equivalent code with a list of lists: ## Here's the equivalent code with a list of lists:
## ```
## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
## ##
## lists = [[1], [2, 3], [], [4, 5, 6, 7]] ## first = List.first lists
## ## last = List.last lists
## first = List.first lists
## last = List.last lists
##
## first
## ##
## first
## ```
## TODO explain how in the former example, when we go to free `nums` at the end, ## TODO explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However, ## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement ## in the case of `lists`, we have to iterate through the list and decrement
@ -206,10 +206,11 @@ interface List
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations. ## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood! ## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
## Check if the list is empty. ## Check if the list is empty.
## ```
## List.isEmpty [1, 2, 3]
## ##
## >>> List.isEmpty [1, 2, 3] ## List.isEmpty []
## ## ```
## >>> List.isEmpty []
isEmpty : List a -> Bool isEmpty : List a -> Bool
isEmpty = \list -> isEmpty = \list ->
List.len list == 0 List.len list == 0
@ -237,9 +238,9 @@ replace = \list, index, newValue ->
{ list, value: newValue } { list, value: newValue }
## Replaces the element at the given index with a replacement. ## Replaces the element at the given index with a replacement.
## ## ```
## >>> List.set ["a", "b", "c"] 1 "B" ## List.set ["a", "b", "c"] 1 "B"
## ## ```
## If the given index is outside the bounds of the list, returns the original ## If the given index is outside the bounds of the list, returns the original
## list unmodified. ## list unmodified.
## ##
@ -249,11 +250,12 @@ set = \list, index, value ->
(List.replace list index value).list (List.replace list index value).list
## Add a single element to the end of a list. ## Add a single element to the end of a list.
## ```
## List.append [1, 2, 3] 4
## ##
## >>> List.append [1, 2, 3] 4 ## [0, 1, 2]
## ## |> List.append 3
## >>> [0, 1, 2] ## ```
## >>> |> List.append 3
append : List a, a -> List a append : List a, a -> List a
append = \list, element -> append = \list, element ->
list list
@ -268,11 +270,12 @@ append = \list, element ->
appendUnsafe : List a, a -> List a appendUnsafe : List a, a -> List a
## Add a single element to the beginning of a list. ## Add a single element to the beginning of a list.
## ```
## List.prepend [1, 2, 3] 0
## ##
## >>> List.prepend [1, 2, 3] 0 ## [2, 3, 4]
## ## |> List.prepend 1
## >>> [2, 3, 4] ## ```
## >>> |> List.prepend 1
prepend : List a, a -> List a prepend : List a, a -> List a
## Returns the length of the list - the number of elements it contains. ## Returns the length of the list - the number of elements it contains.
@ -289,11 +292,12 @@ withCapacity : Nat -> List a
reserve : List a, Nat -> List a reserve : List a, Nat -> List a
## Put two lists together. ## Put two lists together.
## ```
## List.concat [1, 2, 3] [4, 5]
## ##
## >>> List.concat [1, 2, 3] [4, 5] ## [0, 1, 2]
## ## |> List.concat [3, 4]
## >>> [0, 1, 2] ## ```
## >>> |> List.concat [3, 4]
concat : List a, List a -> List a concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty. ## Returns the last element in the list, or `ListWasEmpty` if it was empty.
@ -306,17 +310,15 @@ last = \list ->
## A list with a single element in it. ## A list with a single element in it.
## ##
## This is useful in pipelines, like so: ## This is useful in pipelines, like so:
## ## ```
## websites = ## websites =
## Str.concat domain ".com" ## Str.concat domain ".com"
## |> List.single ## |> List.single
## ## ```
single : a -> List a single : a -> List a
single = \x -> [x] single = \x -> [x]
## Returns a list with the given length, where every element is the given value. ## Returns a list with the given length, where every element is the given value.
##
##
repeat : a, Nat -> List a repeat : a, Nat -> List a
repeat = \value, count -> repeat = \value, count ->
repeatHelp value count (List.withCapacity count) repeatHelp value count (List.withCapacity count)
@ -329,8 +331,9 @@ repeatHelp = \value, count, accum ->
accum accum
## Returns the list with its elements reversed. ## Returns the list with its elements reversed.
## ## ```
## >>> List.reverse [1, 2, 3] ## List.reverse [1, 2, 3]
## ```
reverse : List a -> List a reverse : List a -> List a
reverse = \list -> reverse = \list ->
reverseHelp list 0 (Num.subSaturated (List.len list) 1) reverseHelp list 0 (Num.subSaturated (List.len list) 1)
@ -342,12 +345,11 @@ reverseHelp = \list, left, right ->
list list
## Join the given lists together into one list. ## Join the given lists together into one list.
## ## ```
## >>> List.join [[1, 2, 3], [4, 5], [], [6, 7]] ## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
## ## List.join [[], []]
## >>> List.join [[], []] ## List.join []
## ## ```
## >>> List.join []
join : List (List a) -> List a join : List (List a) -> List a
join = \lists -> join = \lists ->
totalLength = totalLength =
@ -366,10 +368,10 @@ contains = \list, needle ->
## which updates the `state`. It returns the final `state` at the end. ## which updates the `state`. It returns the final `state` at the end.
## ##
## You can use it in a pipeline: ## You can use it in a pipeline:
## ## ```
## [2, 4, 8] ## [2, 4, 8]
## |> List.walk 0 Num.add ## |> List.walk 0 Num.add
## ## ```
## This returns 14 because: ## This returns 14 because:
## * `state` starts at 0 ## * `state` starts at 0
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. ## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
@ -385,10 +387,10 @@ contains = \list, needle ->
## 6 | 8 | 14 ## 6 | 8 | 14
## ##
## The following returns -6: ## The following returns -6:
## ## ```
## [1, 2, 3] ## [1, 2, 3]
## |> List.walk 0 Num.sub ## |> List.walk 0 Num.sub
## ## ```
## Note that in other languages, `walk` is sometimes called `reduce`, ## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`. ## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state walk : List elem, state, (state, elem -> state) -> state
@ -494,9 +496,9 @@ all = \list, predicate ->
## Run the given function on each element of a list, and return all the ## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.true`. ## elements for which the function returned `Bool.true`.
## ## ```
## >>> List.keepIf [1, 2, 3, 4] (\num -> num > 2) ## List.keepIf [1, 2, 3, 4] (\num -> num > 2)
## ## ```
## ## Performance Details ## ## Performance Details
## ##
## [List.keepIf] always returns a list that takes up exactly the same amount ## [List.keepIf] always returns a list that takes up exactly the same amount
@ -531,9 +533,9 @@ keepIfHelp = \list, predicate, kept, index, length ->
## Run the given function on each element of a list, and return all the ## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.false`. ## elements for which the function returned `Bool.false`.
## ## ```
## >>> List.dropIf [1, 2, 3, 4] (\num -> num > 2) ## List.dropIf [1, 2, 3, 4] (\num -> num > 2)
## ## ```
## ## Performance Details ## ## Performance Details
## ##
## `List.dropIf` has the same performance characteristics as [List.keepIf]. ## `List.dropIf` has the same performance characteristics as [List.keepIf].
@ -556,12 +558,13 @@ countIf = \list, predicate ->
## This works like [List.map], except only the transformed values that are ## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. ## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
## ```
## List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
## ##
## >>> List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last ## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## ##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) ## List.keepOks ["", "a", "bc", "", "d", "ef", ""]
## >>> ## ```
## >>> List.keepOks ["", "a", "bc", "", "d", "ef", ""]
keepOks : List before, (before -> Result after *) -> List after keepOks : List before, (before -> Result after *) -> List after
keepOks = \list, toResult -> keepOks = \list, toResult ->
walker = \accum, element -> walker = \accum, element ->
@ -573,12 +576,13 @@ keepOks = \list, toResult ->
## This works like [List.map], except only the transformed values that are ## This works like [List.map], except only the transformed values that are
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped. ## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
## ```
## List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
## ##
## >>> List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last ## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## ##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) ## List.keepErrs ["", "a", "bc", "", "d", "ef", ""]
## >>> ## ```
## >>> List.keepErrs ["", "a", "bc", "", "d", "ef", ""]
keepErrs : List before, (before -> Result * after) -> List after keepErrs : List before, (before -> Result * after) -> List after
keepErrs = \list, toResult -> keepErrs = \list, toResult ->
walker = \accum, element -> walker = \accum, element ->
@ -590,10 +594,11 @@ keepErrs = \list, toResult ->
## Convert each element in the list to something new, by calling a conversion ## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values. ## function on each of them. Then return a new list of the converted values.
## ```
## List.map [1, 2, 3] (\num -> num + 1)
## ##
## > List.map [1, 2, 3] (\num -> num + 1) ## List.map ["", "a", "bc"] Str.isEmpty
## ## ```
## > List.map ["", "a", "bc"] Str.isEmpty
map : List a, (a -> b) -> List b map : List a, (a -> b) -> List b
## Run a transformation function on the first element of each list, ## Run a transformation function on the first element of each list,
@ -602,8 +607,9 @@ map : List a, (a -> b) -> List b
## ##
## Some languages have a function named `zip`, which does something similar to ## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`: ## calling [List.map2] passing two lists and `Pair`:
## ## ```
## >>> zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair ## zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
## ```
map2 : List a, List b, (a, b -> c) -> List c map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list, ## Run a transformation function on the first element of each list,
@ -640,92 +646,110 @@ mapWithIndexHelp = \src, dest, func, index, length ->
## Returns a list of all the integers between `start` and `end`. ## Returns a list of all the integers between `start` and `end`.
## ##
## To include the `start` and `end` integers themselves, use `At` like so: ## To include the `start` and `end` integers themselves, use `At` like so:
## ## ```
## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5] ## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
## ## ```
## To exclude them, use `After` and `Before`, like so: ## To exclude them, use `After` and `Before`, like so:
## ## ```
## List.range { start: After 2, end: Before 5 } # returns [3, 4] ## List.range { start: After 2, end: Before 5 } # returns [3, 4]
## ## ```
## You can have the list end at a certain length rather than a certain integer: ## You can have the list end at a certain length rather than a certain integer:
## ## ```
## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9] ## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
## ## ```
## If `step` is specified, each integer increases by that much. (`step: 1` is the default.) ## If `step` is specified, each integer increases by that much. (`step: 1` is the default.)
## ## ```
## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6] ## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
## ## ```
## List.range will also generate a reversed list if step is negative or end comes before start:
## ```
## List.range { start: At 5, end: At 2 } # returns [5, 4, 3, 2]
## ```
## All of these options are compatible with the others. For example, you can use `At` or `After` ## All of these options are compatible with the others. For example, you can use `At` or `After`
## with `start` regardless of what `end` and `step` are set to. ## with `start` regardless of what `end` and `step` are set to.
range : _ range : _
range = \{ start, end, step ? 0 } -> range = \{ start, end, step ? 0 } ->
{ incByStep, stepIsPositive } = { calcNext, stepIsPositive } =
if step == 0 then if step == 0 then
when T start end is when T start end is
T (At x) (At y) | T (At x) (Before y) | T (After x) (At y) | T (After x) (Before y) -> T (At x) (At y) | T (At x) (Before y) | T (After x) (At y) | T (After x) (Before y) ->
if x < y then if x < y then
{ {
incByStep: \i -> i + 1, calcNext: \i -> Num.addChecked i 1,
stepIsPositive: Bool.true, stepIsPositive: Bool.true,
} }
else else
{ {
incByStep: \i -> i - 1, calcNext: \i -> Num.subChecked i 1,
stepIsPositive: Bool.false, stepIsPositive: Bool.false,
} }
T (At _) (Length _) | T (After _) (Length _) -> T (At _) (Length _) | T (After _) (Length _) ->
{ {
incByStep: \i -> i + 1, calcNext: \i -> Num.addChecked i 1,
stepIsPositive: Bool.true, stepIsPositive: Bool.true,
} }
else else
{ {
incByStep: \i -> i + step, calcNext: \i -> Num.addChecked i step,
stepIsPositive: step > 0, stepIsPositive: step > 0,
} }
inclusiveStart = inclusiveStart =
when start is when start is
At x -> x At x -> Ok x
After x -> incByStep x After x -> calcNext x
when end is when end is
At at -> At at ->
isComplete = isValid =
if stepIsPositive then if stepIsPositive then
\i -> i > at \i -> i <= at
else else
\i -> i < at \i -> i >= at
# TODO: switch to List.withCapacity # TODO: switch to List.withCapacity
rangeHelp [] inclusiveStart incByStep isComplete rangeHelp [] inclusiveStart calcNext isValid
Before before -> Before before ->
isComplete = isValid =
if stepIsPositive then if stepIsPositive then
\i -> i >= before \i -> i < before
else else
\i -> i <= before \i -> i > before
# TODO: switch to List.withCapacity # TODO: switch to List.withCapacity
rangeHelp [] inclusiveStart incByStep isComplete rangeHelp [] inclusiveStart calcNext isValid
Length l -> Length l ->
rangeLengthHelp (List.withCapacity l) inclusiveStart l incByStep rangeLengthHelp (List.withCapacity l) inclusiveStart l calcNext
rangeHelp = \accum, i, incByStep, isComplete -> rangeHelp = \accum, i, calcNext, isValid ->
if isComplete i then when i is
accum Ok val ->
else if isValid val then
# TODO: change this to List.appendUnsafe once capacity is set correctly # TODO: change this to List.appendUnsafe once capacity is set correctly
rangeHelp (List.append accum i) (incByStep i) incByStep isComplete rangeHelp (List.append accum val) (calcNext val) calcNext isValid
else
accum
rangeLengthHelp = \accum, i, remaining, incByStep -> Err _ ->
# We went past the end of the numeric range and there is no next.
# return the generated list.
accum
rangeLengthHelp = \accum, i, remaining, calcNext ->
if remaining == 0 then if remaining == 0 then
accum accum
else else
rangeLengthHelp (List.appendUnsafe accum i) (incByStep i) (remaining - 1) incByStep when i is
Ok val ->
rangeLengthHelp (List.appendUnsafe accum val) (calcNext val) (remaining - 1) calcNext
Err _ ->
# We went past the end of the numeric range and there is no next.
# The list is not the correct length yet, so we must crash.
crash "List.range: failed to generate enough elements to fill the range before overflowing the numeric type"
expect expect
List.range { start: At 0, end: At 4 } == [0, 1, 2, 3, 4] List.range { start: At 0, end: At 4 } == [0, 1, 2, 3, 4]
@ -754,6 +778,18 @@ expect
expect expect
List.range { start: At 4, end: Length 5, step: -3 } == [4, 1, -2, -5, -8] List.range { start: At 4, end: Length 5, step: -3 } == [4, 1, -2, -5, -8]
expect
List.range { start: After 250u8, end: At 255 } == [251, 252, 253, 254, 255]
expect
List.range { start: After 250u8, end: At 255, step: 10 } == []
expect
List.range { start: After 250u8, end: At 245, step: 10 } == []
expect
List.range { start: At 4, end: At 0 } == [4, 3, 2, 1, 0]
## Sort with a custom comparison function ## Sort with a custom comparison function
sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a
@ -795,14 +831,14 @@ dropLast = \list ->
List.dropAt list (Num.subSaturated (List.len list) 1) List.dropAt list (Num.subSaturated (List.len list) 1)
## Returns the given number of elements from the beginning of the list. ## Returns the given number of elements from the beginning of the list.
## ## ```
## >>> List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4 ## List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
## ## ```
## If there are fewer elements in the list than the requested number, ## If there are fewer elements in the list than the requested number,
## returns the entire list. ## returns the entire list.
## ## ```
## >>> List.takeFirst [1, 2] 5 ## List.takeFirst [1, 2] 5
## ## ```
## To *remove* elements from the beginning of the list, use `List.takeLast`. ## To *remove* elements from the beginning of the list, use `List.takeLast`.
## ##
## To remove elements from both the beginning and end of the list, ## To remove elements from both the beginning and end of the list,
@ -824,14 +860,14 @@ takeFirst = \list, outputLength ->
List.sublist list { start: 0, len: outputLength } List.sublist list { start: 0, len: outputLength }
## Returns the given number of elements from the end of the list. ## Returns the given number of elements from the end of the list.
## ## ```
## >>> List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4 ## List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
## ## ```
## If there are fewer elements in the list than the requested number, ## If there are fewer elements in the list than the requested number,
## returns the entire list. ## returns the entire list.
## ## ```
## >>> List.takeLast [1, 2] 5 ## List.takeLast [1, 2] 5
## ## ```
## To *remove* elements from the end of the list, use `List.takeFirst`. ## To *remove* elements from the end of the list, use `List.takeFirst`.
## ##
## To remove elements from both the beginning and end of the list, ## To remove elements from both the beginning and end of the list,
@ -971,13 +1007,13 @@ findLastIndex = \list, matches ->
## including a total of `len` elements. ## including a total of `len` elements.
## ##
## If `start` is outside the bounds of the given list, returns the empty list. ## If `start` is outside the bounds of the given list, returns the empty list.
## ## ```
## >>> List.sublist [1, 2, 3] { start: 4, len: 0 } ## List.sublist [1, 2, 3] { start: 4, len: 0 }
## ## ```
## If more elements are requested than exist in the list, returns as many as it can. ## If more elements are requested than exist in the list, returns as many as it can.
## ## ```
## >>> List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 } ## List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
## ## ```
## > If you want a sublist which goes all the way to the end of the list, no ## > If you want a sublist which goes all the way to the end of the list, no
## > matter how long the list is, `List.takeLast` can do that more efficiently. ## > matter how long the list is, `List.takeLast` can do that more efficiently.
## ##
@ -993,7 +1029,9 @@ sublist = \list, config ->
sublistLowlevel : List elem, Nat, Nat -> List elem sublistLowlevel : List elem, Nat, Nat -> List elem
## Intersperses `sep` between the elements of `list` ## Intersperses `sep` between the elements of `list`
## >>> List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3] ## ```
## List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3]
## ```
intersperse : List elem, elem -> List elem intersperse : List elem, elem -> List elem
intersperse = \list, sep -> intersperse = \list, sep ->
capacity = 2 * List.len list capacity = 2 * List.len list
@ -1051,8 +1089,9 @@ split = \elements, userSplitIndex ->
## Returns the elements before the first occurrence of a delimiter, as well as the ## Returns the elements before the first occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`. ## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
## ## ```
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] } ## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Z, Baz] }
## ```
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitFirst = \list, delimiter -> splitFirst = \list, delimiter ->
when List.findFirstIndex list (\elem -> elem == delimiter) is when List.findFirstIndex list (\elem -> elem == delimiter) is
@ -1066,8 +1105,9 @@ splitFirst = \list, delimiter ->
## Returns the elements before the last occurrence of a delimiter, as well as the ## Returns the elements before the last occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`. ## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
## ## ```
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] } ## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Z, Bar], after: [Baz] }
## ```
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitLast = \list, delimiter -> splitLast = \list, delimiter ->
when List.findLastIndex list (\elem -> elem == delimiter) is when List.findLastIndex list (\elem -> elem == delimiter) is

View file

@ -68,6 +68,9 @@ interface Num
compare, compare,
pow, pow,
powInt, powInt,
countLeadingZeroBits,
countTrailingZeroBits,
countOneBits,
addWrap, addWrap,
addChecked, addChecked,
addSaturated, addSaturated,
@ -86,6 +89,8 @@ interface Num
intCast, intCast,
bytesToU16, bytesToU16,
bytesToU32, bytesToU32,
bytesToU64,
bytesToU128,
divCeil, divCeil,
divCeilChecked, divCeilChecked,
divTrunc, divTrunc,
@ -151,9 +156,9 @@ interface Num
## Represents a number that could be either an [Int] or a [Frac]. ## Represents a number that could be either an [Int] or a [Frac].
## ##
## This is useful for functions that can work on either, for example [Num.add], whose type is: ## This is useful for functions that can work on either, for example [Num.add], whose type is:
## ## ```
## add : Num a, Num a -> Num a ## add : Num a, Num a -> Num a
## ## ```
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass ## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`. ## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
## ##
@ -191,9 +196,9 @@ interface Num
## ##
## If this default of [I64] is not big enough for your purposes, ## If this default of [I64] is not big enough for your purposes,
## you can add an `i128` to the end of the number literal, like so: ## you can add an `i128` to the end of the number literal, like so:
## ## ```
## >>> Num.toStr 5_000_000_000i128 ## Num.toStr 5_000_000_000i128
## ## ```
## This `i128` suffix specifies that you want this number literal to be ## This `i128` suffix specifies that you want this number literal to be
## an [I128] instead of a `Num *`. All the other numeric types have ## an [I128] instead of a `Num *`. All the other numeric types have
## suffixes just like `i128`; here are some other examples: ## suffixes just like `i128`; here are some other examples:
@ -259,15 +264,11 @@ Num range := range
## ##
## All number literals without decimal points are compatible with [Int] values. ## All number literals without decimal points are compatible with [Int] values.
## ##
## >>> 1
##
## >>> 0
##
## You can optionally put underscores in your [Int] literals. ## You can optionally put underscores in your [Int] literals.
## They have no effect on the number's value, but can make large numbers easier to read. ## They have no effect on the number's value, but can make large numbers easier to read.
## ## ```
## >>> 1_000_000 ## 1_000_000
## ## ```
## Integers come in two flavors: *signed* and *unsigned*. ## Integers come in two flavors: *signed* and *unsigned*.
## ##
## * *Unsigned* integers can never be negative. The lowest value they can hold is zero. ## * *Unsigned* integers can never be negative. The lowest value they can hold is zero.
@ -342,16 +343,16 @@ Int range : Num (Integer range)
## ##
## If you don't specify a type, Roc will default to using [Dec] because it's ## If you don't specify a type, Roc will default to using [Dec] because it's
## the least error-prone overall. For example, suppose you write this: ## the least error-prone overall. For example, suppose you write this:
## ## ```
## wasItPrecise = 0.1 + 0.2 == 0.3 ## wasItPrecise = 0.1 + 0.2 == 0.3
## ## ```
## The value of `wasItPrecise` here will be `Bool.true`, because Roc uses [Dec] ## The value of `wasItPrecise` here will be `Bool.true`, because Roc uses [Dec]
## by default when there are no types specified. ## by default when there are no types specified.
## ##
## In contrast, suppose we use `f32` or `f64` for one of these numbers: ## In contrast, suppose we use `f32` or `f64` for one of these numbers:
## ## ```
## wasItPrecise = 0.1f64 + 0.2 == 0.3 ## wasItPrecise = 0.1f64 + 0.2 == 0.3
## ## ```
## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have ## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have
## been done in a base-2 floating point calculation, which causes noticeable ## been done in a base-2 floating point calculation, which causes noticeable
## precision loss in this case. ## precision loss in this case.
@ -492,15 +493,14 @@ Dec : Num (FloatingPoint Decimal)
## ##
## This is the same as calling `Num.format {}` - so for more details on ## This is the same as calling `Num.format {}` - so for more details on
## exact formatting, see `Num.format`. ## exact formatting, see `Num.format`.
## ## ```
## >>> Num.toStr 42 ## Num.toStr 42
## ## ```
## Only [Frac] values will include a decimal point, and they will always include one. ## Only [Frac] values will include a decimal point, and they will always include one.
## ## ```
## >>> Num.toStr 4.2 ## Num.toStr 4.2
## ## Num.toStr 4.0
## >>> Num.toStr 4.0 ## ```
##
## When this function is given a non-[finite](Num.isFinite) ## When this function is given a non-[finite](Num.isFinite)
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`. ## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
## ##
@ -510,6 +510,8 @@ intCast : Int a -> Int b
bytesToU16Lowlevel : List U8, Nat -> U16 bytesToU16Lowlevel : List U8, Nat -> U16
bytesToU32Lowlevel : List U8, Nat -> U32 bytesToU32Lowlevel : List U8, Nat -> U32
bytesToU64Lowlevel : List U8, Nat -> U64
bytesToU128Lowlevel : List U8, Nat -> U128
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds] bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
bytesToU16 = \bytes, index -> bytesToU16 = \bytes, index ->
@ -531,6 +533,26 @@ bytesToU32 = \bytes, index ->
else else
Err OutOfBounds Err OutOfBounds
bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds]
bytesToU64 = \bytes, index ->
# we need at least 7 more bytes
offset = 7
if 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 index + offset < List.len bytes then
Ok (bytesToU128Lowlevel bytes index)
else
Err OutOfBounds
compare : Num a, Num a -> [LT, EQ, GT] compare : Num a, Num a -> [LT, EQ, GT]
## Returns `Bool.true` if the first number is less than the second. ## Returns `Bool.true` if the first number is less than the second.
@ -539,9 +561,10 @@ compare : Num a, Num a -> [LT, EQ, GT]
## ##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN* ## 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).) ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
## ## ```
## >>> 5 ## 5
## >>> |> Num.isLt 6 ## |> Num.isLt 6
## ```
isLt : Num a, Num a -> Bool isLt : Num a, Num a -> Bool
## Returns `Bool.true` if the first number is greater than the second. ## Returns `Bool.true` if the first number is greater than the second.
@ -550,9 +573,10 @@ isLt : Num a, Num a -> Bool
## ##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN* ## 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).) ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
## ## ```
## >>> 6 ## 6
## >>> |> Num.isGt 5 ## |> Num.isGt 5
## ```
isGt : Num a, Num a -> Bool isGt : Num a, Num a -> Bool
## Returns `Bool.true` if the first number is less than or equal to the second. ## Returns `Bool.true` if the first number is less than or equal to the second.
@ -601,15 +625,15 @@ toFrac : Num * -> Frac *
## * For a positive number, returns the same number. ## * For a positive number, returns the same number.
## * For a negative number, returns the same number except positive. ## * For a negative number, returns the same number except positive.
## * For zero, returns zero. ## * For zero, returns zero.
## ```
## Num.abs 4
## ##
## >>> Num.abs 4 ## Num.abs -2.5
## ##
## >>> Num.abs -2.5 ## Num.abs 0
##
## >>> Num.abs 0
##
## >>> Num.abs 0.0
## ##
## Num.abs 0.0
## ```
## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values. ## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values.
## ##
## For example, calling #Num.abs on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow. ## For example, calling #Num.abs on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow.
@ -620,15 +644,15 @@ toFrac : Num * -> Frac *
abs : Num a -> Num a abs : Num a -> Num a
## Return a negative number when given a positive one, and vice versa. ## Return a negative number when given a positive one, and vice versa.
## ```
## Num.neg 5
## ##
## >>> Num.neg 5 ## Num.neg -2.5
## ##
## >>> Num.neg -2.5 ## Num.neg 0
##
## >>> Num.neg 0
##
## >>> Num.neg 0.0
## ##
## Num.neg 0.0
## ```
## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values. ## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values.
## ##
## For example, calling #Num.neg on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow. ## For example, calling #Num.neg on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow.
@ -645,16 +669,16 @@ neg : Num a -> Num a
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.) ## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
## ##
## `a + b` is shorthand for `Num.add a b`. ## `a + b` is shorthand for `Num.add a b`.
## ```
## 5 + 7
## ##
## >>> 5 + 7 ## Num.add 5 7
## ## ```
## >>> Num.add 5 7
##
## `Num.add` can be convenient in pipelines. ## `Num.add` can be convenient in pipelines.
## ## ```
## >>> Frac.pi ## Frac.pi
## >>> |> Num.add 1.0 ## |> Num.add 1.0
## ## ```
## If the answer to this operation can't fit in the return value (e.g. an ## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an ## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either ## *overflow*. For [F64] and [F32], overflow results in an answer of either
@ -666,16 +690,16 @@ add : Num a, Num a -> Num a
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.) ## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
## ##
## `a - b` is shorthand for `Num.sub a b`. ## `a - b` is shorthand for `Num.sub a b`.
## ```
## 7 - 5
## ##
## >>> 7 - 5 ## Num.sub 7 5
## ## ```
## >>> Num.sub 7 5
##
## `Num.sub` can be convenient in pipelines. ## `Num.sub` can be convenient in pipelines.
## ## ```
## >>> Frac.pi ## Frac.pi
## >>> |> Num.sub 2.0 ## |> Num.sub 2.0
## ## ```
## If the answer to this operation can't fit in the return value (e.g. an ## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an ## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either ## *overflow*. For [F64] and [F32], overflow results in an answer of either
@ -687,16 +711,18 @@ sub : Num a, Num a -> Num a
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.) ## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
## ##
## `a * b` is shorthand for `Num.mul a b`. ## `a * b` is shorthand for `Num.mul a b`.
## ```
## 5 * 7
## ##
## >>> 5 * 7 ## Num.mul 5 7
## ## ```
## >>> Num.mul 5 7
## ##
## `Num.mul` can be convenient in pipelines. ## `Num.mul` can be convenient in pipelines.
## ##
## >>> Frac.pi ## ```
## >>> |> Num.mul 2.0 ## Frac.pi
## ## |> Num.mul 2.0
## ```
## If the answer to this operation can't fit in the return value (e.g. an ## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an ## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either ## *overflow*. For [F64] and [F32], overflow results in an answer of either
@ -731,14 +757,15 @@ atan : Frac a -> Frac a
## > this standard, deviating from these rules has a significant performance ## > this standard, deviating from these rules has a significant performance
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is ## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## > access to hardware-accelerated performance, Roc follows these rules exactly. ## > access to hardware-accelerated performance, Roc follows these rules exactly.
## ```
## Num.sqrt 4.0
## ##
## >>> Num.sqrt 4.0 ## Num.sqrt 1.5
## ##
## >>> Num.sqrt 1.5 ## Num.sqrt 0.0
## ##
## >>> Num.sqrt 0.0 ## Num.sqrt -4.0f64
## ## ```
## >>> Num.sqrt -4.0f64
sqrt : Frac a -> Frac a sqrt : Frac a -> Frac a
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative] sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]
@ -748,6 +775,7 @@ sqrtChecked = \x ->
else else
Ok (Num.sqrt x) Ok (Num.sqrt x)
## Natural logarithm
log : Frac a -> Frac a log : Frac a -> Frac a
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive] logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]
@ -778,15 +806,16 @@ logChecked = \x ->
## ##
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using ## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
## one of the functions in this module like #toDec. ## one of the functions in this module like #toDec.
## ```
## 5.0 / 7.0
## ##
## >>> 5.0 / 7.0 ## Num.div 5 7
## ## ```
## >>> Num.div 5 7
##
## `Num.div` can be convenient in pipelines. ## `Num.div` can be convenient in pipelines.
## ## ```
## >>> Num.pi ## Num.pi
## >>> |> Num.div 2.0 ## |> Num.div 2.0
## ```
div : Frac a, Frac a -> Frac a div : Frac a, Frac a -> Frac a
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero] divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]
@ -812,15 +841,15 @@ divCeilChecked = \a, b ->
## Division by zero is undefined in mathematics. As such, you should make ## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function! If you do, ## sure never to pass zero as the denomaintor to this function! If you do,
## it will crash. ## it will crash.
## ```
## 5 // 7
## ##
## >>> 5 // 7 ## Num.divTrunc 5 7
## ##
## >>> Num.divTrunc 5 7 ## 8 // -3
##
## >>> 8 // -3
##
## >>> Num.divTrunc 8 -3
## ##
## Num.divTrunc 8 -3
## ```
divTrunc : Int a, Int a -> Int a divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero] divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
@ -833,14 +862,15 @@ divTruncChecked = \a, b ->
## Obtain the remainder (truncating modulo) from the division of two integers. ## Obtain the remainder (truncating modulo) from the division of two integers.
## ##
## `a % b` is shorthand for `Num.rem a b`. ## `a % b` is shorthand for `Num.rem a b`.
## ```
## 5 % 7
## ##
## >>> 5 % 7 ## Num.rem 5 7
## ##
## >>> Num.rem 5 7 ## -8 % -3
## ##
## >>> -8 % -3 ## Num.rem -8 -3
## ## ```
## >>> Num.rem -8 -3
rem : Int a, Int a -> Int a rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [DivByZero] remChecked : Int a, Int a -> Result (Int a) [DivByZero]
@ -860,24 +890,24 @@ bitwiseOr : Int a, Int a -> Int a
## ##
## The least significant bits always become 0. This means that shifting left is ## The least significant bits always become 0. This means that shifting left is
## like multiplying by factors of two for unsigned integers. ## like multiplying by factors of two for unsigned integers.
## ```
## shiftLeftBy 0b0000_0011 2 == 0b0000_1100
## ##
## >>> shiftLeftBy 0b0000_0011 2 == 0b0000_1100 ## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
## ## ```
## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
##
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`. ## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
shiftLeftBy : Int a, U8 -> Int a shiftLeftBy : Int a, U8 -> Int a
## Bitwise arithmetic shift of a number by another ## Bitwise arithmetic shift of a number by another
## ##
## The most significant bits are copied from the current. ## The most significant bits are copied from the current.
## ```
## shiftRightBy 0b0000_0011 2 == 0b0000_1100
## ##
## >>> shiftRightBy 0b0000_0011 2 == 0b0000_1100 ## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
## >>> 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
## ##
## 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
## ```
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`. ## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
shiftRightBy : Int a, U8 -> Int a shiftRightBy : Int a, U8 -> Int a
@ -885,13 +915,13 @@ shiftRightBy : Int a, U8 -> Int a
## ##
## The most significant bits always become 0. This means that shifting left is ## The most significant bits always become 0. This means that shifting left is
## like dividing by factors of two for unsigned integers. ## like dividing by factors of two for unsigned integers.
## ```
## shiftRightBy 0b0010_1000 2 == 0b0000_1010
## ##
## >>> shiftRightBy 0b0010_1000 2 == 0b0000_1010 ## 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
## >>> 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
## ##
## 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
## ```
## In some languages `shiftRightBy` is implemented as a binary operator `>>`. ## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
shiftRightZfBy : Int a, U8 -> Int a shiftRightZfBy : Int a, U8 -> Int a
@ -912,21 +942,60 @@ pow : Frac a, Frac a -> Frac a
## ##
## For a [Frac] alternative to this function, which supports negative exponents, ## For a [Frac] alternative to this function, which supports negative exponents,
## see #Num.exp. ## see #Num.exp.
## ```
## Num.exp 5 0
## ##
## >>> Num.exp 5 0 ## Num.exp 5 1
## ##
## >>> Num.exp 5 1 ## Num.exp 5 2
##
## >>> Num.exp 5 2
##
## >>> Num.exp 5 6
## ##
## Num.exp 5 6
## ```
## ## Performance Notes ## ## Performance Notes
## ##
## Be careful! It is very easy for this function to produce an answer ## Be careful! It is very easy for this function to produce an answer
## so large it causes an overflow. ## so large it causes an overflow.
powInt : Int a, Int a -> Int a powInt : Int a, Int a -> Int a
## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
##
## ```
## Num.countLeadingZeroBits 0b0001_1100u8
##
## 3
##
## Num.countLeadingZeroBits 0b0000_0000u8
##
## 8
## ```
countLeadingZeroBits : Int a -> Nat
## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
##
## ```
## Num.countTrailingZeroBits 0b0001_1100u8
##
## 2
##
## Num.countTrailingZeroBits 0b0000_0000u8
##
## 8
## ```
countTrailingZeroBits : Int a -> Nat
## Counts the number of set bits in an integer.
##
## ```
## Num.countOneBits 0b0001_1100u8
##
## 3
##
## Num.countOneBits 0b0000_0000u8
##
## 0
## ```
countOneBits : Int a -> Nat
addWrap : Int range, Int range -> Int range addWrap : Int range, Int range -> Int range
## Add two numbers, clamping on the maximum representable number rather than ## Add two numbers, clamping on the maximum representable number rather than
@ -1261,164 +1330,3 @@ toU128Checked : Int * -> Result U128 [OutOfBounds]
toNatChecked : Int * -> Result Nat [OutOfBounds] toNatChecked : Int * -> Result Nat [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds] toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds] toF64Checked : Num * -> Result F64 [OutOfBounds]
# Special Floating-Point operations
## When given a [F64] or [F32] value, returns `Bool.false` if that value is
## [*NaN*](Num.isNaN), ∞ or -∞, and `Bool.true` otherwise.
##
## Always returns `Bool.true` when given a [Dec].
##
## This is the opposite of #isInfinite, except when given [*NaN*](Num.isNaN). Both
## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN).
# isFinite : Frac * -> Bool
## When given a [F64] or [F32] value, returns `Bool.true` if that value is either
## ∞ or -∞, and `Bool.false` otherwise.
##
## Always returns `Bool.false` when given a [Dec].
##
## This is the opposite of #isFinite, except when given [*NaN*](Num.isNaN). Both
## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN).
# isInfinite : Frac * -> Bool
## When given a [F64] or [F32] value, returns `Bool.true` if that value is
## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `Bool.false` otherwise.
##
## Always returns `Bool.false` when given a [Dec].
##
## >>> Num.isNaN 12.3
##
## >>> Num.isNaN (Num.pow -1 0.5)
##
## *NaN* is unusual from other numberic values in that:
## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `Bool.false` if either argument is *NaN*.
## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `Bool.false` if either argument is *NaN*.
##
## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## floating point standard. Because almost all modern processors are built to
## this standard, deviating from these rules has a significant performance
## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## access to hardware-accelerated performance, Roc follows these rules exactly.
##
## Note that you should never put a *NaN* into a [Set], or use it as the key in
## a [Dict]. The result is entries that can never be removed from those
## collections! See the documentation for [Set.insert] and [Dict.insert] for details.
# isNaN : Frac * -> Bool
## Returns the higher of two numbers.
##
## 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).)
# max : Num a, Num a -> Num a
## Returns the lower of two numbers.
##
## 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).)
# min : Num a, Num a -> Num a
# Branchless implementation that works for all numeric types:
#
# let is_lt = arg1 < arg2;
# let is_eq = arg1 == arg2;
# return (is_lt as i8 - is_eq as i8) + 1;
#
# 1, 1 -> (0 - 1) + 1 == 0 # Eq
# 5, 1 -> (0 - 0) + 1 == 1 # Gt
# 1, 5 -> (1 - 0) + 1 == 2 # Lt
## Returns `Lt` if the first number is less than the second, `Gt` if
## the first is greater than the second, and `Eq` if they're equal.
##
## Although this can be passed to `List.sort`, you'll get better performance
## by using `List.sortAsc` or `List.sortDesc` instead.
# compare : Num a, Num a -> [Lt, Eq, Gt]
## [Endianness](https://en.wikipedia.org/wiki/Endianness)
# Endi : [Big, Little, Native]
## The `Endi` argument does not matter for [U8] and [I8], since they have
## only one byte.
# toBytes : Num *, Endi -> List U8
## when Num.parseBytes bytes Big is
## Ok { val: f64, rest } -> ...
## Err (ExpectedNum (Frac Binary64)) -> ...
# parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ExpectedNum a]*
## when Num.fromBytes bytes Big is
## Ok f64 -> ...
## Err (ExpectedNum (Frac Binary64)) -> ...
# fromBytes : List U8, Endi -> Result (Num a) [ExpectedNum a]*
# Bit shifts
## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left.
##
## `a << b` is shorthand for `Num.shl a b`.
# shl : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left.
##
## This is called `shlWrap` because any bits shifted
## off the beginning of the number will be wrapped around to
## the end. (In contrast, #shl replaces discarded bits with zeroes.)
# shlWrap : Int a, Int a -> Int a
## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right.
##
## `a >> b` is shorthand for `Num.shr a b`.
# shr : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right.
##
## This is called `shrWrap` because any bits shifted
## off the end of the number will be wrapped around to
## the beginning. (In contrast, #shr replaces discarded bits with zeroes.)
# shrWrap : Int a, Int a -> Int a
# ## Convert a number into a [Str], formatted with the given options.
# ##
# ## Default options:
# ## * `base: Decimal`
# ## * `notation: Standard`
# ## * `decimalMark: HideForIntegers "."`
# ## * `decimalDigits: { min: 0, max: All }`
# ## * `minIntDigits: 1`
# ## * `wholeSep: { mark: ",", places: 3 }`
# ##
# ## ## Options
# ##
# ##
# ## ### decimalMark
# ##
# ## * `AlwaysShow` always shows the decimal mark, no matter what.
# ## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0.
# ##
# ## The [Str] included in either of these represents the mark itself.
# ##
# ## ### `decimalDigits
# ##
# ## With 0 decimal digits, the decimal mark will still be rendered if
# ## `decimalMark` is set to `AlwaysShow`.
# ##
# ## If `max` is less than `min`, then first the number will be truncated to `max`
# ## digits, and then zeroes will be added afterwards until it reaches `min` digits.
# ##
# ## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow }
# ##
# ## ### minIntDigits
# ##
# ## If the integer portion of number is fewer than this many digits, zeroes will
# ## be added in front of it until there are at least `minWholeDigits` digits.
# ##
# ## If this is set to zero, then numbers less than 1 will begin with `"."`
# ## rather than `"0."`.
# ##
# ## ### wholeSep
# ##
# ## Examples:
# ##
# ## In some countries (e.g. USA and UK), a comma is used to separate thousands:
# ## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } }
# ##
# ## Sometimes when rendering bits, it's nice to group them into groups of 4:
# ## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } }
# ##
# ## It's also common to render hexadecimal in groups of 2:
# ## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
# format :
# Num *,
# {
# base ? [Decimal, Hexadecimal, Octal, Binary],
# notation ? [Standard, Scientific],
# decimalMark ? [AlwaysShow Str, HideForIntegers],
# decimalDigits ? { min : U16, max : [All, Trunc U16, Round U16, Floor U16, Ceil U16] },
# minWholeDigits ? U16,
# wholeSep ? { mark : Str, places : U64 }
# }
# -> Str

View file

@ -7,8 +7,9 @@ interface Result
Result ok err : [Ok ok, Err err] Result ok err : [Ok ok, Err err]
## Return `Bool.true` if the result indicates a success, else return `Bool.false` ## Return `Bool.true` if the result indicates a success, else return `Bool.false`
## ## ```
## >>> Result.isOk (Ok 5) ## Result.isOk (Ok 5)
## ```
isOk : Result ok err -> Bool isOk : Result ok err -> Bool
isOk = \result -> isOk = \result ->
when result is when result is
@ -16,8 +17,9 @@ isOk = \result ->
Err _ -> Bool.false Err _ -> Bool.false
## Return `Bool.true` if the result indicates a failure, else return `Bool.false` ## Return `Bool.true` if the result indicates a failure, else return `Bool.false`
## ## ```
## >>> Result.isErr (Err "uh oh") ## Result.isErr (Err "uh oh")
## ```
isErr : Result ok err -> Bool isErr : Result ok err -> Bool
isErr = \result -> isErr = \result ->
when result is when result is
@ -26,10 +28,10 @@ isErr = \result ->
## If the result is `Ok`, return the value it holds. Otherwise, return ## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value. ## the given default value.
## ## ```
## >>> Result.withDefault (Ok 7) 42 ## Result.withDefault (Ok 7) 42
## ## Result.withDefault (Err "uh oh") 42
## >>> Result.withDefault (Err "uh oh") 42 ## ```
withDefault : Result ok err, ok -> ok withDefault : Result ok err, ok -> ok
withDefault = \result, default -> withDefault = \result, default ->
when result is when result is
@ -40,10 +42,10 @@ withDefault = \result, default ->
## function on it. Then return a new `Ok` holding the transformed value. ## function on it. Then return a new `Ok` holding the transformed value.
## ##
## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.) ## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.)
## ## ```
## >>> Result.map (Ok 12) Num.negate ## Result.map (Ok 12) Num.negate
## ## Result.map (Err "yipes!") Num.negate
## >>> Result.map (Err "yipes!") Num.negate ## ```
## ##
## `map` functions like this are common in Roc, and they all work similarly. ## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`. ## See for example [List.map], `Set.map`, and `Dict.map`.
@ -57,10 +59,10 @@ map = \result, transform ->
## function on it. Then return a new `Err` holding the transformed value. ## function on it. Then return a new `Err` holding the transformed value.
## ##
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.) ## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
## ## ```
## >>> Result.mapErr (Err "yipes!") Str.isEmpty ## Result.mapErr (Err "yipes!") Str.isEmpty
## ## Result.mapErr (Ok 12) Str.isEmpty
## >>> Result.mapErr (Ok 12) Str.isEmpty ## ```
mapErr : Result ok a, (a -> b) -> Result ok b mapErr : Result ok a, (a -> b) -> Result ok b
mapErr = \result, transform -> mapErr = \result, transform ->
when result is when result is
@ -71,10 +73,10 @@ mapErr = \result, transform ->
## function on the value the `Ok` holds. Then return that new result. ## function on the value the `Ok` holds. Then return that new result.
## ##
## (If the result is `Err`, this has no effect. Use `onErr` to transform an `Err`.) ## (If the result is `Err`, this has no effect. Use `onErr` to transform an `Err`.)
## ## ```
## >>> Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num ## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
## ## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
## >>> Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num ## ```
try : Result a err, (a -> Result b err) -> Result b err try : Result a err, (a -> Result b err) -> Result b err
try = \result, transform -> try = \result, transform ->
when result is when result is
@ -85,10 +87,10 @@ try = \result, transform ->
## function on the value the `Err` holds. Then return that new result. ## function on the value the `Err` holds. Then return that new result.
## ##
## (If the result is `Ok`, this has no effect. Use `try` to transform an `Ok`.) ## (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 (Ok 10) \errorNum -> Str.toNat errorNum
## ## Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
## >>> Result.onErr (Err "42") \errorNum -> Str.toNat errorNum ## ```
onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
onErr = \result, transform -> onErr = \result, transform ->
when result is when result is

View file

@ -4,11 +4,11 @@
## ##
## Unicode can represent text values which span multiple languages, symbols, and emoji. ## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings: ## Here are some valid Roc strings:
## ## ```
## "Roc!" ## "Roc!"
## "鹏" ## "鹏"
## "🕊" ## "🕊"
## ## ```
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster). ## 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 ## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦". ## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
@ -17,11 +17,11 @@
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster." ## 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: ## You can get the number of graphemes in a string by calling `Str.countGraphemes` on it:
## ## ```
## Str.countGraphemes "Roc!" ## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙" ## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊" ## Str.countGraphemes "🕊"
## ## ```
## > The `countGraphemes` function walks through the entire string to get its answer, ## > 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 ## > 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`. ## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
@ -31,9 +31,9 @@
## If you put a `\` in a Roc string literal, it begins an *escape sequence*. ## 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. ## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string: ## For example, suppose you write this Roc string:
## ## ```
## "I took the one less traveled by,\nAnd that has made all the difference." ## "I took the one less traveled by,\nAnd that has made all the difference."
## ## ```
## The `"\n"` in the middle will insert a line break into this string. There are ## 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. ## other ways of getting a line break in there, but `"\n"` is the most common.
## ##
@ -58,12 +58,11 @@
## * `\v` - [vertical 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: ## You can also use escape sequences to insert named strings into other strings, like so:
## ## ```
## name = "Lee" ## name = "Lee"
## city = "Roctown" ## city = "Roctown"
## ## greeting = "Hello there, \(name)! Welcome to \(city)."
## greeting = "Hello there, \(name)! Welcome to \(city)." ## ```
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`. ## 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), ## 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 ## and you can use it as many times as you like inside a string. The name
@ -138,16 +137,18 @@ Utf8ByteProblem : [
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem } Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise. ## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
## ## ```
## expect Str.isEmpty "hi!" == Bool.false ## expect Str.isEmpty "hi!" == Bool.false
## expect Str.isEmpty "" == Bool.true ## expect Str.isEmpty "" == Bool.true
## ```
isEmpty : Str -> Bool isEmpty : Str -> Bool
## Concatenates two strings together. ## Concatenates two strings together.
## ## ```
## expect Str.concat "ab" "cd" == "abcd" ## expect Str.concat "ab" "cd" == "abcd"
## expect Str.concat "hello" "" == "hello" ## expect Str.concat "hello" "" == "hello"
## expect Str.concat "" "" == "" ## expect Str.concat "" "" == ""
## ```
concat : Str, Str -> Str concat : Str, Str -> Str
## Returns a string of the specified capacity without any content. ## Returns a string of the specified capacity without any content.
@ -155,9 +156,10 @@ withCapacity : Nat -> Str
## Combines a [List] of strings into a single string, with a separator ## Combines a [List] of strings into a single string, with a separator
## string in between each. ## string in between each.
## ## ```
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three" ## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4" ## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
## ```
joinWith : List Str, Str -> Str joinWith : List Str, Str -> Str
## Split a string around a separator. ## Split a string around a separator.
@ -165,20 +167,22 @@ joinWith : List Str, Str -> Str
## Passing `""` for the separator is not useful; ## Passing `""` for the separator is not useful;
## it returns the original string wrapped in a [List]. To split a string ## 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` ## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
## ## ```
## expect Str.split "1,2,3" "," == ["1","2","3"] ## expect Str.split "1,2,3" "," == ["1","2","3"]
## expect Str.split "1,2,3" "" == ["1,2,3"] ## expect Str.split "1,2,3" "" == ["1,2,3"]
## ```
split : Str, Str -> List Str split : Str, Str -> List Str
## Repeats a string the given number of times. ## Repeats a string the given number of times.
## ## ```
## expect Str.repeat "z" 3 == "zzz" ## expect Str.repeat "z" 3 == "zzz"
## expect Str.repeat "na" 8 == "nananananananana" ## expect Str.repeat "na" 8 == "nananananananana"
## ## ```
## Returns `""` when given `""` for the string or `0` for the count. ## Returns `""` when given `""` for the string or `0` for the count.
## ## ```
## expect Str.repeat "" 10 == "" ## expect Str.repeat "" 10 == ""
## expect Str.repeat "anything" 0 == "" ## expect Str.repeat "anything" 0 == ""
## ```
repeat : Str, Nat -> Str repeat : Str, Nat -> Str
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster) ## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
@ -186,11 +190,11 @@ repeat : Str, Nat -> Str
## ##
## Note that the number of extended grapheme clusters can be different from the number ## Note that the number of extended grapheme clusters can be different from the number
## of visual glyphs rendered! Consider the following examples: ## of visual glyphs rendered! Consider the following examples:
## ## ```
## expect Str.countGraphemes "Roc" == 3 ## expect Str.countGraphemes "Roc" == 3
## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4 ## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4
## expect Str.countGraphemes "🕊" == 1 ## expect Str.countGraphemes "🕊" == 1
## ## ```
## Note that "👩‍👩‍👦‍👦" takes up 4 graphemes (even though visually it appears as a single ## 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. ## 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 ## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
@ -205,11 +209,11 @@ graphemes : Str -> List Str
## ##
## If the given string is empty, or if the given [U32] is not a valid ## If the given string is empty, or if the given [U32] is not a valid
## code point, returns [Bool.false]. ## code point, returns [Bool.false].
## ## ```
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527 ## 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 "9" 9 # the Unicode scalar for "9" is 57, not 9
## expect !Str.startsWithScalar "" 40527 ## expect !Str.startsWithScalar "" 40527
## ## ```
## **Performance Note:** This runs slightly faster than [Str.startsWith], so ## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable ## 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 '鹏'` ## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
@ -225,36 +229,39 @@ startsWithScalar : Str, U32 -> Bool
## ##
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point), ## (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).) ## 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 "Roc" == [82, 111, 99]
## expect Str.toScalars "鹏" == [40527] ## expect Str.toScalars "鹏" == [40527]
## expect Str.toScalars "சி" == [2970, 3007] ## expect Str.toScalars "சி" == [2970, 3007]
## expect Str.toScalars "🐦" == [128038] ## expect Str.toScalars "🐦" == [128038]
## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102] ## 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 "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
## expect Str.toScalars "" == [] ## expect Str.toScalars "" == []
## ```
toScalars : Str -> List U32 toScalars : Str -> List U32
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit). ## 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, ## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split].) ## see [Str.split].)
## ## ```
## expect Str.toUtf8 "Roc" == [82, 111, 99] ## expect Str.toUtf8 "Roc" == [82, 111, 99]
## expect Str.toUtf8 "鹏" == [233, 185, 143] ## expect Str.toUtf8 "鹏" == [233, 185, 143]
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191] ## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166] ## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
## ```
toUtf8 : Str -> List U8 toUtf8 : Str -> List U8
## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string. ## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string.
## ##
## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`. ## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`.
## ## ```
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc" ## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏" ## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி" ## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦" ## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
## expect Str.fromUtf8 [] == Ok "" ## expect Str.fromUtf8 [] == Ok ""
## expect Str.fromUtf8 [255] |> Result.isErr ## expect Str.fromUtf8 [255] |> Result.isErr
## ```
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat] fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 = \bytes -> fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes) result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
@ -266,8 +273,9 @@ fromUtf8 = \bytes ->
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) ## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str] ## into a [Str]
## ## ```
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi" ## 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 : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]
fromUtf8Range = \bytes, config -> fromUtf8Range = \bytes, config ->
if config.start + config.count <= List.len bytes then if config.start + config.count <= List.len bytes then
@ -290,57 +298,65 @@ FromUtf8Result : {
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
## Check if the given [Str] starts with a value. ## Check if the given [Str] starts with a value.
## ## ```
## expect Str.startsWith "ABC" "A" == Bool.true ## expect Str.startsWith "ABC" "A" == Bool.true
## expect Str.startsWith "ABC" "X" == Bool.false ## expect Str.startsWith "ABC" "X" == Bool.false
## ```
startsWith : Str, Str -> Bool startsWith : Str, Str -> Bool
## Check if the given [Str] ends with a value. ## Check if the given [Str] ends with a value.
## ## ```
## expect Str.endsWith "ABC" "C" == Bool.true ## expect Str.endsWith "ABC" "C" == Bool.true
## expect Str.endsWith "ABC" "X" == Bool.false ## expect Str.endsWith "ABC" "X" == Bool.false
## ```
endsWith : Str, Str -> Bool endsWith : Str, Str -> Bool
## Return the [Str] with all whitespace removed from both the beginning ## Return the [Str] with all whitespace removed from both the beginning
## as well as the end. ## as well as the end.
## ## ```
## expect Str.trim " Hello \n\n" == "Hello" ## expect Str.trim " Hello \n\n" == "Hello"
## ```
trim : Str -> Str trim : Str -> Str
## Return the [Str] with all whitespace removed from the beginning. ## Return the [Str] with all whitespace removed from the beginning.
## ## ```
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n" ## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
## ```
trimLeft : Str -> Str trimLeft : Str -> Str
## Return the [Str] with all whitespace removed from the end. ## Return the [Str] with all whitespace removed from the end.
## ## ```
## expect Str.trimRight " Hello \n\n" == " Hello" ## expect Str.trimRight " Hello \n\n" == " Hello"
## ```
trimRight : Str -> Str trimRight : Str -> Str
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal ## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic). ## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
## ## ```
## expect Str.toDec "10" == Ok 10dec ## expect Str.toDec "10" == Ok 10dec
## expect Str.toDec "-0.25" == Ok -0.25dec ## expect Str.toDec "-0.25" == Ok -0.25dec
## expect Str.toDec "not a number" == Err InvalidNumStr ## expect Str.toDec "not a number" == Err InvalidNumStr
## ```
toDec : Str -> Result Dec [InvalidNumStr] toDec : Str -> Result Dec [InvalidNumStr]
toDec = \string -> strToNumHelp string toDec = \string -> strToNumHelp string
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit ## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be ## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
## specified with a `f64` suffix. ## specified with a `f64` suffix.
## ## ```
## expect Str.toF64 "0.10" == Ok 0.10f64 ## expect Str.toF64 "0.10" == Ok 0.10f64
## expect Str.toF64 "not a number" == Err InvalidNumStr ## expect Str.toF64 "not a number" == Err InvalidNumStr
## ```
toF64 : Str -> Result F64 [InvalidNumStr] toF64 : Str -> Result F64 [InvalidNumStr]
toF64 = \string -> strToNumHelp string toF64 = \string -> strToNumHelp string
## Encode a [Str] to a [F32].A [F32] value is a 32-bit ## Encode a [Str] to a [F32].A [F32] value is a 32-bit
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be ## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
## specified with a `f32` suffix. ## specified with a `f32` suffix.
## ## ```
## expect Str.toF32 "0.10" == Ok 0.10f32 ## expect Str.toF32 "0.10" == Ok 0.10f32
## expect Str.toF32 "not a number" == Err InvalidNumStr ## expect Str.toF32 "not a number" == Err InvalidNumStr
## ```
toF32 : Str -> Result F32 [InvalidNumStr] toF32 : Str -> Result F32 [InvalidNumStr]
toF32 = \string -> strToNumHelp string toF32 = \string -> strToNumHelp string
@ -356,20 +372,22 @@ toF32 = \string -> strToNumHelp string
## Calling `Str.toNat "9_000_000_000"` on a 64-bit system will return ## 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 ## 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`. ## 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 "9_000_000_000" == Ok 9000000000
## expect Str.toNat "not a number" == Err InvalidNumStr ## expect Str.toNat "not a number" == Err InvalidNumStr
## ```
toNat : Str -> Result Nat [InvalidNumStr] toNat : Str -> Result Nat [InvalidNumStr]
toNat = \string -> strToNumHelp string toNat = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers ## 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 ## 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. ## 340 undecillion). It can be specified with a u128 suffix.
## ## ```
## expect Str.toU128 "1500" == Ok 1500u128 ## expect Str.toU128 "1500" == Ok 1500u128
## expect Str.toU128 "0.1" == Err InvalidNumStr ## expect Str.toU128 "0.1" == Err InvalidNumStr
## expect Str.toU128 "-1" == Err InvalidNumStr ## expect Str.toU128 "-1" == Err InvalidNumStr
## expect Str.toU128 "not a number" == Err InvalidNumStr ## expect Str.toU128 "not a number" == Err InvalidNumStr
## ```
toU128 : Str -> Result U128 [InvalidNumStr] toU128 : Str -> Result U128 [InvalidNumStr]
toU128 = \string -> strToNumHelp string toU128 = \string -> strToNumHelp string
@ -377,96 +395,105 @@ toU128 = \string -> strToNumHelp string
## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to ## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified ## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
## with a i128 suffix. ## with a i128 suffix.
## ## ```
## expect Str.toI128 "1500" == Ok 1500i128 ## expect Str.toI128 "1500" == Ok 1500i128
## expect Str.toI128 "-1" == Ok -1i128 ## expect Str.toI128 "-1" == Ok -1i128
## expect Str.toI128 "0.1" == Err InvalidNumStr ## expect Str.toI128 "0.1" == Err InvalidNumStr
## expect Str.toI128 "not a number" == Err InvalidNumStr ## expect Str.toI128 "not a number" == Err InvalidNumStr
## ```
toI128 : Str -> Result I128 [InvalidNumStr] toI128 : Str -> Result I128 [InvalidNumStr]
toI128 = \string -> strToNumHelp string toI128 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers ## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It ## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It
## can be specified with a u64 suffix. ## can be specified with a u64 suffix.
## ## ```
## expect Str.toU64 "1500" == Ok 1500u64 ## expect Str.toU64 "1500" == Ok 1500u64
## expect Str.toU64 "0.1" == Err InvalidNumStr ## expect Str.toU64 "0.1" == Err InvalidNumStr
## expect Str.toU64 "-1" == Err InvalidNumStr ## expect Str.toU64 "-1" == Err InvalidNumStr
## expect Str.toU64 "not a number" == Err InvalidNumStr ## expect Str.toU64 "not a number" == Err InvalidNumStr
## ```
toU64 : Str -> Result U64 [InvalidNumStr] toU64 : Str -> Result U64 [InvalidNumStr]
toU64 = \string -> strToNumHelp string toU64 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers ## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be ## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be
## specified with a i64 suffix. ## specified with a i64 suffix.
## ## ```
## expect Str.toI64 "1500" == Ok 1500i64 ## expect Str.toI64 "1500" == Ok 1500i64
## expect Str.toI64 "-1" == Ok -1i64 ## expect Str.toI64 "-1" == Ok -1i64
## expect Str.toI64 "0.1" == Err InvalidNumStr ## expect Str.toI64 "0.1" == Err InvalidNumStr
## expect Str.toI64 "not a number" == Err InvalidNumStr ## expect Str.toI64 "not a number" == Err InvalidNumStr
## ```
toI64 : Str -> Result I64 [InvalidNumStr] toI64 : Str -> Result I64 [InvalidNumStr]
toI64 = \string -> strToNumHelp string toI64 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers ## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
## from `0` to `4_294_967_295` (over 4 billion). It can be specified with ## from `0` to `4_294_967_295` (over 4 billion). It can be specified with
## a u32 suffix. ## a u32 suffix.
## ## ```
## expect Str.toU32 "1500" == Ok 1500u32 ## expect Str.toU32 "1500" == Ok 1500u32
## expect Str.toU32 "0.1" == Err InvalidNumStr ## expect Str.toU32 "0.1" == Err InvalidNumStr
## expect Str.toU32 "-1" == Err InvalidNumStr ## expect Str.toU32 "-1" == Err InvalidNumStr
## expect Str.toU32 "not a number" == Err InvalidNumStr ## expect Str.toU32 "not a number" == Err InvalidNumStr
## ```
toU32 : Str -> Result U32 [InvalidNumStr] toU32 : Str -> Result U32 [InvalidNumStr]
toU32 = \string -> strToNumHelp string toU32 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers ## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
## from `-2_147_483_648` to `2_147_483_647`. It can be ## from `-2_147_483_648` to `2_147_483_647`. It can be
## specified with a i32 suffix. ## specified with a i32 suffix.
## ## ```
## expect Str.toI32 "1500" == Ok 1500i32 ## expect Str.toI32 "1500" == Ok 1500i32
## expect Str.toI32 "-1" == Ok -1i32 ## expect Str.toI32 "-1" == Ok -1i32
## expect Str.toI32 "0.1" == Err InvalidNumStr ## expect Str.toI32 "0.1" == Err InvalidNumStr
## expect Str.toI32 "not a number" == Err InvalidNumStr ## expect Str.toI32 "not a number" == Err InvalidNumStr
## ```
toI32 : Str -> Result I32 [InvalidNumStr] toI32 : Str -> Result I32 [InvalidNumStr]
toI32 = \string -> strToNumHelp string toI32 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers ## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
## from `0` to `65_535`. It can be specified with a u16 suffix. ## from `0` to `65_535`. It can be specified with a u16 suffix.
## ## ```
## expect Str.toU16 "1500" == Ok 1500u16 ## expect Str.toU16 "1500" == Ok 1500u16
## expect Str.toU16 "0.1" == Err InvalidNumStr ## expect Str.toU16 "0.1" == Err InvalidNumStr
## expect Str.toU16 "-1" == Err InvalidNumStr ## expect Str.toU16 "-1" == Err InvalidNumStr
## expect Str.toU16 "not a number" == Err InvalidNumStr ## expect Str.toU16 "not a number" == Err InvalidNumStr
## ```
toU16 : Str -> Result U16 [InvalidNumStr] toU16 : Str -> Result U16 [InvalidNumStr]
toU16 = \string -> strToNumHelp string toU16 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers ## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
## from `-32_768` to `32_767`. It can be ## from `-32_768` to `32_767`. It can be
## specified with a i16 suffix. ## specified with a i16 suffix.
## ## ```
## expect Str.toI16 "1500" == Ok 1500i16 ## expect Str.toI16 "1500" == Ok 1500i16
## expect Str.toI16 "-1" == Ok -1i16 ## expect Str.toI16 "-1" == Ok -1i16
## expect Str.toI16 "0.1" == Err InvalidNumStr ## expect Str.toI16 "0.1" == Err InvalidNumStr
## expect Str.toI16 "not a number" == Err InvalidNumStr ## expect Str.toI16 "not a number" == Err InvalidNumStr
## ```
toI16 : Str -> Result I16 [InvalidNumStr] toI16 : Str -> Result I16 [InvalidNumStr]
toI16 = \string -> strToNumHelp string toI16 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers ## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
## from `0` to `255`. It can be specified with a u8 suffix. ## from `0` to `255`. It can be specified with a u8 suffix.
## ## ```
## expect Str.toU8 "250" == Ok 250u8 ## expect Str.toU8 "250" == Ok 250u8
## expect Str.toU8 "-0.1" == Err InvalidNumStr ## expect Str.toU8 "-0.1" == Err InvalidNumStr
## expect Str.toU8 "not a number" == Err InvalidNumStr ## expect Str.toU8 "not a number" == Err InvalidNumStr
## expect Str.toU8 "1500" == Err InvalidNumStr ## expect Str.toU8 "1500" == Err InvalidNumStr
## ```
toU8 : Str -> Result U8 [InvalidNumStr] toU8 : Str -> Result U8 [InvalidNumStr]
toU8 = \string -> strToNumHelp string toU8 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers ## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
## from `-128` to `127`. It can be ## from `-128` to `127`. It can be
## specified with a i8 suffix. ## specified with a i8 suffix.
## ## ```
## expect Str.toI8 "-15" == Ok -15i8 ## expect Str.toI8 "-15" == Ok -15i8
## expect Str.toI8 "150.00" == Err InvalidNumStr ## expect Str.toI8 "150.00" == Err InvalidNumStr
## expect Str.toI8 "not a number" == Err InvalidNumStr ## expect Str.toI8 "not a number" == Err InvalidNumStr
## ```
toI8 : Str -> Result I8 [InvalidNumStr] toI8 : Str -> Result I8 [InvalidNumStr]
toI8 = \string -> strToNumHelp string toI8 = \string -> strToNumHelp string
@ -474,8 +501,9 @@ toI8 = \string -> strToNumHelp string
getUnsafe : Str, Nat -> U8 getUnsafe : Str, Nat -> U8
## Gives the number of bytes in a [Str] value. ## Gives the number of bytes in a [Str] value.
## ## ```
## expect Str.countUtf8Bytes "Hello World" == 11 ## expect Str.countUtf8Bytes "Hello World" == 11
## ```
countUtf8Bytes : Str -> Nat countUtf8Bytes : Str -> Nat
## string slice that does not do bounds checking or utf-8 verification ## string slice that does not do bounds checking or utf-8 verification
@ -483,9 +511,10 @@ substringUnsafe : Str, Nat, Nat -> Str
## Returns the given [Str] with each occurrence of a substring replaced. ## Returns the given [Str] with each occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found. ## Returns [Err NotFound] if the substring is not found.
## ## ```
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz" ## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == Err NotFound ## expect Str.replaceEach "not here" "/" "_" == Err NotFound
## ```
replaceEach : Str, Str, Str -> Result Str [NotFound] replaceEach : Str, Str, Str -> Result Str [NotFound]
replaceEach = \haystack, needle, flower -> replaceEach = \haystack, needle, flower ->
when splitFirst haystack needle is when splitFirst haystack needle is
@ -515,9 +544,10 @@ expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
## Returns the given [Str] with the first occurrence of a substring replaced. ## Returns the given [Str] with the first occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found. ## Returns [Err NotFound] if the substring is not found.
## ## ```
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz" ## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound ## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
## ```
replaceFirst : Str, Str, Str -> Result Str [NotFound] replaceFirst : Str, Str, Str -> Result Str [NotFound]
replaceFirst = \haystack, needle, flower -> replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is when splitFirst haystack needle is
@ -530,9 +560,10 @@ expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
## Returns the given [Str] with the last occurrence of a substring replaced. ## Returns the given [Str] with the last occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found. ## Returns [Err NotFound] if the substring is not found.
## ## ```
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz" ## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound ## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
## ```
replaceLast : Str, Str, Str -> Result Str [NotFound] replaceLast : Str, Str, Str -> Result Str [NotFound]
replaceLast = \haystack, needle, flower -> replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is when splitLast haystack needle is
@ -546,9 +577,10 @@ expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well ## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
## as the rest of the string after that occurrence. ## as the rest of the string after that occurrence.
## Returns [ Err NotFound] if the delimiter is not found. ## Returns [ Err NotFound] if the delimiter is not found.
## ## ```
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" } ## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
## expect Str.splitFirst "no slashes here" "/" == Err NotFound ## expect Str.splitFirst "no slashes here" "/" == Err NotFound
## ```
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound] splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitFirst = \haystack, needle -> splitFirst = \haystack, needle ->
when firstMatch haystack needle is when firstMatch haystack needle is
@ -599,9 +631,10 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
## Returns the given [Str] before the last occurrence of a delimiter, as well as ## Returns the given [Str] before the last occurrence of a delimiter, as well as
## the rest of the string after that occurrence. ## the rest of the string after that occurrence.
## Returns [Err NotFound] if the delimiter is not found. ## Returns [Err NotFound] if the delimiter is not found.
## ## ```
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" } ## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
## expect Str.splitLast "no slashes here" "/" == Err NotFound ## expect Str.splitLast "no slashes here" "/" == Err NotFound
## ```
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound] splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitLast = \haystack, needle -> splitLast = \haystack, needle ->
when lastMatch haystack needle is when lastMatch haystack needle is
@ -690,10 +723,11 @@ matchesAtHelp = \state ->
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update ## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
## state for each byte. The index for that byte in the string is provided ## state for each byte. The index for that byte in the string is provided
## to the update function. ## to the update function.
## ## ```
## f : List U8, U8, Nat -> List U8 ## f : List U8, U8, Nat -> List U8
## f = \state, byte, _ -> List.append state byte ## f = \state, byte, _ -> List.append state byte
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67] ## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
## ```
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
walkUtf8WithIndex = \string, state, step -> walkUtf8WithIndex = \string, state, step ->
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string) walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
@ -716,9 +750,10 @@ appendScalarUnsafe : Str, U32 -> Str
## Append a [U32] scalar to the given string. If the given scalar is not a valid ## Append a [U32] scalar to the given string. If the given scalar is not a valid
## unicode value, it returns [Err InvalidScalar]. ## unicode value, it returns [Err InvalidScalar].
## ## ```
## expect Str.appendScalar "H" 105 == Ok "Hi" ## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar ## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
## ```
appendScalar : Str, U32 -> Result Str [InvalidScalar] appendScalar : Str, U32 -> Result Str [InvalidScalar]
appendScalar = \string, scalar -> appendScalar = \string, scalar ->
if isValidScalar scalar then if isValidScalar scalar then
@ -734,10 +769,11 @@ getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
## Walks over the unicode [U32] values for the given [Str] and calls a function ## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each. ## to update state for each.
## ## ```
## f : List U32, U32 -> List U32 ## f : List U32, U32 -> List U32
## f = \state, scalar -> List.append state scalar ## f = \state, scalar -> List.append state scalar
## expect Str.walkScalars "ABC" [] f == [65, 66, 67] ## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
## ```
walkScalars : Str, state, (state, U32 -> state) -> state walkScalars : Str, state, (state, U32 -> state) -> state
walkScalars = \string, init, step -> walkScalars = \string, init, step ->
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string) walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
@ -754,16 +790,17 @@ walkScalarsHelp = \string, state, step, index, length ->
## Walks over the unicode [U32] values for the given [Str] and calls a function ## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each. ## to update state for each.
## ## ```
## f : List U32, U32 -> [Break (List U32), Continue (List U32)] ## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
## f = \state, scalar -> ## f = \state, scalar ->
## check = 66 ## check = 66
## if scalar == check then ## if scalar == check then
## Break [check] ## Break [check]
## else ## else
## Continue (List.append state scalar) ## Continue (List.append state scalar)
## expect Str.walkScalarsUntil "ABC" [] f == [66] ## expect Str.walkScalarsUntil "ABC" [] f == [66]
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67] ## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
## ```
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
walkScalarsUntil = \string, init, step -> walkScalarsUntil = \string, init, step ->
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string) walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
@ -795,7 +832,8 @@ strToNumHelp = \string ->
Err InvalidNumStr Err InvalidNumStr
## Adds a prefix to the given [Str]. ## Adds a prefix to the given [Str].
## ## ```
## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome" ## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
## ```
withPrefix : Str, Str -> Str withPrefix : Str, Str -> Str
withPrefix = \str, prefix -> Str.concat prefix str withPrefix = \str, prefix -> Str.concat prefix str

View file

@ -1,73 +1,6 @@
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use std::ops::Index; use std::ops::Index;
use tempfile::NamedTempFile;
pub const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-wasm32.o"));
// TODO: in the future, we should use Zig's cross-compilation to generate and store these
// for all targets, so that we can do cross-compilation!
#[cfg(unix)]
pub const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-host.o"));
#[cfg(windows)]
pub const HOST_WINDOWS: &[u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/bitcode/builtins-windows-x86_64.obj"
));
pub fn host_wasm_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".wasm")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WASM)?;
Ok(tempfile)
}
#[cfg(unix)]
fn host_unix_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".o")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_UNIX)?;
Ok(tempfile)
}
#[cfg(windows)]
fn host_windows_tempfile() -> std::io::Result<NamedTempFile> {
let tempfile = tempfile::Builder::new()
.prefix("host_bitcode")
.suffix(".obj")
.rand_bytes(8)
.tempfile()?;
std::fs::write(tempfile.path(), HOST_WINDOWS)?;
Ok(tempfile)
}
pub fn host_tempfile() -> std::io::Result<NamedTempFile> {
#[cfg(unix)]
{
host_unix_tempfile()
}
#[cfg(windows)]
{
host_windows_tempfile()
}
#[cfg(not(any(windows, unix)))]
{
unreachable!()
}
}
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub struct IntrinsicName { pub struct IntrinsicName {
@ -356,8 +289,16 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.
pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName = pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName =
float_intrinsic!("roc_builtins.num.mul_with_overflow"); float_intrinsic!("roc_builtins.num.mul_with_overflow");
pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_leading_zero_bits");
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_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_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_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";

View file

@ -1,28 +1,29 @@
[package] [package]
name = "roc_can" name = "roc_can"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Canonicalize a roc abstract syntax tree, resolving symbols, re-ordering definitions, and preparing a module for type inference." description = "Canonicalize a roc abstract syntax tree, resolving symbols, re-ordering definitions, and preparing a module for type inference."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
roc_exhaustive = { path = "../exhaustive" } roc_exhaustive = { path = "../exhaustive" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_region = { path = "../region" }
roc_serialize = { path = "../serialize" } roc_serialize = { path = "../serialize" }
roc_types = { path = "../types" }
bumpalo.workspace = true
static_assertions.workspace = true
bitvec.workspace = true
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bitvec.workspace = true
bumpalo.workspace = true
static_assertions.workspace = true
[dev-dependencies] [dev-dependencies]
pretty_assertions.workspace = true
indoc.workspace = true indoc.workspace = true
pretty_assertions.workspace = true

View file

@ -187,6 +187,8 @@ map_symbol_to_lowlevel_and_arity! {
NumAsin; NUM_ASIN; 1, NumAsin; NUM_ASIN; 1,
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2, NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
NumBytesToU32; NUM_BYTES_TO_U32_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, NumBitwiseAnd; NUM_BITWISE_AND; 2,
NumBitwiseXor; NUM_BITWISE_XOR; 2, NumBitwiseXor; NUM_BITWISE_XOR; 2,
NumBitwiseOr; NUM_BITWISE_OR; 2, NumBitwiseOr; NUM_BITWISE_OR; 2,
@ -194,6 +196,9 @@ map_symbol_to_lowlevel_and_arity! {
NumShiftRightBy; NUM_SHIFT_RIGHT; 2, NumShiftRightBy; NUM_SHIFT_RIGHT; 2,
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2, NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
NumToStr; NUM_TO_STR; 1, NumToStr; NUM_TO_STR; 1,
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
Eq; BOOL_STRUCTURAL_EQ; 2, Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2, NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,

View file

@ -385,7 +385,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
), ),
Crash { .. } => todo!(), Crash { .. } => todo!(),
ZeroArgumentTag { .. } => todo!(), ZeroArgumentTag { .. } => todo!(),
OpaqueRef { .. } => todo!(), OpaqueRef { name, argument, .. } => maybe_paren!(
Free,
p,
|| true,
pp_sym(c, f, *name)
.append(f.space())
.append(expr(c, AppArg, f, &argument.1.value))
.group()
),
Dbg { .. } => todo!(), Dbg { .. } => todo!(),
Expect { .. } => todo!(), Expect { .. } => todo!(),
ExpectFx { .. } => todo!(), ExpectFx { .. } => todo!(),

View file

@ -1,16 +1,17 @@
[package] [package]
name = "roc_collections" name = "roc_collections"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Domain-specific collections created for the needs of the compiler." description = "Domain-specific collections created for the needs of the compiler."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
fnv.workspace = true
im.workspace = true
im-rc.workspace = true
wyhash.workspace = true
bumpalo.workspace = true
hashbrown.workspace = true
bitvec.workspace = true bitvec.workspace = true
bumpalo.workspace = true
fnv.workspace = true
hashbrown.workspace = true
im-rc.workspace = true
im.workspace = true
wyhash.workspace = true

View file

@ -1,18 +1,20 @@
[package] [package]
name = "roc_constrain" name = "roc_constrain"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Responsible for building the set of constraints that are used during type inference of a program, and for gathering context needed for pleasant error messages when a type error occurs." description = "Responsible for building the set of constraints that are used during type inference of a program, and for gathering context needed for pleasant error messages when a type error occurs."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_can = { path = "../can" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" }
arrayvec = "0.7.2" arrayvec.workspace = true

View file

@ -1,9 +1,10 @@
[package] [package]
name = "roc_debug_flags" name = "roc_debug_flags"
version = "0.0.1"
edition = "2021"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
description = "Environment variables that can be toggled to aid debugging of the compiler itself." description = "Environment variables that can be toggled to aid debugging of the compiler itself."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]

View file

@ -1,25 +1,27 @@
[package] [package]
name = "roc_derive" name = "roc_derive"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides auto-derivers for builtin abilities like `Hash` and `Decode`." description = "Provides auto-derivers for builtin abilities like `Hash` and `Decode`."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_derive_key = { path = "../derive_key" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_derive_key = { path = "../derive_key" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
bumpalo.workspace = true bumpalo.workspace = true
[features] [features]
default = []
debug-derived-symbols = ["roc_module/debug-symbols"] debug-derived-symbols = ["roc_module/debug-symbols"]
default = []
# Enables open extension variables for constructed records and tag unions. # Enables open extension variables for constructed records and tag unions.
# This is not necessary for code generation, but may be necessary if you are # This is not necessary for code generation, but may be necessary if you are
# constraining and solving generated derived bodies. # constraining and solving generated derived bodies.

View file

@ -1,14 +1,14 @@
[package] [package]
name = "roc_derive_key" name = "roc_derive_key"
version = "0.0.1"
authors = ["The Roc Contributors"] authors.workspace = true
license = "UPL-1.0" edition.workspace = true
edition = "2021" license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_region = { path = "../region" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" }

View file

@ -1,14 +1,15 @@
[package] [package]
name = "roc_exhaustive" name = "roc_exhaustive"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides exhaustiveness checking for Roc." description = "Provides exhaustiveness checking for Roc."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_region = { path = "../region" }

View file

@ -1,14 +1,16 @@
[package] [package]
name = "roc_fmt" name = "roc_fmt"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "The roc code formatter." description = "The roc code formatter."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
bumpalo.workspace = true roc_region = { path = "../region" }
bumpalo.workspace = true

View file

@ -38,7 +38,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
let braces_indent = indent; let braces_indent = indent;
let item_indent = braces_indent + INDENT; let item_indent = braces_indent + INDENT;
if newline == Newlines::Yes { if newline == Newlines::Yes {
buf.newline(); buf.ensure_ends_with_newline();
} }
buf.indent(braces_indent); buf.indent(braces_indent);
buf.push(start); buf.push(start);

View file

@ -61,7 +61,7 @@ impl<'a> Formattable for TypeDef<'a> {
&self, &self,
buf: &mut Buf<'buf>, buf: &mut Buf<'buf>,
_parens: Parens, _parens: Parens,
_newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
) { ) {
use roc_parse::ast::TypeDef::*; use roc_parse::ast::TypeDef::*;
@ -97,22 +97,10 @@ impl<'a> Formattable for TypeDef<'a> {
ann.format(buf, indent) ann.format(buf, indent)
} }
Opaque { Opaque {
header: TypeHeader { name, vars }, header,
typ: ann, typ: ann,
derived: has_abilities, derived: has_abilities,
} => { } => {
buf.indent(indent);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
}
buf.push_str(" :=");
buf.spaces(1);
let ann_is_where_clause = let ann_is_where_clause =
matches!(ann.extract_spaces().item, TypeAnnotation::Where(..)); matches!(ann.extract_spaces().item, TypeAnnotation::Where(..));
@ -126,7 +114,7 @@ impl<'a> Formattable for TypeDef<'a> {
let make_multiline = ann.is_multiline() || has_abilities_multiline; let make_multiline = ann.is_multiline() || has_abilities_multiline;
ann.format(buf, indent); fmt_general_def(header, buf, indent, ":=", &ann.value, newlines);
if let Some(has_abilities) = has_abilities { if let Some(has_abilities) = has_abilities {
buf.spaces(1); buf.spaces(1);
@ -178,6 +166,29 @@ impl<'a> Formattable for TypeDef<'a> {
} }
} }
impl<'a> Formattable for TypeHeader<'a> {
fn is_multiline(&self) -> bool {
self.vars.iter().any(|v| v.is_multiline())
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(self.name.value);
for var in self.vars.iter() {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
}
}
}
impl<'a> Formattable for ValueDef<'a> { impl<'a> Formattable for ValueDef<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use roc_parse::ast::ValueDef::*; use roc_parse::ast::ValueDef::*;
@ -204,63 +215,14 @@ impl<'a> Formattable for ValueDef<'a> {
use roc_parse::ast::ValueDef::*; use roc_parse::ast::ValueDef::*;
match self { match self {
Annotation(loc_pattern, loc_annotation) => { Annotation(loc_pattern, loc_annotation) => {
loc_pattern.format(buf, indent); fmt_general_def(
buf.indent(indent); loc_pattern,
buf,
if loc_annotation.is_multiline() { indent,
buf.push_str(" :"); ":",
buf.spaces(1); &loc_annotation.value,
newlines,
let should_outdent = match loc_annotation.value { );
TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def {
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines && sub_def.is_multiline()
}
_ => false,
},
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true,
_ => false,
};
if should_outdent {
match loc_annotation.value {
TypeAnnotation::SpaceBefore(sub_def, _) => {
sub_def.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
_ => {
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
}
} else {
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
newlines,
indent + INDENT,
);
}
} else {
buf.spaces(1);
buf.push(':');
buf.spaces(1);
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
} }
Body(loc_pattern, loc_expr) => { Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent); fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
@ -277,34 +239,7 @@ impl<'a> Formattable for ValueDef<'a> {
body_pattern, body_pattern,
body_expr, body_expr,
} => { } => {
let is_type_multiline = ann_type.is_multiline(); fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines);
let is_type_function = matches!(
ann_type.value,
TypeAnnotation::Function(..)
| TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..)
| TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..)
);
let next_indent = if is_type_multiline {
indent + INDENT
} else {
indent
};
ann_pattern.format(buf, indent);
buf.push_str(" :");
if is_type_multiline && is_type_function {
ann_type.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
next_indent,
);
} else {
buf.spaces(1);
ann_type.format(buf, indent);
}
if let Some(comment_str) = comment { if let Some(comment_str) = comment {
buf.push_str(" #"); buf.push_str(" #");
@ -319,6 +254,66 @@ impl<'a> Formattable for ValueDef<'a> {
} }
} }
fn fmt_general_def<L: Formattable>(
lhs: L,
buf: &mut Buf,
indent: u16,
sep: &str,
rhs: &TypeAnnotation,
newlines: Newlines,
) {
lhs.format(buf, indent);
buf.indent(indent);
if rhs.is_multiline() {
buf.spaces(1);
buf.push_str(sep);
buf.spaces(1);
let should_outdent = should_outdent(rhs);
if should_outdent {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, _) => {
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
_ => {
rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
} else {
rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
}
} else {
buf.spaces(1);
buf.push_str(sep);
buf.spaces(1);
rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
fn should_outdent(mut rhs: &TypeAnnotation) -> bool {
loop {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, spaces) => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
if !is_only_newlines || !sub_def.is_multiline() {
return false;
}
rhs = sub_def;
}
TypeAnnotation::Where(ann, _clauses) => {
if !ann.is_multiline() {
return false;
}
rhs = &ann.value;
}
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true,
_ => return false,
}
}
}
fn fmt_dbg_in_def<'a, 'buf>( fn fmt_dbg_in_def<'a, 'buf>(
buf: &mut Buf<'buf>, buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>, condition: &'a Loc<Expr<'a>>,

View file

@ -1,4 +1,4 @@
use crate::annotation::{except_last, Formattable, Newlines, Parens}; use crate::annotation::{except_last, is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces}; use crate::collection::{fmt_collection, Braces};
use crate::def::fmt_defs; use crate::def::fmt_defs;
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
@ -49,7 +49,7 @@ impl<'a> Formattable for Expr<'a> {
// These expressions always have newlines // These expressions always have newlines
Defs(_, _) | When(_, _) => true, Defs(_, _) | When(_, _) => true,
List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()), List(items) => is_collection_multiline(items),
Str(literal) => is_str_multiline(literal), Str(literal) => is_str_multiline(literal),
Apply(loc_expr, args, _) => { Apply(loc_expr, args, _) => {
@ -96,9 +96,9 @@ impl<'a> Formattable for Expr<'a> {
.any(|loc_pattern| loc_pattern.is_multiline()) .any(|loc_pattern| loc_pattern.is_multiline())
} }
Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()), Record(fields) => is_collection_multiline(fields),
Tuple(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()), Tuple(fields) => is_collection_multiline(fields),
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()), RecordUpdate { fields, .. } => is_collection_multiline(fields),
} }
} }
@ -1319,7 +1319,7 @@ fn fmt_record<'a, 'buf>(
let loc_fields = fields.items; let loc_fields = fields.items;
let final_comments = fields.final_comments(); let final_comments = fields.final_comments();
buf.indent(indent); buf.indent(indent);
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) { if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) && update.is_none() {
buf.push_str("{}"); buf.push_str("{}");
} else { } else {
buf.push('{'); buf.push('{');

View file

@ -1,28 +1,29 @@
[package] [package]
name = "roc_gen_dev" name = "roc_gen_dev"
description = "The development backend for the Roc compiler" description = "The development backend for the Roc compiler"
version = "0.0.1"
authors = ["The Roc Contributors"] authors.workspace = true
license = "UPL-1.0" edition.workspace = true
edition = "2021" license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" } roc_collections = { path = "../collections" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
bumpalo.workspace = true bumpalo.workspace = true
target-lexicon.workspace = true
object.workspace = true object.workspace = true
packed_struct.workspace = true packed_struct.workspace = true
target-lexicon.workspace = true
[dev-dependencies] [dev-dependencies]
roc_can = { path = "../can" } roc_can = { path = "../can" }

View file

@ -105,10 +105,10 @@ This is the general procedure I follow with some helpful links:
Also, sometimes it doesn't seem to generate things quite as you expect. Also, sometimes it doesn't seem to generate things quite as you expect.
- [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) - - [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) -
Like previous but with more architecture options. Like previous but with more architecture options.
- [x86 and amd64 instruction reference](https://www.felixcloutier.com/x86/) - - [x86 and amd64 instruction reference](https://web.archive.org/web/20230221053750/https://www.felixcloutier.com/x86/) -
Great for looking up x86_64 instructions and there bytes. Great for looking up x86_64 instructions and there bytes.
Definitely missing information if you aren't used to reading it. Definitely missing information if you aren't used to reading it.
- [Intel 64 ISA Reference](https://software.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf) - - [Intel 64 ISA Reference](https://community.intel.com/legacyfs/online/drupal_files/managed/a4/60/325383-sdm-vol-2abcd.pdf) -
Super dense manual. Super dense manual.
Contains everything you would need to know for x86_64. Contains everything you would need to know for x86_64.
Also is like 2000 pages. Also is like 2000 pages.

View file

@ -2,10 +2,13 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::Relocation; use crate::Relocation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use packed_struct::prelude::*; use packed_struct::prelude::*;
use roc_builtins::bitcode::FloatWidth;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{InLayout, STLayoutInterner}; use roc_mono::layout::{InLayout, STLayoutInterner};
use super::CompareOperation;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum AArch64GeneralReg { pub enum AArch64GeneralReg {
@ -609,9 +612,31 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
} }
#[inline(always)] #[inline(always)]
fn mov_reg32_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
todo!()
}
#[inline(always)]
fn mov_reg16_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
todo!()
}
#[inline(always)]
fn mov_reg8_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
todo!()
}
#[inline(always)]
fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
todo!("saving floating point reg to base offset for AArch64"); todo!("saving floating point reg to base offset for AArch64");
} }
#[inline(always)]
fn movesd_mem64_offset32_freg64(
_buf: &mut Vec<'_, u8>,
_ptr: AArch64GeneralReg,
_offset: i32,
_src: AArch64FloatReg,
) {
todo!()
}
#[inline(always)] #[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 { if offset < 0 {
@ -624,6 +649,19 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
} }
#[inline(always)]
fn mov_base32_reg32(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
todo!()
}
#[inline(always)]
fn mov_base32_reg16(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
todo!()
}
#[inline(always)]
fn mov_base32_reg8(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
todo!()
}
#[inline(always)] #[inline(always)]
fn mov_reg64_mem64_offset32( fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -640,6 +678,41 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("mem offsets over 32k for AArch64"); todo!("mem offsets over 32k for AArch64");
} }
} }
#[inline(always)]
fn mov_reg32_mem32_offset32(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src: AArch64GeneralReg,
offset: i32,
) {
if offset < 0 {
todo!("negative mem offsets for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
ldr_reg64_reg64_imm12(buf, dst, src, (offset as u16) >> 3);
} else {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn mov_reg16_mem16_offset32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src: AArch64GeneralReg,
_offset: i32,
) {
todo!()
}
#[inline(always)]
fn mov_reg8_mem8_offset32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src: AArch64GeneralReg,
_offset: i32,
) {
todo!()
}
#[inline(always)] #[inline(always)]
fn mov_mem64_offset32_reg64( fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -657,6 +730,36 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
} }
#[inline(always)]
fn mov_mem32_offset32_reg32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_offset: i32,
_src: AArch64GeneralReg,
) {
todo!()
}
#[inline(always)]
fn mov_mem16_offset32_reg16(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_offset: i32,
_src: AArch64GeneralReg,
) {
todo!()
}
#[inline(always)]
fn mov_mem8_offset32_reg8(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_offset: i32,
_src: AArch64GeneralReg,
) {
todo!()
}
#[inline(always)] #[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) { fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8); debug_assert!(size <= 8);
@ -740,12 +843,12 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_reg64( fn sub_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg, dst: AArch64GeneralReg,
_src1: AArch64GeneralReg, src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, src2: AArch64GeneralReg,
) { ) {
todo!("registers subtractions for AArch64"); sub_reg64_reg64_reg64(buf, dst, src1, src2);
} }
#[inline(always)] #[inline(always)]
@ -788,6 +891,18 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("registers unsigned less than for AArch64"); todo!("registers unsigned less than for AArch64");
} }
#[inline(always)]
fn cmp_freg_freg_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
_width: FloatWidth,
_operation: CompareOperation,
) {
todo!("registers float comparison for AArch64");
}
#[inline(always)] #[inline(always)]
fn igt_reg64_reg64_reg64( fn igt_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>, _buf: &mut Vec<'_, u8>,
@ -938,6 +1053,14 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
{ {
todo!("sar for AArch64") todo!("sar for AArch64")
} }
fn sqrt_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
todo!("sqrt")
}
fn sqrt_freg32_freg32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
todo!("sqrt")
}
} }
impl AArch64Assembler {} impl AArch64Assembler {}
@ -1354,6 +1477,19 @@ fn sub_reg64_reg64_imm12(
buf.extend(inst.bytes()); buf.extend(inst.bytes());
} }
/// `SUB Xd, Xm, Xn` -> Subtract Xm and Xn and place the result into Xd.
#[inline(always)]
fn sub_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src1: AArch64GeneralReg,
src2: AArch64GeneralReg,
) {
let inst = ArithmeticShifted::new(true, false, ShiftType::LSL, 0, src2, src1, dst);
buf.extend(inst.bytes());
}
/// `RET Xn` -> Return to the address stored in Xn. /// `RET Xn` -> Return to the address stored in Xn.
#[inline(always)] #[inline(always)]
fn ret_reg64(buf: &mut Vec<'_, u8>, xn: AArch64GeneralReg) { fn ret_reg64(buf: &mut Vec<'_, u8>, xn: AArch64GeneralReg) {
@ -1574,6 +1710,34 @@ mod tests {
); );
} }
#[test]
fn test_sub_reg64_reg64_reg64() {
disassembler_test!(
sub_reg64_reg64_reg64,
|reg1: AArch64GeneralReg, reg2: AArch64GeneralReg, reg3: AArch64GeneralReg| {
if reg2 == AArch64GeneralReg::ZRSP {
// When the second register is ZR, it gets disassembled as neg,
// which is an alias for sub.
format!(
"neg {}, {}",
reg1.capstone_string(UsesZR),
reg3.capstone_string(UsesZR)
)
} else {
format!(
"sub {}, {}, {}",
reg1.capstone_string(UsesZR),
reg2.capstone_string(UsesZR),
reg3.capstone_string(UsesZR)
)
}
},
ALL_GENERAL_REGS,
ALL_GENERAL_REGS,
ALL_GENERAL_REGS
);
}
#[test] #[test]
fn test_ret_reg64() { fn test_ret_reg64() {
disassembler_test!( disassembler_test!(

View file

@ -2,7 +2,7 @@ use crate::{
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env, single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env,
Relocation, Relocation,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::{CollectIn, Vec};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
@ -113,6 +113,13 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
); );
} }
pub enum CompareOperation {
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
}
/// Assembler contains calls to the backend assembly generator. /// Assembler contains calls to the backend assembly generator.
/// These calls do not necessarily map directly to a single assembly instruction. /// These calls do not necessarily map directly to a single assembly instruction.
/// They are higher level in cases where an instruction would not be common and shared between multiple architectures. /// They are higher level in cases where an instruction would not be common and shared between multiple architectures.
@ -236,22 +243,67 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
// base32 is similar to stack based instructions but they reference the base/frame pointer. // base32 is similar to stack based instructions but they reference the base/frame pointer.
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
// move from memory (a pointer) to register
fn mov_reg64_mem64_offset32( fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
src: GeneralReg, src: GeneralReg,
offset: i32, offset: i32,
); );
fn mov_reg32_mem32_offset32(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src: GeneralReg,
offset: i32,
);
fn mov_reg16_mem16_offset32(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src: GeneralReg,
offset: i32,
);
fn mov_reg8_mem8_offset32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg, offset: i32);
// move from register to memory
fn mov_mem64_offset32_reg64( fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
offset: i32, offset: i32,
src: GeneralReg, src: GeneralReg,
); );
fn mov_mem32_offset32_reg32(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
offset: i32,
src: GeneralReg,
);
fn mov_mem16_offset32_reg16(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
offset: i32,
src: GeneralReg,
);
fn mov_mem8_offset32_reg8(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg);
fn movesd_mem64_offset32_freg64(
buf: &mut Vec<'_, u8>,
ptr: GeneralReg,
offset: i32,
src: FloatReg,
);
/// Sign extends the data at `offset` with `size` as it copies it to `dst` /// Sign extends the data at `offset` with `size` as it copies it to `dst`
/// size must be less than or equal to 8. /// size must be less than or equal to 8.
@ -265,6 +317,9 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn mul_freg32_freg32_freg32( fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -361,6 +416,15 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
src2: GeneralReg, src2: GeneralReg,
); );
fn cmp_freg_freg_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: FloatReg,
src2: FloatReg,
width: FloatWidth,
operation: CompareOperation,
);
fn igt_reg64_reg64_reg64( fn igt_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
@ -958,6 +1022,30 @@ impl<
} }
} }
fn build_num_sub_checked(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
num_layout: &InLayout<'a>,
return_layout: &InLayout<'a>,
) {
let function_name = match self.interner().get(*num_layout) {
Layout::Builtin(Builtin::Int(width)) => &bitcode::NUM_SUB_CHECKED_INT[width],
Layout::Builtin(Builtin::Float(width)) => &bitcode::NUM_SUB_CHECKED_FLOAT[width],
Layout::Builtin(Builtin::Decimal) => bitcode::DEC_SUB_WITH_OVERFLOW,
x => internal_error!("NumSubChecked is not defined for {:?}", x),
};
self.build_fn_call(
dst,
function_name.to_string(),
&[*src1, *src2],
&[*num_layout, *num_layout],
return_layout,
)
}
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) { fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
use Builtin::Int; use Builtin::Int;
@ -1102,7 +1190,7 @@ impl<
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) { fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) {
match *arg_layout { match *arg_layout {
single_register_int_builtins!() => { single_register_int_builtins!() | Layout::BOOL => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .storage_manager
@ -1112,13 +1200,23 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::STR => {
// use a zig call
self.build_fn_call(
dst,
bitcode::STR_EQUAL.to_string(),
&[*src1, *src2],
&[Layout::STR, Layout::STR],
&Layout::BOOL,
)
}
x => todo!("NumEq: layout, {:?}", x), x => todo!("NumEq: layout, {:?}", x),
} }
} }
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) { fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) {
match self.layout_interner.get(*arg_layout) { match *arg_layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { single_register_int_builtins!() | Layout::BOOL => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .storage_manager
@ -1128,10 +1226,44 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::STR => {
self.build_fn_call(
dst,
bitcode::STR_EQUAL.to_string(),
&[*src1, *src2],
&[Layout::STR, Layout::STR],
&Layout::BOOL,
);
// negate the result
let tmp = &Symbol::DEV_TMP;
let tmp_reg = self.storage_manager.claim_general_reg(&mut self.buf, tmp);
ASM::mov_reg64_imm64(&mut self.buf, tmp_reg, 164);
let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst);
ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, tmp_reg);
}
x => todo!("NumNeq: layout, {:?}", x), x => todo!("NumNeq: layout, {:?}", x),
} }
} }
fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) {
match *arg_layout {
Layout::BOOL => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
// Not would usually be implemented as `xor src, -1` followed by `and src, 1`
// but since our booleans are represented as `0x101010101010101` currently, we can simply XOR with that
let bool_val = [true as u8; 8];
ASM::mov_reg64_imm64(&mut self.buf, dst_reg, i64::from_ne_bytes(bool_val));
ASM::xor_reg64_reg64_reg64(&mut self.buf, src_reg, src_reg, dst_reg);
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
}
x => todo!("Not: layout, {:?}", x),
}
}
fn build_num_lt( fn build_num_lt(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -1160,6 +1292,20 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::ult_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::ult_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::Builtin(Builtin::Float(width)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::cmp_freg_freg_reg64(
&mut self.buf,
dst_reg,
src1_reg,
src2_reg,
width,
CompareOperation::LessThan,
);
}
x => todo!("NumLt: layout, {:?}", x), x => todo!("NumLt: layout, {:?}", x),
} }
} }
@ -1192,6 +1338,20 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::ugt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::ugt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::Builtin(Builtin::Float(width)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::cmp_freg_freg_reg64(
&mut self.buf,
dst_reg,
src1_reg,
src2_reg,
width,
CompareOperation::GreaterThan,
);
}
x => todo!("NumGt: layout, {:?}", x), x => todo!("NumGt: layout, {:?}", x),
} }
} }
@ -1272,6 +1432,26 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::F64 | Layout::F32 => {
let width = if *arg_layout == Layout::F64 {
FloatWidth::F64
} else {
FloatWidth::F32
};
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::cmp_freg_freg_reg64(
&mut self.buf,
dst_reg,
src1_reg,
src2_reg,
width,
CompareOperation::LessThanOrEqual,
);
}
x => todo!("NumLte: layout, {:?}", x), x => todo!("NumLte: layout, {:?}", x),
} }
} }
@ -1294,6 +1474,26 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::F64 | Layout::F32 => {
let width = if *arg_layout == Layout::F64 {
FloatWidth::F64
} else {
FloatWidth::F32
};
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::cmp_freg_freg_reg64(
&mut self.buf,
dst_reg,
src1_reg,
src2_reg,
width,
CompareOperation::GreaterThanOrEqual,
);
}
x => todo!("NumGte: layout, {:?}", x), x => todo!("NumGte: layout, {:?}", x),
} }
} }
@ -1518,43 +1718,14 @@ impl<
ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr); ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr);
let element_ptr = tmp; let element_ptr = tmp;
match *ret_layout { Self::ptr_read(
single_register_integers!() if ret_stack_size == 8 => { buf,
let dst_reg = storage_manager.claim_general_reg(buf, dst); storage_manager,
ASM::mov_reg64_mem64_offset32(buf, dst_reg, element_ptr, 0); self.layout_interner,
} element_ptr,
single_register_floats!() => { *ret_layout,
let dst_reg = storage_manager.claim_float_reg(buf, dst); *dst,
ASM::mov_freg64_freg64(buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); );
}
Layout::STR => {
// the `list_ptr` register is now unused, and we can use it as scratch space
let tmp_reg = list_ptr;
Self::unbox_str_or_list(
buf,
storage_manager,
*dst,
element_ptr,
tmp_reg,
);
}
other => {
//
match self.layout_interner.get(other) {
Layout::Boxed(_) => {
let dst_reg = storage_manager.claim_general_reg(buf, dst);
ASM::mov_reg64_reg64(buf, dst_reg, CC::GENERAL_RETURN_REGS[0]);
}
_ => {
todo!(
"cannot load {} from the heap yet",
self.layout_interner.dbg(other)
);
}
}
}
}
}); });
}, },
); );
@ -1822,10 +1993,11 @@ impl<
fn create_array( fn create_array(
&mut self, &mut self,
sym: &Symbol, sym: &Symbol,
element_layout: &InLayout<'a>, element_in_layout: &InLayout<'a>,
elements: &'a [ListLiteralElement<'a>], elements: &[ListLiteralElement<'a>],
) { ) {
let element_width = self.layout_interner.stack_size(*element_layout) as u64; let element_layout = self.layout_interner.get(*element_in_layout);
let element_width = self.layout_interner.stack_size(*element_in_layout) as u64;
// load the total size of the data we want to store (excludes refcount) // load the total size of the data we want to store (excludes refcount)
let data_bytes_symbol = Symbol::DEV_TMP; let data_bytes_symbol = Symbol::DEV_TMP;
@ -1855,54 +2027,34 @@ impl<
.load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3); .load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3);
// Copy everything into output array. // Copy everything into output array.
let mut elem_offset = 0; let mut element_offset = 0;
for elem in elements { for elem in elements {
// TODO: this could be a lot faster when loading large lists // TODO: this could be a lot faster when loading large lists
// if we move matching on the element layout to outside this loop. // if we move matching on the element layout to outside this loop.
// It also greatly bloats the code here. // It also greatly bloats the code here.
// Refactor this and switch to one external match. // Refactor this and switch to one external match.
// We also could make loadining indivitual literals much faster // We also could make loadining indivitual literals much faster
let elem_sym = match elem { let element_symbol = match elem {
ListLiteralElement::Symbol(sym) => sym, ListLiteralElement::Symbol(sym) => *sym,
ListLiteralElement::Literal(lit) => { ListLiteralElement::Literal(lit) => {
self.load_literal(&Symbol::DEV_TMP, element_layout, lit); self.load_literal(&Symbol::DEV_TMP, element_in_layout, lit);
&Symbol::DEV_TMP Symbol::DEV_TMP
} }
}; };
// TODO: Expand to all types.
match self.layout_interner.get(*element_layout) { Self::ptr_write(
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64) | Builtin::Bool) => { &mut self.buf,
let sym_reg = self &mut self.storage_manager,
.storage_manager ptr_reg,
.load_to_general_reg(&mut self.buf, elem_sym); element_offset,
ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, elem_offset, sym_reg); element_width,
} element_layout,
_ if element_width == 0 => {} element_symbol,
_ if element_width > 8 => { );
let (from_offset, size) = self.storage_manager.stack_offset_and_size(elem_sym);
debug_assert!(from_offset % 8 == 0); element_offset += element_width as i32;
debug_assert!(size % 8 == 0); if element_symbol == Symbol::DEV_TMP {
debug_assert_eq!(size as u64, element_width); self.free_symbol(&element_symbol);
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|_storage_manager, buf, tmp_reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i);
ASM::mov_mem64_offset32_reg64(
buf,
ptr_reg,
elem_offset + i,
tmp_reg,
);
}
},
);
}
x => todo!("copying data to list with layout, {:?}", x),
}
elem_offset += element_width as i32;
if elem_sym == &Symbol::DEV_TMP {
self.free_symbol(elem_sym);
} }
} }
@ -2000,38 +2152,15 @@ impl<
let element_width = self.layout_interner.stack_size(element_layout) as u64; let element_width = self.layout_interner.stack_size(element_layout) as u64;
let element_offset = 0; let element_offset = 0;
// TODO: Expand to all types. Self::ptr_write(
match self.layout_interner.get(element_layout) { &mut self.buf,
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { &mut self.storage_manager,
let sym_reg = self ptr_reg,
.storage_manager element_offset,
.load_to_general_reg(&mut self.buf, &value); element_width,
ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, element_offset, sym_reg); self.layout_interner.get(element_layout),
} value,
_ if element_width == 0 => {} );
_ if element_width > 8 => {
let (from_offset, size) = self.storage_manager.stack_offset_and_size(&value);
debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 8 == 0);
debug_assert_eq!(size as u64, element_width);
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|_storage_manager, buf, tmp_reg| {
// a crude memcpy
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i);
ASM::mov_mem64_offset32_reg64(
buf,
ptr_reg,
element_offset + i,
tmp_reg,
);
}
},
);
}
x => todo!("copying data to list with layout, {:?}", x),
}
if value == Symbol::DEV_TMP { if value == Symbol::DEV_TMP {
self.free_symbol(&value); self.free_symbol(&value);
@ -2049,25 +2178,14 @@ impl<
.storage_manager .storage_manager
.load_to_general_reg(&mut self.buf, &ptr); .load_to_general_reg(&mut self.buf, &ptr);
let ret_stack_size = self.layout_interner.stack_size(element_layout); Self::ptr_read(
&mut self.buf,
match element_layout { &mut self.storage_manager,
single_register_integers!() if ret_stack_size == 8 => { self.layout_interner,
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); ptr_reg,
ASM::mov_reg64_mem64_offset32(&mut self.buf, dst_reg, ptr_reg, 0); element_layout,
} dst,
Layout::STR => { );
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|storage_manager, buf, tmp_reg| {
Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg);
},
);
}
_ => {
todo!("unboxing of {:?}", self.layout_interner.dbg(element_layout))
}
}
} }
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
@ -2116,6 +2234,33 @@ impl<
let val = *x; let val = *x;
ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64);
} }
(
Literal::Int(bytes),
Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)),
) => {
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|storage_manager, buf, reg| {
let base_offset = storage_manager.claim_stack_area(sym, 16);
let mut num_bytes = [0; 8];
num_bytes.copy_from_slice(&bytes[..8]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset, reg);
num_bytes.copy_from_slice(&bytes[8..16]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset + 8, reg);
},
);
}
(Literal::Byte(x), Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8))) => {
let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
let val = *x;
ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64);
}
(Literal::Bool(x), Layout::Builtin(Builtin::Bool)) => { (Literal::Bool(x), Layout::Builtin(Builtin::Bool)) => {
let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
let val = [*x as u8; 16]; let val = [*x as u8; 16];
@ -2131,15 +2276,11 @@ impl<
let val = *x as f32; let val = *x as f32;
ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val); ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val);
} }
(Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 24 => { (Literal::Decimal(bytes), Layout::Builtin(Builtin::Decimal)) => {
// Load small string.
self.storage_manager.with_tmp_general_reg( self.storage_manager.with_tmp_general_reg(
&mut self.buf, &mut self.buf,
|storage_manager, buf, reg| { |storage_manager, buf, reg| {
let base_offset = storage_manager.claim_stack_area(sym, 24); let base_offset = storage_manager.claim_stack_area(sym, 16);
let mut bytes = [0; 24];
bytes[..x.len()].copy_from_slice(x.as_bytes());
bytes[23] = (x.len() as u8) | 0b1000_0000;
let mut num_bytes = [0; 8]; let mut num_bytes = [0; 8];
num_bytes.copy_from_slice(&bytes[..8]); num_bytes.copy_from_slice(&bytes[..8]);
@ -2151,14 +2292,49 @@ impl<
let num = i64::from_ne_bytes(num_bytes); let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num); ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset + 8, reg); ASM::mov_base32_reg64(buf, base_offset + 8, reg);
num_bytes.copy_from_slice(&bytes[16..]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset + 16, reg);
}, },
); );
} }
(Literal::Str(x), Layout::Builtin(Builtin::Str)) => {
if x.len() < 24 {
// Load small string.
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|storage_manager, buf, reg| {
let base_offset = storage_manager.claim_stack_area(sym, 24);
let mut bytes = [0; 24];
bytes[..x.len()].copy_from_slice(x.as_bytes());
bytes[23] = (x.len() as u8) | 0b1000_0000;
let mut num_bytes = [0; 8];
num_bytes.copy_from_slice(&bytes[..8]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset, reg);
num_bytes.copy_from_slice(&bytes[8..16]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset + 8, reg);
num_bytes.copy_from_slice(&bytes[16..]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset + 16, reg);
},
);
} else {
// load large string (pretend it's a `List U8`). We should move this data into
// the binary eventually because our RC algorithm won't free this value
let elements: Vec<_> = x
.as_bytes()
.iter()
.map(|b| ListLiteralElement::Literal(Literal::Byte(*b)))
.collect_in(self.storage_manager.env.arena);
self.create_array(sym, &Layout::U8, elements.into_bump_slice())
}
}
x => todo!("loading literal, {:?}", x), x => todo!("loading literal, {:?}", x),
} }
} }
@ -2398,6 +2574,18 @@ impl<
} }
} }
} }
fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth) {
let buf = &mut self.buf;
let dst_reg = self.storage_manager.claim_float_reg(buf, &dst);
let src_reg = self.storage_manager.load_to_float_reg(buf, &src);
match float_width {
FloatWidth::F32 => ASM::sqrt_freg32_freg32(buf, dst_reg, src_reg),
FloatWidth::F64 => ASM::sqrt_freg64_freg64(buf, dst_reg, src_reg),
}
}
} }
/// This impl block is for ir related instructions that need backend specific information. /// This impl block is for ir related instructions that need backend specific information.
@ -2445,6 +2633,115 @@ impl<
ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg);
} }
fn ptr_read(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
layout_interner: &STLayoutInterner<'a>,
ptr_reg: GeneralReg,
element_in_layout: InLayout<'a>,
dst: Symbol,
) {
match layout_interner.get(element_in_layout) {
Layout::Builtin(builtin) => match builtin {
Builtin::Int(int_width) => match int_width {
IntWidth::I128 | IntWidth::U128 => {
// can we treat this as 2 u64's?
todo!()
}
IntWidth::I64 | IntWidth::U64 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0);
}
IntWidth::I32 | IntWidth::U32 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg32_mem32_offset32(buf, dst_reg, ptr_reg, 0);
}
IntWidth::I16 | IntWidth::U16 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, 0);
}
IntWidth::I8 | IntWidth::U8 => {
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0);
}
},
Builtin::Float(_) => {
let dst_reg = storage_manager.claim_float_reg(buf, &dst);
ASM::mov_freg64_freg64(buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
}
Builtin::Bool => {
// the same as an 8-bit integer
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0);
}
Builtin::Decimal => {
// same as 128-bit integer
}
Builtin::Str | Builtin::List(_) => {
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg);
});
}
},
Layout::Boxed(_) => {
// the same as 64-bit integer (for 64-bit targets)
let dst_reg = storage_manager.claim_general_reg(buf, &dst);
ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0);
}
_ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)),
}
}
fn ptr_write(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
ptr_reg: GeneralReg,
element_offset: i32,
element_width: u64,
element_layout: Layout<'a>,
value: Symbol,
) {
match element_layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
let sym_reg = storage_manager.load_to_general_reg(buf, &value);
ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg);
}
Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::U32)) => {
let sym_reg = storage_manager.load_to_general_reg(buf, &value);
ASM::mov_mem32_offset32_reg32(buf, ptr_reg, element_offset, sym_reg);
}
Layout::Builtin(Builtin::Int(IntWidth::I16 | IntWidth::U16)) => {
let sym_reg = storage_manager.load_to_general_reg(buf, &value);
ASM::mov_mem16_offset32_reg16(buf, ptr_reg, element_offset, sym_reg);
}
Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8) | Builtin::Bool) => {
let sym_reg = storage_manager.load_to_general_reg(buf, &value);
ASM::mov_mem8_offset32_reg8(buf, ptr_reg, element_offset, sym_reg);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64 | FloatWidth::F32)) => {
let sym_reg = storage_manager.load_to_float_reg(buf, &value);
ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg);
}
_ if element_width == 0 => {}
_ if element_width > 8 => {
let (from_offset, size) = storage_manager.stack_offset_and_size(&value);
debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 8 == 0);
debug_assert_eq!(size as u64, element_width);
storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| {
// a crude memcpy
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i);
ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset + i, tmp_reg);
}
});
}
x => todo!("copying data to list with layout, {:?}", x),
}
}
/// Updates a jump instruction to a new offset and returns the number of bytes written. /// Updates a jump instruction to a new offset and returns the number of bytes written.
fn update_jmp_imm32_offset( fn update_jmp_imm32_offset(
&mut self, &mut self,

View file

@ -9,7 +9,6 @@ use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::{ use roc_mono::{
borrow::Ownership,
ir::{JoinPointId, Param}, ir::{JoinPointId, Param},
layout::{ layout::{
Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout, Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
@ -315,7 +314,7 @@ impl<
reg: Some(Float(_)), reg: Some(Float(_)),
.. ..
}) => { }) => {
internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}")
} }
Stack(Primitive { Stack(Primitive {
reg: None, reg: None,
@ -350,8 +349,10 @@ impl<
self.free_reference(sym); self.free_reference(sym);
reg reg
} }
Stack(Complex { .. }) => { Stack(Complex { size, .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym) internal_error!(
"Cannot load large values (size {size}) into general registers: {sym:?}",
)
} }
NoData => { NoData => {
internal_error!("Cannot load no data into general registers: {}", sym) internal_error!("Cannot load no data into general registers: {}", sym)
@ -448,7 +449,7 @@ impl<
reg: Some(Float(_)), reg: Some(Float(_)),
.. ..
}) => { }) => {
internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}",)
} }
Stack(Primitive { Stack(Primitive {
reg: None, reg: None,
@ -458,19 +459,25 @@ impl<
ASM::mov_reg64_base32(buf, reg, *base_offset); ASM::mov_reg64_base32(buf, reg, *base_offset);
} }
Stack(ReferencedPrimitive { Stack(ReferencedPrimitive {
base_offset, size, .. base_offset,
}) if base_offset % 8 == 0 && *size == 8 => { size,
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. sign_extend,
ASM::mov_reg64_base32(buf, reg, *base_offset); }) => {
debug_assert!(*size <= 8);
if *sign_extend {
ASM::movsx_reg64_base32(buf, reg, *base_offset, *size as u8)
} else {
ASM::movzx_reg64_base32(buf, reg, *base_offset, *size as u8)
}
} }
Stack(ReferencedPrimitive { .. }) => { Stack(Complex { size, .. }) => {
todo!("loading referenced primitives") internal_error!(
} "Cannot load large values (size {size}) into general registers: {sym:?}",
Stack(Complex { .. }) => { )
internal_error!("Cannot load large values into general registers: {}", sym)
} }
NoData => { NoData => {
internal_error!("Cannot load no data into general registers: {}", sym) internal_error!("Cannot load no data into general registers: {:?}", sym)
} }
} }
} }
@ -553,7 +560,7 @@ impl<
self.allocation_map.insert(*sym, owned_data); self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, *sym,
Stack(if is_primitive(layout) { Stack(if is_primitive(layout_interner, layout) {
ReferencedPrimitive { ReferencedPrimitive {
base_offset: data_offset, base_offset: data_offset,
size, size,
@ -739,15 +746,73 @@ impl<
layout: &InLayout<'a>, layout: &InLayout<'a>,
) { ) {
match layout_interner.get(*layout) { match layout_interner.get(*layout) {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { Layout::Builtin(builtin) => match builtin {
Builtin::Int(int_width) => match int_width {
IntWidth::I128 | IntWidth::U128 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(from_offset % 8, 0);
debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
IntWidth::I64 | IntWidth::U64 => {
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg);
}
IntWidth::I32 | IntWidth::U32 => {
debug_assert_eq!(to_offset % 4, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg32(buf, to_offset, reg);
}
IntWidth::I16 | IntWidth::U16 => {
debug_assert_eq!(to_offset % 2, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg16(buf, to_offset, reg);
}
IntWidth::I8 | IntWidth::U8 => {
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg8(buf, to_offset, reg);
}
},
Builtin::Float(float_width) => match float_width {
FloatWidth::F64 => {
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg);
}
FloatWidth::F32 => todo!(),
},
Builtin::Bool => {
// same as 8-bit integer
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg8(buf, to_offset, reg);
}
Builtin::Decimal => todo!(),
Builtin::Str | Builtin::List(_) => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert_eq!(from_offset % 8, 0);
debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
},
Layout::Boxed(_) => {
// like a 64-bit integer
debug_assert_eq!(to_offset % 8, 0); debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym); let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg); ASM::mov_base32_reg64(buf, to_offset, reg);
} }
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { Layout::LambdaSet(lambda_set) => {
debug_assert_eq!(to_offset % 8, 0); // like its runtime representation
let reg = self.load_to_float_reg(buf, sym); self.copy_symbol_to_stack_offset(
ASM::mov_base32_freg64(buf, to_offset, reg); layout_interner,
buf,
to_offset,
sym,
&lambda_set.runtime_representation(),
)
} }
_ if layout_interner.stack_size(*layout) == 0 => {} _ if layout_interner.stack_size(*layout) == 0 => {}
// TODO: Verify this is always true. // TODO: Verify this is always true.
@ -756,20 +821,64 @@ impl<
// Later, it will be reloaded and stored in refcounted as needed. // Later, it will be reloaded and stored in refcounted as needed.
_ if layout_interner.stack_size(*layout) > 8 => { _ if layout_interner.stack_size(*layout) > 8 => {
let (from_offset, size) = self.stack_offset_and_size(sym); let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(from_offset % 8 == 0); debug_assert_eq!(from_offset % 8, 0);
debug_assert!(size % 8 == 0); debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout)); debug_assert_eq!(size, layout_interner.stack_size(*layout));
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { self.copy_to_stack_offset(buf, size, from_offset, to_offset)
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + i);
ASM::mov_base32_reg64(buf, to_offset + i, reg);
}
});
} }
x => todo!("copying data to the stack with layout, {:?}", x), x => todo!("copying data to the stack with layout, {:?}", x),
} }
} }
pub fn copy_to_stack_offset(
&mut self,
buf: &mut Vec<'a, u8>,
size: u32,
from_offset: i32,
to_offset: i32,
) {
let mut copied = 0;
let size = size as i32;
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
if size - copied >= 8 {
for _ in (0..(size - copied)).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg64(buf, to_offset + copied, reg);
copied += 8;
}
}
if size - copied >= 4 {
for _ in (0..(size - copied)).step_by(4) {
ASM::mov_reg32_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg32(buf, to_offset + copied, reg);
copied += 4;
}
}
if size - copied >= 2 {
for _ in (0..(size - copied)).step_by(2) {
ASM::mov_reg16_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg16(buf, to_offset + copied, reg);
copied += 2;
}
}
if size - copied >= 1 {
for _ in (0..(size - copied)).step_by(1) {
ASM::mov_reg8_base32(buf, reg, from_offset + copied);
ASM::mov_base32_reg8(buf, to_offset + copied, reg);
copied += 1;
}
}
});
}
#[allow(dead_code)] #[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free. /// Ensures that a register is free. If it is not free, data will be moved to make it free.
pub fn ensure_reg_free( pub fn ensure_reg_free(
@ -1008,15 +1117,10 @@ impl<
param_storage.reserve(params.len()); param_storage.reserve(params.len());
for Param { for Param {
symbol, symbol,
ownership, ownership: _,
layout, layout,
} in params } in params
{ {
if *ownership == Ownership::Borrowed {
// These probably need to be passed by pointer/reference?
// Otherwise, we probably need to copy back to the param at the end of the joinpoint.
todo!("joinpoints with borrowed parameters");
}
// Claim a location for every join point parameter to be loaded at. // Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity. // Put everything on the stack for simplicity.
match *layout { match *layout {
@ -1331,6 +1435,15 @@ impl<
} }
} }
fn is_primitive(layout: InLayout<'_>) -> bool { fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool {
matches!(layout, single_register_layouts!()) match layout {
single_register_layouts!() => true,
_ => match layout_interner.get(layout) {
Layout::Boxed(_) => true,
Layout::LambdaSet(lambda_set) => {
is_primitive(layout_interner, lambda_set.runtime_representation())
}
_ => false,
},
}
} }

File diff suppressed because it is too large Load diff

View file

@ -447,6 +447,9 @@ trait Backend<'a> {
LowLevel::NumAddChecked => { LowLevel::NumAddChecked => {
self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout) self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
} }
LowLevel::NumSubChecked => {
self.build_num_sub_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
}
LowLevel::NumAcos => self.build_fn_call( LowLevel::NumAcos => self.build_fn_call(
sym, sym,
bitcode::NUM_ACOS[FloatWidth::F64].to_string(), bitcode::NUM_ACOS[FloatWidth::F64].to_string(),
@ -551,6 +554,27 @@ trait Backend<'a> {
); );
self.build_num_sub_wrap(sym, &args[0], &args[1], ret_layout) self.build_num_sub_wrap(sym, &args[0], &args[1], ret_layout)
} }
LowLevel::NumSubSaturated => match self.interner().get(*ret_layout) {
Layout::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
sym,
bitcode::NUM_SUB_SATURATED_INT[int_width].to_string(),
args,
arg_layouts,
ret_layout,
),
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
// saturated sub is just normal sub
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
}
Layout::Builtin(Builtin::Decimal) => {
// self.load_args_and_call_zig(backend, bitcode::DEC_SUB_SATURATED)
todo!()
}
_ => internal_error!("invalid return type"),
},
LowLevel::NumBitwiseAnd => { LowLevel::NumBitwiseAnd => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], int_width) self.build_int_bitwise_and(sym, &args[0], &args[1], int_width)
@ -572,6 +596,20 @@ trait Backend<'a> {
internal_error!("bitwise xor on a non-integer") internal_error!("bitwise xor on a non-integer")
} }
} }
LowLevel::And => {
if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise and on a non-integer")
}
}
LowLevel::Or => {
if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) {
self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise or on a non-integer")
}
}
LowLevel::NumShiftLeftBy => { LowLevel::NumShiftLeftBy => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_shift_left(sym, &args[0], &args[1], int_width) self.build_int_shift_left(sym, &args[0], &args[1], int_width)
@ -623,6 +661,15 @@ trait Backend<'a> {
); );
self.build_neq(sym, &args[0], &args[1], &arg_layouts[0]) self.build_neq(sym, &args[0], &args[1], &arg_layouts[0])
} }
LowLevel::Not => {
debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument");
debug_assert_eq!(
Layout::BOOL,
*ret_layout,
"Not: expected to have return layout of type Bool"
);
self.build_not(sym, &args[0], &arg_layouts[0])
}
LowLevel::NumLt => { LowLevel::NumLt => {
debug_assert_eq!( debug_assert_eq!(
2, 2,
@ -704,6 +751,30 @@ trait Backend<'a> {
); );
self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0]) self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0])
} }
LowLevel::NumLogUnchecked => {
let float_width = match arg_layouts[0] {
Layout::F64 => FloatWidth::F64,
Layout::F32 => FloatWidth::F32,
_ => unreachable!("invalid layout for sqrt"),
};
self.build_fn_call(
sym,
bitcode::NUM_LOG[float_width].to_string(),
args,
arg_layouts,
ret_layout,
)
}
LowLevel::NumSqrtUnchecked => {
let float_width = match arg_layouts[0] {
Layout::F64 => FloatWidth::F64,
Layout::F32 => FloatWidth::F32,
_ => unreachable!("invalid layout for sqrt"),
};
self.build_num_sqrt(*sym, args[0], float_width);
}
LowLevel::NumRound => self.build_fn_call( LowLevel::NumRound => self.build_fn_call(
sym, sym,
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(), bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
@ -784,6 +855,13 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::StrJoinWith => self.build_fn_call(
sym,
bitcode::STR_JOIN_WITH.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrSplit => self.build_fn_call( LowLevel::StrSplit => self.build_fn_call(
sym, sym,
bitcode::STR_SPLIT.to_string(), bitcode::STR_SPLIT.to_string(),
@ -805,6 +883,13 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::StrAppendScalar => self.build_fn_call(
sym,
bitcode::STR_APPEND_SCALAR.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrEndsWith => self.build_fn_call( LowLevel::StrEndsWith => self.build_fn_call(
sym, sym,
bitcode::STR_ENDS_WITH.to_string(), bitcode::STR_ENDS_WITH.to_string(),
@ -819,6 +904,122 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::StrSubstringUnsafe => self.build_fn_call(
sym,
bitcode::STR_SUBSTRING_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToUtf8 => self.build_fn_call(
sym,
bitcode::STR_TO_UTF8.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrCountUtf8Bytes => self.build_fn_call(
sym,
bitcode::STR_COUNT_UTF8_BYTES.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrFromUtf8Range => self.build_fn_call(
sym,
bitcode::STR_FROM_UTF8_RANGE.to_string(),
args,
arg_layouts,
ret_layout,
),
// LowLevel::StrToUtf8 => self.build_fn_call(
// sym,
// bitcode::STR_TO_UTF8.to_string(),
// args,
// arg_layouts,
// ret_layout,
// ),
LowLevel::StrRepeat => self.build_fn_call(
sym,
bitcode::STR_REPEAT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrTrim => self.build_fn_call(
sym,
bitcode::STR_TRIM.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrTrimLeft => self.build_fn_call(
sym,
bitcode::STR_TRIM_LEFT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrTrimRight => self.build_fn_call(
sym,
bitcode::STR_TRIM_RIGHT.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrReserve => self.build_fn_call(
sym,
bitcode::STR_RESERVE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrWithCapacity => self.build_fn_call(
sym,
bitcode::STR_WITH_CAPACITY.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToScalars => self.build_fn_call(
sym,
bitcode::STR_TO_SCALARS.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrGetUnsafe => self.build_fn_call(
sym,
bitcode::STR_GET_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrGetScalarUnsafe => self.build_fn_call(
sym,
bitcode::STR_GET_SCALAR_UNSAFE.to_string(),
args,
arg_layouts,
ret_layout,
),
LowLevel::StrToNum => {
let number_layout = match self.interner().get(*ret_layout) {
Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct?
_ => unreachable!(),
};
// match on the return layout to figure out which zig builtin we need
let intrinsic = match self.interner().get(number_layout) {
Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width],
Layout::Builtin(Builtin::Float(float_width)) => {
&bitcode::STR_TO_FLOAT[float_width]
}
Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR,
_ => unreachable!(),
};
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
}
LowLevel::PtrCast => { LowLevel::PtrCast => {
debug_assert_eq!( debug_assert_eq!(
1, 1,
@ -885,13 +1086,6 @@ trait Backend<'a> {
self.load_literal_symbols(args); self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
} }
Symbol::NUM_ADD_CHECKED => {
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
Symbol::BOOL_TRUE => { Symbol::BOOL_TRUE => {
let bool_layout = Layout::BOOL; let bool_layout = Layout::BOOL;
self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(true)); self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(true));
@ -904,7 +1098,24 @@ trait Backend<'a> {
self.return_symbol(&Symbol::DEV_TMP, &bool_layout); self.return_symbol(&Symbol::DEV_TMP, &bool_layout);
self.free_symbol(&Symbol::DEV_TMP) self.free_symbol(&Symbol::DEV_TMP)
} }
_ => todo!("the function, {:?}", func_sym), Symbol::STR_IS_VALID_SCALAR => {
// just call the function
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
other => {
eprintln!("maybe {other:?} should have a custom implementation?");
// just call the function
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
} }
} }
@ -935,6 +1146,16 @@ trait Backend<'a> {
return_layout: &InLayout<'a>, return_layout: &InLayout<'a>,
); );
/// build_num_sub_checked stores the sum of src1 and src2 into dst.
fn build_num_sub_checked(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
num_layout: &InLayout<'a>,
return_layout: &InLayout<'a>,
);
/// build_num_mul stores `src1 * src2` into dst. /// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>); fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>);
@ -1016,6 +1237,9 @@ trait Backend<'a> {
/// build_neq stores the result of `src1 != src2` into dst. /// build_neq stores the result of `src1 != src2` into dst.
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>); fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>);
/// build_not stores the result of `!src` into dst.
fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>);
/// build_num_lt stores the result of `src1 < src2` into dst. /// build_num_lt stores the result of `src1 < src2` into dst.
fn build_num_lt( fn build_num_lt(
&mut self, &mut self,
@ -1061,6 +1285,9 @@ trait Backend<'a> {
arg_layout: &InLayout<'a>, arg_layout: &InLayout<'a>,
); );
/// build_sqrt stores the result of `sqrt(src)` into dst.
fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth);
/// build_list_len returns the length of a list. /// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol); fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);

View file

@ -1,24 +1,26 @@
[package] [package]
name = "roc_gen_llvm" name = "roc_gen_llvm"
description = "The LLVM backend for the Roc compiler" description = "The LLVM backend for the Roc compiler"
version = "0.0.1"
authors = ["The Roc Contributors"] authors.workspace = true
license = "UPL-1.0" edition.workspace = true
edition = "2021" license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_alias_analysis = { path = "../alias_analysis" }
roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_builtins = { path = "../builtins" }
roc_error_macros = { path = "../../error_macros" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" }
roc_debug_flags = { path = "../debug_flags" }
roc_region = { path = "../region" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
roc_alias_analysis = { path = "../alias_analysis" }
roc_bitcode_bc = { path = "../builtins/bitcode/bc" }
roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" }
roc_debug_flags = { path = "../debug_flags" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_region = { path = "../region" }
roc_std = { path = "../../roc_std" }
roc_target = { path = "../roc_target" }
bumpalo.workspace = true bumpalo.workspace = true
inkwell.workspace = true
target-lexicon.workspace = true target-lexicon.workspace = true
inkwell.workspace = true

View file

@ -39,8 +39,8 @@ use roc_debug_flags::dbg_do;
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION; use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{ use roc_mono::ir::{
BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc, BranchInfo, CallType, CrashTag, EntryPoint, HostExposedLambdaSet, JoinPointId,
OptLevel, ProcLayout, SingleEntryPoint, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
}; };
use roc_mono::layout::{ use roc_mono::layout::{
Builtin, InLayout, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, Niche, Builtin, InLayout, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, Niche,
@ -3977,7 +3977,8 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// In C, this is modelled as a function returning void // In C, this is modelled as a function returning void
( (
&params[..], &params[..],
&param_types[..param_types.len().saturating_sub(1)], // &param_types[..param_types.len().saturating_sub(1)],
&param_types[..],
) )
} }
_ => (&params[..], &param_types[..]), _ => (&params[..], &param_types[..]),
@ -4938,26 +4939,24 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
mod_solutions: &'a ModSolutions, mod_solutions: &'a ModSolutions,
proc_name: LambdaName, proc_name: LambdaName,
alias_symbol: Symbol, alias_symbol: Symbol,
exposed_function_symbol: Symbol, hels: &HostExposedLambdaSet<'a>,
top_level: ProcLayout<'a>,
layout: RawFunctionLayout<'a>,
) { ) {
let ident_string = proc_name.name().as_str(&env.interns); let ident_string = proc_name.name().as_str(&env.interns);
let fn_name: String = format!("{}_1", ident_string); let fn_name: String = format!("{}_1", ident_string);
match layout { match hels.raw_function_layout {
RawFunctionLayout::Function(arguments, closure, result) => { RawFunctionLayout::Function(arguments, closure, result) => {
// define closure size and return value size, e.g. // define closure size and return value size, e.g.
// //
// * roc__mainForHost_1_Update_size() -> i64 // * roc__mainForHost_1_Update_size() -> i64
// * roc__mainForHost_1_Update_result_size() -> i64 // * roc__mainForHost_1_Update_result_size() -> i64
let it = top_level.arguments.iter().copied(); let it = hels.proc_layout.arguments.iter().copied();
let bytes = roc_alias_analysis::func_name_bytes_help( let bytes = roc_alias_analysis::func_name_bytes_help(
exposed_function_symbol, hels.symbol,
it, it,
Niche::NONE, Niche::NONE,
top_level.result, hels.proc_layout.result,
); );
let func_name = FuncName(&bytes); let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
@ -4973,17 +4972,17 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
function_value_by_func_spec( function_value_by_func_spec(
env, env,
*func_spec, *func_spec,
exposed_function_symbol, hels.symbol,
top_level.arguments, hels.proc_layout.arguments,
Niche::NONE, Niche::NONE,
top_level.result, hels.proc_layout.result,
) )
} }
None => { None => {
// morphic did not generate a specialization for this function, // morphic did not generate a specialization for this function,
// therefore it must actually be unused. // therefore it must actually be unused.
// An example is our closure callers // An example is our closure callers
panic!("morphic did not specialize {:?}", exposed_function_symbol); panic!("morphic did not specialize {:?}", hels.symbol);
} }
}; };
@ -5237,16 +5236,14 @@ pub fn build_proc<'a, 'ctx, 'env>(
/* no host, or exposing types is not supported */ /* no host, or exposing types is not supported */
} }
Binary | BinaryDev => { Binary | BinaryDev => {
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() { for (alias_name, hels) in aliases.iter() {
expose_alias_to_host( expose_alias_to_host(
env, env,
layout_interner, layout_interner,
mod_solutions, mod_solutions,
proc.name, proc.name,
*alias_name, *alias_name,
*generated_function, hels,
*top_level,
*layout,
) )
} }
} }

View file

@ -848,9 +848,24 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!(), _ => unreachable!(),
} }
} }
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos NumAbs
| NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin | NumNeg
| NumToIntChecked => { | NumRound
| NumSqrtUnchecked
| NumLogUnchecked
| NumSin
| NumCos
| NumCeiling
| NumFloor
| NumToFrac
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumToIntChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => {
arguments_with_layouts!((arg, arg_layout)); arguments_with_layouts!((arg, arg_layout));
match layout_interner.get(arg_layout) { match layout_interner.get(arg_layout) {
@ -914,6 +929,28 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
bitcode::NUM_BYTES_TO_U32, bitcode::NUM_BYTES_TO_U32,
) )
} }
NumBytesToU64 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U64,
)
}
NumBytesToU128 => {
arguments!(list, position);
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[position],
BitcodeReturns::Basic,
bitcode::NUM_BYTES_TO_U128,
)
}
NumCompare => { NumCompare => {
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
@ -2045,6 +2082,19 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked") complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked")
} }
} }
NumCountLeadingZeroBits => call_bitcode_fn(
env,
&[arg.into()],
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width],
),
NumCountTrailingZeroBits => call_bitcode_fn(
env,
&[arg.into()],
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width],
),
NumCountOneBits => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width])
}
_ => { _ => {
unreachable!("Unrecognized int unary operation: {:?}", op); unreachable!("Unrecognized int unary operation: {:?}", op);
} }

View file

@ -1345,11 +1345,16 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
union_layout, union_layout,
UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. } UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
) { ) {
debug_assert_eq!(cases.len(), 1); debug_assert!(cases.len() <= 1, "{cases:?}");
// in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id if cases.is_empty() {
let (_, only_branch) = cases.pop().unwrap(); // The only other layout doesn't need refcounting. Pass through.
env.builder.build_unconditional_branch(only_branch); builder.build_return(None);
} else {
// in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch);
}
} else { } else {
let default_block = env.context.append_basic_block(parent, "switch_default"); let default_block = env.context.append_basic_block(parent, "switch_default");
@ -1372,6 +1377,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
} }
} }
#[derive(Debug)]
struct UnionLayoutTags<'a> { struct UnionLayoutTags<'a> {
nullable_id: Option<u16>, nullable_id: Option<u16>,
tags: &'a [&'a [InLayout<'a>]], tags: &'a [&'a [InLayout<'a>]],

View file

@ -1,19 +1,20 @@
[package] [package]
name = "roc_gen_wasm" name = "roc_gen_wasm"
version = "0.0.1"
edition = "2021"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
description = "Provides the WASM backend to generate Roc binaries." description = "Provides the WASM backend to generate Roc binaries."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
roc_error_macros = { path = "../../error_macros" } roc_target = { path = "../roc_target" }
roc_wasm_module = { path = "../../wasm_module" } roc_wasm_module = { path = "../../wasm_module" }
bitvec.workspace = true bitvec.workspace = true

View file

@ -1489,6 +1489,40 @@ impl<'a> LowLevelCall<'a> {
} }
_ => panic_ret_type(), _ => panic_ret_type(),
}, },
NumCountLeadingZeroBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(
backend,
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[width],
);
}
_ => panic_ret_type(),
},
NumCountTrailingZeroBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(
backend,
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[width],
);
}
_ => panic_ret_type(),
},
NumCountOneBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(backend, &bitcode::NUM_COUNT_ONE_BITS[width]);
}
_ => panic_ret_type(),
},
NumRound => { NumRound => {
self.load_args(backend); self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
@ -1577,6 +1611,8 @@ impl<'a> LowLevelCall<'a> {
}, },
NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16), NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16),
NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32), NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32),
NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64),
NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128),
NumBitwiseAnd => { NumBitwiseAnd => {
self.load_args(backend); self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) { match CodeGenNumType::from(self.ret_layout) {

View file

@ -1,7 +1,8 @@
[package] [package]
name = "roc_ident" name = "roc_ident"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Implements data structures used for efficiently representing small strings, like identifiers." description = "Implements data structures used for efficiently representing small strings, like identifiers."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true

View file

@ -1,19 +1,20 @@
[package] [package]
name = "roc_late_solve" name = "roc_late_solve"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides type unification and solving primitives from the perspective of the compiler backend." description = "Provides type unification and solving primitives from the perspective of the compiler backend."
[dependencies] authors.workspace = true
roc_types = { path = "../types" } edition.workspace = true
roc_can = { path = "../can" } license.workspace = true
roc_derive = { path = "../derive" } version.workspace = true
roc_module = { path = "../module" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
bumpalo.workspace = true [dependencies]
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_derive = { path = "../derive" }
roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_solve = { path = "../solve" }
roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
bumpalo.workspace = true

View file

@ -1,30 +1,31 @@
[package] [package]
name = "roc_load" name = "roc_load"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Used to load a .roc file and coordinate the compiler pipeline, including parsing, type checking, and code generation." description = "Used to load a .roc file and coordinate the compiler pipeline, including parsing, type checking, and code generation."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_load_internal = { path = "../load_internal" }
roc_target = { path = "../roc_target" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_types = { path = "../types" }
roc_module = { path = "../module" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_load_internal = { path = "../load_internal" }
roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" } roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
bumpalo.workspace = true bumpalo.workspace = true
[build-dependencies] [build-dependencies]
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_can = { path = "../can" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" } roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_can = { path = "../can" }
bumpalo.workspace = true bumpalo.workspace = true

View file

@ -1,45 +1,46 @@
[package] [package]
name = "roc_load_internal" name = "roc_load_internal"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "The internal implementation of roc_load, separate from roc_load to support caching." description = "The internal implementation of roc_load, separate from roc_load to support caching."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_constrain = { path = "../constrain" }
roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" } roc_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_collections = { path = "../collections" }
roc_constrain = { path = "../constrain" }
roc_debug_flags = { path = "../debug_flags" }
roc_derive = { path = "../derive" }
roc_derive_key = { path = "../derive_key" }
roc_error_macros = { path = "../../error_macros" }
roc_late_solve = { path = "../late_solve" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_packaging = { path = "../../packaging" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_solve_problem = { path = "../solve_problem" } roc_solve_problem = { path = "../solve_problem" }
roc_late_solve = { path = "../late_solve" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_tracing = { path = "../../tracing" } roc_tracing = { path = "../../tracing" }
roc_packaging = { path = "../../packaging" } roc_types = { path = "../types" }
roc_reporting = { path = "../../reporting" } roc_unify = { path = "../unify" }
roc_debug_flags = { path = "../debug_flags" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo.workspace = true bumpalo.workspace = true
parking_lot.workspace = true
crossbeam.workspace = true crossbeam.workspace = true
parking_lot.workspace = true
tempfile.workspace = true tempfile.workspace = true
[dev-dependencies] [dev-dependencies]
roc_test_utils = { path = "../../test_utils" } roc_test_utils = { path = "../../test_utils" }
pretty_assertions.workspace = true
indoc.workspace = true indoc.workspace = true
maplit.workspace = true maplit.workspace = true
pretty_assertions.workspace = true

View file

@ -1,20 +1,22 @@
[package] [package]
name = "roc_module" name = "roc_module"
version = "0.0.1"
authors = ["The Roc Contributors"]
edition = "2021"
license = "UPL-1.0"
description = "Implements data structures used for efficiently representing unique modules and identifiers in Roc programs." description = "Implements data structures used for efficiently representing unique modules and identifiers in Roc programs."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_region = { path = "../region" }
roc_ident = { path = "../ident" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = {path = "../../error_macros"} roc_error_macros = { path = "../../error_macros" }
roc_ident = { path = "../ident" }
roc_region = { path = "../region" }
bumpalo.workspace = true bumpalo.workspace = true
static_assertions.workspace = true
snafu.workspace = true snafu.workspace = true
static_assertions.workspace = true
[features] [features]
default = []
debug-symbols = [] debug-symbols = []
default = []

View file

@ -90,6 +90,8 @@ pub enum LowLevel {
NumAsin, NumAsin,
NumBytesToU16, NumBytesToU16,
NumBytesToU32, NumBytesToU32,
NumBytesToU64,
NumBytesToU128,
NumBitwiseAnd, NumBitwiseAnd,
NumBitwiseXor, NumBitwiseXor,
NumBitwiseOr, NumBitwiseOr,
@ -101,6 +103,9 @@ pub enum LowLevel {
NumToIntChecked, NumToIntChecked,
NumToFloatChecked, NumToFloatChecked,
NumToStr, NumToStr,
NumCountLeadingZeroBits,
NumCountTrailingZeroBits,
NumCountOneBits,
Eq, Eq,
NotEq, NotEq,
And, And,
@ -305,6 +310,8 @@ map_symbol_to_lowlevel! {
NumAsin <= NUM_ASIN, NumAsin <= NUM_ASIN,
NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL, NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL,
NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL, NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL,
NumBytesToU64 <= NUM_BYTES_TO_U64_LOWLEVEL,
NumBytesToU128 <= NUM_BYTES_TO_U128_LOWLEVEL,
NumBitwiseAnd <= NUM_BITWISE_AND, NumBitwiseAnd <= NUM_BITWISE_AND,
NumBitwiseXor <= NUM_BITWISE_XOR, NumBitwiseXor <= NUM_BITWISE_XOR,
NumBitwiseOr <= NUM_BITWISE_OR, NumBitwiseOr <= NUM_BITWISE_OR,
@ -312,6 +319,9 @@ map_symbol_to_lowlevel! {
NumShiftRightBy <= NUM_SHIFT_RIGHT, NumShiftRightBy <= NUM_SHIFT_RIGHT,
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
NumToStr <= NUM_TO_STR, NumToStr <= NUM_TO_STR,
NumCountLeadingZeroBits <= NUM_COUNT_LEADING_ZERO_BITS,
NumCountTrailingZeroBits <= NUM_COUNT_TRAILING_ZERO_BITS,
NumCountOneBits <= NUM_COUNT_ONE_BITS,
Eq <= BOOL_STRUCTURAL_EQ, Eq <= BOOL_STRUCTURAL_EQ,
NotEq <= BOOL_STRUCTURAL_NOT_EQ, NotEq <= BOOL_STRUCTURAL_NOT_EQ,
And <= BOOL_AND, And <= BOOL_AND,

View file

@ -1189,63 +1189,70 @@ define_builtins! {
88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias 88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias
89 NUM_BYTES_TO_U16: "bytesToU16" 89 NUM_BYTES_TO_U16: "bytesToU16"
90 NUM_BYTES_TO_U32: "bytesToU32" 90 NUM_BYTES_TO_U32: "bytesToU32"
91 NUM_CAST_TO_NAT: "#castToNat" 91 NUM_BYTES_TO_U64: "bytesToU64"
92 NUM_DIV_CEIL: "divCeil" 92 NUM_BYTES_TO_U128: "bytesToU128"
93 NUM_DIV_CEIL_CHECKED: "divCeilChecked" 93 NUM_CAST_TO_NAT: "#castToNat"
94 NUM_TO_STR: "toStr" 94 NUM_DIV_CEIL: "divCeil"
95 NUM_MIN_I8: "minI8" 95 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
96 NUM_MAX_I8: "maxI8" 96 NUM_TO_STR: "toStr"
97 NUM_MIN_U8: "minU8" 97 NUM_MIN_I8: "minI8"
98 NUM_MAX_U8: "maxU8" 98 NUM_MAX_I8: "maxI8"
99 NUM_MIN_I16: "minI16" 99 NUM_MIN_U8: "minU8"
100 NUM_MAX_I16: "maxI16" 100 NUM_MAX_U8: "maxU8"
101 NUM_MIN_U16: "minU16" 101 NUM_MIN_I16: "minI16"
102 NUM_MAX_U16: "maxU16" 102 NUM_MAX_I16: "maxI16"
103 NUM_MIN_I32: "minI32" 103 NUM_MIN_U16: "minU16"
104 NUM_MAX_I32: "maxI32" 104 NUM_MAX_U16: "maxU16"
105 NUM_MIN_U32: "minU32" 105 NUM_MIN_I32: "minI32"
106 NUM_MAX_U32: "maxU32" 106 NUM_MAX_I32: "maxI32"
107 NUM_MIN_I64: "minI64" 107 NUM_MIN_U32: "minU32"
108 NUM_MAX_I64: "maxI64" 108 NUM_MAX_U32: "maxU32"
109 NUM_MIN_U64: "minU64" 109 NUM_MIN_I64: "minI64"
110 NUM_MAX_U64: "maxU64" 110 NUM_MAX_I64: "maxI64"
111 NUM_MIN_I128: "minI128" 111 NUM_MIN_U64: "minU64"
112 NUM_MAX_I128: "maxI128" 112 NUM_MAX_U64: "maxU64"
113 NUM_MIN_U128: "minU128" 113 NUM_MIN_I128: "minI128"
114 NUM_MAX_U128: "maxU128" 114 NUM_MAX_I128: "maxI128"
115 NUM_TO_I8: "toI8" 115 NUM_MIN_U128: "minU128"
116 NUM_TO_I8_CHECKED: "toI8Checked" 116 NUM_MAX_U128: "maxU128"
117 NUM_TO_I16: "toI16" 117 NUM_TO_I8: "toI8"
118 NUM_TO_I16_CHECKED: "toI16Checked" 118 NUM_TO_I8_CHECKED: "toI8Checked"
119 NUM_TO_I32: "toI32" 119 NUM_TO_I16: "toI16"
120 NUM_TO_I32_CHECKED: "toI32Checked" 120 NUM_TO_I16_CHECKED: "toI16Checked"
121 NUM_TO_I64: "toI64" 121 NUM_TO_I32: "toI32"
122 NUM_TO_I64_CHECKED: "toI64Checked" 122 NUM_TO_I32_CHECKED: "toI32Checked"
123 NUM_TO_I128: "toI128" 123 NUM_TO_I64: "toI64"
124 NUM_TO_I128_CHECKED: "toI128Checked" 124 NUM_TO_I64_CHECKED: "toI64Checked"
125 NUM_TO_U8: "toU8" 125 NUM_TO_I128: "toI128"
126 NUM_TO_U8_CHECKED: "toU8Checked" 126 NUM_TO_I128_CHECKED: "toI128Checked"
127 NUM_TO_U16: "toU16" 127 NUM_TO_U8: "toU8"
128 NUM_TO_U16_CHECKED: "toU16Checked" 128 NUM_TO_U8_CHECKED: "toU8Checked"
129 NUM_TO_U32: "toU32" 129 NUM_TO_U16: "toU16"
130 NUM_TO_U32_CHECKED: "toU32Checked" 130 NUM_TO_U16_CHECKED: "toU16Checked"
131 NUM_TO_U64: "toU64" 131 NUM_TO_U32: "toU32"
132 NUM_TO_U64_CHECKED: "toU64Checked" 132 NUM_TO_U32_CHECKED: "toU32Checked"
133 NUM_TO_U128: "toU128" 133 NUM_TO_U64: "toU64"
134 NUM_TO_U128_CHECKED: "toU128Checked" 134 NUM_TO_U64_CHECKED: "toU64Checked"
135 NUM_TO_NAT: "toNat" 135 NUM_TO_U128: "toU128"
136 NUM_TO_NAT_CHECKED: "toNatChecked" 136 NUM_TO_U128_CHECKED: "toU128Checked"
137 NUM_TO_F32: "toF32" 137 NUM_TO_NAT: "toNat"
138 NUM_TO_F32_CHECKED: "toF32Checked" 138 NUM_TO_NAT_CHECKED: "toNatChecked"
139 NUM_TO_F64: "toF64" 139 NUM_TO_F32: "toF32"
140 NUM_TO_F64_CHECKED: "toF64Checked" 140 NUM_TO_F32_CHECKED: "toF32Checked"
141 NUM_MAX_F64: "maxF64" 141 NUM_TO_F64: "toF64"
142 NUM_MIN_F64: "minF64" 142 NUM_TO_F64_CHECKED: "toF64Checked"
143 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel" 143 NUM_MAX_F64: "maxF64"
144 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel" 144 NUM_MIN_F64: "minF64"
145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel" 145 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel" 146 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel" 147 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
148 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
149 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
150 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel"
151 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel"
152 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits"
153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits"
154 NUM_COUNT_ONE_BITS: "countOneBits"
} }
4 BOOL: "Bool" => { 4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
@ -1319,6 +1326,7 @@ define_builtins! {
53 STR_WITH_CAPACITY: "withCapacity" 53 STR_WITH_CAPACITY: "withCapacity"
54 STR_WITH_PREFIX: "withPrefix" 54 STR_WITH_PREFIX: "withPrefix"
55 STR_GRAPHEMES: "graphemes" 55 STR_GRAPHEMES: "graphemes"
56 STR_IS_VALID_SCALAR: "isValidScalar"
} }
6 LIST: "List" => { 6 LIST: "List" => {
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias 0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias
@ -1401,6 +1409,7 @@ define_builtins! {
77 LIST_COUNT_IF: "countIf" 77 LIST_COUNT_IF: "countIf"
78 LIST_WALK_FROM: "walkFrom" 78 LIST_WALK_FROM: "walkFrom"
79 LIST_WALK_FROM_UNTIL: "walkFromUntil" 79 LIST_WALK_FROM_UNTIL: "walkFromUntil"
80 LIST_ITER_HELP: "iterHelp"
} }
7 RESULT: "Result" => { 7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias 0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias

View file

@ -1,32 +1,33 @@
[package] [package]
name = "roc_mono" name = "roc_mono"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Roc's main intermediate representation (IR), which is responsible for monomorphization, defunctionalization, inserting ref-count instructions, and transforming a Roc program into a form that is easy to consume by a backend." description = "Roc's main intermediate representation (IR), which is responsible for monomorphization, defunctionalization, inserting ref-count instructions, and transforming a Roc program into a form that is easy to consume by a backend."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" }
roc_exhaustive = { path = "../exhaustive" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive" }
roc_late_solve = { path = "../late_solve" }
roc_std = { path = "../../roc_std" }
roc_problem = { path = "../problem" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_debug_flags = { path = "../debug_flags" }
roc_derive = { path = "../derive" }
roc_derive_key = { path = "../derive_key" }
roc_error_macros = { path = "../../error_macros" }
roc_exhaustive = { path = "../exhaustive" }
roc_late_solve = { path = "../late_solve" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_std = { path = "../../roc_std" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_error_macros = {path="../../error_macros"}
roc_debug_flags = {path="../debug_flags"}
roc_tracing = { path = "../../tracing" } roc_tracing = { path = "../../tracing" }
roc_types = { path = "../types" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bitvec.workspace = true
bumpalo.workspace = true bumpalo.workspace = true
hashbrown.workspace = true hashbrown.workspace = true
static_assertions.workspace = true
bitvec.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
static_assertions.workspace = true

View file

@ -100,8 +100,14 @@ pub fn infer_borrow<'a>(
// host-exposed functions must always own their arguments. // host-exposed functions must always own their arguments.
let is_host_exposed = host_exposed_procs.contains(&key.0); let is_host_exposed = host_exposed_procs.contains(&key.0);
let param_offset = param_map.get_param_offset(key.0, key.1); let param_offset = param_map.get_param_offset(interner, key.0, key.1);
env.collect_proc(&mut param_map, proc, param_offset, is_host_exposed); env.collect_proc(
interner,
&mut param_map,
proc,
param_offset,
is_host_exposed,
);
} }
if !env.modified { if !env.modified {
@ -167,6 +173,7 @@ impl<'a> DeclarationToIndex<'a> {
fn get_param_offset( fn get_param_offset(
&self, &self,
interner: &STLayoutInterner<'a>,
needle_symbol: Symbol, needle_symbol: Symbol,
needle_layout: ProcLayout<'a>, needle_layout: ProcLayout<'a>,
) -> ParamOffset { ) -> ParamOffset {
@ -181,12 +188,14 @@ impl<'a> DeclarationToIndex<'a> {
.elements .elements
.iter() .iter()
.filter_map(|(Declaration { symbol, layout }, _)| { .filter_map(|(Declaration { symbol, layout }, _)| {
(*symbol == needle_symbol).then_some(layout) (*symbol == needle_symbol)
.then_some(layout)
.map(|l| l.dbg_deep(interner))
}) })
.collect::<std::vec::Vec<_>>(); .collect::<std::vec::Vec<_>>();
unreachable!( unreachable!(
"symbol/layout {:?} {:#?} combo must be in DeclarationToIndex\nHowever {} similar layouts were found:\n{:#?}", "symbol/layout {:?} {:#?} combo must be in DeclarationToIndex\nHowever {} similar layouts were found:\n{:#?}",
needle_symbol, needle_layout, similar.len(), similar needle_symbol, needle_layout.dbg_deep(interner), similar.len(), similar,
) )
} }
} }
@ -206,13 +215,24 @@ pub struct ParamMap<'a> {
} }
impl<'a> ParamMap<'a> { impl<'a> ParamMap<'a> {
pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset { pub fn get_param_offset(
self.declaration_to_index.get_param_offset(symbol, layout) &self,
interner: &STLayoutInterner<'a>,
symbol: Symbol,
layout: ProcLayout<'a>,
) -> ParamOffset {
self.declaration_to_index
.get_param_offset(interner, symbol, layout)
} }
pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&[Param<'a>]> { pub fn get_symbol(
&self,
interner: &STLayoutInterner<'a>,
symbol: Symbol,
layout: ProcLayout<'a>,
) -> Option<&[Param<'a>]> {
// let index: usize = self.declaration_to_index[&(symbol, layout)].into(); // let index: usize = self.declaration_to_index[&(symbol, layout)].into();
let index: usize = self.get_param_offset(symbol, layout).into(); let index: usize = self.get_param_offset(interner, symbol, layout).into();
self.declarations.get(index..index + layout.arguments.len()) self.declarations.get(index..index + layout.arguments.len())
} }
@ -292,7 +312,7 @@ impl<'a> ParamMap<'a> {
return; return;
} }
let index: usize = self.get_param_offset(key.0, key.1).into(); let index: usize = self.get_param_offset(interner, key.0, key.1).into();
for (i, param) in Self::init_borrow_args(arena, interner, proc.args) for (i, param) in Self::init_borrow_args(arena, interner, proc.args)
.iter() .iter()
@ -312,7 +332,7 @@ impl<'a> ParamMap<'a> {
proc: &Proc<'a>, proc: &Proc<'a>,
key: (Symbol, ProcLayout<'a>), key: (Symbol, ProcLayout<'a>),
) { ) {
let index: usize = self.get_param_offset(key.0, key.1).into(); let index: usize = self.get_param_offset(interner, key.0, key.1).into();
for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args) for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args)
.iter() .iter()
@ -534,7 +554,13 @@ impl<'a> BorrowInfState<'a> {
/// ///
/// and determines whether z and which of the symbols used in e /// and determines whether z and which of the symbols used in e
/// must be taken as owned parameters /// must be taken as owned parameters
fn collect_call(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &crate::ir::Call<'a>) { fn collect_call(
&mut self,
interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>,
z: Symbol,
e: &crate::ir::Call<'a>,
) {
use crate::ir::CallType::*; use crate::ir::CallType::*;
let crate::ir::Call { let crate::ir::Call {
@ -553,7 +579,7 @@ impl<'a> BorrowInfState<'a> {
// get the borrow signature of the applied function // get the borrow signature of the applied function
let ps = param_map let ps = param_map
.get_symbol(name.name(), top_level) .get_symbol(interner, name.name(), top_level)
.expect("function is defined"); .expect("function is defined");
// the return value will be owned // the return value will be owned
@ -595,11 +621,14 @@ impl<'a> BorrowInfState<'a> {
niche: passed_function.name.niche(), niche: passed_function.name.niche(),
}; };
let function_ps = let function_ps = match param_map.get_symbol(
match param_map.get_symbol(passed_function.name.name(), closure_layout) { interner,
Some(function_ps) => function_ps, passed_function.name.name(),
None => unreachable!(), closure_layout,
}; ) {
Some(function_ps) => function_ps,
None => unreachable!(),
};
match op { match op {
ListMap { xs } => { ListMap { xs } => {
@ -671,7 +700,13 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn collect_expr(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &Expr<'a>) { fn collect_expr(
&mut self,
interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>,
z: Symbol,
e: &Expr<'a>,
) {
use Expr::*; use Expr::*;
match e { match e {
@ -724,7 +759,7 @@ impl<'a> BorrowInfState<'a> {
self.own_var(z); self.own_var(z);
} }
Call(call) => self.collect_call(param_map, z, call), Call(call) => self.collect_call(interner, param_map, z, call),
Literal(_) | RuntimeErrorFunction(_) => {} Literal(_) | RuntimeErrorFunction(_) => {}
@ -757,6 +792,7 @@ impl<'a> BorrowInfState<'a> {
#[allow(clippy::many_single_char_names)] #[allow(clippy::many_single_char_names)]
fn preserve_tail_call( fn preserve_tail_call(
&mut self, &mut self,
interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>, param_map: &mut ParamMap<'a>,
x: Symbol, x: Symbol,
v: &Expr<'a>, v: &Expr<'a>,
@ -782,7 +818,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == g.name() && x == *z { if self.current_proc == g.name() && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine // can never be tail-recursive, so this is fine
if let Some(ps) = param_map.get_symbol(g.name(), top_level) { if let Some(ps) = param_map.get_symbol(interner, g.name(), top_level) {
self.own_params_using_args(ys, ps) self.own_params_using_args(ys, ps)
} }
} }
@ -801,7 +837,12 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn collect_stmt(&mut self, param_map: &mut ParamMap<'a>, stmt: &Stmt<'a>) { fn collect_stmt(
&mut self,
interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>,
stmt: &Stmt<'a>,
) {
use Stmt::*; use Stmt::*;
match stmt { match stmt {
@ -813,11 +854,11 @@ impl<'a> BorrowInfState<'a> {
} => { } => {
let old = self.param_set.clone(); let old = self.param_set.clone();
self.update_param_set(ys); self.update_param_set(ys);
self.collect_stmt(param_map, v); self.collect_stmt(interner, param_map, v);
self.param_set = old; self.param_set = old;
self.update_param_map_join_point(param_map, *j); self.update_param_map_join_point(param_map, *j);
self.collect_stmt(param_map, b); self.collect_stmt(interner, param_map, b);
} }
Let(x, v, _, mut b) => { Let(x, v, _, mut b) => {
@ -830,17 +871,17 @@ impl<'a> BorrowInfState<'a> {
stack.push((*symbol, expr)); stack.push((*symbol, expr));
} }
self.collect_stmt(param_map, b); self.collect_stmt(interner, param_map, b);
let mut it = stack.into_iter().rev(); let mut it = stack.into_iter().rev();
// collect the final expr, and see if we need to preserve a tail call // collect the final expr, and see if we need to preserve a tail call
let (x, v) = it.next().unwrap(); let (x, v) = it.next().unwrap();
self.collect_expr(param_map, x, v); self.collect_expr(interner, param_map, x, v);
self.preserve_tail_call(param_map, x, v, b); self.preserve_tail_call(interner, param_map, x, v, b);
for (x, v) in it { for (x, v) in it {
self.collect_expr(param_map, x, v); self.collect_expr(interner, param_map, x, v);
} }
} }
@ -859,21 +900,21 @@ impl<'a> BorrowInfState<'a> {
.. ..
} => { } => {
for (_, _, b) in branches.iter() { for (_, _, b) in branches.iter() {
self.collect_stmt(param_map, b); self.collect_stmt(interner, param_map, b);
} }
self.collect_stmt(param_map, default_branch.1); self.collect_stmt(interner, param_map, default_branch.1);
} }
Dbg { remainder, .. } => { Dbg { remainder, .. } => {
self.collect_stmt(param_map, remainder); self.collect_stmt(interner, param_map, remainder);
} }
Expect { remainder, .. } => { Expect { remainder, .. } => {
self.collect_stmt(param_map, remainder); self.collect_stmt(interner, param_map, remainder);
} }
ExpectFx { remainder, .. } => { ExpectFx { remainder, .. } => {
self.collect_stmt(param_map, remainder); self.collect_stmt(interner, param_map, remainder);
} }
Refcounting(_, _) => unreachable!("these have not been introduced yet"), Refcounting(_, _) => unreachable!("these have not been introduced yet"),
@ -891,6 +932,7 @@ impl<'a> BorrowInfState<'a> {
fn collect_proc( fn collect_proc(
&mut self, &mut self,
interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>, param_map: &mut ParamMap<'a>,
proc: &Proc<'a>, proc: &Proc<'a>,
param_offset: ParamOffset, param_offset: ParamOffset,
@ -912,7 +954,7 @@ impl<'a> BorrowInfState<'a> {
owned_entry.extend(params.iter().map(|p| p.symbol)); owned_entry.extend(params.iter().map(|p| p.symbol));
} }
self.collect_stmt(param_map, &proc.body); self.collect_stmt(interner, param_map, &proc.body);
self.update_param_map_declaration(param_map, param_offset, proc.args.len()); self.update_param_map_declaration(param_map, param_offset, proc.args.len());
self.param_set = old; self.param_set = old;
@ -984,13 +1026,33 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked NumToStr
| NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos | NumAbs
| NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => { | NumNeg
arena.alloc_slice_copy(&[irrelevant]) | NumSin
} | NumCos
| NumSqrtUnchecked
| NumLogUnchecked
| NumRound
| NumCeiling
| NumFloor
| NumToFrac
| Not
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumIntCast
| NumToIntChecked
| NumToFloatCast
| NumToFloatChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU128 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),

View file

@ -605,7 +605,7 @@ impl<'a, 'i> Context<'a, 'i> {
// get the borrow signature // get the borrow signature
let ps = self let ps = self
.param_map .param_map
.get_symbol(name.name(), top_level) .get_symbol(self.layout_interner, name.name(), top_level)
.expect("function is defined"); .expect("function is defined");
let v = Expr::Call(crate::ir::Call { let v = Expr::Call(crate::ir::Call {
@ -653,10 +653,11 @@ impl<'a, 'i> Context<'a, 'i> {
niche: passed_function.name.niche(), niche: passed_function.name.niche(),
}; };
let function_ps = match self let function_ps = match self.param_map.get_symbol(
.param_map self.layout_interner,
.get_symbol(passed_function.name.name(), function_layout) passed_function.name.name(),
{ function_layout,
) {
Some(function_ps) => function_ps, Some(function_ps) => function_ps,
None => unreachable!(), None => unreachable!(),
}; };
@ -671,14 +672,14 @@ impl<'a, 'i> Context<'a, 'i> {
match ownership { match ownership {
DataOwnedFunctionOwns | DataBorrowedFunctionOwns => { DataOwnedFunctionOwns | DataBorrowedFunctionOwns => {
// elements have been consumed, must still consume the list itself // elements have been consumed, must still consume the list itself
let rest = self.arena.alloc($stmt); let rest = self.arena.alloc(stmt);
let rc = Stmt::Refcounting(ModifyRc::DecRef(argument), rest); let rc = Stmt::Refcounting(ModifyRc::DecRef(argument), rest);
stmt = self.arena.alloc(rc); stmt = self.arena.alloc(rc);
} }
DataOwnedFunctionBorrows => { DataOwnedFunctionBorrows => {
// must consume list and elements // must consume list and elements
let rest = self.arena.alloc($stmt); let rest = self.arena.alloc(stmt);
let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest); let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest);
stmt = self.arena.alloc(rc); stmt = self.arena.alloc(rc);
@ -1510,19 +1511,28 @@ pub fn visit_procs<'a, 'i>(
}; };
for (key, proc) in procs.iter_mut() { for (key, proc) in procs.iter_mut() {
visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1); visit_proc(
arena,
layout_interner,
&mut codegen,
param_map,
&ctx,
proc,
key.1,
);
} }
} }
fn visit_proc<'a, 'i>( fn visit_proc<'a, 'i>(
arena: &'a Bump, arena: &'a Bump,
interner: &STLayoutInterner<'a>,
codegen: &mut CodegenTools<'i>, codegen: &mut CodegenTools<'i>,
param_map: &'a ParamMap<'a>, param_map: &'a ParamMap<'a>,
ctx: &Context<'a, 'i>, ctx: &Context<'a, 'i>,
proc: &mut Proc<'a>, proc: &mut Proc<'a>,
layout: ProcLayout<'a>, layout: ProcLayout<'a>,
) { ) {
let params = match param_map.get_symbol(proc.name.name(), layout) { let params = match param_map.get_symbol(interner, proc.name.name(), layout) {
Some(slice) => slice, Some(slice) => slice,
None => Vec::from_iter_in( None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param { proc.args.iter().cloned().map(|(layout, symbol)| Param {

File diff suppressed because it is too large Load diff

View file

@ -1313,6 +1313,14 @@ impl<'a> Niche<'a> {
]), ]),
} }
} }
pub fn dbg_deep<'r, I: LayoutInterner<'a>>(
&'r self,
interner: &'r I,
) -> crate::layout::intern::dbg::DbgFields<'a, 'r, I> {
let NichePriv::Captures(caps) = &self.0;
interner.dbg_deep_iter(caps)
}
} }
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
@ -2717,6 +2725,60 @@ impl<'a> Layout<'a> {
} }
} }
pub fn has_varying_stack_size<I>(self, interner: &I, arena: &bumpalo::Bump) -> bool
where
I: LayoutInterner<'a>,
{
let mut stack: Vec<Layout> = bumpalo::collections::Vec::new_in(arena);
stack.push(self);
while let Some(layout) = stack.pop() {
match layout {
Layout::Builtin(builtin) => match builtin {
Builtin::Int(_)
| Builtin::Float(_)
| Builtin::Bool
| Builtin::Decimal
| Builtin::Str
// If there's any layer of indirection (behind a pointer), then it doesn't vary!
| Builtin::List(_) => { /* do nothing */ }
},
// If there's any layer of indirection (behind a pointer), then it doesn't vary!
Layout::Struct { field_layouts, .. } => {
stack.extend(field_layouts.iter().map(|interned| interner.get(*interned)))
}
Layout::Union(tag_union) => match tag_union {
UnionLayout::NonRecursive(tags) | UnionLayout::Recursive(tags) => {
for tag in tags {
stack.extend(tag.iter().map(|interned| interner.get(*interned)));
}
}
UnionLayout::NonNullableUnwrapped(fields) => {
stack.extend(fields.iter().map(|interned| interner.get(*interned)));
}
UnionLayout::NullableWrapped { other_tags, .. } => {
for tag in other_tags {
stack.extend(tag.iter().map(|interned| interner.get(*interned)));
}
}
UnionLayout::NullableUnwrapped { other_fields, .. } => {
stack.extend(other_fields.iter().map(|interned| interner.get(*interned)));
}
},
Layout::LambdaSet(_) => return true,
Layout::Boxed(_) => {
// If there's any layer of indirection (behind a pointer), then it doesn't vary!
}
Layout::RecursivePointer(_) => {
/* do nothing, we've already generated for this type through the Union(_) */
}
}
}
false
}
/// Used to build a `Layout::Struct` where the field name order is irrelevant. /// Used to build a `Layout::Struct` where the field name order is irrelevant.
pub fn struct_no_name_order(field_layouts: &'a [InLayout]) -> Self { pub fn struct_no_name_order(field_layouts: &'a [InLayout]) -> Self {
if field_layouts.is_empty() { if field_layouts.is_empty() {

View file

@ -365,6 +365,10 @@ pub trait LayoutInterner<'a>: Sized {
fn dbg_deep<'r>(&'r self, layout: InLayout<'a>) -> dbg::Dbg<'a, 'r, Self> { fn dbg_deep<'r>(&'r self, layout: InLayout<'a>) -> dbg::Dbg<'a, 'r, Self> {
dbg::Dbg(self, layout) dbg::Dbg(self, layout)
} }
fn dbg_deep_iter<'r>(&'r self, layouts: &'a [InLayout<'a>]) -> dbg::DbgFields<'a, 'r, Self> {
dbg::DbgFields(self, layouts)
}
} }
/// An interned layout. /// An interned layout.
@ -1274,7 +1278,7 @@ mod equiv {
} }
} }
mod dbg { pub mod dbg {
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use crate::layout::{Builtin, LambdaSet, Layout, UnionLayout}; use crate::layout::{Builtin, LambdaSet, Layout, UnionLayout};
@ -1311,7 +1315,7 @@ mod dbg {
} }
} }
struct DbgFields<'a, 'r, I: LayoutInterner<'a>>(&'r I, &'a [InLayout<'a>]); pub struct DbgFields<'a, 'r, I: LayoutInterner<'a>>(pub &'r I, pub &'a [InLayout<'a>]);
impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgFields<'a, 'r, I> { impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgFields<'a, 'r, I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

@ -6,6 +6,8 @@
#![warn(clippy::dbg_macro)] #![warn(clippy::dbg_macro)]
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
// Not a useful lint for us
#![allow(clippy::too_many_arguments)]
pub mod borrow; pub mod borrow;
pub mod code_gen_help; pub mod code_gen_help;

View file

@ -120,6 +120,8 @@ enum FirstOrder {
NumShiftRightBy, NumShiftRightBy,
NumBytesToU16, NumBytesToU16,
NumBytesToU32, NumBytesToU32,
NumBytesToU64,
NumBytesToU128,
NumShiftRightZfBy, NumShiftRightZfBy,
NumIntCast, NumIntCast,
NumFloatCast, NumFloatCast,

View file

@ -1,32 +1,31 @@
[package] [package]
name = "roc_parse" name = "roc_parse"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Implements the Roc parser, which transforms a textual representation of a Roc program to an AST." description = "Implements the Roc parser, which transforms a textual representation of a Roc program to an AST."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[features] [features]
"parse_debug_trace" = [] "parse_debug_trace" = []
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_region = { path = "../region" }
bumpalo.workspace = true bumpalo.workspace = true
encode_unicode.workspace = true encode_unicode.workspace = true
[dev-dependencies] [dev-dependencies]
roc_test_utils = { path = "../../test_utils" }
proptest = "1.0.0"
criterion.workspace = true criterion.workspace = true
pretty_assertions.workspace = true
indoc.workspace = true indoc.workspace = true
pretty_assertions.workspace = true
proptest.workspace = true
quickcheck.workspace = true quickcheck.workspace = true
quickcheck_macros.workspace = true quickcheck_macros.workspace = true
[[bench]] [[bench]]
name = "bench_parse"
harness = false harness = false
name = "bench_parse"

View file

@ -1,5 +1,6 @@
use crate::ast::CommentOrNewline; use crate::ast::CommentOrNewline;
use crate::ast::Spaceable; use crate::ast::Spaceable;
use crate::parser::Progress;
use crate::parser::SpaceProblem; use crate::parser::SpaceProblem;
use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*}; use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*};
use crate::state::State; use crate::state::State;
@ -7,6 +8,7 @@ use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Loc; use roc_region::all::Loc;
use roc_region::all::Position; use roc_region::all::Position;
use roc_region::all::Region;
pub fn space0_around_ee<'a, P, S, E>( pub fn space0_around_ee<'a, P, S, E>(
parser: P, parser: P,
@ -386,98 +388,132 @@ pub fn spaces<'a, E>() -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
where where
E: 'a + SpaceProblem, E: 'a + SpaceProblem,
{ {
move |arena, mut state: State<'a>, _min_indent: u32| { move |arena, state: State<'a>, _min_indent: u32| {
let mut newlines = Vec::new_in(arena); let mut newlines = Vec::new_in(arena);
let mut progress = NoProgress;
loop {
let whitespace = fast_eat_whitespace(state.bytes());
if whitespace > 0 {
state.advance_mut(whitespace);
progress = MadeProgress;
}
match state.bytes().first() { match consume_spaces(state, |_, space, _| newlines.push(space)) {
Some(b'#') => { Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)),
state.advance_mut(1); Err((progress, err)) => Err((progress, err)),
let is_doc_comment = state.bytes().first() == Some(&b'#')
&& (state.bytes().get(1) == Some(&b' ')
|| state.bytes().get(1) == Some(&b'\n')
|| begins_with_crlf(&state.bytes()[1..])
|| Option::is_none(&state.bytes().get(1)));
if is_doc_comment {
state.advance_mut(1);
if state.bytes().first() == Some(&b' ') {
state.advance_mut(1);
}
}
let len = fast_eat_until_control_character(state.bytes());
// We already checked that the string is valid UTF-8
debug_assert!(std::str::from_utf8(&state.bytes()[..len]).is_ok());
let text = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..len]) };
let comment = if is_doc_comment {
CommentOrNewline::DocComment(text)
} else {
CommentOrNewline::LineComment(text)
};
newlines.push(comment);
state.advance_mut(len);
if begins_with_crlf(state.bytes()) {
state.advance_mut(1);
state = state.advance_newline();
} else if state.bytes().first() == Some(&b'\n') {
state = state.advance_newline();
}
progress = MadeProgress;
}
Some(b'\r') => {
if state.bytes().get(1) == Some(&b'\n') {
newlines.push(CommentOrNewline::Newline);
state.advance_mut(1);
state = state.advance_newline();
progress = MadeProgress;
} else {
return Err((
progress,
E::space_problem(
BadInputError::HasMisplacedCarriageReturn,
state.pos(),
),
));
}
}
Some(b'\n') => {
newlines.push(CommentOrNewline::Newline);
state = state.advance_newline();
progress = MadeProgress;
}
Some(b'\t') => {
return Err((
progress,
E::space_problem(BadInputError::HasTab, state.pos()),
));
}
Some(x) if *x < b' ' => {
return Err((
progress,
E::space_problem(BadInputError::HasAsciiControl, state.pos()),
));
}
_ => {
if !newlines.is_empty() {
state = state.mark_current_indent();
}
break;
}
}
} }
Ok((progress, newlines.into_bump_slice(), state))
} }
} }
pub fn loc_spaces<'a, E>() -> impl Parser<'a, &'a [Loc<CommentOrNewline<'a>>], E>
where
E: 'a + SpaceProblem,
{
move |arena, state: State<'a>, _min_indent: u32| {
let mut newlines = Vec::new_in(arena);
match consume_spaces(state, |start, space, end| {
newlines.push(Loc::at(Region::between(start, end), space))
}) {
Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)),
Err((progress, err)) => Err((progress, err)),
}
}
}
fn consume_spaces<'a, E, F>(
mut state: State<'a>,
mut on_space: F,
) -> Result<(Progress, State<'a>), (Progress, E)>
where
E: 'a + SpaceProblem,
F: FnMut(Position, CommentOrNewline<'a>, Position),
{
let mut progress = NoProgress;
let mut found_newline = false;
loop {
let whitespace = fast_eat_whitespace(state.bytes());
if whitespace > 0 {
state.advance_mut(whitespace);
progress = MadeProgress;
}
let start = state.pos();
match state.bytes().first() {
Some(b'#') => {
state.advance_mut(1);
let is_doc_comment = state.bytes().first() == Some(&b'#')
&& (state.bytes().get(1) == Some(&b' ')
|| state.bytes().get(1) == Some(&b'\n')
|| begins_with_crlf(&state.bytes()[1..])
|| Option::is_none(&state.bytes().get(1)));
if is_doc_comment {
state.advance_mut(1);
if state.bytes().first() == Some(&b' ') {
state.advance_mut(1);
}
}
let len = fast_eat_until_control_character(state.bytes());
// We already checked that the string is valid UTF-8
debug_assert!(std::str::from_utf8(&state.bytes()[..len]).is_ok());
let text = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..len]) };
let comment = if is_doc_comment {
CommentOrNewline::DocComment(text)
} else {
CommentOrNewline::LineComment(text)
};
state.advance_mut(len);
on_space(start, comment, state.pos());
found_newline = true;
if begins_with_crlf(state.bytes()) {
state.advance_mut(1);
state = state.advance_newline();
} else if state.bytes().first() == Some(&b'\n') {
state = state.advance_newline();
}
progress = MadeProgress;
}
Some(b'\r') => {
if state.bytes().get(1) == Some(&b'\n') {
state.advance_mut(1);
state = state.advance_newline();
on_space(start, CommentOrNewline::Newline, state.pos());
found_newline = true;
progress = MadeProgress;
} else {
return Err((
progress,
E::space_problem(BadInputError::HasMisplacedCarriageReturn, state.pos()),
));
}
}
Some(b'\n') => {
state = state.advance_newline();
on_space(start, CommentOrNewline::Newline, state.pos());
found_newline = true;
progress = MadeProgress;
}
Some(b'\t') => {
return Err((
progress,
E::space_problem(BadInputError::HasTab, state.pos()),
));
}
Some(x) if *x < b' ' => {
return Err((
progress,
E::space_problem(BadInputError::HasAsciiControl, state.pos()),
));
}
_ => {
if found_newline {
state = state.mark_current_indent();
}
break;
}
}
}
Ok((progress, state))
}

View file

@ -1,6 +1,6 @@
use crate::ast::{ use crate::ast::{
AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Has, HasAbilities, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Has, HasAbilities,
Pattern, Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef, Pattern, Spaceable, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
}; };
use crate::blankspace::{ use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e, space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
@ -1085,6 +1085,29 @@ enum AliasOrOpaque {
Opaque, Opaque,
} }
fn extract_tag_and_spaces<'a>(arena: &'a Bump, expr: Expr<'a>) -> Option<Spaces<'a, &'a str>> {
let mut expr = expr.extract_spaces();
loop {
match &expr.item {
Expr::ParensAround(inner_expr) => {
let inner_expr = inner_expr.extract_spaces();
expr.item = inner_expr.item;
expr.before = merge_spaces(arena, expr.before, inner_expr.before);
expr.after = merge_spaces(arena, inner_expr.after, expr.after);
}
Expr::Tag(tag) => {
return Some(Spaces {
before: expr.before,
item: tag,
after: expr.after,
});
}
_ => return None,
}
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn finish_parsing_alias_or_opaque<'a>( fn finish_parsing_alias_or_opaque<'a>(
min_indent: u32, min_indent: u32,
@ -1105,120 +1128,113 @@ fn finish_parsing_alias_or_opaque<'a>(
let mut defs = Defs::default(); let mut defs = Defs::default();
let state = match &expr.value.extract_spaces().item { let state = if let Some(tag) = extract_tag_and_spaces(arena, expr.value) {
Expr::ParensAround(Expr::SpaceBefore(Expr::Tag(name), _)) let name = tag.item;
| Expr::ParensAround(Expr::SpaceAfter(Expr::Tag(name), _)) let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
| Expr::ParensAround(Expr::Tag(name))
| Expr::Tag(name) => {
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
for argument in arguments { for argument in arguments {
match expr_to_pattern_help(arena, &argument.value) { match expr_to_pattern_help(arena, &argument.value) {
Ok(good) => { Ok(good) => {
type_arguments.push(Loc::at(argument.region, good)); type_arguments.push(Loc::at(argument.region, good));
}
Err(()) => {
return Err((
MadeProgress,
EExpr::Pattern(
arena.alloc(EPattern::NotAPattern(state.pos())),
state.pos(),
),
));
}
} }
} Err(()) => {
return Err((
match kind { MadeProgress,
AliasOrOpaque::Alias => { EExpr::Pattern(
let (_, signature, state) = arena.alloc(EPattern::NotAPattern(state.pos())),
alias_signature_with_space_before().parse(arena, state, min_indent)?; state.pos(),
),
let def_region = Region::span_across(&expr.region, &signature.region); ));
let header = TypeHeader {
name: Loc::at(expr.region, name),
vars: type_arguments.into_bump_slice(),
};
let def = TypeDef::Alias {
header,
ann: signature,
};
defs.push_type_def(def, def_region, &[], &[]);
state
}
AliasOrOpaque::Opaque => {
let (_, (signature, derived), state) =
opaque_signature_with_space_before().parse(arena, state, indented_more)?;
let def_region = Region::span_across(&expr.region, &signature.region);
let header = TypeHeader {
name: Loc::at(expr.region, name),
vars: type_arguments.into_bump_slice(),
};
let def = TypeDef::Opaque {
header,
typ: signature,
derived,
};
defs.push_type_def(def, def_region, &[], &[]);
state
} }
} }
} }
_ => { match kind {
let call = to_call(arena, arguments, expr); AliasOrOpaque::Alias => {
let (_, signature, state) =
alias_signature_with_space_before().parse(arena, state, min_indent)?;
match expr_to_pattern_help(arena, &call.value) { let def_region = Region::span_across(&expr.region, &signature.region);
Ok(good) => {
let parser = specialize(
EExpr::Type,
space0_before_e(
set_min_indent(indented_more, type_annotation::located(false)),
EType::TIndentStart,
),
);
match parser.parse(arena, state.clone(), min_indent) { let header = TypeHeader {
Err((_, fail)) => return Err((MadeProgress, fail)), name: Loc::at(expr.region, name),
Ok((_, mut ann_type, state)) => { vars: type_arguments.into_bump_slice(),
// put the spaces from after the operator in front of the call };
if !spaces_after_operator.is_empty() {
ann_type = arena
.alloc(ann_type.value)
.with_spaces_before(spaces_after_operator, ann_type.region);
}
let def_region = Region::span_across(&call.region, &ann_type.region); let def = TypeDef::Alias {
header,
ann: signature,
};
let value_def = defs.push_type_def(def, def_region, &[], &[]);
ValueDef::Annotation(Loc::at(expr_region, good), ann_type);
defs.push_value_def(value_def, def_region, &[], &[]); state
}
state AliasOrOpaque::Opaque => {
let (_, (signature, derived), state) =
opaque_signature_with_space_before().parse(arena, state, indented_more)?;
let def_region = Region::span_across(&expr.region, &signature.region);
let header = TypeHeader {
name: Loc::at(expr.region, name),
vars: type_arguments.into_bump_slice(),
};
let def = TypeDef::Opaque {
header,
typ: signature,
derived,
};
defs.push_type_def(def, def_region, &[], &[]);
state
}
}
} else {
let call = to_call(arena, arguments, expr);
match expr_to_pattern_help(arena, &call.value) {
Ok(good) => {
let parser = specialize(
EExpr::Type,
space0_before_e(
set_min_indent(indented_more, type_annotation::located(false)),
EType::TIndentStart,
),
);
match parser.parse(arena, state.clone(), min_indent) {
Err((_, fail)) => return Err((MadeProgress, fail)),
Ok((_, mut ann_type, state)) => {
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
ann_type = arena
.alloc(ann_type.value)
.with_spaces_before(spaces_after_operator, ann_type.region);
} }
let def_region = Region::span_across(&call.region, &ann_type.region);
let value_def = ValueDef::Annotation(Loc::at(expr_region, good), ann_type);
defs.push_value_def(value_def, def_region, &[], &[]);
state
} }
} }
Err(_) => { }
// this `:`/`:=` likely occurred inline; treat it as an invalid operator Err(_) => {
let op = match kind { // this `:`/`:=` likely occurred inline; treat it as an invalid operator
AliasOrOpaque::Alias => ":", let op = match kind {
AliasOrOpaque::Opaque => ":=", AliasOrOpaque::Alias => ":",
}; AliasOrOpaque::Opaque => ":=",
let fail = EExpr::BadOperator(op, loc_op.region.start()); };
let fail = EExpr::BadOperator(op, loc_op.region.start());
return Err((MadeProgress, fail)); return Err((MadeProgress, fail));
}
} }
} }
}; };

View file

@ -0,0 +1,661 @@
use encode_unicode::CharExt;
use std::collections::HashSet;
use bumpalo::Bump;
use roc_region::all::{Loc, Region};
use crate::{
ast::CommentOrNewline,
blankspace::loc_spaces,
keyword::KEYWORDS,
number_literal::positive_number_literal,
parser::{EExpr, ParseResult, Parser},
state::State,
string_literal::{parse_str_like_literal, StrLikeLiteral},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Token {
LineComment,
DocComment,
Error,
SingleQuote,
String,
UnicodeEscape,
EscapedChar,
Interpolated,
Keyword,
UpperIdent,
LowerIdent,
Number,
QuestionMark,
Percent,
Caret,
Other,
Minus,
Bang,
BangEquals,
Plus,
Colon,
ColonEquals,
Bar,
DoubleBar,
And,
DoubleAnd,
Equals,
DoubleEquals,
GreaterThan,
GreaterThanEquals,
LessThan,
LessThanEquals,
Comma,
Backslash,
Slash,
DoubleSlash,
Pizza,
Brace,
Bracket,
AtSign,
Paren,
Arrow,
Pipe,
Backpass,
Decimal,
Multiply,
Underscore,
}
pub fn highlight(text: &str) -> Vec<Loc<Token>> {
let mut tokens = Vec::new();
let state = State::new(text.as_bytes());
let arena = Bump::new();
let header_keywords = HEADER_KEYWORDS.iter().copied().collect::<HashSet<_>>();
let body_keywords = KEYWORDS.iter().copied().collect::<HashSet<_>>();
if let Ok((_prog, _, new_state)) = crate::module::header().parse(&arena, state.clone(), 0) {
let inner_state =
State::new(text[..state.bytes().len() - new_state.bytes().len()].as_bytes());
highlight_inner(&arena, inner_state, &mut tokens, &header_keywords);
highlight_inner(&arena, new_state, &mut tokens, &body_keywords);
} else {
highlight_inner(&arena, state, &mut tokens, &body_keywords);
}
tokens
}
fn highlight_inner<'a>(
arena: &'a Bump,
mut state: State<'a>,
tokens: &mut Vec<Loc<Token>>,
keywords: &HashSet<&str>,
) {
loop {
let start = state.pos();
if let Ok((b, _width)) = char::from_utf8_slice_start(state.bytes()) {
match b {
' ' | '\n' | '\t' | '\r' | '#' => {
let res: ParseResult<'a, _, EExpr<'a>> =
loc_spaces().parse(arena, state.clone(), 0);
if let Ok((_, spaces, new_state)) = res {
state = new_state;
for space in spaces {
let token = match space.value {
CommentOrNewline::Newline => {
continue;
}
CommentOrNewline::LineComment(_) => Token::LineComment,
CommentOrNewline::DocComment(_) => Token::DocComment,
};
tokens.push(Loc::at(space.region, token));
}
} else {
fast_forward_to(&mut state, tokens, start, |c| c == b'\n');
}
}
'"' | '\'' => {
if let Ok((_, item, new_state)) =
parse_str_like_literal().parse(arena, state.clone(), 0)
{
state = new_state;
match item {
StrLikeLiteral::SingleQuote(_) => {
tokens.push(Loc::at(
Region::between(start, state.pos()),
Token::SingleQuote,
));
}
StrLikeLiteral::Str(_) => {
tokens.push(Loc::at(
Region::between(start, state.pos()),
Token::String,
));
}
}
} else {
fast_forward_to(&mut state, tokens, start, |c| c == b'\n');
}
}
c if c.is_alphabetic() => {
let buffer = state.bytes();
let mut chomped = 0;
let is_upper = c.is_uppercase();
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
} else {
// we're done
break;
}
}
let ident = std::str::from_utf8(&buffer[..chomped]).unwrap();
state.advance_mut(chomped);
if keywords.contains(ident) {
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Keyword));
} else {
tokens.push(Loc::at(
Region::between(start, state.pos()),
if is_upper {
Token::UpperIdent
} else {
Token::LowerIdent
},
));
}
}
'.' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Decimal));
}
'0'..='9' => {
if let Ok((_, _item, new_state)) =
positive_number_literal().parse(arena, state.clone(), 0)
{
state = new_state;
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Number));
} else {
fast_forward_to(&mut state, tokens, start, |b| !b.is_ascii_digit());
}
}
':' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'=') {
state.advance_mut(1);
Token::ColonEquals
} else {
Token::Colon
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'|' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'>') {
state.advance_mut(1);
Token::Pizza
} else if state.bytes().first() == Some(&b'|') {
state.advance_mut(1);
Token::DoubleBar
} else {
Token::Bar
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'&' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'&') {
state.advance_mut(1);
Token::DoubleAnd
} else {
Token::And
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'-' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'>') {
state.advance_mut(1);
Token::Arrow
} else {
Token::Minus
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'+' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Plus));
}
'=' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'=') {
state.advance_mut(1);
Token::DoubleEquals
} else {
Token::Equals
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'>' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'=') {
state.advance_mut(1);
Token::GreaterThanEquals
} else {
Token::GreaterThan
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'<' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'=') {
state.advance_mut(1);
Token::LessThanEquals
} else if state.bytes().first() == Some(&b'-') {
state.advance_mut(1);
Token::Backpass
} else {
Token::LessThan
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'!' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'=') {
state.advance_mut(1);
Token::BangEquals
} else {
Token::Bang
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
',' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Comma));
}
'_' => {
state.advance_mut(1);
tokens.push(Loc::at(
Region::between(start, state.pos()),
Token::Underscore,
));
}
'?' => {
state.advance_mut(1);
tokens.push(Loc::at(
Region::between(start, state.pos()),
Token::QuestionMark,
));
}
'%' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Percent));
}
'*' => {
state.advance_mut(1);
tokens.push(Loc::at(
Region::between(start, state.pos()),
Token::Multiply,
));
}
'^' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Caret));
}
'\\' => {
state.advance_mut(1);
tokens.push(Loc::at(
Region::between(start, state.pos()),
Token::Backslash,
));
}
'/' => {
state.advance_mut(1);
let tok = if state.bytes().first() == Some(&b'/') {
state.advance_mut(1);
Token::DoubleSlash
} else {
Token::Slash
};
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
}
'@' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::AtSign));
}
'{' | '}' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Brace));
}
'[' | ']' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Bracket));
}
'(' | ')' => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Paren));
}
_ => {
state.advance_mut(1);
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Other));
}
}
} else {
break;
}
}
}
fn fast_forward_to(
state: &mut State,
tokens: &mut Vec<Loc<Token>>,
start: roc_region::all::Position,
end: impl Fn(u8) -> bool,
) {
while let Some(b) = state.bytes().first() {
if end(*b) {
break;
}
state.advance_mut(1);
}
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Error));
}
pub const HEADER_KEYWORDS: [&str; 14] = [
"interface",
"app",
"package",
"platform",
"hosted",
"exposes",
"imports",
"with",
"generates",
"package",
"packages",
"requires",
"provides",
"to",
];
#[cfg(test)]
mod tests {
use roc_region::all::Position;
use super::*;
#[test]
fn test_highlight_comments() {
let text = "# a\n#b\n#c";
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(3)),
Token::LineComment
),
Loc::at(
Region::between(Position::new(4), Position::new(6)),
Token::LineComment
),
Loc::at(
Region::between(Position::new(7), Position::new(9)),
Token::LineComment
),
]
);
}
#[test]
fn test_highlight_doc_comments() {
let text = "## a\n##b\n##c";
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(4)),
Token::DocComment
),
// the next two are line comments because there's not a space at the beginning
Loc::at(
Region::between(Position::new(5), Position::new(8)),
Token::LineComment
),
Loc::at(
Region::between(Position::new(9), Position::new(12)),
Token::LineComment
),
]
);
}
#[test]
fn test_highlight_strings() {
let text = r#""a""#;
let tokens = highlight(text);
assert_eq!(
tokens,
vec![Loc::at(
Region::between(Position::new(0), Position::new(3)),
Token::String
)]
);
}
#[test]
fn test_highlight_single_quotes() {
let text = r#"'a'"#;
let tokens = highlight(text);
assert_eq!(
tokens,
vec![Loc::at(
Region::between(Position::new(0), Position::new(3)),
Token::SingleQuote
)]
);
}
#[test]
fn test_highlight_header() {
let text = r#"app "test-app" provides [] to "./blah""#;
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(3)),
Token::Keyword
),
Loc::at(
Region::between(Position::new(4), Position::new(14)),
Token::String
),
Loc::at(
Region::between(Position::new(15), Position::new(23)),
Token::Keyword
),
Loc::at(
Region::between(Position::new(24), Position::new(25)),
Token::Bracket
),
Loc::at(
Region::between(Position::new(25), Position::new(26)),
Token::Bracket
),
Loc::at(
Region::between(Position::new(27), Position::new(29)),
Token::Keyword
),
Loc::at(
Region::between(Position::new(30), Position::new(38)),
Token::String
),
]
);
}
#[test]
fn test_highlight_numbers() {
let text = "123.0 123 123. 123.0e10 123e10 123e-10 0x123";
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(5)),
Token::Number
),
Loc::at(
Region::between(Position::new(6), Position::new(9)),
Token::Number
),
Loc::at(
Region::between(Position::new(10), Position::new(14)),
Token::Number
),
Loc::at(
Region::between(Position::new(15), Position::new(23)),
Token::Number
),
Loc::at(
Region::between(Position::new(24), Position::new(30)),
Token::Number
),
Loc::at(
Region::between(Position::new(31), Position::new(38)),
Token::Number
),
Loc::at(
Region::between(Position::new(39), Position::new(44)),
Token::Number
),
]
);
}
#[test]
fn test_combine_tokens() {
let text = "-> := <- |> || >= <= ==";
let actual = highlight(text);
let expected = vec![
Loc::at(
Region::between(Position::new(0), Position::new(2)),
Token::Arrow,
),
Loc::at(
Region::between(Position::new(3), Position::new(5)),
Token::ColonEquals,
),
Loc::at(
Region::between(Position::new(6), Position::new(8)),
Token::Backpass,
),
Loc::at(
Region::between(Position::new(9), Position::new(11)),
Token::Pizza,
),
Loc::at(
Region::between(Position::new(12), Position::new(14)),
Token::DoubleBar,
),
Loc::at(
Region::between(Position::new(15), Position::new(17)),
Token::GreaterThanEquals,
),
Loc::at(
Region::between(Position::new(18), Position::new(20)),
Token::LessThanEquals,
),
Loc::at(
Region::between(Position::new(21), Position::new(23)),
Token::DoubleEquals,
),
];
assert_eq!(actual, expected);
}
#[test]
fn test_highlight_pattern_matching() {
let text = "Green | Yellow -> \"not red\"";
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(5)),
Token::UpperIdent
),
Loc::at(
Region::between(Position::new(6), Position::new(7)),
Token::Bar
),
Loc::at(
Region::between(Position::new(8), Position::new(14)),
Token::UpperIdent
),
Loc::at(
Region::between(Position::new(15), Position::new(17)),
Token::Arrow
),
Loc::at(
Region::between(Position::new(18), Position::new(27)),
Token::String
),
]
)
}
#[test]
fn test_highlight_question_mark() {
let text = "title? Str";
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(5)),
Token::LowerIdent
),
Loc::at(
Region::between(Position::new(5), Position::new(6)),
Token::QuestionMark
),
Loc::at(
Region::between(Position::new(7), Position::new(10)),
Token::UpperIdent
),
]
)
}
#[test]
fn test_highlight_slash() {
let text = "first / second";
let tokens = highlight(text);
assert_eq!(
tokens,
vec![
Loc::at(
Region::between(Position::new(0), Position::new(5)),
Token::LowerIdent
),
Loc::at(
Region::between(Position::new(6), Position::new(7)),
Token::Slash
),
Loc::at(
Region::between(Position::new(8), Position::new(14)),
Token::LowerIdent
),
]
)
}
}

View file

@ -10,6 +10,7 @@ pub mod ast;
pub mod blankspace; pub mod blankspace;
pub mod expr; pub mod expr;
pub mod header; pub mod header;
pub mod highlight;
pub mod ident; pub mod ident;
pub mod keyword; pub mod keyword;
pub mod module; pub mod module;

View file

@ -1,14 +1,15 @@
[package] [package]
name = "roc_problem" name = "roc_problem"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Provides types to describe problems that can occur when compiling Roc code." description = "Provides types to describe problems that can occur when compiling Roc code."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_region = { path = "../region" }
roc_types = { path = "../types" }

View file

@ -1,10 +1,11 @@
[package] [package]
name = "roc_region" name = "roc_region"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
description = "Data structures for storing source-code-location information, used heavily for contextual error messages." description = "Data structures for storing source-code-location information, used heavily for contextual error messages."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies] [dependencies]
static_assertions = "1.1.0" static_assertions.workspace = true

View file

@ -129,6 +129,10 @@ impl Position {
offset: self.offset - count as u32, offset: self.offset - count as u32,
} }
} }
pub fn byte_offset(&self) -> usize {
self.offset as usize
}
} }
impl Debug for Position { impl Debug for Position {
@ -322,6 +326,10 @@ impl<T> Loc<T> {
value: transform(self.value), value: transform(self.value),
} }
} }
pub fn byte_range(&self) -> std::ops::Range<usize> {
self.region.start.byte_offset()..self.region.end.byte_offset()
}
} }
impl<T> fmt::Debug for Loc<T> impl<T> fmt::Debug for Loc<T>

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