mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-10 10:02:38 +03:00
Merge branch 'roc-lang:main' into jvm-interop
This commit is contained in:
commit
59f815e0d3
6
.github/workflows/nightly_linux_x86_64.yml
vendored
6
.github/workflows/nightly_linux_x86_64.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- 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 --release --locked
|
||||
# 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
|
||||
@ -41,6 +41,10 @@ jobs:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
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/roc
|
||||
|
||||
- name: Make nightly release tar archive
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
@ -38,6 +38,10 @@ jobs:
|
||||
- name: build nightly release
|
||||
run: cargo build --locked --release
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release/roc
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
|
6
.github/workflows/nightly_macos_x86_64.yml
vendored
6
.github/workflows/nightly_macos_x86_64.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- 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 --release --locked
|
||||
# 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
|
||||
@ -36,6 +36,10 @@ jobs:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release/roc
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
8
.github/workflows/nix_macos_x86_64.yml
vendored
8
.github/workflows/nix_macos_x86_64.yml
vendored
@ -19,13 +19,7 @@ jobs:
|
||||
with:
|
||||
clean: "true"
|
||||
|
||||
- uses: cachix/install-nix-action@v15
|
||||
|
||||
# to cache nix packages
|
||||
- uses: cachix/cachix-action@v10
|
||||
with:
|
||||
name: enigmaticsunrise
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- uses: cachix/install-nix-action@v20
|
||||
|
||||
- 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
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -72,4 +72,11 @@ roc_linux_x86_64.tar.gz
|
||||
result
|
||||
|
||||
# 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
|
1801
Cargo.lock
generated
1801
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
206
Cargo.toml
206
Cargo.toml
@ -1,44 +1,40 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/compiler/*",
|
||||
"crates/vendor/*",
|
||||
"crates/glue",
|
||||
"crates/editor",
|
||||
"crates/ast",
|
||||
"crates/cli",
|
||||
"crates/code_markup",
|
||||
"crates/highlight",
|
||||
"crates/error_macros",
|
||||
"crates/reporting",
|
||||
"crates/packaging",
|
||||
"crates/repl_cli",
|
||||
"crates/repl_eval",
|
||||
"crates/repl_test",
|
||||
"crates/repl_wasm",
|
||||
"crates/repl_expect",
|
||||
"crates/test_utils",
|
||||
"crates/valgrind",
|
||||
"crates/tracing",
|
||||
"crates/utils",
|
||||
"crates/docs",
|
||||
"crates/docs_cli",
|
||||
"crates/linker",
|
||||
"crates/wasi-libc-sys",
|
||||
"crates/wasm_module",
|
||||
"crates/wasm_interp",
|
||||
"crates/compiler/*",
|
||||
"crates/vendor/*",
|
||||
"crates/glue",
|
||||
"crates/editor",
|
||||
"crates/ast",
|
||||
"crates/cli",
|
||||
"crates/cli_utils",
|
||||
"crates/code_markup",
|
||||
"crates/highlight",
|
||||
"crates/error_macros",
|
||||
"crates/reporting",
|
||||
"crates/packaging",
|
||||
"crates/repl_cli",
|
||||
"crates/repl_eval",
|
||||
"crates/repl_test",
|
||||
"crates/repl_wasm",
|
||||
"crates/repl_expect",
|
||||
"crates/roc_std",
|
||||
"crates/test_utils",
|
||||
"crates/valgrind",
|
||||
"crates/tracing",
|
||||
"crates/utils",
|
||||
"crates/docs",
|
||||
"crates/docs_cli",
|
||||
"crates/linker",
|
||||
"crates/wasi-libc-sys",
|
||||
"crates/wasm_module",
|
||||
"crates/wasm_interp",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"ci/benchmarks/bench-runner",
|
||||
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
|
||||
"crates/cli_testing_examples",
|
||||
"examples",
|
||||
# Ignore building these normally. They are only imported by tests.
|
||||
# The tests will still correctly build them.
|
||||
"crates/cli_utils",
|
||||
"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",
|
||||
"ci/benchmarks/bench-runner",
|
||||
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
|
||||
"crates/cli_testing_examples",
|
||||
"examples",
|
||||
]
|
||||
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
|
||||
# see www/build.sh for more.
|
||||
@ -47,6 +43,13 @@ exclude = [
|
||||
# workspace, and without `resolver = "2"` here, you can't use `-p` like this.
|
||||
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]
|
||||
# NOTE: roc-lang/inkwell is a fork of TheDan64/inkwell which does not change anything.
|
||||
#
|
||||
@ -65,72 +68,117 @@ resolver = "2"
|
||||
# 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`.
|
||||
# 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"
|
||||
bitflags = "1.3.2"
|
||||
bitvec = "1.0.1"
|
||||
bumpalo = { version = "3.11.1", features = ["collections"] }
|
||||
capstone = "0.11.0"
|
||||
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
const_format = { version = "0.2.23", features = ["const_generics"] }
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
|
||||
blake3 = "1.3.3"
|
||||
brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
|
||||
bumpalo = { version = "3.12.0", features = ["collections"] }
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
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"
|
||||
dircpy = "0.3.14"
|
||||
distance = "0.4.0"
|
||||
encode_unicode = "1.0.0"
|
||||
errno = "0.2.8"
|
||||
errno = "0.3.0"
|
||||
flate2 = "1.0.25"
|
||||
fnv = "1.0.7"
|
||||
fs_extra = "1.2.0"
|
||||
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
|
||||
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
indoc = "1.0.7"
|
||||
insta = "1.20.0"
|
||||
fs_extra = "1.3.0"
|
||||
futures = "0.3.26"
|
||||
glyph_brush = "0.7.7"
|
||||
hashbrown = { version = "0.13.2", features = ["bumpalo"] }
|
||||
iced-x86 = { version = "1.18.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
||||
im = "15.1.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"
|
||||
libc = "0.2.135"
|
||||
libloading = "0.7.1"
|
||||
libc = "0.2.139" # update roc_std/Cargo.toml on change
|
||||
libfuzzer-sys = "0.4"
|
||||
libloading = "0.7.4"
|
||||
log = "0.4.17"
|
||||
mach_object = "0.1"
|
||||
maplit = "1.0.2"
|
||||
memmap2 = "0.5.7"
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
packed_struct = "0.10.0"
|
||||
page_size = "0.4.2"
|
||||
memmap2 = "0.5.10"
|
||||
mimalloc = { version = "0.1.34", default-features = false }
|
||||
nonempty = "0.8.1"
|
||||
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"
|
||||
peg = "0.8.1"
|
||||
pretty_assertions = "1.3.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
regex = "1.5.5"
|
||||
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.144", features = ["derive"] }
|
||||
signal-hook = "0.3.14"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
static_assertions = "1.1.0"
|
||||
perfcnt = "0.8.0"
|
||||
pest = "2.5.6"
|
||||
pest_derive = "2.5.6"
|
||||
pretty_assertions = "1.3.0" # update roc_std/Cargo.toml on change
|
||||
proc-macro2 = "1.0.51"
|
||||
proptest = "1.1.0"
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
quickcheck = "1.0.3" # update roc_std/Cargo.toml on change
|
||||
quickcheck_macros = "1.0.0" # update roc_std/Cargo.toml on change
|
||||
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"
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
target-lexicon = "0.12.3"
|
||||
tempfile = "3.2.0"
|
||||
unicode-segmentation = "1.10.0"
|
||||
strum_macros = "0.24.3"
|
||||
syn = { version = "1.0.109", features = ["full", "extra-traits"] }
|
||||
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"
|
||||
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"
|
||||
|
||||
# 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
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
lto = "thin"
|
||||
|
||||
# debug = true # enable when profiling
|
||||
[profile.bench]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
lto = "thin"
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
inherits = "release"
|
||||
|
18
README.md
18
README.md
@ -12,12 +12,24 @@ If you'd like to get involved in contributing to the language, the Zulip chat is
|
||||
|
||||
## 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)
|
||||
|
||||
[<img src="https://www.rwx.com/rwx_banner.svg" height="60" alt="rwx logo"/>](https://www.rwx.com)
|
||||
|
||||
[<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!
|
||||
|
422
ci/benchmarks/bench-runner/Cargo.lock
generated
422
ci/benchmarks/bench-runner/Cargo.lock
generated
@ -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"
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.1.15", features = ["derive"] }
|
||||
regex = "1.5.5"
|
||||
data-encoding = "2.3.2"
|
||||
is_executable = "1.0.1"
|
||||
regex = "1.5.5"
|
||||
ring = "0.16.20"
|
||||
data-encoding = "2.3.2"
|
@ -60,7 +60,6 @@ The compiler includes the following sub-crates;
|
||||
- `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_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_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.
|
||||
|
@ -1,39 +1,39 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_builtins = { path = "../compiler/builtins"}
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
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_packaging = { path = "../packaging" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_problem = { path = "../compiler/problem" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
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_region = { path = "../compiler/region" }
|
||||
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" }
|
||||
|
||||
arrayvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
libc.workspace = true
|
||||
page_size.workspace = true
|
||||
snafu.workspace = true
|
||||
libc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
indoc.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["memoryapi"]}
|
||||
|
||||
winapi.workspace = true
|
||||
|
@ -1,87 +1,81 @@
|
||||
[package]
|
||||
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."
|
||||
default-run = "roc"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[bin]]
|
||||
bench = false
|
||||
name = "roc"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[features]
|
||||
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
|
||||
|
||||
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
||||
i386-cli-run = ["target-x86"]
|
||||
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
||||
|
||||
editor = ["roc_editor"]
|
||||
|
||||
run-wasm32 = ["roc_wasm_interp"]
|
||||
|
||||
# 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-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_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-aarch64",
|
||||
"target-arm",
|
||||
"target-x86",
|
||||
"target-x86_64",
|
||||
"target-wasm32"
|
||||
]
|
||||
target-all = ["target-aarch64", "target-arm", "target-x86", "target-x86_64", "target-wasm32"]
|
||||
|
||||
sanitizers = ["roc_build/sanitizers"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_can = { path = "../compiler/can" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
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_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_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_reporting = { path = "../reporting" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_tracing = { path = "../tracing" }
|
||||
roc_gen_llvm = {path = "../compiler/gen_llvm"}
|
||||
roc_wasm_interp = { path = "../wasm_interp", optional = true }
|
||||
|
||||
ven_pretty = { path = "../vendor/pretty" }
|
||||
|
||||
indoc.workspace = true
|
||||
bumpalo.workspace = true
|
||||
clap.workspace = true
|
||||
const_format.workspace = true
|
||||
mimalloc.workspace = true
|
||||
bumpalo.workspace = true
|
||||
libc.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
|
||||
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
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
@ -89,14 +83,15 @@ roc_repl_expect = { path = "../repl_expect" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.3.0"
|
||||
cli_utils = { path = "../cli_utils" }
|
||||
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" }
|
||||
parking_lot = "0.12"
|
||||
|
||||
criterion.workspace = true
|
||||
indoc.workspace = true
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
serial_test.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "time_bench"
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
@ -3,11 +3,12 @@
|
||||
#[macro_use]
|
||||
extern crate const_format;
|
||||
|
||||
use build::BuiltFile;
|
||||
use bumpalo::Bump;
|
||||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||||
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_load::{ExpectMetadata, LoadingProblem, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
@ -29,12 +30,9 @@ use target_lexicon::{
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use tempfile::TempDir;
|
||||
|
||||
pub mod build;
|
||||
mod format;
|
||||
pub use format::format;
|
||||
|
||||
use crate::build::{standard_load_config, BuildFileError, BuildOrdering};
|
||||
|
||||
const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
||||
|
||||
pub const CMD_BUILD: &str = "build";
|
||||
@ -520,7 +518,7 @@ pub fn build(
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
link_type: LinkType,
|
||||
) -> io::Result<i32> {
|
||||
use build::build_file;
|
||||
use roc_build::program::build_file;
|
||||
use BuildConfig::*;
|
||||
|
||||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! The `roc` binary that brings together all functionality in the Roc toolset.
|
||||
use roc_build::link::LinkType;
|
||||
use roc_cli::build::check_file;
|
||||
use roc_build::program::check_file;
|
||||
use roc_cli::{
|
||||
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,
|
||||
|
@ -533,6 +533,7 @@ mod cli_run {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(zig_platform)]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn platform_switching_zig() {
|
||||
test_roc_app_slim(
|
||||
@ -676,6 +677,7 @@ mod cli_run {
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, ignore)] // flaky error; issue #5024
|
||||
#[serial(breakout)]
|
||||
#[test]
|
||||
fn breakout() {
|
||||
test_roc_app_slim(
|
||||
@ -688,6 +690,7 @@ mod cli_run {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(breakout)]
|
||||
fn breakout_hello_gui() {
|
||||
test_roc_app_slim(
|
||||
"examples/gui/breakout",
|
||||
@ -861,6 +864,7 @@ mod cli_run {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(zig_platform)]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_movies_csv() {
|
||||
test_roc_app_slim(
|
||||
|
@ -1,27 +1,25 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
# 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]
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_utils = { path = "../utils" }
|
||||
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde-xml-rs = "0.5.1"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
tempfile = "3.2.0"
|
||||
bumpalo.workspace = true
|
||||
criterion.workspace = true
|
||||
serde-xml-rs.workspace = true
|
||||
serde.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
rlimit = "0.6.2"
|
||||
rlimit.workspace = true
|
||||
|
@ -387,7 +387,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
|
||||
// Descend into examples/{dir_name}
|
||||
path.push("crates");
|
||||
path.push("cli_testing_examples");
|
||||
path.extend(dir_name.split("/")); // Make slashes cross-target
|
||||
path.extend(dir_name.split('/')); // Make slashes cross-target
|
||||
|
||||
path
|
||||
}
|
||||
@ -396,7 +396,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
|
||||
pub fn dir_path_from_root(dir_name: &str) -> PathBuf {
|
||||
let mut path = root_dir();
|
||||
|
||||
path.extend(dir_name.split("/")); // Make slashes cross-target
|
||||
path.extend(dir_name.split('/')); // Make slashes cross-target
|
||||
|
||||
path
|
||||
}
|
||||
@ -419,7 +419,7 @@ pub fn fixtures_dir(dir_name: &str) -> PathBuf {
|
||||
path.push("cli");
|
||||
path.push("tests");
|
||||
path.push("fixtures");
|
||||
path.extend(dir_name.split("/")); // Make slashes cross-target
|
||||
path.extend(dir_name.split('/')); // Make slashes cross-target
|
||||
|
||||
path
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_ast = { path = "../ast" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_utils = { path = "../utils" }
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
palette = "0.6.1"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
bumpalo = { version = "3.11.1", features = ["collections"] }
|
||||
|
||||
palette.workspace = true
|
||||
|
||||
bumpalo.workspace = true
|
||||
serde.workspace = true
|
||||
snafu.workspace = true
|
||||
|
@ -1,15 +1,17 @@
|
||||
[package]
|
||||
authors = ["The Roc Contributors"]
|
||||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
name = "roc_alias_analysis"
|
||||
version = "0.0.1"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
morphic_lib = {path = "../../vendor/morphic_lib"}
|
||||
roc_collections = {path = "../collections"}
|
||||
roc_error_macros = {path = "../../error_macros"}
|
||||
roc_module = {path = "../module"}
|
||||
roc_mono = {path = "../mono"}
|
||||
roc_debug_flags = {path = "../debug_flags"}
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
|
@ -188,20 +188,28 @@ where
|
||||
let func_name = FuncName(&bytes);
|
||||
|
||||
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
|
||||
for (_, (symbol, top_level, layout)) in aliases {
|
||||
match layout {
|
||||
for (_, hels) in aliases {
|
||||
match hels.raw_function_layout {
|
||||
RawFunctionLayout::Function(_, _, _) => {
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes =
|
||||
func_name_bytes_help(*symbol, it, Niche::NONE, top_level.result);
|
||||
let it = hels.proc_layout.arguments.iter().copied();
|
||||
let bytes = func_name_bytes_help(
|
||||
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(_) => {
|
||||
let bytes =
|
||||
func_name_bytes_help(*symbol, [], Niche::NONE, top_level.result);
|
||||
let bytes = func_name_bytes_help(
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
@ -1,52 +1,55 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_solve_problem = { path = "../solve_problem" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
||||
roc_gen_llvm = { path = "../gen_llvm" }
|
||||
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_error_macros = { path = "../../error_macros" }
|
||||
roc_solve_problem = { path = "../solve_problem" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_utils = { path = "../../utils" }
|
||||
|
||||
wasi_libc_sys = { path = "../../wasi-libc-sys" }
|
||||
|
||||
const_format.workspace = true
|
||||
bumpalo.workspace = true
|
||||
libloading.workspace = true
|
||||
tempfile.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
indoc.workspace = true
|
||||
inkwell.workspace = true
|
||||
libloading.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
serde_json = "1.0.85"
|
||||
serde_json.workspace = true
|
||||
|
||||
[features]
|
||||
target-arm = []
|
||||
target-aarch64 = ["roc_gen_dev/target-aarch64"]
|
||||
target-arm = []
|
||||
target-wasm32 = []
|
||||
target-x86 = []
|
||||
target-x86_64 = ["roc_gen_dev/target-x86_64"]
|
||||
target-wasm32 = []
|
||||
|
||||
# This is used to enable fuzzing and sanitizers.
|
||||
# Example use is describe here: https://github.com/bhansconnect/roc-fuzz
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::target::{arch_str, target_zig_str};
|
||||
use const_format::concatcp;
|
||||
use libloading::{Error, Library};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_error_macros::internal_error;
|
||||
@ -15,13 +14,7 @@ use std::{env, fs};
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||
use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum LinkType {
|
||||
// These numbers correspond to the --lib and --no-link flags
|
||||
Executable = 0,
|
||||
Dylib = 1,
|
||||
None = 2,
|
||||
}
|
||||
pub use roc_linker::LinkType;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum LinkingStrategy {
|
||||
@ -61,134 +54,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"
|
||||
pub fn legacy_host_filename(target: &Triple) -> Option<String> {
|
||||
let os = roc_target::OperatingSystem::from(target.operating_system);
|
||||
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 {
|
||||
|
@ -1,13 +1,30 @@
|
||||
use crate::link::{
|
||||
legacy_host_filename, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
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_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::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
thread::JoinHandle,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
use roc_collections::all::MutSet;
|
||||
@ -579,3 +596,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 =
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_utils = { path = "../../utils" }
|
||||
|
||||
tempfile.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
roc_utils = { path = "../../utils" }
|
||||
|
||||
# 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
|
||||
|
@ -2159,125 +2159,45 @@ test "isWhitespace" {
|
||||
pub fn strTrim(input_string: RocStr) callconv(.C) RocStr {
|
||||
var string = input_string;
|
||||
|
||||
if (!string.isEmpty()) {
|
||||
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 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;
|
||||
}
|
||||
if (string.isEmpty()) {
|
||||
string.deinit();
|
||||
return RocStr.empty();
|
||||
}
|
||||
|
||||
return RocStr.empty();
|
||||
}
|
||||
const bytes_ptr = string.asU8ptrMut();
|
||||
|
||||
pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr {
|
||||
if (string.str_bytes) |bytes_ptr| {
|
||||
const leading_bytes = countLeadingWhitespaceBytes(string);
|
||||
const original_len = string.len();
|
||||
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;
|
||||
}
|
||||
if (original_len == leading_bytes) {
|
||||
string.deinit();
|
||||
return RocStr.empty();
|
||||
}
|
||||
|
||||
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.str_bytes) |bytes_ptr| {
|
||||
const trailing_bytes = countTrailingWhitespaceBytes(string);
|
||||
const original_len = string.len();
|
||||
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);
|
||||
|
||||
if (original_len == trailing_bytes) {
|
||||
string.deinit();
|
||||
return RocStr.empty();
|
||||
}
|
||||
string.decref();
|
||||
|
||||
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()) {
|
||||
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);
|
||||
while (index != new_len) : (index += 1) {
|
||||
bytes_ptr[index] = src[index];
|
||||
}
|
||||
}
|
||||
|
||||
var new_string = string;
|
||||
@ -2285,8 +2205,99 @@ pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
|
||||
|
||||
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 {
|
||||
|
@ -49,23 +49,25 @@ false = @Bool False
|
||||
## gate. The infix operator `&&` can also be used as shorthand for
|
||||
## `Bool.and`.
|
||||
##
|
||||
## expect (Bool.and Bool.true Bool.true) == Bool.true
|
||||
## expect (Bool.true && Bool.true) == Bool.true
|
||||
## expect (Bool.false && Bool.true) == Bool.false
|
||||
## expect (Bool.true && Bool.false) == Bool.false
|
||||
## expect (Bool.false && Bool.false) == Bool.false
|
||||
## ```
|
||||
## expect (Bool.and Bool.true Bool.true) == Bool.true
|
||||
## expect (Bool.true && Bool.true) == Bool.true
|
||||
## expect (Bool.false && Bool.true) == 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
|
||||
## other function. However, in some languages `&&` and `||` are special-cased.
|
||||
## In these languages the compiler will skip evaluating the expression after the
|
||||
## first operator under certain circumstances. For example an expression like
|
||||
## `enablePets && likesDogs user` would compile to.
|
||||
##
|
||||
## if enablePets then
|
||||
## likesDogs user
|
||||
## else
|
||||
## Bool.false
|
||||
##
|
||||
## ```
|
||||
## if enablePets then
|
||||
## likesDogs user
|
||||
## else
|
||||
## Bool.false
|
||||
## ```
|
||||
## 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
|
||||
## 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
|
||||
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
|
||||
## The infix operator `||` can also be used as shorthand for `Bool.or`.
|
||||
##
|
||||
## expect (Bool.or Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.true || Bool.true) == Bool.true
|
||||
## expect (Bool.false || Bool.true) == Bool.true
|
||||
## expect (Bool.true || Bool.false) == Bool.true
|
||||
## expect (Bool.false || Bool.false) == Bool.false
|
||||
##
|
||||
## ```
|
||||
## expect (Bool.or Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.true || Bool.true) == Bool.true
|
||||
## expect (Bool.false || Bool.true) == Bool.true
|
||||
## expect (Bool.true || Bool.false) == Bool.true
|
||||
## expect (Bool.false || Bool.false) == Bool.false
|
||||
## ```
|
||||
## **Performance Note** that in Roc the `&&` and `||` work the same way as any
|
||||
## other functions. However, in some languages `&&` and `||` are special-cased.
|
||||
## 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
|
||||
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
|
||||
## gate. The operator `!` can also be used as shorthand for `Bool.not`.
|
||||
##
|
||||
## expect (Bool.not Bool.false) == Bool.true
|
||||
## expect (!Bool.false) == Bool.true
|
||||
## ```
|
||||
## expect (Bool.not Bool.false) == Bool.true
|
||||
## expect (!Bool.false) == Bool.true
|
||||
## ```
|
||||
not : Bool -> Bool
|
||||
|
||||
## 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
|
||||
## functions.
|
||||
##
|
||||
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.false != Bool.false) == Bool.false
|
||||
## expect "Apples" != "Oranges"
|
||||
## ```
|
||||
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.false != Bool.false) == Bool.false
|
||||
## expect "Apples" != "Oranges"
|
||||
## ```
|
||||
isNotEq : a, a -> Bool | a has Eq
|
||||
isNotEq = \a, b -> structuralNotEq a b
|
||||
|
||||
|
@ -6,13 +6,15 @@ interface Box
|
||||
## 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
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
# # we'd need reset/reuse for box for this to be efficient
|
||||
|
@ -45,15 +45,15 @@ interface Dict
|
||||
##
|
||||
## Here's an example of a dictionary which uses a city's name as the key, and
|
||||
## its population as the associated value.
|
||||
##
|
||||
## populationByCity =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "London" 8_961_989
|
||||
## |> Dict.insert "Philadelphia" 1_603_797
|
||||
## |> Dict.insert "Shanghai" 24_870_895
|
||||
## |> Dict.insert "Delhi" 16_787_941
|
||||
## |> Dict.insert "Amsterdam" 872_680
|
||||
##
|
||||
## ```
|
||||
## populationByCity =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "London" 8_961_989
|
||||
## |> Dict.insert "Philadelphia" 1_603_797
|
||||
## |> Dict.insert "Shanghai" 24_870_895
|
||||
## |> Dict.insert "Delhi" 16_787_941
|
||||
## |> Dict.insert "Amsterdam" 872_680
|
||||
## ```
|
||||
## ### Accessing keys or values
|
||||
##
|
||||
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or
|
||||
@ -66,13 +66,13 @@ interface Dict
|
||||
## ### Removing
|
||||
##
|
||||
## We can remove an element from the dictionary, like so:
|
||||
##
|
||||
## populationByCity
|
||||
## |> Dict.remove "Philadelphia"
|
||||
## |> Dict.keys
|
||||
## ==
|
||||
## ["London", "Amsterdam", "Shanghai", "Delhi"]
|
||||
##
|
||||
## ```
|
||||
## populationByCity
|
||||
## |> Dict.remove "Philadelphia"
|
||||
## |> Dict.keys
|
||||
## ==
|
||||
## ["London", "Amsterdam", "Shanghai", "Delhi"]
|
||||
## ```
|
||||
## 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
|
||||
## spot where Philadelphia was previously. This is exactly what [Dict.remove]
|
||||
@ -125,36 +125,39 @@ withCapacity = \_ ->
|
||||
empty {}
|
||||
|
||||
## Returns a dictionary containing the key and value provided as input.
|
||||
##
|
||||
## expect
|
||||
## Dict.single "A" "B"
|
||||
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
|
||||
## ```
|
||||
## expect
|
||||
## Dict.single "A" "B"
|
||||
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
|
||||
## ```
|
||||
single : k, v -> Dict k v | k has Hash & Eq
|
||||
single = \k, v ->
|
||||
insert (empty {}) k v
|
||||
|
||||
## Returns dictionary with the keys and values specified by the input [List].
|
||||
##
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
|
||||
## ```
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 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 = \data ->
|
||||
# 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)
|
||||
|
||||
## Returns the number of values in the dictionary.
|
||||
##
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "One" "A Song"
|
||||
## |> Dict.insert "Two" "Candy Canes"
|
||||
## |> Dict.insert "Three" "Boughs of Holly"
|
||||
## |> Dict.len
|
||||
## |> Bool.isEq 3
|
||||
## ```
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "One" "A Song"
|
||||
## |> Dict.insert "Two" "Candy Canes"
|
||||
## |> Dict.insert "Three" "Boughs of Holly"
|
||||
## |> Dict.len
|
||||
## |> Bool.isEq 3
|
||||
## ```
|
||||
len : Dict k v -> Nat | k has Hash & Eq
|
||||
len = \@Dict { size } ->
|
||||
size
|
||||
@ -180,13 +183,14 @@ clear = \@Dict { metadata, dataIndices, data } ->
|
||||
## 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
|
||||
## initial `state` value provided for the first call.
|
||||
##
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
## |> Dict.insert "Orange" 24
|
||||
## |> Dict.walk 0 (\count, _, qty -> count + qty)
|
||||
## |> Bool.isEq 36
|
||||
## ```
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
## |> Dict.insert "Orange" 24
|
||||
## |> Dict.walk 0 (\count, _, qty -> count + qty)
|
||||
## |> Bool.isEq 36
|
||||
## ```
|
||||
walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq
|
||||
walk = \@Dict { data }, initialState, transform ->
|
||||
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
|
||||
## will return [Ok value], otherwise return [Err KeyNotFound].
|
||||
## ```
|
||||
## dictionary =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1 "Apple"
|
||||
## |> Dict.insert 2 "Orange"
|
||||
##
|
||||
## dictionary =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1 "Apple"
|
||||
## |> Dict.insert 2 "Orange"
|
||||
##
|
||||
## expect Dict.get dictionary 1 == Ok "Apple"
|
||||
## expect Dict.get dictionary 2000 == Err KeyNotFound
|
||||
## 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 { metadata, dataIndices, data }, key ->
|
||||
hashKey =
|
||||
@ -237,12 +242,13 @@ get = \@Dict { metadata, dataIndices, data }, key ->
|
||||
Err KeyNotFound
|
||||
|
||||
## Check if the dictionary has a value for a specified key.
|
||||
##
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1234 "5678"
|
||||
## |> Dict.contains 1234
|
||||
## |> Bool.isEq Bool.true
|
||||
## ```
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1234 "5678"
|
||||
## |> Dict.contains 1234
|
||||
## |> Bool.isEq Bool.true
|
||||
## ```
|
||||
contains : Dict k v, k -> Bool | k has Hash & Eq
|
||||
contains = \@Dict { metadata, dataIndices, data }, key ->
|
||||
hashKey =
|
||||
@ -261,12 +267,13 @@ contains = \@Dict { metadata, dataIndices, data }, key ->
|
||||
Bool.false
|
||||
|
||||
## Insert a value into the dictionary at a specified key.
|
||||
##
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
## |> Dict.get "Apples"
|
||||
## |> Bool.isEq (Ok 12)
|
||||
## ```
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
## |> Dict.get "Apples"
|
||||
## |> Bool.isEq (Ok 12)
|
||||
## ```
|
||||
insert : Dict k v, k, v -> Dict k v | k has Hash & Eq
|
||||
insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
|
||||
hashKey =
|
||||
@ -305,13 +312,14 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
|
||||
insertNotFoundHelper rehashedDict key value h1Key h2Key
|
||||
|
||||
## Remove a value from the dictionary for a specified key.
|
||||
##
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Some" "Value"
|
||||
## |> Dict.remove "Some"
|
||||
## |> Dict.len
|
||||
## |> Bool.isEq 0
|
||||
## ```
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Some" "Value"
|
||||
## |> Dict.remove "Some"
|
||||
## |> Dict.len
|
||||
## |> Bool.isEq 0
|
||||
## ```
|
||||
remove : Dict k v, k -> Dict k v | k has Hash & Eq
|
||||
remove = \@Dict { metadata, dataIndices, data, size }, key ->
|
||||
# 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
|
||||
## is missing. This is more efficient than doing both a `Dict.get` and then a
|
||||
## `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]
|
||||
## alterValue = \possibleValue ->
|
||||
## when possibleValue is
|
||||
## 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 {}
|
||||
## 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, key, alter ->
|
||||
# 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].
|
||||
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
##
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Dict.toList
|
||||
## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
|
||||
## ```
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Dict.toList
|
||||
## |> 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 { data } ->
|
||||
data
|
||||
|
||||
## Returns the keys of a dictionary as a [List].
|
||||
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
##
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Dict.keys
|
||||
## |> Bool.isEq [1,2,3,4]
|
||||
## ```
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Dict.keys
|
||||
## |> Bool.isEq [1,2,3,4]
|
||||
## ```
|
||||
keys : Dict k v -> List k | k has Hash & Eq
|
||||
keys = \@Dict { data } ->
|
||||
List.map data (\T k _ -> k)
|
||||
|
||||
## Returns the values of a dictionary as a [List].
|
||||
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
##
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Dict.values
|
||||
## |> Bool.isEq ["One","Two","Three","Four"]
|
||||
## ```
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
## |> Dict.insert 3 "Three"
|
||||
## |> Dict.insert 4 "Four"
|
||||
## |> Dict.values
|
||||
## |> Bool.isEq ["One","Two","Three","Four"]
|
||||
## ```
|
||||
values : Dict k v -> List v | k has Hash & Eq
|
||||
values = \@Dict { data } ->
|
||||
List.map data (\T _ v -> v)
|
||||
@ -414,24 +426,25 @@ values = \@Dict { data } ->
|
||||
## both dictionaries will be combined. Note that where there are pairs
|
||||
## with the same key, the value contained in the second input will be
|
||||
## retained, and the value in the first input will be removed.
|
||||
## ```
|
||||
## first =
|
||||
## Dict.single 1 "Not Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
##
|
||||
## first =
|
||||
## Dict.single 1 "Not Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
## second =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 3 "Me Too"
|
||||
## |> Dict.insert 4 "And Also Me"
|
||||
##
|
||||
## second =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 3 "Me Too"
|
||||
## |> Dict.insert 4 "And Also Me"
|
||||
## expected =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
## |> Dict.insert 3 "Me Too"
|
||||
## |> Dict.insert 4 "And Also Me"
|
||||
##
|
||||
## expected =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
## |> Dict.insert 3 "Me Too"
|
||||
## |> Dict.insert 4 "And Also Me"
|
||||
##
|
||||
## expect
|
||||
## Dict.insertAll first second == expected
|
||||
## expect
|
||||
## Dict.insertAll first second == expected
|
||||
## ```
|
||||
insertAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
|
||||
insertAll = \xs, ys ->
|
||||
walk ys xs insert
|
||||
@ -441,18 +454,19 @@ insertAll = \xs, ys ->
|
||||
## 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,
|
||||
## and the value in the second input will be removed.
|
||||
## ```
|
||||
## first =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
##
|
||||
## first =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
## second =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
## |> Dict.insert 3 "But Not Me"
|
||||
## |> Dict.insert 4 "Or Me"
|
||||
##
|
||||
## second =
|
||||
## 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
|
||||
## expect Dict.keepShared first second == first
|
||||
## ```
|
||||
keepShared : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
|
||||
keepShared = \xs, ys ->
|
||||
walk
|
||||
@ -469,21 +483,22 @@ keepShared = \xs, ys ->
|
||||
## 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
|
||||
## 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 =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
## |> Dict.insert 3 "Remove Me"
|
||||
## second =
|
||||
## Dict.single 3 "Remove Me"
|
||||
## |> Dict.insert 4 "I do nothing..."
|
||||
##
|
||||
## second =
|
||||
## Dict.single 3 "Remove Me"
|
||||
## |> Dict.insert 4 "I do nothing..."
|
||||
## expected =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
##
|
||||
## expected =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
##
|
||||
## expect Dict.removeAll first second == expected
|
||||
## expect Dict.removeAll first second == expected
|
||||
## ```
|
||||
removeAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
|
||||
removeAll = \xs, ys ->
|
||||
walk ys xs (\state, k, _ -> remove state k)
|
||||
|
@ -73,11 +73,11 @@ interface List
|
||||
|
||||
## Types
|
||||
## A sequential list of values.
|
||||
##
|
||||
## >>> [1, 2, 3] # a list of numbers
|
||||
## >>> ["a", "b", "c"] # a list of strings
|
||||
## >>> [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
|
||||
##
|
||||
## ```
|
||||
## [1, 2, 3] # a list of numbers
|
||||
## ["a", "b", "c"] # a list of strings
|
||||
## [[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
|
||||
## 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)
|
||||
@ -105,11 +105,11 @@ interface List
|
||||
## will be immediately freed.
|
||||
##
|
||||
## 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
|
||||
## 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.
|
||||
##
|
||||
## Let's turn this example into a function.
|
||||
## ```
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
## { foo: ratings, bar: ratings }
|
||||
##
|
||||
## getRatings 5
|
||||
## { foo: ratings, bar: ratings }
|
||||
##
|
||||
## getRatings 5
|
||||
## ```
|
||||
## 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
|
||||
## 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.
|
||||
##
|
||||
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
|
||||
## ```
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
## { foo: ratings, bar: ratings }
|
||||
##
|
||||
## (getRatings 5).bar
|
||||
## { foo: ratings, bar: ratings }
|
||||
##
|
||||
## (getRatings 5).bar
|
||||
## ```
|
||||
## 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
|
||||
## 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.
|
||||
##
|
||||
## 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
|
||||
## 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
|
||||
@ -167,25 +167,25 @@ interface List
|
||||
## and then with a list of lists, to see how they differ.
|
||||
##
|
||||
## 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
|
||||
## first = List.first nums
|
||||
## last = List.last nums
|
||||
##
|
||||
## 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:
|
||||
## ```
|
||||
## 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
|
||||
## first = List.first lists
|
||||
## last = List.last lists
|
||||
##
|
||||
## first
|
||||
## ```
|
||||
## 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,
|
||||
## 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.
|
||||
## * 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.
|
||||
## ```
|
||||
## List.isEmpty [1, 2, 3]
|
||||
##
|
||||
## >>> List.isEmpty [1, 2, 3]
|
||||
##
|
||||
## >>> List.isEmpty []
|
||||
## List.isEmpty []
|
||||
## ```
|
||||
isEmpty : List a -> Bool
|
||||
isEmpty = \list ->
|
||||
List.len list == 0
|
||||
@ -237,9 +238,9 @@ replace = \list, index, newValue ->
|
||||
{ list, value: newValue }
|
||||
|
||||
## 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
|
||||
## list unmodified.
|
||||
##
|
||||
@ -249,11 +250,12 @@ set = \list, index, value ->
|
||||
(List.replace list index value).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, element ->
|
||||
list
|
||||
@ -268,11 +270,12 @@ append = \list, element ->
|
||||
appendUnsafe : List a, a -> List a
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
## 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.
|
||||
##
|
||||
## This is useful in pipelines, like so:
|
||||
##
|
||||
## websites =
|
||||
## Str.concat domain ".com"
|
||||
## |> List.single
|
||||
##
|
||||
## ```
|
||||
## websites =
|
||||
## Str.concat domain ".com"
|
||||
## |> List.single
|
||||
## ```
|
||||
single : a -> List a
|
||||
single = \x -> [x]
|
||||
|
||||
## Returns a list with the given length, where every element is the given value.
|
||||
##
|
||||
##
|
||||
repeat : a, Nat -> List a
|
||||
repeat = \value, count ->
|
||||
repeatHelp value count (List.withCapacity count)
|
||||
@ -329,8 +331,9 @@ repeatHelp = \value, count, accum ->
|
||||
accum
|
||||
|
||||
## Returns the list with its elements reversed.
|
||||
##
|
||||
## >>> List.reverse [1, 2, 3]
|
||||
## ```
|
||||
## List.reverse [1, 2, 3]
|
||||
## ```
|
||||
reverse : List a -> List a
|
||||
reverse = \list ->
|
||||
reverseHelp list 0 (Num.subSaturated (List.len list) 1)
|
||||
@ -342,12 +345,11 @@ reverseHelp = \list, left, right ->
|
||||
list
|
||||
|
||||
## Join the given lists together into one list.
|
||||
##
|
||||
## >>> List.join [[1, 2, 3], [4, 5], [], [6, 7]]
|
||||
##
|
||||
## >>> List.join [[], []]
|
||||
##
|
||||
## >>> List.join []
|
||||
## ```
|
||||
## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
|
||||
## List.join [[], []]
|
||||
## List.join []
|
||||
## ```
|
||||
join : List (List a) -> List a
|
||||
join = \lists ->
|
||||
totalLength =
|
||||
@ -366,10 +368,10 @@ contains = \list, needle ->
|
||||
## which updates the `state`. It returns the final `state` at the end.
|
||||
##
|
||||
## You can use it in a pipeline:
|
||||
##
|
||||
## [2, 4, 8]
|
||||
## |> List.walk 0 Num.add
|
||||
##
|
||||
## ```
|
||||
## [2, 4, 8]
|
||||
## |> List.walk 0 Num.add
|
||||
## ```
|
||||
## This returns 14 because:
|
||||
## * `state` starts at 0
|
||||
## * 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
|
||||
##
|
||||
## The following returns -6:
|
||||
##
|
||||
## [1, 2, 3]
|
||||
## |> List.walk 0 Num.sub
|
||||
##
|
||||
## ```
|
||||
## [1, 2, 3]
|
||||
## |> List.walk 0 Num.sub
|
||||
## ```
|
||||
## Note that in other languages, `walk` is sometimes called `reduce`,
|
||||
## `fold`, `foldLeft`, or `foldl`.
|
||||
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
|
||||
## 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
|
||||
##
|
||||
## [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
|
||||
## 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
|
||||
##
|
||||
## `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
|
||||
## 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, toResult ->
|
||||
walker = \accum, element ->
|
||||
@ -573,12 +576,13 @@ keepOks = \list, toResult ->
|
||||
|
||||
## 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.
|
||||
## ```
|
||||
## 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, toResult ->
|
||||
walker = \accum, element ->
|
||||
@ -590,10 +594,11 @@ keepErrs = \list, toResult ->
|
||||
|
||||
## 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.
|
||||
## ```
|
||||
## 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
|
||||
|
||||
## 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
|
||||
## 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
|
||||
|
||||
## 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`.
|
||||
##
|
||||
## 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:
|
||||
##
|
||||
## 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:
|
||||
##
|
||||
## 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.)
|
||||
##
|
||||
## 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`
|
||||
## with `start` regardless of what `end` and `step` are set to.
|
||||
range : _
|
||||
range = \{ start, end, step ? 0 } ->
|
||||
{ incByStep, stepIsPositive } =
|
||||
{ calcNext, stepIsPositive } =
|
||||
if step == 0 then
|
||||
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) ->
|
||||
if x < y then
|
||||
{
|
||||
incByStep: \i -> i + 1,
|
||||
calcNext: \i -> Num.addChecked i 1,
|
||||
stepIsPositive: Bool.true,
|
||||
}
|
||||
else
|
||||
{
|
||||
incByStep: \i -> i - 1,
|
||||
calcNext: \i -> Num.subChecked i 1,
|
||||
stepIsPositive: Bool.false,
|
||||
}
|
||||
|
||||
T (At _) (Length _) | T (After _) (Length _) ->
|
||||
{
|
||||
incByStep: \i -> i + 1,
|
||||
calcNext: \i -> Num.addChecked i 1,
|
||||
stepIsPositive: Bool.true,
|
||||
}
|
||||
else
|
||||
{
|
||||
incByStep: \i -> i + step,
|
||||
calcNext: \i -> Num.addChecked i step,
|
||||
stepIsPositive: step > 0,
|
||||
}
|
||||
|
||||
inclusiveStart =
|
||||
when start is
|
||||
At x -> x
|
||||
After x -> incByStep x
|
||||
At x -> Ok x
|
||||
After x -> calcNext x
|
||||
|
||||
when end is
|
||||
At at ->
|
||||
isComplete =
|
||||
isValid =
|
||||
if stepIsPositive then
|
||||
\i -> i > at
|
||||
\i -> i <= at
|
||||
else
|
||||
\i -> i < at
|
||||
\i -> i >= at
|
||||
|
||||
# TODO: switch to List.withCapacity
|
||||
rangeHelp [] inclusiveStart incByStep isComplete
|
||||
rangeHelp [] inclusiveStart calcNext isValid
|
||||
|
||||
Before before ->
|
||||
isComplete =
|
||||
isValid =
|
||||
if stepIsPositive then
|
||||
\i -> i >= before
|
||||
\i -> i < before
|
||||
else
|
||||
\i -> i <= before
|
||||
\i -> i > before
|
||||
|
||||
# TODO: switch to List.withCapacity
|
||||
rangeHelp [] inclusiveStart incByStep isComplete
|
||||
rangeHelp [] inclusiveStart calcNext isValid
|
||||
|
||||
Length l ->
|
||||
rangeLengthHelp (List.withCapacity l) inclusiveStart l incByStep
|
||||
rangeLengthHelp (List.withCapacity l) inclusiveStart l calcNext
|
||||
|
||||
rangeHelp = \accum, i, incByStep, isComplete ->
|
||||
if isComplete i then
|
||||
accum
|
||||
else
|
||||
# TODO: change this to List.appendUnsafe once capacity is set correctly
|
||||
rangeHelp (List.append accum i) (incByStep i) incByStep isComplete
|
||||
rangeHelp = \accum, i, calcNext, isValid ->
|
||||
when i is
|
||||
Ok val ->
|
||||
if isValid val then
|
||||
# TODO: change this to List.appendUnsafe once capacity is set correctly
|
||||
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
|
||||
accum
|
||||
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
|
||||
List.range { start: At 0, end: At 4 } == [0, 1, 2, 3, 4]
|
||||
@ -754,6 +778,18 @@ expect
|
||||
expect
|
||||
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
|
||||
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)
|
||||
|
||||
## 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,
|
||||
## 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 both the beginning and end of the list,
|
||||
@ -824,14 +860,14 @@ takeFirst = \list, outputLength ->
|
||||
List.sublist list { start: 0, len: outputLength }
|
||||
|
||||
## 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,
|
||||
## 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 both the beginning and end of the list,
|
||||
@ -971,13 +1007,13 @@ findLastIndex = \list, matches ->
|
||||
## including a total of `len` elements.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
## >>> 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
|
||||
## > 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
|
||||
|
||||
## 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, sep ->
|
||||
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
|
||||
## 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, delimiter ->
|
||||
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
|
||||
## 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, delimiter ->
|
||||
when List.findLastIndex list (\elem -> elem == delimiter) is
|
||||
|
@ -151,9 +151,9 @@ interface Num
|
||||
## 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:
|
||||
##
|
||||
## 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
|
||||
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
|
||||
##
|
||||
@ -191,9 +191,9 @@ interface Num
|
||||
##
|
||||
## 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:
|
||||
##
|
||||
## >>> Num.toStr 5_000_000_000i128
|
||||
##
|
||||
## ```
|
||||
## Num.toStr 5_000_000_000i128
|
||||
## ```
|
||||
## This `i128` suffix specifies that you want this number literal to be
|
||||
## an [I128] instead of a `Num *`. All the other numeric types have
|
||||
## suffixes just like `i128`; here are some other examples:
|
||||
@ -259,15 +259,11 @@ Num range := range
|
||||
##
|
||||
## All number literals without decimal points are compatible with [Int] values.
|
||||
##
|
||||
## >>> 1
|
||||
##
|
||||
## >>> 0
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
## >>> 1_000_000
|
||||
##
|
||||
## ```
|
||||
## 1_000_000
|
||||
## ```
|
||||
## Integers come in two flavors: *signed* and *unsigned*.
|
||||
##
|
||||
## * *Unsigned* integers can never be negative. The lowest value they can hold is zero.
|
||||
@ -342,16 +338,16 @@ Int range : Num (Integer range)
|
||||
##
|
||||
## 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:
|
||||
##
|
||||
## 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]
|
||||
## by default when there are no types specified.
|
||||
##
|
||||
## 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
|
||||
## been done in a base-2 floating point calculation, which causes noticeable
|
||||
## precision loss in this case.
|
||||
@ -492,15 +488,14 @@ Dec : Num (FloatingPoint Decimal)
|
||||
##
|
||||
## This is the same as calling `Num.format {}` - so for more details on
|
||||
## 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.
|
||||
##
|
||||
## >>> Num.toStr 4.2
|
||||
##
|
||||
## >>> Num.toStr 4.0
|
||||
##
|
||||
## ```
|
||||
## Num.toStr 4.2
|
||||
## Num.toStr 4.0
|
||||
## ```
|
||||
## When this function is given a non-[finite](Num.isFinite)
|
||||
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
|
||||
##
|
||||
@ -539,9 +534,10 @@ compare : Num a, Num a -> [LT, EQ, GT]
|
||||
##
|
||||
## 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).)
|
||||
##
|
||||
## >>> 5
|
||||
## >>> |> Num.isLt 6
|
||||
## ```
|
||||
## 5
|
||||
## |> Num.isLt 6
|
||||
## ```
|
||||
isLt : Num a, Num a -> Bool
|
||||
|
||||
## Returns `Bool.true` if the first number is greater than the second.
|
||||
@ -550,9 +546,10 @@ isLt : Num a, Num a -> Bool
|
||||
##
|
||||
## 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).)
|
||||
##
|
||||
## >>> 6
|
||||
## >>> |> Num.isGt 5
|
||||
## ```
|
||||
## 6
|
||||
## |> Num.isGt 5
|
||||
## ```
|
||||
isGt : Num a, Num a -> Bool
|
||||
|
||||
## Returns `Bool.true` if the first number is less than or equal to the second.
|
||||
@ -601,15 +598,15 @@ toFrac : Num * -> Frac *
|
||||
## * For a positive number, returns the same number.
|
||||
## * For a negative number, returns the same number except positive.
|
||||
## * For zero, returns zero.
|
||||
## ```
|
||||
## Num.abs 4
|
||||
##
|
||||
## >>> Num.abs 4
|
||||
## Num.abs -2.5
|
||||
##
|
||||
## >>> Num.abs -2.5
|
||||
##
|
||||
## >>> Num.abs 0
|
||||
##
|
||||
## >>> Num.abs 0.0
|
||||
## Num.abs 0
|
||||
##
|
||||
## Num.abs 0.0
|
||||
## ```
|
||||
## 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.
|
||||
@ -620,15 +617,15 @@ toFrac : Num * -> Frac *
|
||||
abs : Num a -> Num a
|
||||
|
||||
## 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.0
|
||||
## Num.neg 0
|
||||
##
|
||||
## Num.neg 0.0
|
||||
## ```
|
||||
## 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.
|
||||
@ -645,16 +642,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.)
|
||||
##
|
||||
## `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.
|
||||
##
|
||||
## >>> Frac.pi
|
||||
## >>> |> Num.add 1.0
|
||||
##
|
||||
## ```
|
||||
## Frac.pi
|
||||
## |> Num.add 1.0
|
||||
## ```
|
||||
## 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
|
||||
## *overflow*. For [F64] and [F32], overflow results in an answer of either
|
||||
@ -666,16 +663,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.)
|
||||
##
|
||||
## `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.
|
||||
##
|
||||
## >>> Frac.pi
|
||||
## >>> |> Num.sub 2.0
|
||||
##
|
||||
## ```
|
||||
## Frac.pi
|
||||
## |> Num.sub 2.0
|
||||
## ```
|
||||
## 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
|
||||
## *overflow*. For [F64] and [F32], overflow results in an answer of either
|
||||
@ -687,16 +684,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.)
|
||||
##
|
||||
## `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.
|
||||
##
|
||||
## >>> 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
|
||||
## [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
|
||||
@ -731,14 +730,15 @@ atan : Frac a -> Frac a
|
||||
## > 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.
|
||||
## ```
|
||||
## 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
|
||||
|
||||
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]
|
||||
@ -748,6 +748,7 @@ sqrtChecked = \x ->
|
||||
else
|
||||
Ok (Num.sqrt x)
|
||||
|
||||
## Natural logarithm
|
||||
log : Frac a -> Frac a
|
||||
|
||||
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]
|
||||
@ -778,15 +779,16 @@ logChecked = \x ->
|
||||
##
|
||||
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
|
||||
## 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.pi
|
||||
## >>> |> Num.div 2.0
|
||||
## ```
|
||||
## Num.pi
|
||||
## |> Num.div 2.0
|
||||
## ```
|
||||
div : Frac a, Frac a -> Frac a
|
||||
|
||||
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]
|
||||
@ -812,15 +814,15 @@ divCeilChecked = \a, b ->
|
||||
## 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,
|
||||
## it will crash.
|
||||
## ```
|
||||
## 5 // 7
|
||||
##
|
||||
## >>> 5 // 7
|
||||
## Num.divTrunc 5 7
|
||||
##
|
||||
## >>> Num.divTrunc 5 7
|
||||
##
|
||||
## >>> 8 // -3
|
||||
##
|
||||
## >>> Num.divTrunc 8 -3
|
||||
## 8 // -3
|
||||
##
|
||||
## Num.divTrunc 8 -3
|
||||
## ```
|
||||
divTrunc : Int a, Int a -> Int a
|
||||
|
||||
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
|
||||
@ -833,14 +835,15 @@ divTruncChecked = \a, b ->
|
||||
## Obtain the remainder (truncating modulo) from the division of two integers.
|
||||
##
|
||||
## `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
|
||||
|
||||
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
|
||||
@ -860,24 +863,24 @@ bitwiseOr : Int a, Int a -> Int a
|
||||
##
|
||||
## The least significant bits always become 0. This means that shifting left is
|
||||
## 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 `<<`.
|
||||
shiftLeftBy : Int a, U8 -> Int a
|
||||
|
||||
## Bitwise arithmetic shift of a number by another
|
||||
##
|
||||
## 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
|
||||
##
|
||||
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
|
||||
## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
|
||||
##
|
||||
## 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
|
||||
## ```
|
||||
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
|
||||
shiftRightBy : Int a, U8 -> Int a
|
||||
|
||||
@ -885,13 +888,13 @@ shiftRightBy : Int a, U8 -> Int a
|
||||
##
|
||||
## The most significant bits always become 0. This means that shifting left is
|
||||
## 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
|
||||
##
|
||||
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
|
||||
## 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
|
||||
##
|
||||
## 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
|
||||
## ```
|
||||
## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
|
||||
shiftRightZfBy : Int a, U8 -> Int a
|
||||
|
||||
@ -912,15 +915,15 @@ pow : Frac a, Frac a -> Frac a
|
||||
##
|
||||
## For a [Frac] alternative to this function, which supports negative exponents,
|
||||
## 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 6
|
||||
## Num.exp 5 2
|
||||
##
|
||||
## Num.exp 5 6
|
||||
## ```
|
||||
## ## Performance Notes
|
||||
##
|
||||
## Be careful! It is very easy for this function to produce an answer
|
||||
@ -1261,164 +1264,3 @@ toU128Checked : Int * -> Result U128 [OutOfBounds]
|
||||
toNatChecked : Int * -> Result Nat [OutOfBounds]
|
||||
toF32Checked : Num * -> Result F32 [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
|
||||
|
@ -7,8 +7,9 @@ interface Result
|
||||
Result ok err : [Ok ok, Err err]
|
||||
|
||||
## 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 ->
|
||||
when result is
|
||||
@ -16,8 +17,9 @@ isOk = \result ->
|
||||
Err _ -> 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 ->
|
||||
when result is
|
||||
@ -26,10 +28,10 @@ isErr = \result ->
|
||||
|
||||
## If the result is `Ok`, return the value it holds. Otherwise, return
|
||||
## the given default value.
|
||||
##
|
||||
## >>> Result.withDefault (Ok 7) 42
|
||||
##
|
||||
## >>> Result.withDefault (Err "uh oh") 42
|
||||
## ```
|
||||
## Result.withDefault (Ok 7) 42
|
||||
## Result.withDefault (Err "uh oh") 42
|
||||
## ```
|
||||
withDefault : Result ok err, ok -> ok
|
||||
withDefault = \result, default ->
|
||||
when result is
|
||||
@ -40,10 +42,10 @@ withDefault = \result, default ->
|
||||
## 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`.)
|
||||
##
|
||||
## >>> Result.map (Ok 12) Num.negate
|
||||
##
|
||||
## >>> Result.map (Err "yipes!") Num.negate
|
||||
## ```
|
||||
## Result.map (Ok 12) Num.negate
|
||||
## Result.map (Err "yipes!") Num.negate
|
||||
## ```
|
||||
##
|
||||
## `map` functions like this are common in Roc, and they all work similarly.
|
||||
## 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.
|
||||
##
|
||||
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
|
||||
##
|
||||
## >>> Result.mapErr (Err "yipes!") Str.isEmpty
|
||||
##
|
||||
## >>> Result.mapErr (Ok 12) Str.isEmpty
|
||||
## ```
|
||||
## Result.mapErr (Err "yipes!") Str.isEmpty
|
||||
## Result.mapErr (Ok 12) Str.isEmpty
|
||||
## ```
|
||||
mapErr : Result ok a, (a -> b) -> Result ok b
|
||||
mapErr = \result, transform ->
|
||||
when result is
|
||||
@ -71,10 +73,10 @@ mapErr = \result, transform ->
|
||||
## 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`.)
|
||||
##
|
||||
## >>> 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 (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
|
||||
## ```
|
||||
try : Result a err, (a -> Result b err) -> Result b err
|
||||
try = \result, transform ->
|
||||
when result is
|
||||
@ -85,10 +87,10 @@ try = \result, transform ->
|
||||
## 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`.)
|
||||
##
|
||||
## >>> Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
|
||||
##
|
||||
## >>> Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
|
||||
## ```
|
||||
## Result.onErr (Ok 10) \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, transform ->
|
||||
when result is
|
||||
|
@ -4,11 +4,11 @@
|
||||
##
|
||||
## Unicode can represent text values which span multiple languages, symbols, and emoji.
|
||||
## Here are some valid Roc strings:
|
||||
##
|
||||
## ```
|
||||
## "Roc!"
|
||||
## "鹏"
|
||||
## "🕊"
|
||||
##
|
||||
## ```
|
||||
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
|
||||
## An extended grapheme cluster represents what a person reading a string might
|
||||
## call a "character" - like "A" or "ö" or "👩👩👦👦".
|
||||
@ -17,11 +17,11 @@
|
||||
## 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:
|
||||
##
|
||||
## Str.countGraphemes "Roc!"
|
||||
## Str.countGraphemes "折り紙"
|
||||
## Str.countGraphemes "🕊"
|
||||
##
|
||||
## ```
|
||||
## Str.countGraphemes "Roc!"
|
||||
## Str.countGraphemes "折り紙"
|
||||
## Str.countGraphemes "🕊"
|
||||
## ```
|
||||
## > The `countGraphemes` function walks through the entire string to get its answer,
|
||||
## > so if you want to check whether a string is empty, you'll get much better performance
|
||||
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
|
||||
@ -31,9 +31,9 @@
|
||||
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
|
||||
## An escape sequence is a convenient way to insert certain strings into other strings.
|
||||
## For example, suppose you write this Roc string:
|
||||
##
|
||||
## "I took the one less traveled by,\nAnd that has made all the difference."
|
||||
##
|
||||
## ```
|
||||
## "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
|
||||
## 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)
|
||||
##
|
||||
## You can also use escape sequences to insert named strings into other strings, like so:
|
||||
##
|
||||
## name = "Lee"
|
||||
## city = "Roctown"
|
||||
##
|
||||
## greeting = "Hello there, \(name)! Welcome to \(city)."
|
||||
##
|
||||
## ```
|
||||
## name = "Lee"
|
||||
## city = "Roctown"
|
||||
## greeting = "Hello there, \(name)! Welcome to \(city)."
|
||||
## ```
|
||||
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
|
||||
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
|
||||
## and you can use it as many times as you like inside a string. The name
|
||||
@ -138,16 +137,18 @@ Utf8ByteProblem : [
|
||||
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
|
||||
|
||||
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
|
||||
##
|
||||
## expect Str.isEmpty "hi!" == Bool.false
|
||||
## expect Str.isEmpty "" == Bool.true
|
||||
## ```
|
||||
## expect Str.isEmpty "hi!" == Bool.false
|
||||
## expect Str.isEmpty "" == Bool.true
|
||||
## ```
|
||||
isEmpty : Str -> Bool
|
||||
|
||||
## Concatenates two strings together.
|
||||
##
|
||||
## expect Str.concat "ab" "cd" == "abcd"
|
||||
## expect Str.concat "hello" "" == "hello"
|
||||
## expect Str.concat "" "" == ""
|
||||
## ```
|
||||
## expect Str.concat "ab" "cd" == "abcd"
|
||||
## expect Str.concat "hello" "" == "hello"
|
||||
## expect Str.concat "" "" == ""
|
||||
## ```
|
||||
concat : Str, Str -> Str
|
||||
|
||||
## 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
|
||||
## string in between each.
|
||||
##
|
||||
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
|
||||
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
|
||||
## ```
|
||||
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
|
||||
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
|
||||
## ```
|
||||
joinWith : List Str, Str -> Str
|
||||
|
||||
## Split a string around a separator.
|
||||
@ -165,20 +167,22 @@ joinWith : List Str, Str -> Str
|
||||
## Passing `""` for the separator is not useful;
|
||||
## it returns the original string wrapped in a [List]. To split a string
|
||||
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
|
||||
##
|
||||
## 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
|
||||
|
||||
## Repeats a string the given number of times.
|
||||
##
|
||||
## expect Str.repeat "z" 3 == "zzz"
|
||||
## expect Str.repeat "na" 8 == "nananananananana"
|
||||
##
|
||||
## ```
|
||||
## expect Str.repeat "z" 3 == "zzz"
|
||||
## expect Str.repeat "na" 8 == "nananananananana"
|
||||
## ```
|
||||
## Returns `""` when given `""` for the string or `0` for the count.
|
||||
##
|
||||
## expect Str.repeat "" 10 == ""
|
||||
## expect Str.repeat "anything" 0 == ""
|
||||
## ```
|
||||
## expect Str.repeat "" 10 == ""
|
||||
## expect Str.repeat "anything" 0 == ""
|
||||
## ```
|
||||
repeat : Str, Nat -> Str
|
||||
|
||||
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
|
||||
@ -186,11 +190,11 @@ repeat : Str, Nat -> Str
|
||||
##
|
||||
## Note that the number of extended grapheme clusters can be different from the number
|
||||
## of visual glyphs rendered! Consider the following examples:
|
||||
##
|
||||
## expect Str.countGraphemes "Roc" == 3
|
||||
## expect Str.countGraphemes "👩👩👦👦" == 4
|
||||
## expect Str.countGraphemes "🕊" == 1
|
||||
##
|
||||
## ```
|
||||
## expect Str.countGraphemes "Roc" == 3
|
||||
## expect Str.countGraphemes "👩👩👦👦" == 4
|
||||
## expect Str.countGraphemes "🕊" == 1
|
||||
## ```
|
||||
## Note that "👩👩👦👦" takes up 4 graphemes (even though visually it appears as a single
|
||||
## glyph) because under the hood it's represented using an emoji modifier sequence.
|
||||
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
|
||||
@ -205,11 +209,11 @@ graphemes : Str -> List Str
|
||||
##
|
||||
## If the given string is empty, or if the given [U32] is not a valid
|
||||
## code point, returns [Bool.false].
|
||||
##
|
||||
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
|
||||
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
|
||||
## expect !Str.startsWithScalar "" 40527
|
||||
##
|
||||
## ```
|
||||
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
|
||||
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
|
||||
## expect !Str.startsWithScalar "" 40527
|
||||
## ```
|
||||
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
|
||||
## if you want to check whether a string begins with something that's representable
|
||||
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
|
||||
@ -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),
|
||||
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
|
||||
##
|
||||
## expect Str.toScalars "Roc" == [82, 111, 99]
|
||||
## expect Str.toScalars "鹏" == [40527]
|
||||
## expect Str.toScalars "சி" == [2970, 3007]
|
||||
## expect Str.toScalars "🐦" == [128038]
|
||||
## expect Str.toScalars "👩👩👦👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
|
||||
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
|
||||
## expect Str.toScalars "" == []
|
||||
## ```
|
||||
## expect Str.toScalars "Roc" == [82, 111, 99]
|
||||
## expect Str.toScalars "鹏" == [40527]
|
||||
## expect Str.toScalars "சி" == [2970, 3007]
|
||||
## expect Str.toScalars "🐦" == [128038]
|
||||
## expect Str.toScalars "👩👩👦👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
|
||||
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
|
||||
## expect Str.toScalars "" == []
|
||||
## ```
|
||||
toScalars : Str -> List U32
|
||||
|
||||
## 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,
|
||||
## see [Str.split].)
|
||||
##
|
||||
## expect Str.toUtf8 "Roc" == [82, 111, 99]
|
||||
## expect Str.toUtf8 "鹏" == [233, 185, 143]
|
||||
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
|
||||
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
|
||||
## ```
|
||||
## expect Str.toUtf8 "Roc" == [82, 111, 99]
|
||||
## expect Str.toUtf8 "鹏" == [233, 185, 143]
|
||||
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
|
||||
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
|
||||
## ```
|
||||
toUtf8 : Str -> List U8
|
||||
|
||||
## 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 `[]`.
|
||||
##
|
||||
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
|
||||
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
|
||||
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
|
||||
## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
|
||||
## expect Str.fromUtf8 [] == Ok ""
|
||||
## expect Str.fromUtf8 [255] |> Result.isErr
|
||||
## ```
|
||||
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
|
||||
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
|
||||
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
|
||||
## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
|
||||
## expect Str.fromUtf8 [] == Ok ""
|
||||
## expect Str.fromUtf8 [255] |> Result.isErr
|
||||
## ```
|
||||
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
|
||||
fromUtf8 = \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)
|
||||
## 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 = \bytes, config ->
|
||||
if config.start + config.count <= List.len bytes then
|
||||
@ -290,57 +298,65 @@ FromUtf8Result : {
|
||||
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
|
||||
|
||||
## Check if the given [Str] starts with a value.
|
||||
##
|
||||
## expect Str.startsWith "ABC" "A" == Bool.true
|
||||
## expect Str.startsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
## expect Str.startsWith "ABC" "A" == Bool.true
|
||||
## expect Str.startsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
startsWith : Str, Str -> Bool
|
||||
|
||||
## Check if the given [Str] ends with a value.
|
||||
##
|
||||
## expect Str.endsWith "ABC" "C" == Bool.true
|
||||
## expect Str.endsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
## expect Str.endsWith "ABC" "C" == Bool.true
|
||||
## expect Str.endsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
endsWith : Str, Str -> Bool
|
||||
|
||||
## Return the [Str] with all whitespace removed from both the beginning
|
||||
## as well as the end.
|
||||
##
|
||||
## expect Str.trim " Hello \n\n" == "Hello"
|
||||
## ```
|
||||
## expect Str.trim " Hello \n\n" == "Hello"
|
||||
## ```
|
||||
trim : Str -> Str
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
## 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).
|
||||
##
|
||||
## expect Str.toDec "10" == Ok 10dec
|
||||
## expect Str.toDec "-0.25" == Ok -0.25dec
|
||||
## expect Str.toDec "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toDec "10" == Ok 10dec
|
||||
## expect Str.toDec "-0.25" == Ok -0.25dec
|
||||
## expect Str.toDec "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toDec : Str -> Result Dec [InvalidNumStr]
|
||||
toDec = \string -> strToNumHelp string
|
||||
|
||||
## 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
|
||||
## specified with a `f64` suffix.
|
||||
##
|
||||
## expect Str.toF64 "0.10" == Ok 0.10f64
|
||||
## expect Str.toF64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toF64 "0.10" == Ok 0.10f64
|
||||
## expect Str.toF64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toF64 : Str -> Result F64 [InvalidNumStr]
|
||||
toF64 = \string -> strToNumHelp string
|
||||
|
||||
## 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
|
||||
## specified with a `f32` suffix.
|
||||
##
|
||||
## expect Str.toF32 "0.10" == Ok 0.10f32
|
||||
## expect Str.toF32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toF32 "0.10" == Ok 0.10f32
|
||||
## expect Str.toF32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toF32 : Str -> Result F32 [InvalidNumStr]
|
||||
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
|
||||
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
|
||||
## hold up to `Num.maxU64`, and 9_000_000_000 is smaller than `Num.maxU64`.
|
||||
##
|
||||
## expect Str.toNat "9_000_000_000" == Ok 9000000000
|
||||
## expect Str.toNat "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toNat "9_000_000_000" == Ok 9000000000
|
||||
## expect Str.toNat "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toNat : Str -> Result Nat [InvalidNumStr]
|
||||
toNat = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
|
||||
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
|
||||
## 340 undecillion). It can be specified with a u128 suffix.
|
||||
##
|
||||
## expect Str.toU128 "1500" == Ok 1500u128
|
||||
## expect Str.toU128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toU128 "1500" == Ok 1500u128
|
||||
## expect Str.toU128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toU128 : Str -> Result U128 [InvalidNumStr]
|
||||
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
|
||||
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
|
||||
## with a i128 suffix.
|
||||
##
|
||||
## expect Str.toI128 "1500" == Ok 1500i128
|
||||
## expect Str.toI128 "-1" == Ok -1i128
|
||||
## expect Str.toI128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI128 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toI128 "1500" == Ok 1500i128
|
||||
## expect Str.toI128 "-1" == Ok -1i128
|
||||
## expect Str.toI128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI128 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toI128 : Str -> Result I128 [InvalidNumStr]
|
||||
toI128 = \string -> strToNumHelp string
|
||||
|
||||
## 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
|
||||
## can be specified with a u64 suffix.
|
||||
##
|
||||
## expect Str.toU64 "1500" == Ok 1500u64
|
||||
## expect Str.toU64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toU64 "1500" == Ok 1500u64
|
||||
## expect Str.toU64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toU64 : Str -> Result U64 [InvalidNumStr]
|
||||
toU64 = \string -> strToNumHelp string
|
||||
|
||||
## 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
|
||||
## specified with a i64 suffix.
|
||||
##
|
||||
## expect Str.toI64 "1500" == Ok 1500i64
|
||||
## expect Str.toI64 "-1" == Ok -1i64
|
||||
## expect Str.toI64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toI64 "1500" == Ok 1500i64
|
||||
## expect Str.toI64 "-1" == Ok -1i64
|
||||
## expect Str.toI64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toI64 : Str -> Result I64 [InvalidNumStr]
|
||||
toI64 = \string -> strToNumHelp string
|
||||
|
||||
## 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
|
||||
## a u32 suffix.
|
||||
##
|
||||
## expect Str.toU32 "1500" == Ok 1500u32
|
||||
## expect Str.toU32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toU32 "1500" == Ok 1500u32
|
||||
## expect Str.toU32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toU32 : Str -> Result U32 [InvalidNumStr]
|
||||
toU32 = \string -> strToNumHelp string
|
||||
|
||||
## 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
|
||||
## specified with a i32 suffix.
|
||||
##
|
||||
## expect Str.toI32 "1500" == Ok 1500i32
|
||||
## expect Str.toI32 "-1" == Ok -1i32
|
||||
## expect Str.toI32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toI32 "1500" == Ok 1500i32
|
||||
## expect Str.toI32 "-1" == Ok -1i32
|
||||
## expect Str.toI32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toI32 : Str -> Result I32 [InvalidNumStr]
|
||||
toI32 = \string -> strToNumHelp string
|
||||
|
||||
## 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.
|
||||
##
|
||||
## expect Str.toU16 "1500" == Ok 1500u16
|
||||
## expect Str.toU16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toU16 "1500" == Ok 1500u16
|
||||
## expect Str.toU16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toU16 : Str -> Result U16 [InvalidNumStr]
|
||||
toU16 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
|
||||
## from `-32_768` to `32_767`. It can be
|
||||
## specified with a i16 suffix.
|
||||
##
|
||||
## expect Str.toI16 "1500" == Ok 1500i16
|
||||
## expect Str.toI16 "-1" == Ok -1i16
|
||||
## expect Str.toI16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI16 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toI16 "1500" == Ok 1500i16
|
||||
## expect Str.toI16 "-1" == Ok -1i16
|
||||
## expect Str.toI16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI16 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toI16 : Str -> Result I16 [InvalidNumStr]
|
||||
toI16 = \string -> strToNumHelp string
|
||||
|
||||
## 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.
|
||||
##
|
||||
## expect Str.toU8 "250" == Ok 250u8
|
||||
## expect Str.toU8 "-0.1" == Err InvalidNumStr
|
||||
## expect Str.toU8 "not a number" == Err InvalidNumStr
|
||||
## expect Str.toU8 "1500" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toU8 "250" == Ok 250u8
|
||||
## expect Str.toU8 "-0.1" == Err InvalidNumStr
|
||||
## expect Str.toU8 "not a number" == Err InvalidNumStr
|
||||
## expect Str.toU8 "1500" == Err InvalidNumStr
|
||||
## ```
|
||||
toU8 : Str -> Result U8 [InvalidNumStr]
|
||||
toU8 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
|
||||
## from `-128` to `127`. It can be
|
||||
## specified with a i8 suffix.
|
||||
##
|
||||
## expect Str.toI8 "-15" == Ok -15i8
|
||||
## expect Str.toI8 "150.00" == Err InvalidNumStr
|
||||
## expect Str.toI8 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
## expect Str.toI8 "-15" == Ok -15i8
|
||||
## expect Str.toI8 "150.00" == Err InvalidNumStr
|
||||
## expect Str.toI8 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
toI8 : Str -> Result I8 [InvalidNumStr]
|
||||
toI8 = \string -> strToNumHelp string
|
||||
|
||||
@ -474,8 +501,9 @@ toI8 = \string -> strToNumHelp string
|
||||
getUnsafe : Str, Nat -> U8
|
||||
|
||||
## Gives the number of bytes in a [Str] value.
|
||||
##
|
||||
## expect Str.countUtf8Bytes "Hello World" == 11
|
||||
## ```
|
||||
## expect Str.countUtf8Bytes "Hello World" == 11
|
||||
## ```
|
||||
countUtf8Bytes : Str -> Nat
|
||||
|
||||
## 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 [Err NotFound] if the substring is not found.
|
||||
##
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
|
||||
## ```
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
|
||||
## ```
|
||||
replaceEach : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceEach = \haystack, needle, flower ->
|
||||
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 [Err NotFound] if the substring is not found.
|
||||
##
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
|
||||
## ```
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
|
||||
## ```
|
||||
replaceFirst : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceFirst = \haystack, needle, flower ->
|
||||
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 [Err NotFound] if the substring is not found.
|
||||
##
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
|
||||
## ```
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
|
||||
## ```
|
||||
replaceLast : Str, Str, Str -> Result Str [NotFound]
|
||||
replaceLast = \haystack, needle, flower ->
|
||||
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
|
||||
## as the rest of the string after that occurrence.
|
||||
## Returns [ Err NotFound] if the delimiter is not found.
|
||||
##
|
||||
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]
|
||||
splitFirst = \haystack, needle ->
|
||||
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
|
||||
## the rest of the string after that occurrence.
|
||||
## Returns [Err NotFound] if the delimiter is not found.
|
||||
##
|
||||
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
## expect Str.splitLast "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
## expect Str.splitLast "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]
|
||||
splitLast = \haystack, needle ->
|
||||
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
|
||||
## state for each byte. The index for that byte in the string is provided
|
||||
## to the update function.
|
||||
##
|
||||
## f : List U8, U8, Nat -> List U8
|
||||
## f = \state, byte, _ -> List.append state byte
|
||||
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
|
||||
## ```
|
||||
## f : List U8, U8, Nat -> List U8
|
||||
## f = \state, byte, _ -> List.append state byte
|
||||
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
|
||||
## ```
|
||||
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
|
||||
walkUtf8WithIndex = \string, state, step ->
|
||||
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
|
||||
## unicode value, it returns [Err InvalidScalar].
|
||||
##
|
||||
## expect Str.appendScalar "H" 105 == Ok "Hi"
|
||||
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
|
||||
## ```
|
||||
## expect Str.appendScalar "H" 105 == Ok "Hi"
|
||||
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
|
||||
## ```
|
||||
appendScalar : Str, U32 -> Result Str [InvalidScalar]
|
||||
appendScalar = \string, scalar ->
|
||||
if isValidScalar scalar then
|
||||
@ -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
|
||||
## to update state for each.
|
||||
##
|
||||
## f : List U32, U32 -> List U32
|
||||
## f = \state, scalar -> List.append state scalar
|
||||
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
|
||||
## ```
|
||||
## f : List U32, U32 -> List U32
|
||||
## f = \state, scalar -> List.append state scalar
|
||||
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
|
||||
## ```
|
||||
walkScalars : Str, state, (state, U32 -> state) -> state
|
||||
walkScalars = \string, init, step ->
|
||||
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
@ -754,16 +790,17 @@ walkScalarsHelp = \string, state, step, index, length ->
|
||||
|
||||
## Walks over the unicode [U32] values for the given [Str] and calls a function
|
||||
## to update state for each.
|
||||
##
|
||||
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
|
||||
## f = \state, scalar ->
|
||||
## check = 66
|
||||
## if scalar == check then
|
||||
## Break [check]
|
||||
## else
|
||||
## Continue (List.append state scalar)
|
||||
## expect Str.walkScalarsUntil "ABC" [] f == [66]
|
||||
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
|
||||
## ```
|
||||
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
|
||||
## f = \state, scalar ->
|
||||
## check = 66
|
||||
## if scalar == check then
|
||||
## Break [check]
|
||||
## else
|
||||
## Continue (List.append state scalar)
|
||||
## expect Str.walkScalarsUntil "ABC" [] f == [66]
|
||||
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
|
||||
## ```
|
||||
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
|
||||
walkScalarsUntil = \string, init, step ->
|
||||
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
@ -795,7 +832,8 @@ strToNumHelp = \string ->
|
||||
Err InvalidNumStr
|
||||
|
||||
## 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, prefix -> Str.concat prefix str
|
||||
|
@ -1,28 +1,29 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_exhaustive = { path = "../exhaustive" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_serialize = { path = "../serialize" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
static_assertions.workspace = true
|
||||
bitvec.workspace = true
|
||||
roc_types = { path = "../types" }
|
||||
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
|
||||
bitvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
static_assertions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
indoc.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
|
@ -385,7 +385,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
),
|
||||
Crash { .. } => 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!(),
|
||||
Expect { .. } => todo!(),
|
||||
ExpectFx { .. } => todo!(),
|
||||
|
@ -1,16 +1,17 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fnv.workspace = true
|
||||
im.workspace = true
|
||||
im-rc.workspace = true
|
||||
wyhash.workspace = true
|
||||
bumpalo.workspace = true
|
||||
hashbrown.workspace = true
|
||||
bitvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
fnv.workspace = true
|
||||
hashbrown.workspace = true
|
||||
im-rc.workspace = true
|
||||
im.workspace = true
|
||||
wyhash.workspace = true
|
||||
|
@ -1,18 +1,20 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
arrayvec = "0.7.2"
|
||||
|
||||
arrayvec.workspace = true
|
||||
|
@ -1,9 +1,10 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
@ -1,25 +1,27 @@
|
||||
[package]
|
||||
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`."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[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_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" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
debug-derived-symbols = ["roc_module/debug-symbols"]
|
||||
default = []
|
||||
# Enables open extension variables for constructed records and tag unions.
|
||||
# This is not necessary for code generation, but may be necessary if you are
|
||||
# constraining and solving generated derived bodies.
|
||||
|
@ -1,14 +1,14 @@
|
||||
[package]
|
||||
name = "roc_derive_key"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
|
@ -1,14 +1,15 @@
|
||||
[package]
|
||||
name = "roc_exhaustive"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Provides exhaustiveness checking for Roc."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_region = { path = "../region" }
|
||||
|
@ -1,14 +1,16 @@
|
||||
[package]
|
||||
name = "roc_fmt"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "The roc code formatter."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
bumpalo.workspace = true
|
||||
roc_region = { path = "../region" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
|
@ -38,7 +38,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||
let braces_indent = indent;
|
||||
let item_indent = braces_indent + INDENT;
|
||||
if newline == Newlines::Yes {
|
||||
buf.newline();
|
||||
buf.ensure_ends_with_newline();
|
||||
}
|
||||
buf.indent(braces_indent);
|
||||
buf.push(start);
|
||||
|
@ -61,7 +61,7 @@ impl<'a> Formattable for TypeDef<'a> {
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
use roc_parse::ast::TypeDef::*;
|
||||
@ -97,22 +97,10 @@ impl<'a> Formattable for TypeDef<'a> {
|
||||
ann.format(buf, indent)
|
||||
}
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
header,
|
||||
typ: ann,
|
||||
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 =
|
||||
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;
|
||||
|
||||
ann.format(buf, indent);
|
||||
fmt_general_def(header, buf, indent, ":=", &ann.value, newlines);
|
||||
|
||||
if let Some(has_abilities) = has_abilities {
|
||||
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> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::ValueDef::*;
|
||||
@ -204,63 +215,14 @@ impl<'a> Formattable for ValueDef<'a> {
|
||||
use roc_parse::ast::ValueDef::*;
|
||||
match self {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
loc_pattern.format(buf, indent);
|
||||
buf.indent(indent);
|
||||
|
||||
if loc_annotation.is_multiline() {
|
||||
buf.push_str(" :");
|
||||
buf.spaces(1);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
fmt_general_def(
|
||||
loc_pattern,
|
||||
buf,
|
||||
indent,
|
||||
":",
|
||||
&loc_annotation.value,
|
||||
newlines,
|
||||
);
|
||||
}
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
|
||||
@ -277,34 +239,7 @@ impl<'a> Formattable for ValueDef<'a> {
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => {
|
||||
let is_type_multiline = ann_type.is_multiline();
|
||||
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);
|
||||
}
|
||||
fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines);
|
||||
|
||||
if let Some(comment_str) = comment {
|
||||
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>(
|
||||
buf: &mut Buf<'buf>,
|
||||
condition: &'a Loc<Expr<'a>>,
|
||||
|
@ -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::def::fmt_defs;
|
||||
use crate::pattern::fmt_pattern;
|
||||
@ -49,7 +49,7 @@ impl<'a> Formattable for Expr<'a> {
|
||||
// These expressions always have newlines
|
||||
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),
|
||||
Apply(loc_expr, args, _) => {
|
||||
@ -96,9 +96,9 @@ impl<'a> Formattable for Expr<'a> {
|
||||
.any(|loc_pattern| loc_pattern.is_multiline())
|
||||
}
|
||||
|
||||
Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
Tuple(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
Record(fields) => is_collection_multiline(fields),
|
||||
Tuple(fields) => is_collection_multiline(fields),
|
||||
RecordUpdate { fields, .. } => is_collection_multiline(fields),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1319,7 +1319,7 @@ fn fmt_record<'a, 'buf>(
|
||||
let loc_fields = fields.items;
|
||||
let final_comments = fields.final_comments();
|
||||
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("{}");
|
||||
} else {
|
||||
buf.push('{');
|
||||
|
@ -1,28 +1,29 @@
|
||||
[package]
|
||||
name = "roc_gen_dev"
|
||||
description = "The development backend for the Roc compiler"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[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_unify = { path = "../unify" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_collections = { path = "../collections" }
|
||||
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
|
||||
target-lexicon.workspace = true
|
||||
object.workspace = true
|
||||
packed_struct.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
|
@ -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.
|
||||
- [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) -
|
||||
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.
|
||||
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.
|
||||
Contains everything you would need to know for x86_64.
|
||||
Also is like 2000 pages.
|
||||
|
@ -2,10 +2,13 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::Relocation;
|
||||
use bumpalo::collections::Vec;
|
||||
use packed_struct::prelude::*;
|
||||
use roc_builtins::bitcode::FloatWidth;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{InLayout, STLayoutInterner};
|
||||
|
||||
use super::CompareOperation;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum AArch64GeneralReg {
|
||||
@ -609,9 +612,31 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
}
|
||||
}
|
||||
#[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) {
|
||||
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)]
|
||||
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
|
||||
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)]
|
||||
fn mov_reg64_mem64_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -640,6 +678,41 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
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)]
|
||||
fn mov_mem64_offset32_reg64(
|
||||
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)]
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
@ -740,12 +843,12 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
}
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: AArch64GeneralReg,
|
||||
src1: AArch64GeneralReg,
|
||||
src2: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("registers subtractions for AArch64");
|
||||
sub_reg64_reg64_reg64(buf, dst, src1, src2);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -788,6 +891,18 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
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)]
|
||||
fn igt_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
@ -938,6 +1053,14 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
{
|
||||
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 {}
|
||||
@ -1354,6 +1477,19 @@ fn sub_reg64_reg64_imm12(
|
||||
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.
|
||||
#[inline(always)]
|
||||
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]
|
||||
fn test_ret_reg64() {
|
||||
disassembler_test!(
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env,
|
||||
Relocation,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::collections::{CollectIn, Vec};
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
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.
|
||||
/// 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.
|
||||
@ -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.
|
||||
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(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src: GeneralReg,
|
||||
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(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
offset: i32,
|
||||
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`
|
||||
/// 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_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 mul_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -361,6 +416,15 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
||||
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(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
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>) {
|
||||
use Builtin::Int;
|
||||
|
||||
@ -1102,7 +1190,7 @@ impl<
|
||||
|
||||
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) {
|
||||
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 src1_reg = self
|
||||
.storage_manager
|
||||
@ -1112,13 +1200,23 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) {
|
||||
match self.layout_interner.get(*arg_layout) {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
|
||||
match *arg_layout {
|
||||
single_register_int_builtins!() | Layout::BOOL => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
@ -1128,10 +1226,44 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
@ -1160,6 +1292,20 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -1192,6 +1338,20 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -1272,6 +1432,26 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -1294,6 +1474,26 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -1518,43 +1718,14 @@ impl<
|
||||
ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr);
|
||||
let element_ptr = tmp;
|
||||
|
||||
match *ret_layout {
|
||||
single_register_integers!() if ret_stack_size == 8 => {
|
||||
let dst_reg = storage_manager.claim_general_reg(buf, dst);
|
||||
ASM::mov_reg64_mem64_offset32(buf, dst_reg, element_ptr, 0);
|
||||
}
|
||||
single_register_floats!() => {
|
||||
let dst_reg = storage_manager.claim_float_reg(buf, 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::ptr_read(
|
||||
buf,
|
||||
storage_manager,
|
||||
self.layout_interner,
|
||||
element_ptr,
|
||||
*ret_layout,
|
||||
*dst,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -1822,10 +1993,11 @@ impl<
|
||||
fn create_array(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
element_layout: &InLayout<'a>,
|
||||
elements: &'a [ListLiteralElement<'a>],
|
||||
element_in_layout: &InLayout<'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)
|
||||
let data_bytes_symbol = Symbol::DEV_TMP;
|
||||
@ -1855,54 +2027,34 @@ impl<
|
||||
.load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3);
|
||||
|
||||
// Copy everything into output array.
|
||||
let mut elem_offset = 0;
|
||||
let mut element_offset = 0;
|
||||
for elem in elements {
|
||||
// TODO: this could be a lot faster when loading large lists
|
||||
// if we move matching on the element layout to outside this loop.
|
||||
// It also greatly bloats the code here.
|
||||
// Refactor this and switch to one external match.
|
||||
// We also could make loadining indivitual literals much faster
|
||||
let elem_sym = match elem {
|
||||
ListLiteralElement::Symbol(sym) => sym,
|
||||
let element_symbol = match elem {
|
||||
ListLiteralElement::Symbol(sym) => *sym,
|
||||
ListLiteralElement::Literal(lit) => {
|
||||
self.load_literal(&Symbol::DEV_TMP, element_layout, lit);
|
||||
&Symbol::DEV_TMP
|
||||
self.load_literal(&Symbol::DEV_TMP, element_in_layout, lit);
|
||||
Symbol::DEV_TMP
|
||||
}
|
||||
};
|
||||
// TODO: Expand to all types.
|
||||
match self.layout_interner.get(*element_layout) {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64) | Builtin::Bool) => {
|
||||
let sym_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, elem_sym);
|
||||
ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, elem_offset, sym_reg);
|
||||
}
|
||||
_ if element_width == 0 => {}
|
||||
_ if element_width > 8 => {
|
||||
let (from_offset, size) = self.storage_manager.stack_offset_and_size(elem_sym);
|
||||
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| {
|
||||
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);
|
||||
|
||||
Self::ptr_write(
|
||||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
ptr_reg,
|
||||
element_offset,
|
||||
element_width,
|
||||
element_layout,
|
||||
element_symbol,
|
||||
);
|
||||
|
||||
element_offset += element_width as i32;
|
||||
if element_symbol == Symbol::DEV_TMP {
|
||||
self.free_symbol(&element_symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2000,38 +2152,15 @@ impl<
|
||||
let element_width = self.layout_interner.stack_size(element_layout) as u64;
|
||||
let element_offset = 0;
|
||||
|
||||
// TODO: Expand to all types.
|
||||
match self.layout_interner.get(element_layout) {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
|
||||
let sym_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, &value);
|
||||
ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, element_offset, sym_reg);
|
||||
}
|
||||
_ 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),
|
||||
}
|
||||
Self::ptr_write(
|
||||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
ptr_reg,
|
||||
element_offset,
|
||||
element_width,
|
||||
self.layout_interner.get(element_layout),
|
||||
value,
|
||||
);
|
||||
|
||||
if value == Symbol::DEV_TMP {
|
||||
self.free_symbol(&value);
|
||||
@ -2049,25 +2178,14 @@ impl<
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, &ptr);
|
||||
|
||||
let ret_stack_size = self.layout_interner.stack_size(element_layout);
|
||||
|
||||
match element_layout {
|
||||
single_register_integers!() if ret_stack_size == 8 => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst);
|
||||
ASM::mov_reg64_mem64_offset32(&mut self.buf, dst_reg, ptr_reg, 0);
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
Self::ptr_read(
|
||||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
self.layout_interner,
|
||||
ptr_reg,
|
||||
element_layout,
|
||||
dst,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
|
||||
@ -2116,6 +2234,33 @@ impl<
|
||||
let val = *x;
|
||||
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)) => {
|
||||
let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
|
||||
let val = [*x as u8; 16];
|
||||
@ -2131,15 +2276,11 @@ impl<
|
||||
let val = *x as f32;
|
||||
ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val);
|
||||
}
|
||||
(Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 24 => {
|
||||
// Load small string.
|
||||
(Literal::Decimal(bytes), Layout::Builtin(Builtin::Decimal)) => {
|
||||
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 base_offset = storage_manager.claim_stack_area(sym, 16);
|
||||
|
||||
let mut num_bytes = [0; 8];
|
||||
num_bytes.copy_from_slice(&bytes[..8]);
|
||||
@ -2151,14 +2292,49 @@ impl<
|
||||
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);
|
||||
},
|
||||
);
|
||||
}
|
||||
(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),
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
@ -2445,6 +2633,115 @@ impl<
|
||||
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.
|
||||
fn update_jmp_imm32_offset(
|
||||
&mut self,
|
||||
|
@ -9,7 +9,6 @@ use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::{
|
||||
borrow::Ownership,
|
||||
ir::{JoinPointId, Param},
|
||||
layout::{
|
||||
Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
@ -315,7 +314,7 @@ impl<
|
||||
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 {
|
||||
reg: None,
|
||||
@ -350,8 +349,10 @@ impl<
|
||||
self.free_reference(sym);
|
||||
reg
|
||||
}
|
||||
Stack(Complex { .. }) => {
|
||||
internal_error!("Cannot load large values into general registers: {}", sym)
|
||||
Stack(Complex { size, .. }) => {
|
||||
internal_error!(
|
||||
"Cannot load large values (size {size}) into general registers: {sym:?}",
|
||||
)
|
||||
}
|
||||
NoData => {
|
||||
internal_error!("Cannot load no data into general registers: {}", sym)
|
||||
@ -448,7 +449,7 @@ impl<
|
||||
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 {
|
||||
reg: None,
|
||||
@ -458,19 +459,25 @@ impl<
|
||||
ASM::mov_reg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && *size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_reg64_base32(buf, reg, *base_offset);
|
||||
base_offset,
|
||||
size,
|
||||
sign_extend,
|
||||
}) => {
|
||||
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 { .. }) => {
|
||||
todo!("loading referenced primitives")
|
||||
}
|
||||
Stack(Complex { .. }) => {
|
||||
internal_error!("Cannot load large values into general registers: {}", sym)
|
||||
Stack(Complex { size, .. }) => {
|
||||
internal_error!(
|
||||
"Cannot load large values (size {size}) into general registers: {sym:?}",
|
||||
)
|
||||
}
|
||||
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.symbol_storage_map.insert(
|
||||
*sym,
|
||||
Stack(if is_primitive(layout) {
|
||||
Stack(if is_primitive(layout_interner, layout) {
|
||||
ReferencedPrimitive {
|
||||
base_offset: data_offset,
|
||||
size,
|
||||
@ -739,15 +746,73 @@ impl<
|
||||
layout: &InLayout<'a>,
|
||||
) {
|
||||
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);
|
||||
let reg = self.load_to_general_reg(buf, sym);
|
||||
ASM::mov_base32_reg64(buf, to_offset, reg);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(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);
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
// like its runtime representation
|
||||
self.copy_symbol_to_stack_offset(
|
||||
layout_interner,
|
||||
buf,
|
||||
to_offset,
|
||||
sym,
|
||||
&lambda_set.runtime_representation(),
|
||||
)
|
||||
}
|
||||
_ if layout_interner.stack_size(*layout) == 0 => {}
|
||||
// TODO: Verify this is always true.
|
||||
@ -756,20 +821,64 @@ impl<
|
||||
// Later, it will be reloaded and stored in refcounted as needed.
|
||||
_ if layout_interner.stack_size(*layout) > 8 => {
|
||||
let (from_offset, size) = self.stack_offset_and_size(sym);
|
||||
debug_assert!(from_offset % 8 == 0);
|
||||
debug_assert!(size % 8 == 0);
|
||||
debug_assert_eq!(from_offset % 8, 0);
|
||||
debug_assert_eq!(size % 8, 0);
|
||||
debug_assert_eq!(size, layout_interner.stack_size(*layout));
|
||||
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
|
||||
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);
|
||||
}
|
||||
});
|
||||
self.copy_to_stack_offset(buf, size, from_offset, to_offset)
|
||||
}
|
||||
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)]
|
||||
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
|
||||
pub fn ensure_reg_free(
|
||||
@ -1008,15 +1117,10 @@ impl<
|
||||
param_storage.reserve(params.len());
|
||||
for Param {
|
||||
symbol,
|
||||
ownership,
|
||||
ownership: _,
|
||||
layout,
|
||||
} 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.
|
||||
// Put everything on the stack for simplicity.
|
||||
match *layout {
|
||||
@ -1331,6 +1435,15 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
fn is_primitive(layout: InLayout<'_>) -> bool {
|
||||
matches!(layout, single_register_layouts!())
|
||||
fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool {
|
||||
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
@ -447,6 +447,9 @@ trait Backend<'a> {
|
||||
LowLevel::NumAddChecked => {
|
||||
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(
|
||||
sym,
|
||||
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)
|
||||
}
|
||||
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 => {
|
||||
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)
|
||||
@ -572,6 +596,20 @@ trait Backend<'a> {
|
||||
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 => {
|
||||
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)
|
||||
@ -623,6 +661,15 @@ trait Backend<'a> {
|
||||
);
|
||||
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 => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
@ -704,6 +751,30 @@ trait Backend<'a> {
|
||||
);
|
||||
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(
|
||||
sym,
|
||||
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
|
||||
@ -784,6 +855,13 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
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(
|
||||
sym,
|
||||
bitcode::STR_SPLIT.to_string(),
|
||||
@ -805,6 +883,13 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
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(
|
||||
sym,
|
||||
bitcode::STR_ENDS_WITH.to_string(),
|
||||
@ -819,6 +904,122 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
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 => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
@ -885,13 +1086,6 @@ trait Backend<'a> {
|
||||
self.load_literal_symbols(args);
|
||||
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 => {
|
||||
let bool_layout = Layout::BOOL;
|
||||
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.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>,
|
||||
);
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
fn build_num_lt(
|
||||
&mut self,
|
||||
@ -1061,6 +1285,9 @@ trait Backend<'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.
|
||||
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
|
||||
|
||||
|
@ -1,24 +1,25 @@
|
||||
[package]
|
||||
name = "roc_gen_llvm"
|
||||
description = "The LLVM backend for the Roc compiler"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[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" }
|
||||
roc_alias_analysis = { path = "../alias_analysis" }
|
||||
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
|
||||
inkwell.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
inkwell.workspace = true
|
||||
|
@ -39,8 +39,8 @@ use roc_debug_flags::dbg_do;
|
||||
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::ir::{
|
||||
BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc,
|
||||
OptLevel, ProcLayout, SingleEntryPoint,
|
||||
BranchInfo, CallType, CrashTag, EntryPoint, HostExposedLambdaSet, JoinPointId,
|
||||
ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
|
||||
};
|
||||
use roc_mono::layout::{
|
||||
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
|
||||
(
|
||||
¶ms[..],
|
||||
¶m_types[..param_types.len().saturating_sub(1)],
|
||||
// ¶m_types[..param_types.len().saturating_sub(1)],
|
||||
¶m_types[..],
|
||||
)
|
||||
}
|
||||
_ => (¶ms[..], ¶m_types[..]),
|
||||
@ -4938,26 +4939,24 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
|
||||
mod_solutions: &'a ModSolutions,
|
||||
proc_name: LambdaName,
|
||||
alias_symbol: Symbol,
|
||||
exposed_function_symbol: Symbol,
|
||||
top_level: ProcLayout<'a>,
|
||||
layout: RawFunctionLayout<'a>,
|
||||
hels: &HostExposedLambdaSet<'a>,
|
||||
) {
|
||||
let ident_string = proc_name.name().as_str(&env.interns);
|
||||
let fn_name: String = format!("{}_1", ident_string);
|
||||
|
||||
match layout {
|
||||
match hels.raw_function_layout {
|
||||
RawFunctionLayout::Function(arguments, closure, result) => {
|
||||
// define closure size and return value size, e.g.
|
||||
//
|
||||
// * roc__mainForHost_1_Update_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(
|
||||
exposed_function_symbol,
|
||||
hels.symbol,
|
||||
it,
|
||||
Niche::NONE,
|
||||
top_level.result,
|
||||
hels.proc_layout.result,
|
||||
);
|
||||
let func_name = FuncName(&bytes);
|
||||
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(
|
||||
env,
|
||||
*func_spec,
|
||||
exposed_function_symbol,
|
||||
top_level.arguments,
|
||||
hels.symbol,
|
||||
hels.proc_layout.arguments,
|
||||
Niche::NONE,
|
||||
top_level.result,
|
||||
hels.proc_layout.result,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
// morphic did not generate a specialization for this function,
|
||||
// therefore it must actually be unused.
|
||||
// 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 */
|
||||
}
|
||||
Binary | BinaryDev => {
|
||||
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
|
||||
for (alias_name, hels) in aliases.iter() {
|
||||
expose_alias_to_host(
|
||||
env,
|
||||
layout_interner,
|
||||
mod_solutions,
|
||||
proc.name,
|
||||
*alias_name,
|
||||
*generated_function,
|
||||
*top_level,
|
||||
*layout,
|
||||
hels,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1345,11 +1345,16 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||
union_layout,
|
||||
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
|
||||
let (_, only_branch) = cases.pop().unwrap();
|
||||
env.builder.build_unconditional_branch(only_branch);
|
||||
if cases.is_empty() {
|
||||
// The only other layout doesn't need refcounting. Pass through.
|
||||
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 {
|
||||
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> {
|
||||
nullable_id: Option<u16>,
|
||||
tags: &'a [&'a [InLayout<'a>]],
|
||||
|
@ -1,19 +1,20 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_wasm_module = { path = "../../wasm_module" }
|
||||
|
||||
bitvec.workspace = true
|
||||
|
@ -1,7 +1,8 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
@ -1,19 +1,20 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
[dependencies]
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_derive = { path = "../derive" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
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
|
||||
|
@ -1,30 +1,31 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_types = { path = "../types" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_can = { path = "../can" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
|
||||
|
@ -1,45 +1,46 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[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_problem = { path = "../problem" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_can = { path = "../can" }
|
||||
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_problem = { path = "../problem" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_solve_problem = { path = "../solve_problem" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_tracing = { path = "../../tracing" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_unify = { path = "../unify" }
|
||||
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
parking_lot.workspace = true
|
||||
crossbeam.workspace = true
|
||||
parking_lot.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
|
||||
pretty_assertions.workspace = true
|
||||
indoc.workspace = true
|
||||
maplit.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
|
@ -1,20 +1,22 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_region = { path = "../region" }
|
||||
roc_ident = { path = "../ident" }
|
||||
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
|
||||
static_assertions.workspace = true
|
||||
snafu.workspace = true
|
||||
static_assertions.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
debug-symbols = []
|
||||
default = []
|
||||
|
@ -1319,6 +1319,7 @@ define_builtins! {
|
||||
53 STR_WITH_CAPACITY: "withCapacity"
|
||||
54 STR_WITH_PREFIX: "withPrefix"
|
||||
55 STR_GRAPHEMES: "graphemes"
|
||||
56 STR_IS_VALID_SCALAR: "isValidScalar"
|
||||
}
|
||||
6 LIST: "List" => {
|
||||
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias
|
||||
@ -1401,6 +1402,7 @@ define_builtins! {
|
||||
77 LIST_COUNT_IF: "countIf"
|
||||
78 LIST_WALK_FROM: "walkFrom"
|
||||
79 LIST_WALK_FROM_UNTIL: "walkFromUntil"
|
||||
80 LIST_ITER_HELP: "iterHelp"
|
||||
}
|
||||
7 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
|
||||
|
@ -1,32 +1,33 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[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_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_error_macros = {path="../../error_macros"}
|
||||
roc_debug_flags = {path="../debug_flags"}
|
||||
roc_tracing = { path = "../../tracing" }
|
||||
roc_types = { path = "../types" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
|
||||
bitvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
hashbrown.workspace = true
|
||||
static_assertions.workspace = true
|
||||
bitvec.workspace = true
|
||||
parking_lot.workspace = true
|
||||
static_assertions.workspace = true
|
||||
|
@ -100,8 +100,14 @@ pub fn infer_borrow<'a>(
|
||||
// host-exposed functions must always own their arguments.
|
||||
let is_host_exposed = host_exposed_procs.contains(&key.0);
|
||||
|
||||
let param_offset = param_map.get_param_offset(key.0, key.1);
|
||||
env.collect_proc(&mut param_map, proc, param_offset, is_host_exposed);
|
||||
let param_offset = param_map.get_param_offset(interner, key.0, key.1);
|
||||
env.collect_proc(
|
||||
interner,
|
||||
&mut param_map,
|
||||
proc,
|
||||
param_offset,
|
||||
is_host_exposed,
|
||||
);
|
||||
}
|
||||
|
||||
if !env.modified {
|
||||
@ -167,6 +173,7 @@ impl<'a> DeclarationToIndex<'a> {
|
||||
|
||||
fn get_param_offset(
|
||||
&self,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
needle_symbol: Symbol,
|
||||
needle_layout: ProcLayout<'a>,
|
||||
) -> ParamOffset {
|
||||
@ -181,12 +188,14 @@ impl<'a> DeclarationToIndex<'a> {
|
||||
.elements
|
||||
.iter()
|
||||
.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<_>>();
|
||||
unreachable!(
|
||||
"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> {
|
||||
pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset {
|
||||
self.declaration_to_index.get_param_offset(symbol, layout)
|
||||
pub fn get_param_offset(
|
||||
&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.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())
|
||||
}
|
||||
@ -292,7 +312,7 @@ impl<'a> ParamMap<'a> {
|
||||
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)
|
||||
.iter()
|
||||
@ -312,7 +332,7 @@ impl<'a> ParamMap<'a> {
|
||||
proc: &Proc<'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)
|
||||
.iter()
|
||||
@ -534,7 +554,13 @@ impl<'a> BorrowInfState<'a> {
|
||||
///
|
||||
/// and determines whether z and which of the symbols used in e
|
||||
/// 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::*;
|
||||
|
||||
let crate::ir::Call {
|
||||
@ -553,7 +579,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
|
||||
// get the borrow signature of the applied function
|
||||
let ps = param_map
|
||||
.get_symbol(name.name(), top_level)
|
||||
.get_symbol(interner, name.name(), top_level)
|
||||
.expect("function is defined");
|
||||
|
||||
// the return value will be owned
|
||||
@ -595,11 +621,14 @@ impl<'a> BorrowInfState<'a> {
|
||||
niche: passed_function.name.niche(),
|
||||
};
|
||||
|
||||
let function_ps =
|
||||
match param_map.get_symbol(passed_function.name.name(), closure_layout) {
|
||||
Some(function_ps) => function_ps,
|
||||
None => unreachable!(),
|
||||
};
|
||||
let function_ps = match param_map.get_symbol(
|
||||
interner,
|
||||
passed_function.name.name(),
|
||||
closure_layout,
|
||||
) {
|
||||
Some(function_ps) => function_ps,
|
||||
None => unreachable!(),
|
||||
};
|
||||
|
||||
match op {
|
||||
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::*;
|
||||
|
||||
match e {
|
||||
@ -724,7 +759,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
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(_) => {}
|
||||
|
||||
@ -757,6 +792,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn preserve_tail_call(
|
||||
&mut self,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
param_map: &mut ParamMap<'a>,
|
||||
x: Symbol,
|
||||
v: &Expr<'a>,
|
||||
@ -782,7 +818,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
if self.current_proc == g.name() && x == *z {
|
||||
// anonymous functions (for which the ps may not be known)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@ -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::*;
|
||||
|
||||
match stmt {
|
||||
@ -813,11 +854,11 @@ impl<'a> BorrowInfState<'a> {
|
||||
} => {
|
||||
let old = self.param_set.clone();
|
||||
self.update_param_set(ys);
|
||||
self.collect_stmt(param_map, v);
|
||||
self.collect_stmt(interner, param_map, v);
|
||||
self.param_set = old;
|
||||
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) => {
|
||||
@ -830,17 +871,17 @@ impl<'a> BorrowInfState<'a> {
|
||||
stack.push((*symbol, expr));
|
||||
}
|
||||
|
||||
self.collect_stmt(param_map, b);
|
||||
self.collect_stmt(interner, param_map, b);
|
||||
|
||||
let mut it = stack.into_iter().rev();
|
||||
|
||||
// collect the final expr, and see if we need to preserve a tail call
|
||||
let (x, v) = it.next().unwrap();
|
||||
self.collect_expr(param_map, x, v);
|
||||
self.preserve_tail_call(param_map, x, v, b);
|
||||
self.collect_expr(interner, param_map, x, v);
|
||||
self.preserve_tail_call(interner, param_map, x, v, b);
|
||||
|
||||
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() {
|
||||
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, .. } => {
|
||||
self.collect_stmt(param_map, remainder);
|
||||
self.collect_stmt(interner, param_map, remainder);
|
||||
}
|
||||
|
||||
Expect { remainder, .. } => {
|
||||
self.collect_stmt(param_map, remainder);
|
||||
self.collect_stmt(interner, param_map, remainder);
|
||||
}
|
||||
|
||||
ExpectFx { remainder, .. } => {
|
||||
self.collect_stmt(param_map, remainder);
|
||||
self.collect_stmt(interner, param_map, remainder);
|
||||
}
|
||||
|
||||
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
|
||||
@ -891,6 +932,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
|
||||
fn collect_proc(
|
||||
&mut self,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
param_map: &mut ParamMap<'a>,
|
||||
proc: &Proc<'a>,
|
||||
param_offset: ParamOffset,
|
||||
@ -912,7 +954,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
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.param_set = old;
|
||||
|
@ -605,7 +605,7 @@ impl<'a, 'i> Context<'a, 'i> {
|
||||
// get the borrow signature
|
||||
let ps = self
|
||||
.param_map
|
||||
.get_symbol(name.name(), top_level)
|
||||
.get_symbol(self.layout_interner, name.name(), top_level)
|
||||
.expect("function is defined");
|
||||
|
||||
let v = Expr::Call(crate::ir::Call {
|
||||
@ -653,10 +653,11 @@ impl<'a, 'i> Context<'a, 'i> {
|
||||
niche: passed_function.name.niche(),
|
||||
};
|
||||
|
||||
let function_ps = match self
|
||||
.param_map
|
||||
.get_symbol(passed_function.name.name(), function_layout)
|
||||
{
|
||||
let function_ps = match self.param_map.get_symbol(
|
||||
self.layout_interner,
|
||||
passed_function.name.name(),
|
||||
function_layout,
|
||||
) {
|
||||
Some(function_ps) => function_ps,
|
||||
None => unreachable!(),
|
||||
};
|
||||
@ -671,14 +672,14 @@ impl<'a, 'i> Context<'a, 'i> {
|
||||
match ownership {
|
||||
DataOwnedFunctionOwns | DataBorrowedFunctionOwns => {
|
||||
// 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);
|
||||
|
||||
stmt = self.arena.alloc(rc);
|
||||
}
|
||||
DataOwnedFunctionBorrows => {
|
||||
// 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);
|
||||
|
||||
stmt = self.arena.alloc(rc);
|
||||
@ -1510,19 +1511,28 @@ pub fn visit_procs<'a, 'i>(
|
||||
};
|
||||
|
||||
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>(
|
||||
arena: &'a Bump,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
codegen: &mut CodegenTools<'i>,
|
||||
param_map: &'a ParamMap<'a>,
|
||||
ctx: &Context<'a, 'i>,
|
||||
proc: &mut Proc<'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,
|
||||
None => Vec::from_iter_in(
|
||||
proc.args.iter().cloned().map(|(layout, symbol)| Param {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)]
|
||||
@ -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.
|
||||
pub fn struct_no_name_order(field_layouts: &'a [InLayout]) -> Self {
|
||||
if field_layouts.is_empty() {
|
||||
|
@ -365,6 +365,10 @@ pub trait LayoutInterner<'a>: Sized {
|
||||
fn dbg_deep<'r>(&'r self, layout: InLayout<'a>) -> dbg::Dbg<'a, 'r, Self> {
|
||||
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.
|
||||
@ -1274,7 +1278,7 @@ mod equiv {
|
||||
}
|
||||
}
|
||||
|
||||
mod dbg {
|
||||
pub mod dbg {
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
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> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -6,6 +6,8 @@
|
||||
#![warn(clippy::dbg_macro)]
|
||||
// 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)]
|
||||
// Not a useful lint for us
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub mod borrow;
|
||||
pub mod code_gen_help;
|
||||
|
@ -1,32 +1,31 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[features]
|
||||
"parse_debug_trace" = []
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_region = { path = "../region" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
encode_unicode.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
proptest = "1.0.0"
|
||||
|
||||
criterion.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
indoc.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
proptest.workspace = true
|
||||
quickcheck.workspace = true
|
||||
quickcheck_macros.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "bench_parse"
|
||||
harness = false
|
||||
name = "bench_parse"
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::ast::CommentOrNewline;
|
||||
use crate::ast::Spaceable;
|
||||
use crate::parser::Progress;
|
||||
use crate::parser::SpaceProblem;
|
||||
use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*};
|
||||
use crate::state::State;
|
||||
@ -7,6 +8,7 @@ use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_region::all::Loc;
|
||||
use roc_region::all::Position;
|
||||
use roc_region::all::Region;
|
||||
|
||||
pub fn space0_around_ee<'a, P, S, E>(
|
||||
parser: P,
|
||||
@ -386,98 +388,132 @@ pub fn spaces<'a, E>() -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
|
||||
where
|
||||
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 progress = NoProgress;
|
||||
loop {
|
||||
let whitespace = fast_eat_whitespace(state.bytes());
|
||||
if whitespace > 0 {
|
||||
state.advance_mut(whitespace);
|
||||
progress = MadeProgress;
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
match consume_spaces(state, |_, space, _| newlines.push(space)) {
|
||||
Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)),
|
||||
Err((progress, err)) => Err((progress, err)),
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::ast::{
|
||||
AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Has, HasAbilities,
|
||||
Pattern, Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
Pattern, Spaceable, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use crate::blankspace::{
|
||||
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
|
||||
@ -1085,6 +1085,29 @@ enum AliasOrOpaque {
|
||||
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)]
|
||||
fn finish_parsing_alias_or_opaque<'a>(
|
||||
min_indent: u32,
|
||||
@ -1105,120 +1128,113 @@ fn finish_parsing_alias_or_opaque<'a>(
|
||||
|
||||
let mut defs = Defs::default();
|
||||
|
||||
let state = match &expr.value.extract_spaces().item {
|
||||
Expr::ParensAround(Expr::SpaceBefore(Expr::Tag(name), _))
|
||||
| Expr::ParensAround(Expr::SpaceAfter(Expr::Tag(name), _))
|
||||
| Expr::ParensAround(Expr::Tag(name))
|
||||
| Expr::Tag(name) => {
|
||||
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
|
||||
let state = if let Some(tag) = extract_tag_and_spaces(arena, expr.value) {
|
||||
let name = tag.item;
|
||||
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
|
||||
|
||||
for argument in arguments {
|
||||
match expr_to_pattern_help(arena, &argument.value) {
|
||||
Ok(good) => {
|
||||
type_arguments.push(Loc::at(argument.region, good));
|
||||
}
|
||||
Err(()) => {
|
||||
return Err((
|
||||
MadeProgress,
|
||||
EExpr::Pattern(
|
||||
arena.alloc(EPattern::NotAPattern(state.pos())),
|
||||
state.pos(),
|
||||
),
|
||||
));
|
||||
}
|
||||
for argument in arguments {
|
||||
match expr_to_pattern_help(arena, &argument.value) {
|
||||
Ok(good) => {
|
||||
type_arguments.push(Loc::at(argument.region, good));
|
||||
}
|
||||
}
|
||||
|
||||
match kind {
|
||||
AliasOrOpaque::Alias => {
|
||||
let (_, signature, state) =
|
||||
alias_signature_with_space_before().parse(arena, state, min_indent)?;
|
||||
|
||||
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
|
||||
Err(()) => {
|
||||
return Err((
|
||||
MadeProgress,
|
||||
EExpr::Pattern(
|
||||
arena.alloc(EPattern::NotAPattern(state.pos())),
|
||||
state.pos(),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let call = to_call(arena, arguments, expr);
|
||||
match kind {
|
||||
AliasOrOpaque::Alias => {
|
||||
let (_, signature, state) =
|
||||
alias_signature_with_space_before().parse(arena, state, min_indent)?;
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
let def_region = Region::span_across(&expr.region, &signature.region);
|
||||
|
||||
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 header = TypeHeader {
|
||||
name: Loc::at(expr.region, name),
|
||||
vars: type_arguments.into_bump_slice(),
|
||||
};
|
||||
|
||||
let def_region = Region::span_across(&call.region, &ann_type.region);
|
||||
let def = TypeDef::Alias {
|
||||
header,
|
||||
ann: signature,
|
||||
};
|
||||
|
||||
let value_def =
|
||||
ValueDef::Annotation(Loc::at(expr_region, good), ann_type);
|
||||
defs.push_type_def(def, def_region, &[], &[]);
|
||||
|
||||
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
|
||||
let op = match kind {
|
||||
AliasOrOpaque::Alias => ":",
|
||||
AliasOrOpaque::Opaque => ":=",
|
||||
};
|
||||
let fail = EExpr::BadOperator(op, loc_op.region.start());
|
||||
}
|
||||
Err(_) => {
|
||||
// this `:`/`:=` likely occurred inline; treat it as an invalid operator
|
||||
let op = match kind {
|
||||
AliasOrOpaque::Alias => ":",
|
||||
AliasOrOpaque::Opaque => ":=",
|
||||
};
|
||||
let fail = EExpr::BadOperator(op, loc_op.region.start());
|
||||
|
||||
return Err((MadeProgress, fail));
|
||||
}
|
||||
return Err((MadeProgress, fail));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
640
crates/compiler/parse/src/highlight.rs
Normal file
640
crates/compiler/parse/src/highlight.rs
Normal file
@ -0,0 +1,640 @@
|
||||
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,
|
||||
Paren,
|
||||
Arrow,
|
||||
Pipe,
|
||||
Backpass,
|
||||
Decimal,
|
||||
}
|
||||
|
||||
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::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::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::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
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ pub mod ast;
|
||||
pub mod blankspace;
|
||||
pub mod expr;
|
||||
pub mod header;
|
||||
pub mod highlight;
|
||||
pub mod ident;
|
||||
pub mod keyword;
|
||||
pub mod module;
|
||||
|
@ -1,14 +1,15 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_types = { path = "../types" }
|
||||
|
@ -1,10 +1,11 @@
|
||||
[package]
|
||||
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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
static_assertions = "1.1.0"
|
||||
static_assertions.workspace = true
|
||||
|
@ -129,6 +129,10 @@ impl Position {
|
||||
offset: self.offset - count as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn byte_offset(&self) -> usize {
|
||||
self.offset as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Position {
|
||||
@ -322,6 +326,10 @@ impl<T> Loc<T> {
|
||||
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>
|
||||
|
@ -1,12 +1,13 @@
|
||||
[package]
|
||||
name = "roc_target"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Provides types and helpers for compiler targets such as default_x86_64."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
target-lexicon = "0.12.3"
|
||||
strum = "0.24.0"
|
||||
strum_macros = "0.24"
|
||||
strum.workspace = true
|
||||
strum_macros.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
|
@ -150,3 +150,57 @@ impl From<target_lexicon::Architecture> for Architecture {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const WASM_TARGET_STR: &str = "wasm32";
|
||||
pub const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64";
|
||||
pub const LINUX_ARM64_TARGET_STR: &str = "linux-arm64";
|
||||
pub const MACOS_ARM64_TARGET_STR: &str = "macos-arm64";
|
||||
pub const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64";
|
||||
pub const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64";
|
||||
pub const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32";
|
||||
pub const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64";
|
||||
|
||||
pub fn get_target_triple_str(target: &target_lexicon::Triple) -> Option<&'static str> {
|
||||
match target {
|
||||
target_lexicon::Triple {
|
||||
architecture: target_lexicon::Architecture::Wasm32,
|
||||
..
|
||||
} => Some(WASM_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Linux,
|
||||
architecture: target_lexicon::Architecture::X86_64,
|
||||
..
|
||||
} => Some(LINUX_X86_64_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Linux,
|
||||
architecture: target_lexicon::Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(LINUX_ARM64_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Darwin,
|
||||
architecture: target_lexicon::Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(MACOS_ARM64_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Darwin,
|
||||
architecture: target_lexicon::Architecture::X86_64,
|
||||
..
|
||||
} => Some(MACOS_X86_64_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Windows,
|
||||
architecture: target_lexicon::Architecture::X86_64,
|
||||
..
|
||||
} => Some(WINDOWS_X86_64_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Windows,
|
||||
architecture: target_lexicon::Architecture::X86_32(_),
|
||||
..
|
||||
} => Some(WINDOWS_X86_32_TARGET_STR),
|
||||
target_lexicon::Triple {
|
||||
operating_system: target_lexicon::OperatingSystem::Windows,
|
||||
architecture: target_lexicon::Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(WIDNOWS_ARM64_TARGET_STR),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
[package]
|
||||
name = "roc_serialize"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Provides helpers for serializing and deserializing to/from bytes."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
|
@ -1,44 +1,45 @@
|
||||
[package]
|
||||
name = "roc_solve"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "The entry point of Roc's type inference system. Implements type inference and specialization of abilities."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
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_packaging = { path = "../../packaging" }
|
||||
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_packaging = { path = "../../packaging" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_solve_problem = { path = "../solve_problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
|
||||
arrayvec.workspace = true
|
||||
bumpalo.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
roc_load = { path = "../load" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
|
||||
roc_load = { path = "../load" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
|
||||
|
||||
pretty_assertions.workspace = true
|
||||
indoc.workspace = true
|
||||
tempfile.workspace = true
|
||||
bumpalo.workspace = true
|
||||
regex.workspace = true
|
||||
lazy_static.workspace = true
|
||||
indoc.workspace = true
|
||||
insta.workspace = true
|
||||
lazy_static.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
regex.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
@ -1,16 +1,17 @@
|
||||
[package]
|
||||
name = "roc_solve_problem"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Provides types to describe problems that can occur during solving."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_exhaustive = { path = "../exhaustive" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_types = { path = "../types" }
|
||||
|
@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "roc_str"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Provides a Roc Str with reference-counting so that it may be mutated in-place."
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_problem = { path = "../problem" }
|
||||
bumpalo = { version = "3.6.1", features = ["collections"] }
|
||||
|
||||
[dev-dependencies]
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
@ -1,209 +0,0 @@
|
||||
# `Str`
|
||||
|
||||
This is the in-memory representation for `Str`. To explain how `Str` is laid out in memory, it's helpful to start with how `List` is laid out.
|
||||
|
||||
## Empty list
|
||||
|
||||
An empty `List Str` is essentially this Rust type with all 0s in memory:
|
||||
|
||||
```rust
|
||||
struct List {
|
||||
pointer: *Str, // pointers are the same size as `usize`
|
||||
length: usize
|
||||
}
|
||||
```
|
||||
|
||||
On a 64-bit system, this `struct` would take up 16B in memory. On a 32-bit system, it would take up 8B.
|
||||
|
||||
Here's what the fields mean:
|
||||
|
||||
- `pointer` is the memory address of the heap-allocated memory containing the `Bool` elements. For an empty list, the pointer is null (that is, 0).
|
||||
- `length` is the number of `Bool` elements in the list. For an empty list, this is also 0.
|
||||
|
||||
## Nonempty list
|
||||
|
||||
Now let's say we define a `List Str` with two elements in it, like so: `["foo", "bar"]`.
|
||||
|
||||
First we'd have the `struct` above, with both `length` and `capacity` set to 2. Then, we'd have some memory allocated on the heap, and `pointer` would store that memory's address.
|
||||
|
||||
Here's how that heap memory would be laid out on a 64-bit system. It's a total of 48 bytes.
|
||||
|
||||
```text
|
||||
|------16B------|------16B------|---8B---|---8B---|
|
||||
string #1 string #2 refcount unused
|
||||
```
|
||||
|
||||
Just like how `List` is a `struct` that takes up `2 * usize` bytes in memory, `Str` takes up the same amount of memory - namely, 16B on a 64-bit system. That's why each of the two strings take up 16B of this heap-allocated memory. (Those structs may also point to other heap memory, but they could also be empty strings! Either way we just store the structs in the list, which take up 16B.)
|
||||
|
||||
We'll get to what the refcount is for shortly, but first let's talk about the memory layout. The refcount is a `usize` integer, so 8B on our 64-bit system. Why is there 8B of unused memory after it?
|
||||
|
||||
This is because of memory alignment. Whenever a system loads some memory from a memory address, it's much more efficient if the address is a multiple of the number of bytes it wants to get. So if we want to load a 16B string struct, we want its address to be a multiple of 16.
|
||||
|
||||
When we're allocating memory on the heap, the way we specify what alignment we want is to say how big each element is, and how many of them we want. In this case, we say we want 16B elements, and we want 3 of them. Then we use the first 16B slot to store the 8B refcount, and the 8B after it are unused.
|
||||
|
||||
This is memory-inefficient, but it's the price we pay for having all the 16B strings stored in addresses that are multiples of 16. It'd be worse for performance if we tried to pack everything tightly, so we accept the memory inefficiency as a cost of achieving better overall execution speed.
|
||||
|
||||
> Note: if we happened to have 8B elements instead of 16B elements, the alignment would be 8 anyway and we'd have no unused memory.
|
||||
|
||||
## Reference counting
|
||||
|
||||
Let's go back to the refcount - short for "reference count."
|
||||
|
||||
The refcount is a `usize` integer which counts how many times this `List` has been shared. For example, if we named this list `myList` and then wrote `[myList, myList, myList]` then we'd increment that refcount 3 times because `myList` is now being shared three more times.
|
||||
|
||||
If we were to later call `List.pop` on that list, and the result was an in-place mutation that removed one of the `myList` entries, we'd decrement the refcount. If we did that again and again until the refcount got all the way down to 0, meaning nothing is using it anymore, then we'd deallocate these 48B of heap memory because nobody is using them anymore.
|
||||
|
||||
In some cases, the compiler can detect that no reference counting is necessary. In that scenario, it doesn't bother allocating extra space for the refcount; instead, it inserts an instruction to allocate the memory at the appropriate place, another to free it later, and that's it.
|
||||
|
||||
## Pointing to the first element
|
||||
|
||||
The fact that the reference count may or may not be present could creat a tricky situation for some `List` operations.
|
||||
|
||||
For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B.
|
||||
|
||||
To solve this, the pointer in the List struct *always* points to the first element in the list. That means to access the reference count, it does negative pointer arithmetic to get the address at 16B *preceding* the memory address it has stored in its pointer field.
|
||||
|
||||
## Growing lists
|
||||
|
||||
If uniqueness typing tells us that a list is Unique, we know two things about it:
|
||||
|
||||
1. It doesn't need a refcount, because nothing else ever references it.
|
||||
2. It can be mutated in-place.
|
||||
|
||||
One of the in-place mutations that can happen to a list is that its length can increase. For example, if I call `List.append list1 list2`, and `list1` is unique, then we'll attempt to append `list2`'s contents in-place into `list1`.
|
||||
|
||||
Calling `List.append` on a Shared list results in allocating a new chunk of heap memory large enough to hold both lists (with a fresh refcount, since nothing is referencing the new memory yet), then copying the contents of both lists into the new memory, and finally decrementing the refcount of the old memory.
|
||||
|
||||
Calling `List.append` on a Unique list can potentially be done in-place instead.
|
||||
|
||||
First, `List.append` repurposes the `usize` slot normally used to store refcount, and stores a `capacity` counter in there instead of a refcount. (After all, unique lists don't need to be refcounted.) A list's capacity refers to how many elements the list *can* hold given the memory it has allocated to it, which is always guaranteed to be at least as many as its length.
|
||||
|
||||
When calling `List.append list1 list2` on a unique `list1`, first we'll check to see if `list1.capacity <= list1.length + list2.length`. If it is, then we can copy in the new values without needing to allocate more memory for `list1`.
|
||||
|
||||
If there is not enough capacity to fit both lists, then we can try to call [`realloc`](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/realloc?view=vs-2019) to hopefully extend the size of our allocated memory. If `realloc` succeeds (meaning there happened to be enough free memory right after our current allocation), then we update `capacity` to reflect the new amount of space, and move on.
|
||||
|
||||
> **Note:** The reason we store capacity right after the last element in the list is because of how memory cache lines work. Whenever we need to access `capacity`, it's because we're about to increase the length of the list, which means that we will most certainly be writing to the memory location right after its last element. That in turn means that we'll need to have that memory location in cache, which in turn means that looking up the `capacity` there is guaranteed not to cause a cache miss. (It's possible that writing the new capacity value to a later address could cause a cache miss, but this strategy minimizes the chance of that happening.) An alternate design would be where we store the capacity right before the first element in the list. In that design we wouldn't have to re-write the capacity value at the end of the list every time we grew it, but we'd be much more likely to incur more cache misses that way - because we're working at the end of the list, not at the beginning. Cache misses are many times more expensive than an extra write to a memory address that's in cache already, not to mention the potential extra load instruction to add the length to the memory address of the first element (instead of subtracting 1 from that address), so we optimize for minimizing the highly expensive cache misses by always paying a tiny additional cost when increasing the length of the list, as well as a potential even tinier cost (zero, if the length already happens to be in a register) when looking up its capacity or refcount.
|
||||
|
||||
If `realloc` fails, then we have to fall back on the same "allocate new memory and copy everything" strategy that we do with shared lists.
|
||||
|
||||
When you have a way to anticipate that a list will want to grow incrementally to a certain size, you can avoid such extra allocations by using `List.reserve` to guarantee more capacity up front. (`List.reserve` works like Rust's [`Vec::reserve`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve).)
|
||||
|
||||
> **Note:** Calling `List.reserve 0 myList` will have no effect on a Unique list, but on a Shared list it will clone `myList` and return a Unique one. If you want to do a bunch of in-place mutations on a list, but it's currently Shared, calling `List.reserve 0` on it to get a Unique clone could actually be an effective performance optimization!
|
||||
|
||||
## Capacity and alignment
|
||||
|
||||
Some lists may end up beginning with excess capacity due to memory alignment requirements. Since the refcount is `usize`, all lists need a minimum of that alignment. For example, on a 64-bit system, a `List Bool` has an alignment of 8B even though bools can fit in 1B.
|
||||
|
||||
This means the list `[True, True, False]` would have a memory layout like this):
|
||||
|
||||
```text
|
||||
|--------------8B--------------|--1B--|--1B--|--1B--|-----5B-----|
|
||||
either refcount or capacity bool1 bool2 bool3 unused
|
||||
```
|
||||
|
||||
As such, if this list is Unique, it would start out with a length of 3 and a capacity of 8.
|
||||
|
||||
Since each bool value is a byte, it's okay for them to be packed side-by-side even though the overall alignment of the list elements is 8. This is fine because each of their individual memory addresses will end up being a multiple of their size in bytes.
|
||||
|
||||
Note that unlike in the `List Str` example before, there wouldn't be any unused memory between the refcount (or capacity, depending on whether the list was shared or unique) and the first element in the list. That will always be the case when the size of the refcount is no bigger than the alignment of the list's elements.
|
||||
|
||||
## Distinguishing between refcount and capacity in the host
|
||||
|
||||
If I'm a platform author, and I receive a `List` from the application, it's important that I be able to tell whether I'm dealing with a refcount or a capacity. (The uniqueness type information will have been erased by this time, because some applications will return a Unique list while others return a Shared list, so I need to be able to tell using runtime information only which is which.) This way, I can know to either increment the refcount, or to feel free to mutate it in-place using the capacity value.
|
||||
|
||||
We use a very simple system to distinguish the two: if the high bit in that `usize` value is 0, then it's capacity. If it's 1, then it's a refcount.
|
||||
|
||||
This has a couple of implications:
|
||||
|
||||
- `capacity` actually has a maximum of `isize::MAX`, not `usize::MAX` - because if its high bit flips to 1, then now suddenly it's considered a refcount by the host. As it happens, capacity's maximum value is naturally `isize::MAX` anyway, so there's no downside here.
|
||||
- `refcount` actually begins at `isize::MIN` and increments towards 0, rather than beginning at 0 and incrementing towards a larger number. When a decrement instruction is executed and the refcount is `isize::MIN`, it gets freed instead. Since all refcounts do is count up and down, and they only ever compare the refcount to a fixed constant, there's no real performance cost to comparing to `isize::MIN` instead of to 0. So this has no significant downside either.
|
||||
|
||||
Using this representation, hosts can trivially distinguish any list they receive as being either refcounted or having a capacity value, without any runtime cost in either the refcounted case or the capacity case.
|
||||
|
||||
### Saturated reference count
|
||||
|
||||
What happens if the reference count overflows? As in, we try to reference the same list more than `isize` times?
|
||||
|
||||
In this situation, the reference count becomes unreliable. Suppose we try to increment it 3 more times after it's already been incremented `isize` times, and since we can't store any further numbers without flipping the high bit from 1 to 0 (meaning it will become a capacity value instead of a refcount), we leave it at -1. If we later decrement it `isize` times, we'll be down to `isize::MIN` and will free the memory, even though 3 things are still referencing that memory!
|
||||
|
||||
This would be a total disaster, so what we do instead is that we decide to leak the memory. Once the reference count hits -1, we neither increment nor decrement it ever again, which in turn means we will never free it. So -1 is a special reference count meaning "this memory has become unreclaimable, and must never be freed."
|
||||
|
||||
This has the downside of being potentially wasteful of the program's memory, but it's less detrimental to user experience than a crash, and it doesn't impact correctness at all.
|
||||
|
||||
## Summary of Lists
|
||||
|
||||
Lists are a `2 * usize` struct which contains a length and a pointer.
|
||||
|
||||
That pointer is a memory address (null in the case of an empty list) which points to the first element in a sequential array of memory.
|
||||
|
||||
If that pointer is shared in multiple places, then there will be a `usize` reference count stored right before the first element of the list. There may be unused memory after the refcount if `usize` is smaller than the alignment of one of the list's elements.
|
||||
|
||||
Refcounts get incremented each time a list gets shared somewhere, and decremented each time that shared value is no longer referenced by anything else (for example, by going out of scope). Once there are no more references, the list's heap memory can be safely freed. If a reference count gets all the way up to `usize`, then it will never be decremented again and the memory will never be freed.
|
||||
|
||||
Whenever a list grows, it will grow in-place if it's Unique and there is enough capacity. (Capacity is stored where a refcount would be in a Shared list.) If there isn't enough capacity - even after trying `realloc` - or if the list is Shared, then instead new heap memory will be allocated, all the necessary elements will get copied into it, and the original list's refcount will be decremented.
|
||||
|
||||
## Strings
|
||||
|
||||
Strings have several things in common with lists:
|
||||
|
||||
- They are a `2 * usize` struct, sometimes with a non-null pointer to some heap memory
|
||||
- They have a length and a capacity, and they can grow in basically the same way
|
||||
- They are reference counted in basically the same way
|
||||
|
||||
However, they also have two things going on that lists do not:
|
||||
|
||||
- The Small String Optimization
|
||||
- Literals stored in read-only memory
|
||||
|
||||
## The Small String Optimization
|
||||
|
||||
In practice, a lot of strings are pretty small. For example, the string `"Richard Feldman"` can be stored in 15 UTF-8 bytes. If we stored that string the same way we store a list, then on a 64-bit system we'd need a 16B struct, which would include a pointer to 24B of heap memory (including the refcount/capacity and one unused byte for alignment).
|
||||
|
||||
That's a total of 48B to store 15B of data, when we could have fit the whole string into the original 16B we needed for the struct, with one byte left over.
|
||||
|
||||
The Small String Optimization is where we store strings directly in the struct, assuming they can fit in there. We reserve one of those bytes to indicate whether this is a Small String or a larger one that actually uses a pointer.
|
||||
|
||||
## String Memory Layout
|
||||
|
||||
How do we tell small strings apart from nonsmall strings?
|
||||
|
||||
We make use of the fact that lengths (for both strings *and* lists) are `usize` values which have a maximum value of `isize::MAX` rather than `usize::MAX`. This is because `List.get` compiles down to an array access operation, and LLVM uses `isize` indices for those because they do signed arithmetic on the pointer in case the caller wants to add a negative number to the address. (We don't want to, as it happens, but that's what the low-level API supports, so we are bound by its limitations.)
|
||||
|
||||
Since the string's length is a `usize` value with a maximum of `isize::MAX`, we can be sure that its most significant bit will always be 0, not 1. (If it were a 1, that would be a negative `isize`!) We can use this fact to use that spare bit as a flag indicating whether the string is small: if that bit is a 1, it's a small string; otherwise, it's a nonsmall string.
|
||||
|
||||
This makes calculating the length of the string a multi-step process:
|
||||
|
||||
1. Get the length field out of the struct.
|
||||
2. Look at its highest bit. If that bit is 0, return the length as-is.
|
||||
3. If the bit is 1, then this is a small string, and its length is packed into the highest byte of the `usize` length field we're currently examining. Take that byte and bit shift it by 1 (to drop the `1` flag we used to indicate this is a small string), cast the resulting byte to `usize`, and that's our length. (Actually we bit shift by 4, not 1, because we only need 4 bits to store a length of 0-16, and the leftover 3 bits can potentially be useful in the future.)
|
||||
|
||||
Using this strategy with a [conditional move instruction](https://stackoverflow.com/questions/14131096/why-is-a-conditional-move-not-vulnerable-for-branch-prediction-failure), we can always get the length of a `Str` in 2-3 cheap instructions on a single `usize` value, without any chance of a branch misprediction.
|
||||
|
||||
Thus, the layout of a small string on a 64-bit big-endian architecture would be:
|
||||
|
||||
```text
|
||||
|-----------usize length field----------|-----------usize pointer field---------|
|
||||
|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|
|
||||
len 'R' 'i' 'c' 'h' 'a' 'r' 'd' ' ' 'F' 'e' 'l' 'd' 'm' 'a' 'n'
|
||||
```
|
||||
|
||||
The `len` value here would be the number 15, plus a 1 (to flag that this is a small string) that would always get bit-shifted away. The capacity of a small Unique string is always equal to `2 * usize`, because that's how much you can fit without promoting to a nonsmall string.
|
||||
|
||||
## Endianness
|
||||
|
||||
The preceding memory layout example works on a big-endian architecture, but most CPUs are little-endian. That means the high bit where we want to store the flag (the 0 or 1
|
||||
that would make an `isize` either negative or positive) will actually be the `usize`'s last byte rather than its first byte.
|
||||
|
||||
That means we'd have to move swap the order of the struct's length and pointer fields. Here's how the string `"Roc string"` would be stored on a little-endian system:
|
||||
|
||||
```text
|
||||
|-----------usize pointer field---------|-----------usize length field----------|
|
||||
|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|
|
||||
'R' 'o' 'c' ' ' 's' 't' 'r' 'i' 'n' 'g' 0 0 0 0 0 len
|
||||
```
|
||||
|
||||
Here, `len` would have the same format as before (including the extra 1 in the same position, which we'd bit shift away) except that it'd store a length of 10 instead of 15.
|
||||
|
||||
Notice that the leftover bytes are stored as zeroes. This is handy because it means we can convert small Roc strings into C strings (which are 0-terminated) for free as long as they have at least one unused byte. Also notice that `usize pointer field` and `usize length field` have been swapped compared to the preceding example!
|
||||
|
||||
## Storing string literals in read-only memory
|
@ -1,71 +0,0 @@
|
||||
//! Provides `Roc` styled collection [reference counting](https://en.wikipedia.org/wiki/Reference_counting).
|
||||
//! This means the collection may or may not be safe to mutate in-place, may or
|
||||
//! may not be reference counted, and may or may not need to be freed when no
|
||||
//! longer in use. Whether each of these is true for a given collection can be
|
||||
//! determined by inspecting that collection at runtime.
|
||||
#![crate_type = "lib"]
|
||||
#![no_std]
|
||||
|
||||
/// Str does Roc-style collection reference counting, which means the collection
|
||||
/// may or may not be safe to mutate in-place, may or may not be reference counted,
|
||||
/// and may or may not need to be freed when no longer in use. Whether each of
|
||||
/// these is true for a given collection can be determined by inspecting
|
||||
/// that collection at runtime.
|
||||
///
|
||||
/// Details:
|
||||
///
|
||||
/// 1. If the collection is empty, it does not allocate on the heap.
|
||||
/// 2. If it is nonempty, its pointer points to the first element of a "backing array" on the heap.
|
||||
/// 3. There is an extra `isize` right before that backing array (still on the heap) which stores the
|
||||
/// "flexible reference count" ("flexcount" for short).
|
||||
/// 4. The flexcount can refer to one of three things, depending on whether it is positive,
|
||||
/// negative, or zero.
|
||||
/// 5. If the flexcount is positive, then it's a capacity. The capacity refers to the number of
|
||||
/// collection elements in the backing array. This collection can be mutated in-place, until it
|
||||
/// runs out of capacity. At that point, it will need a new backing array. Once it goes out of
|
||||
/// scope, the backing array should be freed by the system allocator - but free() must be passed
|
||||
/// a pointer to the flexcount slot, not to element 0 (because the flexcount slot is where the
|
||||
/// original allocation began). Capacity will always be at least 1, because otherwise we would
|
||||
/// not have allocated on the heap in the first place.
|
||||
/// 6. If the flexcount is 0, then this collection resides in readonly memory. That means it cannot
|
||||
/// be mutated in-place (and attempting to do so will segfault), and it must not be attempted to
|
||||
/// be freed. It exists in memory forever!
|
||||
/// 7. If the flexcount is negative, then it is a reference count. Treat the collection as immutable, just like
|
||||
/// if the flexcount were 0, except free it when there are no more references to it. Instead of the reference count
|
||||
/// starting at 0 or 1 and incrementing when new references are added, this refcount starts with all bits being 1 (so, isize::MIN) and
|
||||
/// increments towards 0 when new references are added. When a reference is removed, if all bits are 1, then it should be freed. If so many new references are added that it gets incremented all the way from isize::MAX to 0, then, as is best practice when running out of reference counts, it will leak. (Leaking memory is typically less bad than crashing, and this should essentially never happen.) This happens automatically because when the flexcount is 0, it's assumed that the collection is in readonly memory and should not be freed - which is nice because it means there is no extra conditional required to implement this edge case.
|
||||
/// 8. If a collection has a refcount of isize::MIN (meaning nothing else references it), it may or may not be safe to convert it to a capacity,
|
||||
/// depending on whether it contains other refcounted collections. For example, a Str
|
||||
/// is a collection of all bytes, so if it has a refcount of all 1 bits, it can be safely
|
||||
/// converted to a capacity (the initial capacity should be equal to the collection's length),
|
||||
/// after which point it can be safely mutated in-place. However, a refcounted List of Lists with a refcount of isize::MIN will not be safe to convert to a capacity, unless the inner Lists also happen to have refcounts of isize::MIN. This is because mutate-in-place operations like removing an element from a list do not check for refcounts in the elements they remove, which means removing an element from the newly mutable-in-place list would cause memory leaks in its refcounted contents. (They'd have been removed, but their reference counts would not have been adjusted accordingly.)
|
||||
///
|
||||
/// Note that because of these runtime conditionals, modifying and freeing Roc collections are both
|
||||
/// cheaper operations in generated Roc code than in host code. Since the Roc compiler knows
|
||||
/// statically whether a collection is refcounted, unique, or readonly, it does not bother with
|
||||
/// these checks at runtime. A host, however, cannot have that information statically (since it may be different
|
||||
/// for different applications), and so must check at runtime instead.
|
||||
struct Str {
|
||||
bytes: [16, u8];
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn empty_() -> Str {
|
||||
Str {
|
||||
bytes : [0; 16]
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn len_(string: Str) -> usize {
|
||||
let disc = discriminant(str);
|
||||
|
||||
if disc == 0 {
|
||||
// It's a
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn discriminant(string: &Str) -> u8 {
|
||||
// cast the first 8 bytes to be u64, return its lsbyte
|
||||
}
|
@ -1,35 +1,34 @@
|
||||
[package]
|
||||
name = "test_derive"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Tests Roc's auto-derivers."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[test]]
|
||||
name = "test_derive"
|
||||
path = "src/tests.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols", "open-extension-vars"] }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
indoc.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
insta.workspace = true
|
||||
|
@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "test_gen"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Contains all of Roc's code generation tests."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[test]]
|
||||
name = "test_gen"
|
||||
path = "src/tests.rs"
|
||||
@ -14,54 +15,58 @@ path = "src/tests.rs"
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_utils = { path = "../../utils" }
|
||||
wasi_libc_sys = { path = "../../wasi-libc-sys" }
|
||||
|
||||
tempfile.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
roc_gen_llvm = { path = "../gen_llvm" }
|
||||
roc_gen_dev = { path = "../gen_dev" }
|
||||
roc_gen_wasm = { path = "../gen_wasm" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_build = { path = "../build", features = ["target-aarch64", "target-x86_64", "target-wasm32"] }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_utils = { path = "../../utils" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_gen_dev = { path = "../gen_dev" }
|
||||
roc_gen_llvm = { path = "../gen_llvm" }
|
||||
roc_gen_wasm = { path = "../gen_wasm" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_build = { path = "../build", features = ["target-aarch64", "target-x86_64", "target-wasm32"] }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_debug_flags = {path="../debug_flags"}
|
||||
roc_wasm_module = {path="../../wasm_module"}
|
||||
roc_wasm_interp = {path="../../wasm_interp"}
|
||||
roc_types = { path = "../types" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_utils = { path = "../../utils" }
|
||||
roc_wasm_interp = { path = "../../wasm_interp" }
|
||||
roc_wasm_module = { path = "../../wasm_module" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
criterion.workspace = true
|
||||
indoc.workspace = true
|
||||
inkwell.workspace = true
|
||||
lazy_static.workspace = true
|
||||
libc.workspace = true
|
||||
libloading.workspace = true
|
||||
criterion.workspace = true
|
||||
tempfile.workspace = true
|
||||
indoc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
inkwell.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
|
||||
[features]
|
||||
default = ["gen-llvm"]
|
||||
gen-llvm = []
|
||||
gen-dev = []
|
||||
gen-wasm = []
|
||||
gen-llvm = []
|
||||
gen-llvm-wasm = ["gen-llvm"]
|
||||
gen-wasm = []
|
||||
|
||||
[[bench]]
|
||||
name = "list_map"
|
||||
harness = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
development = ["roc_wasm_interp"]
|
@ -787,6 +787,59 @@ fn encode_derived_record_with_many_types() {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
fn encode_derived_generic_record_with_different_field_types() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test"
|
||||
imports [Encode, Json]
|
||||
provides [main] to "./platform"
|
||||
|
||||
Q a b := {a: a, b: b} has [Encoding]
|
||||
|
||||
q = @Q {a: 10u32, b: "fieldb"}
|
||||
|
||||
main =
|
||||
result = Str.fromUtf8 (Encode.toBytes q Json.toUtf8)
|
||||
when result is
|
||||
Ok s -> s
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from(r#"{"a":10,"b":"fieldb"}"#),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
fn encode_derived_generic_tag_with_different_field_types() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test"
|
||||
imports [Encode, Json]
|
||||
provides [main] to "./platform"
|
||||
|
||||
Q a b := [A a, B b] has [Encoding]
|
||||
|
||||
q : Q Str U32
|
||||
q = @Q (B 67)
|
||||
|
||||
main =
|
||||
result = Str.fromUtf8 (Encode.toBytes q Json.toUtf8)
|
||||
when result is
|
||||
Ok s -> s
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from(r#"{"B":[67]}"#),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn decode_use_stdlib() {
|
||||
|
@ -27,7 +27,7 @@ fn eq_i64() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn neq_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -61,7 +61,7 @@ fn eq_u64() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn neq_u64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -78,7 +78,7 @@ fn neq_u64() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn eq_bool_tag() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -95,7 +95,7 @@ fn eq_bool_tag() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn neq_bool_tag() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -111,6 +111,52 @@ fn neq_bool_tag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn bool_logic() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
bool1 = Bool.true
|
||||
bool2 = Bool.false
|
||||
bool3 = !bool1
|
||||
|
||||
(bool1 && bool2) || bool2 && bool3
|
||||
"#
|
||||
),
|
||||
false,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn and_bool() {
|
||||
assert_evals_to!("Bool.true && Bool.true", true, bool);
|
||||
assert_evals_to!("Bool.true && Bool.false", false, bool);
|
||||
assert_evals_to!("Bool.false && Bool.true", false, bool);
|
||||
assert_evals_to!("Bool.false && Bool.false", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn or_bool() {
|
||||
assert_evals_to!("Bool.true || Bool.true", true, bool);
|
||||
assert_evals_to!("Bool.true || Bool.false", true, bool);
|
||||
assert_evals_to!("Bool.false || Bool.true", true, bool);
|
||||
assert_evals_to!("Bool.false || Bool.false", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn not_bool() {
|
||||
assert_evals_to!("!Bool.true", false, bool);
|
||||
assert_evals_to!("!Bool.false", true, bool);
|
||||
|
||||
assert_evals_to!("!(!Bool.true)", true, bool);
|
||||
assert_evals_to!("!(!Bool.false)", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn empty_record() {
|
||||
@ -152,7 +198,7 @@ fn unit() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn newtype() {
|
||||
assert_evals_to!("Identity 42 == Identity 42", true, bool);
|
||||
assert_evals_to!("Identity 42 != Identity 42", false, bool);
|
||||
|
@ -1859,13 +1859,11 @@ fn first_int_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.first [12, 9, 6, 3] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
List.first [12, 9, 6, 3]
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
RocResult::ok(12),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1889,45 +1887,42 @@ fn first_wildcard_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.first [] is
|
||||
Ok _ -> 5
|
||||
Err _ -> -1
|
||||
List.last [] |> Result.map (\_ -> 0i64)
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn first_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.first [] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
list : List I64
|
||||
list = []
|
||||
|
||||
List.first list
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn last_int_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.last [12, 9, 6, 3] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
List.last [12, 9, 6, 3]
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
RocResult::ok(3),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1937,13 +1932,11 @@ fn last_wildcard_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.last [] is
|
||||
Ok _ -> 5
|
||||
Err _ -> -1
|
||||
List.last [] |> Result.map (\_ -> 0i64)
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1953,13 +1946,14 @@ fn last_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.last [] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
list : List I64
|
||||
list = []
|
||||
|
||||
List.last list
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1969,29 +1963,32 @@ fn get_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.get [] 0 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
list : List I64
|
||||
list = []
|
||||
|
||||
List.get list 0
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn get_wildcard_empty_list() {
|
||||
// NOTE: by default, the return type is `Result [] [NotFound]`, which is actually represented
|
||||
// as just `[NotFound]`. Casting that to `RocResult<(), ()>` is invalid! But accepting any `()`
|
||||
// would make the test pointless. Therefore, we must explicitly change the type on the roc side
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.get [] 0 is
|
||||
Ok _ -> 5
|
||||
Err _ -> -1
|
||||
List.get [] 0
|
||||
|> Result.map (\_ -> {})
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<(), ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2010,39 +2007,35 @@ fn get_str_list_ok() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn get_int_list_ok() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.get [12, 9, 6] 1 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
List.get [12, 9, 6] 1
|
||||
"#
|
||||
),
|
||||
9,
|
||||
i64
|
||||
RocResult::ok(9),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn get_int_list_oob() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.get [12, 9, 6] 1000 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
List.get [12, 9, 6] 1000
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn replace_unique_int_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2057,7 +2050,7 @@ fn replace_unique_int_list() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn replace_unique_int_list_out_of_bounds() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2072,7 +2065,7 @@ fn replace_unique_int_list_out_of_bounds() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn replace_unique_int_list_get_old_value() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2087,7 +2080,7 @@ fn replace_unique_int_list_get_old_value() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn replace_unique_get_large_value() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2138,13 +2131,11 @@ fn get_set_unique_int_list_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.get (List.set [12, 9, 7, 3] 1 42) 1 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
List.get (List.set [12, 9, 7, 3] 1 42) 1
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
RocResult::ok(42),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2154,13 +2145,11 @@ fn get_set_unique_int_list_i8() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.get (List.set [12, 9, 7, 3] 1 42i8) 1 is
|
||||
Ok val -> val
|
||||
Err _ -> -1i8
|
||||
List.get (List.set [12, 9, 7, 3] 1 42i8) 1
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i8
|
||||
RocResult::ok(42),
|
||||
RocResult<i8, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2175,7 +2164,7 @@ fn set_unique_int_list() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn set_unique_list_oob() {
|
||||
assert_evals_to!(
|
||||
"List.set [3, 17, 4.1] 1337 9.25",
|
||||
@ -2240,20 +2229,18 @@ fn set_shared_list_oob() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn get_unique_int_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
unique = [2, 4]
|
||||
|
||||
when List.get unique 1 is
|
||||
Ok num -> num
|
||||
Err _ -> -1
|
||||
List.get unique 1
|
||||
"#
|
||||
),
|
||||
4,
|
||||
i64
|
||||
RocResult::ok(4),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2275,7 +2262,7 @@ fn gen_wrap_len() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn gen_wrap_first() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2292,7 +2279,7 @@ fn gen_wrap_first() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn gen_duplicate() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2605,7 +2592,7 @@ fn list_literal_increment_decrement() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn list_pass_to_function() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2625,7 +2612,7 @@ fn list_pass_to_function() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn list_pass_to_set() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2713,24 +2700,22 @@ fn list_min() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.min [] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
List.min []
|
||||
|> Result.map (\_ -> {})
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<(), ()>
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.min [3, 1, 2] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
List.min [3, 1, 2]
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
RocResult::ok(1),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2740,24 +2725,22 @@ fn list_max() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.max [] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
List.max []
|
||||
|> Result.map (\_ -> {})
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
RocResult::err(()),
|
||||
RocResult<(), ()>
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when List.max [3, 1, 2] is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
List.max [3, 1, 2]
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
RocResult::ok(3),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3440,7 +3423,7 @@ fn with_capacity() {
|
||||
r#"
|
||||
l : List U64
|
||||
l = List.withCapacity 10
|
||||
|
||||
|
||||
l
|
||||
"#
|
||||
),
|
||||
@ -3713,6 +3696,24 @@ fn list_walk_from_even_prefix_sum() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
// TODO: update how roc decides whether or not to print `User crashed` or `Roc failed` such that this prints `Roc failed ...``
|
||||
#[should_panic(
|
||||
expected = r#"User crash with message: "List.range: failed to generate enough elements to fill the range before overflowing the numeric type"#
|
||||
)]
|
||||
fn list_range_length_overflow() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
List.range {start: At 255u8, end: Length 2}
|
||||
"#
|
||||
),
|
||||
RocList::<u8>::default(),
|
||||
RocList::<u8>
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
mod pattern_match {
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
|
@ -19,11 +19,11 @@ fn nat_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
i : Num.Nat
|
||||
i = 1
|
||||
i : Num.Nat
|
||||
i = 1
|
||||
|
||||
i
|
||||
"#
|
||||
i
|
||||
"#
|
||||
),
|
||||
1,
|
||||
usize
|
||||
@ -31,16 +31,16 @@ fn nat_alias() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn i128_signed_int_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
i : I128
|
||||
i = 128
|
||||
i : I128
|
||||
i = 128
|
||||
|
||||
i
|
||||
"#
|
||||
i
|
||||
"#
|
||||
),
|
||||
128,
|
||||
i128
|
||||
@ -71,11 +71,11 @@ fn i32_signed_int_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
i : I32
|
||||
i = 32
|
||||
i : I32
|
||||
i = 32
|
||||
|
||||
i
|
||||
"#
|
||||
i
|
||||
"#
|
||||
),
|
||||
32,
|
||||
i32
|
||||
@ -115,7 +115,7 @@ fn i8_signed_int_alias() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn i128_hex_int_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -196,7 +196,7 @@ fn i8_hex_int_alias() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn u128_signed_int_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -277,7 +277,7 @@ fn u8_signed_int_alias() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn u128_hex_int_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -418,7 +418,7 @@ fn character_literal_new_line() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn dec_float_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -451,7 +451,7 @@ fn f64_float_alias() {
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn f32_float_alias() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -468,112 +468,51 @@ fn f32_float_alias() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_sqrt() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrtChecked 100 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
10.0,
|
||||
f64
|
||||
);
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn f64_sqrt_100() {
|
||||
assert_evals_to!("Num.sqrt 100", 10.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn f64_sqrt_checked_0() {
|
||||
assert_evals_to!("Num.sqrt 0", 0.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_log() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.log 7.38905609893
|
||||
"#
|
||||
),
|
||||
1.999999999999912,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_log_checked_one() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.logChecked 1 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
0.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_sqrt_zero() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrtChecked 0 is
|
||||
Ok val -> val
|
||||
Err _ -> -1
|
||||
"#
|
||||
),
|
||||
0.0,
|
||||
f64
|
||||
);
|
||||
fn f64_sqrt_checked_positive() {
|
||||
assert_evals_to!("Num.sqrtChecked 100", RocResult::ok(10.0), RocResult<f64, ()>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_sqrt_checked_negative() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.sqrtChecked -1 is
|
||||
Err _ -> 42
|
||||
Ok val -> val
|
||||
"#
|
||||
),
|
||||
42.0,
|
||||
f64
|
||||
);
|
||||
assert_evals_to!("Num.sqrtChecked -1f64", RocResult::err(()), RocResult<f64, ()>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn f64_log() {
|
||||
assert_evals_to!("Num.log 7.38905609893", 1.999999999999912, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_log_checked_one() {
|
||||
assert_evals_to!("Num.logChecked 1", RocResult::ok(0.0), RocResult<f64, ()>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_log_checked_zero() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Num.logChecked 0 is
|
||||
Err _ -> 42
|
||||
Ok val -> val
|
||||
"#
|
||||
),
|
||||
42.0,
|
||||
f64
|
||||
);
|
||||
assert_evals_to!("Num.logChecked 0", RocResult::err(()), RocResult<f64, ()>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn f64_log_negative() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.log -1
|
||||
"#
|
||||
),
|
||||
true,
|
||||
f64,
|
||||
|f: f64| f.is_nan()
|
||||
);
|
||||
assert_evals_to!("Num.log -1", true, f64, |f: f64| f.is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -890,16 +829,20 @@ fn gen_int_neq() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn gen_int_less_than() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
4 < 5
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
fn int_less_than() {
|
||||
assert_evals_to!("4 < 5", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn float_less_than() {
|
||||
assert_evals_to!("4.0 < 5.0", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn float_greater_than() {
|
||||
assert_evals_to!("5.0 > 4.0", true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3307,10 +3307,46 @@ fn box_str() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_num() {
|
||||
fn box_and_unbox_u64() {
|
||||
assert_evals_to!("Box.unbox (Box.box (123u64))", 123, u64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_u32() {
|
||||
assert_evals_to!("Box.unbox (Box.box (123u32))", 123, u32)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_u16() {
|
||||
assert_evals_to!("Box.unbox (Box.box (123u16))", 123, u16)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_u8() {
|
||||
assert_evals_to!("Box.unbox (Box.box (123u8))", 123, u8)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_bool() {
|
||||
assert_evals_to!("Box.unbox (Box.box (Bool.true))", true, bool)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_f64() {
|
||||
assert_evals_to!("Box.unbox (Box.box (123.0f64))", 123.0, f64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn box_and_unbox_f32() {
|
||||
assert_evals_to!("Box.unbox (Box.box (123.0f32))", 123.0, f32)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn box_and_unbox_record() {
|
||||
|
@ -360,7 +360,7 @@ fn i64_record1_literal() {
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn bool_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1111,3 +1111,22 @@ fn toplevel_accessor_fn_thunk() {
|
||||
u8
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn pass_record_of_u8s() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
ra = \_ -> 1u8
|
||||
|
||||
main =
|
||||
ra { a: 1u8, b: 0u8 }
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
)
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ fn is_err() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn roc_result_ok() {
|
||||
fn roc_result_ok_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -242,6 +242,26 @@ fn roc_result_ok() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn roc_result_ok_f64() {
|
||||
// NOTE: the dev backend does not currently use float registers when returning a more
|
||||
// complex type, but the rust side does expect it to. Hence this test fails with gen-dev
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
result : Result F64 {}
|
||||
result = Ok 42.0
|
||||
|
||||
result
|
||||
"#
|
||||
),
|
||||
RocResult::ok(42.0),
|
||||
RocResult<f64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn roc_result_err() {
|
||||
|
@ -16,7 +16,7 @@ use indoc::indoc;
|
||||
use roc_std::{RocList, RocResult, RocStr};
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_empty_delimiter() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -46,7 +46,7 @@ fn str_split_empty_delimiter() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_bigger_delimiter_small_str() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -110,7 +110,7 @@ fn str_split_small_str_bigger_delimiter() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_big_str_small_delimiter() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -154,7 +154,7 @@ fn str_split_small_str_small_delimiter() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_bigger_delimiter_big_strs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -198,7 +198,7 @@ fn str_split_minimal_example() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_small_str_big_delimiter() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -227,7 +227,7 @@ fn str_split_small_str_big_delimiter() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_small_str_20_char_delimiter() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -243,7 +243,7 @@ fn str_split_small_str_20_char_delimiter() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_concat_big_to_big() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -402,7 +402,7 @@ fn small_str_concat_empty_second_arg() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn small_str_concat_small_to_big() {
|
||||
assert_evals_to!(
|
||||
r#"Str.concat "abc" " this is longer than 15 chars""#,
|
||||
@ -530,7 +530,7 @@ fn str_count_graphemes_three_js() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_count_graphemes_big_str() {
|
||||
assert_evals_to!(
|
||||
r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
|
||||
@ -540,7 +540,7 @@ fn str_count_graphemes_big_str() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_starts_with_same_big_str() {
|
||||
assert_evals_to!(
|
||||
r#"Str.startsWith "123456789123456789" "123456789123456789""#,
|
||||
@ -550,7 +550,7 @@ fn str_starts_with_same_big_str() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_starts_with_different_big_str() {
|
||||
assert_evals_to!(
|
||||
r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
|
||||
@ -560,24 +560,24 @@ fn str_starts_with_different_big_str() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_starts_with_same_small_str() {
|
||||
assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_starts_with_different_small_str() {
|
||||
assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_starts_with_false_small_str() {
|
||||
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_single_ascii() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -593,7 +593,7 @@ fn str_from_utf8_pass_single_ascii() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_many_ascii() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -609,7 +609,7 @@ fn str_from_utf8_pass_many_ascii() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_single_unicode() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -625,7 +625,7 @@ fn str_from_utf8_pass_single_unicode() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_many_unicode() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -641,7 +641,7 @@ fn str_from_utf8_pass_many_unicode() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_single_grapheme() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -657,7 +657,7 @@ fn str_from_utf8_pass_single_grapheme() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_many_grapheme() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -673,7 +673,7 @@ fn str_from_utf8_pass_many_grapheme() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_pass_all() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -689,7 +689,7 @@ fn str_from_utf8_pass_all() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_fail_invalid_start_byte() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -709,7 +709,7 @@ fn str_from_utf8_fail_invalid_start_byte() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_fail_unexpected_end_of_sequence() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -729,7 +729,7 @@ fn str_from_utf8_fail_unexpected_end_of_sequence() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_fail_expected_continuation() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -749,7 +749,7 @@ fn str_from_utf8_fail_expected_continuation() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_fail_overlong_encoding() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -769,7 +769,7 @@ fn str_from_utf8_fail_overlong_encoding() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_fail_codepoint_too_large() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -789,7 +789,7 @@ fn str_from_utf8_fail_codepoint_too_large() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_fail_surrogate_half() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -809,7 +809,7 @@ fn str_from_utf8_fail_surrogate_half() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_equality() {
|
||||
assert_evals_to!(r#""a" == "a""#, true, bool);
|
||||
assert_evals_to!(
|
||||
@ -865,7 +865,7 @@ fn nested_recursive_literal() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_join_comma_small() {
|
||||
assert_evals_to!(
|
||||
r#"Str.joinWith ["1", "2"] ", " "#,
|
||||
@ -875,7 +875,7 @@ fn str_join_comma_small() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_join_comma_big() {
|
||||
assert_evals_to!(
|
||||
r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
|
||||
@ -885,13 +885,13 @@ fn str_join_comma_big() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_join_comma_single() {
|
||||
assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_to_utf8() {
|
||||
assert_evals_to!(
|
||||
r#"Str.toUtf8 "hello""#,
|
||||
@ -909,7 +909,7 @@ fn str_to_utf8() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -926,7 +926,7 @@ fn str_from_utf8_range() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range_slice() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -943,7 +943,7 @@ fn str_from_utf8_range_slice() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range_slice_not_end() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -960,7 +960,7 @@ fn str_from_utf8_range_slice_not_end() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range_order_does_not_matter() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -977,7 +977,7 @@ fn str_from_utf8_range_order_does_not_matter() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range_out_of_bounds_start_value() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -995,7 +995,7 @@ fn str_from_utf8_range_out_of_bounds_start_value() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range_count_too_high() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1013,7 +1013,7 @@ fn str_from_utf8_range_count_too_high() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_from_utf8_range_count_too_high_for_start() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1031,7 +1031,7 @@ fn str_from_utf8_range_count_too_high_for_start() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_repeat_small_stays_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.repeat "Roc" 3"#),
|
||||
@ -1041,7 +1041,7 @@ fn str_repeat_small_stays_small() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_repeat_small_becomes_big() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.repeat "less than 23 characters" 2"#),
|
||||
@ -1051,7 +1051,7 @@ fn str_repeat_small_becomes_big() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_repeat_big() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.repeat "more than 23 characters now" 2"#),
|
||||
@ -1061,27 +1061,26 @@ fn str_repeat_big() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_repeat_empty_string() {
|
||||
let a = indoc!(r#"Str.repeat "" 3"#);
|
||||
let b = RocStr::from("");
|
||||
assert_evals_to!(a, b, RocStr);
|
||||
assert_evals_to!(a, RocStr::from(""), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_repeat_zero_times() {
|
||||
assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_empty_string() {
|
||||
assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_null_byte() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trim (Str.reserve "\u(0000)" 40)"#),
|
||||
@ -1091,13 +1090,13 @@ fn str_trim_null_byte() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_small_blank_string() {
|
||||
assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_small_to_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trim " hello world ""#),
|
||||
@ -1107,7 +1106,7 @@ fn str_trim_small_to_small() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_large_to_large_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#),
|
||||
@ -1117,7 +1116,7 @@ fn str_trim_large_to_large_unique() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_large_to_small_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trim (Str.concat " " "hello world ")"#),
|
||||
@ -1184,13 +1183,13 @@ fn str_trim_small_to_small_shared() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_left_small_blank_string() {
|
||||
assert_evals_to!(indoc!(r#"Str.trimLeft " ""#), RocStr::from(""), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_left_small_to_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimLeft " hello world ""#),
|
||||
@ -1200,7 +1199,7 @@ fn str_trim_left_small_to_small() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_left_large_to_large_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimLeft (Str.concat " " "hello world from a large string ")"#),
|
||||
@ -1210,7 +1209,7 @@ fn str_trim_left_large_to_large_unique() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_left_large_to_small_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#),
|
||||
@ -1277,13 +1276,13 @@ fn str_trim_left_small_to_small_shared() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_right_small_blank_string() {
|
||||
assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_right_small_to_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimRight " hello world ""#),
|
||||
@ -1293,7 +1292,7 @@ fn str_trim_right_small_to_small() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_right_large_to_large_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#),
|
||||
@ -1303,7 +1302,7 @@ fn str_trim_right_large_to_large_unique() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_trim_right_large_to_small_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
|
||||
@ -1370,9 +1369,17 @@ fn str_trim_right_small_to_small_shared() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_to_nat() {
|
||||
assert_evals_to!(r#"Str.toNat "1" |> Result.withDefault 0"#, 1, usize);
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.toNat "1"
|
||||
"#
|
||||
),
|
||||
RocResult::ok(1),
|
||||
RocResult<usize, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1381,14 +1388,11 @@ fn str_to_i128() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toI128 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toI128 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i128
|
||||
RocResult::ok(1),
|
||||
RocResult<i128, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1398,41 +1402,39 @@ fn str_to_u128() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toU128 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toU128 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
u128
|
||||
RocResult::ok(1),
|
||||
RocResult<u128, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_to_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toI64 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toI64 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
RocResult::ok(1),
|
||||
RocResult<i64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_to_u64() {
|
||||
assert_evals_to!(
|
||||
r#"Str.toU64 "1""#,
|
||||
RocResult::ok(1u64),
|
||||
RocResult<u64, u8>
|
||||
indoc!(
|
||||
r#"
|
||||
Str.toU64 "1"
|
||||
"#
|
||||
),
|
||||
RocResult::ok(1),
|
||||
RocResult<u64, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1442,14 +1444,11 @@ fn str_to_i32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toI32 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toI32 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i32
|
||||
RocResult::ok(1),
|
||||
RocResult<i32, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1457,9 +1456,13 @@ fn str_to_i32() {
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn str_to_u32() {
|
||||
assert_evals_to!(
|
||||
r#"Str.toU32 "1""#,
|
||||
RocResult::ok(1u32),
|
||||
RocResult<u32, u8>
|
||||
indoc!(
|
||||
r#"
|
||||
Str.toU32 "1"
|
||||
"#
|
||||
),
|
||||
RocResult::ok(1),
|
||||
RocResult<u32, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1469,14 +1472,11 @@ fn str_to_i16() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toI16 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toI16 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i16
|
||||
RocResult::ok(1),
|
||||
RocResult<i16, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1486,14 +1486,11 @@ fn str_to_u16() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toU16 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toU16 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
u16
|
||||
RocResult::ok(1),
|
||||
RocResult<u16, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1503,14 +1500,11 @@ fn str_to_i8() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toI8 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toI8 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i8
|
||||
RocResult::ok(1),
|
||||
RocResult<i8, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1520,14 +1514,11 @@ fn str_to_u8() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Str.toU8 "1" is
|
||||
Ok n -> n
|
||||
Err _ -> 0
|
||||
|
||||
"#
|
||||
Str.toU8 "1"
|
||||
"#
|
||||
),
|
||||
1,
|
||||
u8
|
||||
RocResult::ok(1),
|
||||
RocResult<u8, ()>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1585,7 +1576,7 @@ fn str_to_dec() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn issue_2811() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1601,7 +1592,7 @@ fn issue_2811() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn to_scalar_1_byte() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1625,7 +1616,7 @@ fn to_scalar_1_byte() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn to_scalar_2_byte() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1649,7 +1640,7 @@ fn to_scalar_2_byte() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn to_scalar_3_byte() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1673,7 +1664,7 @@ fn to_scalar_3_byte() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn to_scalar_4_byte() {
|
||||
// from https://design215.com/toolbox/utf8-4byte-characters.php
|
||||
assert_evals_to!(
|
||||
@ -1698,7 +1689,7 @@ fn to_scalar_4_byte() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_first_one_char() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1714,7 +1705,7 @@ fn str_split_first_one_char() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_first_multiple_chars() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1728,7 +1719,7 @@ fn str_split_first_multiple_chars() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_first_entire_input() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1742,7 +1733,7 @@ fn str_split_first_entire_input() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_first_not_found() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1756,7 +1747,7 @@ fn str_split_first_not_found() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_last_one_char() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1770,7 +1761,7 @@ fn str_split_last_one_char() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_last_multiple_chars() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1784,7 +1775,7 @@ fn str_split_last_multiple_chars() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_last_entire_input() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1798,12 +1789,12 @@ fn str_split_last_entire_input() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_last_not_found() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.splitFirst "foo" "bar"
|
||||
Str.splitLast "foo" "bar"
|
||||
"#
|
||||
),
|
||||
RocResult::err(()),
|
||||
@ -1812,7 +1803,7 @@ fn str_split_last_not_found() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_overlapping_substring_1() {
|
||||
assert_evals_to!(
|
||||
r#"Str.split "aaa" "aa""#,
|
||||
@ -1822,7 +1813,7 @@ fn str_split_overlapping_substring_1() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_split_overlapping_substring_2() {
|
||||
assert_evals_to!(
|
||||
r#"Str.split "aaaa" "aa""#,
|
||||
@ -1832,7 +1823,7 @@ fn str_split_overlapping_substring_2() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_walk_utf8_with_index() {
|
||||
#[cfg(not(feature = "gen-llvm-wasm"))]
|
||||
assert_evals_to!(
|
||||
@ -1872,7 +1863,7 @@ fn str_append_scalar() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
||||
fn str_walk_scalars() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1951,7 +1942,7 @@ fn when_on_strings() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn with_capacity() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1965,7 +1956,7 @@ fn with_capacity() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn with_capacity_concat() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -1979,7 +1970,7 @@ fn with_capacity_concat() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn str_with_prefix() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2003,7 +1994,7 @@ fn str_with_prefix() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn destructure_pattern_assigned_from_thunk_opaque() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -2025,7 +2016,7 @@ fn destructure_pattern_assigned_from_thunk_opaque() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn destructure_pattern_assigned_from_thunk_tag() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -2196,3 +2196,42 @@ fn nullable_wrapped_with_nullable_not_last_index() {
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn refcount_nullable_unwrapped_needing_no_refcount_issue_5027() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Effect : {} -> Str
|
||||
|
||||
after = \effect, buildNext ->
|
||||
\{} ->
|
||||
when buildNext (effect {}) is
|
||||
thunk -> thunk {}
|
||||
|
||||
line : Effect
|
||||
line = \{} -> "done"
|
||||
|
||||
await : Effect, (Str -> Effect) -> Effect
|
||||
await = \fx, cont ->
|
||||
after
|
||||
fx
|
||||
cont
|
||||
|
||||
succeed : {} -> Effect
|
||||
succeed = \{} -> (\{} -> "success")
|
||||
|
||||
test =
|
||||
await line \s ->
|
||||
if s == "done" then succeed {} else test
|
||||
|
||||
main = test {}
|
||||
"#
|
||||
),
|
||||
RocStr::from("success"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -1,25 +1,26 @@
|
||||
[package]
|
||||
name = "test_mono"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
description = "Tests Roc's generation of the mono intermediate representation."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[test]]
|
||||
name = "test_mono"
|
||||
path = "src/tests.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_module = { path = "../module", features = ["debug-symbols"] }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_module = { path = "../module", features = ["debug-symbols"] }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_packaging = { path = "../../packaging" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_tracing = { path = "../../tracing" }
|
||||
|
||||
test_mono_macros = { path = "../test_mono_macros" }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user