mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge branch 'main' of github.com:roc-lang/roc into tutorial_updates
This commit is contained in:
commit
fe066e5567
304 changed files with 14284 additions and 20427 deletions
|
@ -6,14 +6,21 @@ test-gen-llvm-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --f
|
|||
|
||||
[target.wasm32-unknown-unknown]
|
||||
# Rust compiler flags for minimum-sized .wasm binary in the web REPL
|
||||
# opt-level=s Optimizations should focus more on size than speed
|
||||
# lto=fat Spend extra effort on link-time optimization across crates
|
||||
rustflags = ["-Copt-level=s", "-Clto=fat"]
|
||||
# opt-level=s Optimizations should focus more on size than speed
|
||||
# lto=fat Spend extra effort on link-time optimization across crates
|
||||
# embed-bitcode=yes Turn back on lto since it is no longer default
|
||||
rustflags = ["-Copt-level=s", "-Clto=fat", "-Cembed-bitcode=yes"]
|
||||
|
||||
[target.'cfg(not(target = "wasm32-unknown-unknown"))']
|
||||
# Sets the avx, avx2, sse2 and sse4.2 target-features correctly based on your CPU.
|
||||
rustflags = ["-Ctarget-cpu=native"]
|
||||
|
||||
# TODO: there is probably a more proper solution to this.
|
||||
# We are pulling in roc_alloc and friends due to using roc_std.
|
||||
# They ared defined in roc_glue, but windows linking breaks before we get there.
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-Clink-args=/FORCE:UNRESOLVED"]
|
||||
|
||||
[env]
|
||||
# Gives us the path of the workspace root for use in cargo tests without having
|
||||
# to compute it per-package.
|
8
.github/workflows/nightly_linux_x86_64.yml
vendored
8
.github/workflows/nightly_linux_x86_64.yml
vendored
|
@ -16,8 +16,8 @@ jobs:
|
|||
- name: create version.txt
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build release
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
|
||||
- name: build release with lto
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
|
||||
|
||||
- name: get commit SHA
|
||||
|
@ -42,6 +42,10 @@ jobs:
|
|||
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-with-lto/roc
|
||||
|
||||
- name: Make nightly release tar archive
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
|
|
|
@ -36,7 +36,11 @@ jobs:
|
|||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build nightly release
|
||||
run: cargo build --locked --release
|
||||
run: cargo build --locked --profile=release-with-lto
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
|
||||
- name: package release
|
||||
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 --profile=release-with-lto --locked
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
|
||||
|
||||
- name: get commit SHA
|
||||
|
@ -37,6 +37,10 @@ jobs:
|
|||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
|
|
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
|
||||
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -73,3 +73,10 @@ result
|
|||
|
||||
# tutorial
|
||||
www/src/roc-tutorial
|
||||
|
||||
# Only keep Cargo.lock dependencies for the main compiler.
|
||||
# Examples and test only crates should be fine to be unlocked.
|
||||
# This remove unneccessary lock file versioning.
|
||||
# It also ensures the compiler can always pull in 1 version of things and doesn't get restricted by sub lockfiles.
|
||||
/**/Cargo.lock
|
||||
!/Cargo.lock
|
1858
Cargo.lock
generated
1858
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
209
Cargo.toml
209
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,120 @@ 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
|
||||
|
||||
# debug = true # enable when profiling
|
||||
[profile.bench]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
lto = "thin"
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
inherits = "release"
|
||||
|
||||
[profile.release-with-lto]
|
||||
lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
|
||||
inherits = "release"
|
||||
|
|
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"
|
||||
is_executable = "1.0.1"
|
||||
ring = "0.16.20"
|
||||
data-encoding = "2.3.2"
|
||||
is_executable = "1.0.1"
|
||||
regex = "1.5.5"
|
||||
ring = "0.16.20"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
|
||||
set -euxo pipefail
|
||||
|
||||
cp target/release/roc ./roc # to be able to delete "target" later
|
||||
cp target/release-with-lto/roc ./roc # to be able to delete "target" later
|
||||
|
||||
# delete unnecessary files and folders
|
||||
git clean -fdx --exclude roc
|
||||
|
|
|
@ -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"
|
||||
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"
|
||||
roc_test_utils = { path = "../test_utils" }
|
||||
roc_command_utils = { path = "../utils/command" }
|
||||
|
||||
criterion.workspace = true
|
||||
indoc.workspace = true
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
serial_test.workspace = true
|
||||
|
||||
[[bench]]
|
||||
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";
|
||||
|
@ -332,6 +330,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
Arg::new(DIRECTORY_OR_FILES)
|
||||
.multiple_values(true)
|
||||
.required(false)
|
||||
.allow_invalid_utf8(true)
|
||||
.help("(optional) The directory or files to open on launch"),
|
||||
),
|
||||
)
|
||||
|
@ -520,7 +519,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",
|
||||
|
@ -862,6 +865,7 @@ mod cli_run {
|
|||
|
||||
#[test]
|
||||
#[serial(parser_package)]
|
||||
#[serial(zig_platform)]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_movies_csv() {
|
||||
test_roc_app_slim(
|
||||
|
|
|
@ -10,13 +10,22 @@ mod editor_launch_test {
|
|||
|
||||
use cli_utils::helpers::build_roc_bin_cached;
|
||||
use roc_cli::CMD_EDIT;
|
||||
use roc_utils::root_dir;
|
||||
use roc_command_utils::root_dir;
|
||||
use std::io::Read;
|
||||
|
||||
// ignored because we don't want to bring up the editor window during regular tests, only on specific CI machines
|
||||
#[ignore]
|
||||
#[ignore = "we don't want to bring up the editor window during regular tests, only on specific CI machines"]
|
||||
#[test]
|
||||
fn launch() {
|
||||
fn launch_test() {
|
||||
launch(None);
|
||||
|
||||
// with a file arg
|
||||
launch(Some("roc-projects/new-roc-project-1/main.roc"));
|
||||
|
||||
// with a folder arg
|
||||
launch(Some("roc-projects/new-roc-project-1"));
|
||||
}
|
||||
|
||||
fn launch(arg_path_str_opt: Option<&str>) {
|
||||
let root_dir = root_dir();
|
||||
|
||||
// The editor expects to be run from the root of the repo, so it can find the cli-platform to init a new project folder.
|
||||
|
@ -25,8 +34,14 @@ mod editor_launch_test {
|
|||
|
||||
let roc_binary_path = build_roc_bin_cached();
|
||||
|
||||
let mut cmd_args = vec![CMD_EDIT];
|
||||
|
||||
if let Some(arg_path_str) = arg_path_str_opt {
|
||||
cmd_args.push(arg_path_str)
|
||||
}
|
||||
|
||||
let mut roc_process = Command::new(roc_binary_path)
|
||||
.arg(CMD_EDIT)
|
||||
.args(cmd_args)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start editor from cli.");
|
||||
|
|
|
@ -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_utils = { path = "../utils" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_command_utils = { path = "../utils/command" }
|
||||
|
||||
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
|
||||
|
|
|
@ -4,9 +4,7 @@ extern crate roc_load;
|
|||
extern crate roc_module;
|
||||
extern crate tempfile;
|
||||
|
||||
use roc_utils::cargo;
|
||||
use roc_utils::pretty_command_string;
|
||||
use roc_utils::root_dir;
|
||||
use roc_command_utils::{cargo, pretty_command_string, root_dir};
|
||||
use serde::Deserialize;
|
||||
use serde_xml_rs::from_str;
|
||||
use std::env;
|
||||
|
@ -387,7 +385,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 +394,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 +417,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"] }
|
||||
roc_error_utils = { path = "../utils/error" }
|
||||
|
||||
palette.workspace = true
|
||||
|
||||
bumpalo.workspace = true
|
||||
serde.workspace = true
|
||||
snafu.workspace = true
|
||||
|
|
|
@ -16,7 +16,7 @@ use roc_ast::{
|
|||
lang::{core::ast::ASTNodeId, env::Env},
|
||||
mem_pool::pool_str::PoolStr,
|
||||
};
|
||||
use roc_utils::{index_of, slice_get};
|
||||
use roc_error_utils::{index_of, slice_get};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use roc_utils::util_error::UtilError;
|
||||
use roc_error_utils::UtilError;
|
||||
use snafu::{Backtrace, NoneError, ResultExt, Snafu};
|
||||
|
||||
use crate::slow_pool::MarkNodeId;
|
||||
|
|
|
@ -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_bitcode = { path = "../builtins/bitcode" }
|
||||
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_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_utils = { path = "../../utils" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_unify = { path = "../unify" }
|
||||
roc_command_utils = { path = "../../utils/command" }
|
||||
|
||||
wasi_libc_sys = { path = "../../wasi-libc-sys" }
|
||||
|
||||
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,11 +1,8 @@
|
|||
use crate::target::{arch_str, target_zig_str};
|
||||
use const_format::concatcp;
|
||||
use libloading::{Error, Library};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_command_utils::{cargo, clang, get_lib_path, rustup, zig};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_utils::{cargo, clang, zig};
|
||||
use roc_utils::{get_lib_path, rustup};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::DirEntry;
|
||||
use std::io;
|
||||
|
@ -15,13 +12,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 +52,15 @@ pub fn link(
|
|||
}
|
||||
}
|
||||
|
||||
const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future)
|
||||
|
||||
const WASM_TARGET_STR: &str = "wasm32";
|
||||
const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64";
|
||||
const LINUX_ARM64_TARGET_STR: &str = "linux-arm64";
|
||||
const MACOS_ARM64_TARGET_STR: &str = "macos-arm64";
|
||||
const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64";
|
||||
const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64";
|
||||
const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32";
|
||||
const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64";
|
||||
|
||||
pub const fn preprocessed_host_filename(target: &Triple) -> Option<&'static str> {
|
||||
// Don't try to split this match off in a different function, it will not work with concatcp
|
||||
match target {
|
||||
Triple {
|
||||
architecture: Architecture::Wasm32,
|
||||
..
|
||||
} => Some(concatcp!(WASM_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Linux,
|
||||
architecture: Architecture::X86_64,
|
||||
..
|
||||
} => Some(concatcp!(
|
||||
LINUX_X86_64_TARGET_STR,
|
||||
'.',
|
||||
PRECOMPILED_HOST_EXT
|
||||
)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Linux,
|
||||
architecture: Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(concatcp!(LINUX_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
architecture: Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(concatcp!(MACOS_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
architecture: Architecture::X86_64,
|
||||
..
|
||||
} => Some(concatcp!(
|
||||
MACOS_X86_64_TARGET_STR,
|
||||
'.',
|
||||
PRECOMPILED_HOST_EXT
|
||||
)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
architecture: Architecture::X86_64,
|
||||
..
|
||||
} => Some(concatcp!(
|
||||
WINDOWS_X86_64_TARGET_STR,
|
||||
'.',
|
||||
PRECOMPILED_HOST_EXT
|
||||
)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
architecture: Architecture::X86_32(_),
|
||||
..
|
||||
} => Some(concatcp!(
|
||||
WINDOWS_X86_32_TARGET_STR,
|
||||
'.',
|
||||
PRECOMPILED_HOST_EXT
|
||||
)),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
architecture: Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(concatcp!(
|
||||
WIDNOWS_ARM64_TARGET_STR,
|
||||
'.',
|
||||
PRECOMPILED_HOST_EXT
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_target_triple_str(target: &Triple) -> Option<&'static str> {
|
||||
match target {
|
||||
Triple {
|
||||
architecture: Architecture::Wasm32,
|
||||
..
|
||||
} => Some(WASM_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Linux,
|
||||
architecture: Architecture::X86_64,
|
||||
..
|
||||
} => Some(LINUX_X86_64_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Linux,
|
||||
architecture: Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(LINUX_ARM64_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
architecture: Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(MACOS_ARM64_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
architecture: Architecture::X86_64,
|
||||
..
|
||||
} => Some(MACOS_X86_64_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
architecture: Architecture::X86_64,
|
||||
..
|
||||
} => Some(WINDOWS_X86_64_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
architecture: Architecture::X86_32(_),
|
||||
..
|
||||
} => Some(WINDOWS_X86_32_TARGET_STR),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
architecture: Architecture::Aarch64(_),
|
||||
..
|
||||
} => Some(WIDNOWS_ARM64_TARGET_STR),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
|
||||
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 {
|
||||
|
@ -682,7 +554,7 @@ pub fn rebuild_host(
|
|||
let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string());
|
||||
|
||||
let builtins_host_tempfile =
|
||||
bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
|
||||
roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
|
||||
|
||||
if zig_host_src.exists() {
|
||||
// Compile host.zig
|
||||
|
@ -1561,8 +1433,8 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
|
|||
(but seems to be an unofficial API)
|
||||
*/
|
||||
|
||||
let builtins_host_tempfile =
|
||||
bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile");
|
||||
let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile()
|
||||
.expect("failed to write host builtins object to tempfile");
|
||||
|
||||
let mut zig_cmd = zig();
|
||||
let args = &[
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
use crate::link::{
|
||||
legacy_host_filename, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use 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 +595,627 @@ fn gen_from_mono_module_dev_assembly<'a>(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
||||
use std::fmt::Write;
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
" {:9.3} ms {}",
|
||||
duration.as_secs_f64() * 1000.0,
|
||||
label,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub struct BuiltFile<'a> {
|
||||
pub binary_path: PathBuf,
|
||||
pub problems: Problems,
|
||||
pub total_time: Duration,
|
||||
pub expect_metadata: ExpectMetadata<'a>,
|
||||
}
|
||||
|
||||
pub enum BuildOrdering {
|
||||
/// Run up through typechecking first; continue building iff that is successful.
|
||||
BuildIfChecks,
|
||||
/// Always build the Roc binary, even if there are type errors.
|
||||
AlwaysBuild,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum BuildFileError<'a> {
|
||||
LoadingProblem(LoadingProblem<'a>),
|
||||
ErrorModule {
|
||||
module: LoadedModule,
|
||||
total_time: Duration,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> BuildFileError<'a> {
|
||||
fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self {
|
||||
match error {
|
||||
LoadMonomorphizedError::LoadingProblem(problem) => {
|
||||
BuildFileError::LoadingProblem(problem)
|
||||
}
|
||||
LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule {
|
||||
module,
|
||||
total_time: compilation_start.elapsed(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn standard_load_config(
|
||||
target: &Triple,
|
||||
order: BuildOrdering,
|
||||
threading: Threading,
|
||||
) -> LoadConfig {
|
||||
let target_info = TargetInfo::from(target);
|
||||
|
||||
let exec_mode = match order {
|
||||
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
|
||||
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
|
||||
};
|
||||
|
||||
LoadConfig {
|
||||
target_info,
|
||||
render: RenderTarget::ColorTerminal,
|
||||
palette: DEFAULT_PALETTE,
|
||||
threading,
|
||||
exec_mode,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_file<'a>(
|
||||
arena: &'a Bump,
|
||||
target: &Triple,
|
||||
app_module_path: PathBuf,
|
||||
code_gen_options: CodeGenOptions,
|
||||
emit_timings: bool,
|
||||
link_type: LinkType,
|
||||
linking_strategy: LinkingStrategy,
|
||||
prebuilt_requested: bool,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||
let compilation_start = Instant::now();
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let loaded =
|
||||
roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
|
||||
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
|
||||
|
||||
build_loaded_file(
|
||||
arena,
|
||||
target,
|
||||
app_module_path,
|
||||
code_gen_options,
|
||||
emit_timings,
|
||||
link_type,
|
||||
linking_strategy,
|
||||
prebuilt_requested,
|
||||
wasm_dev_stack_bytes,
|
||||
loaded,
|
||||
compilation_start,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_loaded_file<'a>(
|
||||
arena: &'a Bump,
|
||||
target: &Triple,
|
||||
app_module_path: PathBuf,
|
||||
code_gen_options: CodeGenOptions,
|
||||
emit_timings: bool,
|
||||
link_type: LinkType,
|
||||
linking_strategy: LinkingStrategy,
|
||||
prebuilt_requested: bool,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
loaded: roc_load::MonomorphizedModule<'a>,
|
||||
compilation_start: Instant,
|
||||
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||
let operating_system = roc_target::OperatingSystem::from(target.operating_system);
|
||||
|
||||
let platform_main_roc = match &loaded.entry_point {
|
||||
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// the preprocessed host is stored beside the platform's main.roc
|
||||
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
|
||||
if let roc_target::OperatingSystem::Wasi = operating_system {
|
||||
// when compiling a wasm application, we implicitly assume here that the host is in zig
|
||||
// and has a file called "host.zig"
|
||||
platform_main_roc.with_file_name("host.zig")
|
||||
} else {
|
||||
platform_main_roc.with_file_name(legacy_host_filename(target).unwrap())
|
||||
}
|
||||
} else {
|
||||
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
|
||||
};
|
||||
|
||||
// For example, if we're loading the platform from a URL, it's automatically prebuilt
|
||||
// even if the --prebuilt-platform=true CLI flag wasn't set.
|
||||
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
|
||||
|
||||
let cwd = app_module_path.parent().unwrap();
|
||||
let mut output_exe_path = cwd.join(&*loaded.output_path);
|
||||
|
||||
if let Some(extension) = operating_system.executable_file_ext() {
|
||||
output_exe_path.set_extension(extension);
|
||||
}
|
||||
|
||||
// We don't need to spawn a rebuild thread when using a prebuilt host.
|
||||
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
|
||||
None
|
||||
} else if is_platform_prebuilt {
|
||||
if !preprocessed_host_path.exists() {
|
||||
invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if linking_strategy == LinkingStrategy::Surgical {
|
||||
// Copy preprocessed host to executable location.
|
||||
// The surgical linker will modify that copy in-place.
|
||||
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
|
||||
}
|
||||
|
||||
None
|
||||
} else {
|
||||
// TODO this should probably be moved before load_and_monomorphize.
|
||||
// To do this we will need to preprocess files just for their exported symbols.
|
||||
// Also, we should no longer need to do this once we have platforms on
|
||||
// a package repository, as we can then get prebuilt platforms from there.
|
||||
|
||||
let exposed_values = loaded
|
||||
.exposed_to_host
|
||||
.values
|
||||
.keys()
|
||||
.map(|x| x.as_str(&loaded.interns).to_string())
|
||||
.collect();
|
||||
|
||||
let exposed_closure_types = loaded
|
||||
.exposed_to_host
|
||||
.closure_types
|
||||
.iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}_{}",
|
||||
x.module_string(&loaded.interns),
|
||||
x.as_str(&loaded.interns)
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let join_handle = spawn_rebuild_thread(
|
||||
code_gen_options.opt_level,
|
||||
linking_strategy,
|
||||
platform_main_roc.clone(),
|
||||
preprocessed_host_path.clone(),
|
||||
output_exe_path.clone(),
|
||||
target,
|
||||
exposed_values,
|
||||
exposed_closure_types,
|
||||
);
|
||||
|
||||
Some(join_handle)
|
||||
};
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
||||
let mut it = loaded.timings.iter().peekable();
|
||||
while let Some((module_id, module_timing)) = it.next() {
|
||||
let module_name = loaded.interns.module_name(*module_id);
|
||||
|
||||
buf.push_str(" ");
|
||||
|
||||
if module_name.is_empty() {
|
||||
// the App module
|
||||
buf.push_str("Application Module");
|
||||
} else {
|
||||
buf.push_str(module_name);
|
||||
}
|
||||
|
||||
buf.push('\n');
|
||||
|
||||
use std::fmt::Write;
|
||||
write!(buf, "{}", module_timing).unwrap();
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// This only needs to be mutable for report_problems. This can't be done
|
||||
// inside a nested scope without causing a borrow error!
|
||||
let mut loaded = loaded;
|
||||
let problems = report_problems_monomorphized(&mut loaded);
|
||||
let loaded = loaded;
|
||||
|
||||
enum HostRebuildTiming {
|
||||
BeforeApp(u128),
|
||||
ConcurrentWithApp(JoinHandle<u128>),
|
||||
}
|
||||
|
||||
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
|
||||
if linking_strategy == LinkingStrategy::Additive {
|
||||
let rebuild_duration = rebuild_thread
|
||||
.join()
|
||||
.expect("Failed to (re)build platform.");
|
||||
|
||||
if emit_timings && !is_platform_prebuilt {
|
||||
println!(
|
||||
"Finished rebuilding the platform in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
}
|
||||
|
||||
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
|
||||
} else {
|
||||
Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module(
|
||||
arena,
|
||||
loaded,
|
||||
&app_module_path,
|
||||
target,
|
||||
code_gen_options,
|
||||
&preprocessed_host_path,
|
||||
wasm_dev_stack_bytes,
|
||||
);
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(" ");
|
||||
buf.push_str("Code Generation");
|
||||
buf.push('\n');
|
||||
|
||||
report_timing(
|
||||
buf,
|
||||
"Generate Assembly from Mono IR",
|
||||
code_gen_timing.code_gen,
|
||||
);
|
||||
|
||||
let compilation_end = compilation_start.elapsed();
|
||||
let size = roc_app_bytes.len();
|
||||
|
||||
if emit_timings {
|
||||
println!(
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
||||
buf
|
||||
);
|
||||
|
||||
println!(
|
||||
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
|
||||
compilation_end.as_millis(),
|
||||
size,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
|
||||
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
|
||||
|
||||
if emit_timings && !is_platform_prebuilt {
|
||||
println!(
|
||||
"Finished rebuilding the platform in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: link the prebuilt platform and compiled app
|
||||
let link_start = Instant::now();
|
||||
|
||||
match (linking_strategy, link_type) {
|
||||
(LinkingStrategy::Surgical, _) => {
|
||||
roc_linker::link_preprocessed_host(
|
||||
target,
|
||||
&platform_main_roc,
|
||||
&roc_app_bytes,
|
||||
&output_exe_path,
|
||||
);
|
||||
}
|
||||
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
|
||||
// Just copy the object file to the output folder.
|
||||
output_exe_path.set_extension(operating_system.object_file_ext());
|
||||
std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
|
||||
}
|
||||
(LinkingStrategy::Legacy, _) => {
|
||||
let app_o_file = tempfile::Builder::new()
|
||||
.prefix("roc_app")
|
||||
.suffix(&format!(".{}", operating_system.object_file_ext()))
|
||||
.tempfile()
|
||||
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
|
||||
let app_o_file = app_o_file.path();
|
||||
|
||||
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
|
||||
|
||||
let builtins_host_tempfile = roc_bitcode::host_tempfile()
|
||||
.expect("failed to write host builtins object to tempfile");
|
||||
|
||||
let mut inputs = vec![app_o_file.to_str().unwrap()];
|
||||
|
||||
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
|
||||
// the host has been compiled into a .o or .obj file
|
||||
inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
|
||||
}
|
||||
|
||||
if matches!(code_gen_options.backend, CodeGenBackend::Assembly) {
|
||||
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
|
||||
}
|
||||
|
||||
let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
|
||||
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
|
||||
|
||||
let exit_status = child
|
||||
.wait()
|
||||
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
|
||||
|
||||
// Extend the lifetime of the tempfile so it doesn't get dropped
|
||||
// (and thus deleted) before the child process is done using it!
|
||||
let _ = builtins_host_tempfile;
|
||||
|
||||
if !exit_status.success() {
|
||||
todo!(
|
||||
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
|
||||
exit_status.code()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let linking_time = link_start.elapsed();
|
||||
|
||||
if emit_timings {
|
||||
println!("Finished linking in {} ms\n", linking_time.as_millis());
|
||||
}
|
||||
|
||||
let total_time = compilation_start.elapsed();
|
||||
|
||||
Ok(BuiltFile {
|
||||
binary_path: output_exe_path,
|
||||
problems,
|
||||
total_time,
|
||||
expect_metadata,
|
||||
})
|
||||
}
|
||||
|
||||
fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
|
||||
let prefix = match prebuilt_requested {
|
||||
true => "Because I was run with --prebuilt-platform=true, ",
|
||||
false => "",
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
indoc::indoc!(
|
||||
r#"
|
||||
{}I was expecting this file to exist:
|
||||
|
||||
{}
|
||||
|
||||
However, it was not there!
|
||||
|
||||
If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
|
||||
"#
|
||||
),
|
||||
prefix,
|
||||
preprocessed_host_path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn spawn_rebuild_thread(
|
||||
opt_level: OptLevel,
|
||||
linking_strategy: LinkingStrategy,
|
||||
platform_main_roc: PathBuf,
|
||||
preprocessed_host_path: PathBuf,
|
||||
output_exe_path: PathBuf,
|
||||
target: &Triple,
|
||||
exported_symbols: Vec<String>,
|
||||
exported_closure_types: Vec<String>,
|
||||
) -> std::thread::JoinHandle<u128> {
|
||||
let thread_local_target = target.clone();
|
||||
std::thread::spawn(move || {
|
||||
// Printing to stderr because we want stdout to contain only the output of the roc program.
|
||||
// We are aware of the trade-offs.
|
||||
// `cargo run` follows the same approach
|
||||
eprintln!("🔨 Rebuilding platform...");
|
||||
|
||||
let rebuild_host_start = Instant::now();
|
||||
|
||||
match linking_strategy {
|
||||
LinkingStrategy::Additive => {
|
||||
let host_dest = rebuild_host(
|
||||
opt_level,
|
||||
&thread_local_target,
|
||||
platform_main_roc.as_path(),
|
||||
None,
|
||||
);
|
||||
|
||||
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
|
||||
}
|
||||
LinkingStrategy::Surgical => {
|
||||
build_and_preprocess_host(
|
||||
opt_level,
|
||||
&thread_local_target,
|
||||
platform_main_roc.as_path(),
|
||||
preprocessed_host_path.as_path(),
|
||||
exported_symbols,
|
||||
exported_closure_types,
|
||||
);
|
||||
|
||||
// Copy preprocessed host to executable location.
|
||||
// The surgical linker will modify that copy in-place.
|
||||
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
|
||||
}
|
||||
LinkingStrategy::Legacy => {
|
||||
rebuild_host(
|
||||
opt_level,
|
||||
&thread_local_target,
|
||||
platform_main_roc.as_path(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
rebuild_host_start.elapsed().as_millis()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_and_preprocess_host(
|
||||
opt_level: OptLevel,
|
||||
target: &Triple,
|
||||
platform_main_roc: &Path,
|
||||
preprocessed_host_path: &Path,
|
||||
exposed_to_host: Vec<String>,
|
||||
exported_closure_types: Vec<String>,
|
||||
) {
|
||||
let (stub_lib, stub_dll_symbols) = roc_linker::generate_stub_lib_from_loaded(
|
||||
target,
|
||||
platform_main_roc,
|
||||
exposed_to_host,
|
||||
exported_closure_types,
|
||||
);
|
||||
rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
|
||||
|
||||
roc_linker::preprocess_host(
|
||||
target,
|
||||
platform_main_roc,
|
||||
preprocessed_host_path,
|
||||
&stub_lib,
|
||||
&stub_dll_symbols,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_file<'a>(
|
||||
arena: &'a Bump,
|
||||
roc_file_path: PathBuf,
|
||||
emit_timings: bool,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
threading: Threading,
|
||||
) -> Result<(Problems, Duration), LoadingProblem<'a>> {
|
||||
let compilation_start = Instant::now();
|
||||
|
||||
// only used for generating errors. We don't do code generation, so hardcoding should be fine
|
||||
// we need monomorphization for when exhaustiveness checking
|
||||
let target_info = TargetInfo::default_x86_64();
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
render: RenderTarget::ColorTerminal,
|
||||
palette: DEFAULT_PALETTE,
|
||||
threading,
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
let mut loaded =
|
||||
roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
||||
let mut it = loaded.timings.iter().peekable();
|
||||
while let Some((module_id, module_timing)) = it.next() {
|
||||
let module_name = loaded.interns.module_name(*module_id);
|
||||
|
||||
buf.push_str(" ");
|
||||
|
||||
if module_name.is_empty() {
|
||||
// the App module
|
||||
buf.push_str("Application Module");
|
||||
} else {
|
||||
buf.push_str(module_name);
|
||||
}
|
||||
|
||||
buf.push('\n');
|
||||
|
||||
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
|
||||
report_timing(buf, "Parse header", module_timing.parse_header);
|
||||
report_timing(buf, "Parse body", module_timing.parse_body);
|
||||
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
||||
report_timing(buf, "Constrain", module_timing.constrain);
|
||||
report_timing(buf, "Solve", module_timing.solve);
|
||||
report_timing(buf, "Other", module_timing.other());
|
||||
buf.push('\n');
|
||||
report_timing(buf, "Total", module_timing.total());
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
let compilation_end = compilation_start.elapsed();
|
||||
|
||||
if emit_timings {
|
||||
println!(
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
||||
buf
|
||||
);
|
||||
|
||||
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
|
||||
}
|
||||
|
||||
Ok((report_problems_typechecked(&mut loaded), compilation_end))
|
||||
}
|
||||
|
||||
pub fn build_str_test<'a>(
|
||||
arena: &'a Bump,
|
||||
app_module_path: &Path,
|
||||
app_module_source: &'a str,
|
||||
assume_prebuild: bool,
|
||||
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||
let triple = target_lexicon::Triple::host();
|
||||
|
||||
let code_gen_options = CodeGenOptions {
|
||||
backend: CodeGenBackend::Llvm,
|
||||
opt_level: OptLevel::Normal,
|
||||
emit_debug_info: false,
|
||||
};
|
||||
|
||||
let emit_timings = false;
|
||||
let link_type = LinkType::Executable;
|
||||
let linking_strategy = LinkingStrategy::Surgical;
|
||||
let wasm_dev_stack_bytes = None;
|
||||
|
||||
let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
|
||||
let build_ordering = BuildOrdering::AlwaysBuild;
|
||||
let threading = Threading::AtMost(2);
|
||||
|
||||
let load_config = standard_load_config(&triple, build_ordering, threading);
|
||||
|
||||
let compilation_start = std::time::Instant::now();
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
PathBuf::from("valgrind_test.roc"),
|
||||
app_module_source,
|
||||
app_module_path.to_path_buf(),
|
||||
roc_cache_dir,
|
||||
load_config,
|
||||
)
|
||||
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
|
||||
|
||||
build_loaded_file(
|
||||
arena,
|
||||
&triple,
|
||||
app_module_path.to_path_buf(),
|
||||
code_gen_options,
|
||||
emit_timings,
|
||||
link_type,
|
||||
linking_strategy,
|
||||
assume_prebuild,
|
||||
wasm_dev_stack_bytes,
|
||||
loaded,
|
||||
compilation_start,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
[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]
|
||||
# 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
|
||||
|
|
19
crates/compiler/builtins/bitcode/Cargo.toml
Normal file
19
crates/compiler/builtins/bitcode/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "roc_bitcode"
|
||||
description = "Compiles the zig bitcode to `.o` for builtins"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tempfile.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
roc_command_utils = { path = "../../../utils/command" }
|
||||
# dunce can be removed once ziglang/zig#5109 is fixed
|
||||
dunce = "1.0.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||
tempfile.workspace = true
|
16
crates/compiler/builtins/bitcode/bc/Cargo.toml
Normal file
16
crates/compiler/builtins/bitcode/bc/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "roc_bitcode_bc"
|
||||
description = "Compiles the zig bitcode to `.bc` for llvm"
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
roc_command_utils = { path = "../../../../utils/command" }
|
||||
# dunce can be removed once ziglang/zig#5109 is fixed
|
||||
dunce = "1.0.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||
tempfile.workspace = true
|
154
crates/compiler/builtins/bitcode/bc/build.rs
Normal file
154
crates/compiler/builtins/bitcode/bc/build.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use roc_command_utils::{pretty_command_string, zig};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
use std::{env, path::PathBuf, process::Command};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// To debug the zig code with debug prints, we need to disable the wasm code gen
|
||||
const DEBUG: bool = false;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// "." is relative to where "build.rs" is
|
||||
// dunce can be removed once ziglang/zig#5109 is fixed
|
||||
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap().join("..");
|
||||
|
||||
// workaround for github.com/ziglang/zig/issues/9711
|
||||
#[cfg(target_os = "macos")]
|
||||
let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache");
|
||||
#[cfg(target_os = "macos")]
|
||||
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
|
||||
|
||||
// LLVM .bc FILES
|
||||
|
||||
generate_bc_file(&bitcode_path, "ir", "builtins-host");
|
||||
|
||||
if !DEBUG {
|
||||
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
|
||||
}
|
||||
|
||||
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
|
||||
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
|
||||
generate_bc_file(
|
||||
&bitcode_path,
|
||||
"ir-windows-x86_64",
|
||||
"builtins-windows-x86_64",
|
||||
);
|
||||
|
||||
get_zig_files(bitcode_path.as_path(), &|path| {
|
||||
let path: &Path = path;
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
path.to_str().expect("Failed to convert path to str")
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
zig_cache_dir
|
||||
.close()
|
||||
.expect("Failed to delete temp dir zig_cache_dir.");
|
||||
}
|
||||
|
||||
fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
||||
let mut ll_path = bitcode_path.join(file_name);
|
||||
ll_path.set_extension("ll");
|
||||
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
|
||||
|
||||
println!("Compiling host ir to: {}", dest_ir_host);
|
||||
|
||||
let mut bc_path = bitcode_path.join(file_name);
|
||||
bc_path.set_extension("bc");
|
||||
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
|
||||
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
|
||||
|
||||
// workaround for github.com/ziglang/zig/issues/9711
|
||||
#[cfg(target_os = "macos")]
|
||||
let _ = fs::remove_dir_all("./zig-cache");
|
||||
|
||||
let mut zig_cmd = zig();
|
||||
|
||||
zig_cmd
|
||||
.current_dir(bitcode_path)
|
||||
.args(["build", zig_object, "-Drelease=true"]);
|
||||
|
||||
run_command(zig_cmd, 0);
|
||||
}
|
||||
|
||||
pub fn get_lib_dir() -> PathBuf {
|
||||
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
|
||||
// So we just need to add "/bitcode" to that.
|
||||
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
|
||||
// create dir if it does not exist
|
||||
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
|
||||
|
||||
dir
|
||||
}
|
||||
|
||||
fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
||||
let command_str = pretty_command_string(&command);
|
||||
let command_str = command_str.to_string_lossy();
|
||||
|
||||
let output_result = command.output();
|
||||
|
||||
match output_result {
|
||||
Ok(output) => match output.status.success() {
|
||||
true => (),
|
||||
false => {
|
||||
let error_str = match str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => stderr.to_string(),
|
||||
Err(_) => format!("Failed to run \"{}\"", command_str),
|
||||
};
|
||||
|
||||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
if error_str.contains("FileNotFound")
|
||||
|| error_str.contains("unable to save cached ZIR code")
|
||||
|| error_str.contains("LLVM failed to emit asm")
|
||||
{
|
||||
if flaky_fail_counter == 10 {
|
||||
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
|
||||
} else {
|
||||
run_command(command, flaky_fail_counter + 1)
|
||||
}
|
||||
} else if error_str
|
||||
.contains("lld-link: error: failed to write the output file: Permission denied")
|
||||
{
|
||||
panic!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str);
|
||||
} else {
|
||||
panic!("{} failed with:\n\n {}\n", command_str, error_str);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(reason) => panic!("{} failed: {}", command_str, reason),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
|
||||
if dir.is_dir() {
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path_buf = entry.path();
|
||||
if path_buf.is_dir() {
|
||||
if !path_buf.ends_with("zig-cache") {
|
||||
get_zig_files(&path_buf, cb).unwrap();
|
||||
}
|
||||
} else {
|
||||
let path = path_buf.as_path();
|
||||
|
||||
match path.extension() {
|
||||
Some(osstr) if osstr == "zig" => {
|
||||
cb(path);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
1
crates/compiler/builtins/bitcode/bc/src/lib.rs
Normal file
1
crates/compiler/builtins/bitcode/bc/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use roc_utils::zig;
|
||||
use std::env;
|
||||
use roc_command_utils::{pretty_command_string, zig};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
use std::{env, path::PathBuf, process::Command};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tempfile::tempdir;
|
||||
|
@ -18,8 +16,7 @@ fn main() {
|
|||
|
||||
// "." is relative to where "build.rs" is
|
||||
// dunce can be removed once ziglang/zig#5109 is fixed
|
||||
let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap();
|
||||
let bitcode_path = build_script_dir_path.join("bitcode");
|
||||
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap();
|
||||
|
||||
// workaround for github.com/ziglang/zig/issues/9711
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -27,22 +24,6 @@ fn main() {
|
|||
#[cfg(target_os = "macos")]
|
||||
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
|
||||
|
||||
// LLVM .bc FILES
|
||||
|
||||
generate_bc_file(&bitcode_path, "ir", "builtins-host");
|
||||
|
||||
if !DEBUG {
|
||||
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
|
||||
}
|
||||
|
||||
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
|
||||
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
|
||||
generate_bc_file(
|
||||
&bitcode_path,
|
||||
"ir-windows-x86_64",
|
||||
"builtins-windows-x86_64",
|
||||
);
|
||||
|
||||
// OBJECT FILES
|
||||
#[cfg(windows)]
|
||||
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
|
||||
|
@ -107,38 +88,13 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
||||
let mut ll_path = bitcode_path.join(file_name);
|
||||
ll_path.set_extension("ll");
|
||||
let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
|
||||
|
||||
println!("Compiling host ir to: {}", dest_ir_host);
|
||||
|
||||
let mut bc_path = bitcode_path.join(file_name);
|
||||
bc_path.set_extension("bc");
|
||||
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
|
||||
println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
|
||||
|
||||
// workaround for github.com/ziglang/zig/issues/9711
|
||||
#[cfg(target_os = "macos")]
|
||||
let _ = fs::remove_dir_all("./bitcode/zig-cache");
|
||||
|
||||
let mut zig_cmd = zig();
|
||||
|
||||
zig_cmd
|
||||
.current_dir(bitcode_path)
|
||||
.args(["build", zig_object, "-Drelease=true"]);
|
||||
|
||||
run_command(zig_cmd, 0);
|
||||
}
|
||||
|
||||
pub fn get_lib_dir() -> PathBuf {
|
||||
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
|
||||
// So we just need to add "/bitcode" to that.
|
||||
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bitcode");
|
||||
let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
|
||||
// create dir if it does not exist
|
||||
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/bitcode dir.");
|
||||
fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
|
||||
|
||||
dir
|
||||
}
|
||||
|
@ -192,7 +148,7 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
|
|||
}
|
||||
|
||||
fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
||||
let command_str = roc_utils::pretty_command_string(&command);
|
||||
let command_str = pretty_command_string(&command);
|
||||
let command_str = command_str.to_string_lossy();
|
||||
|
||||
let output_result = command.output();
|
65
crates/compiler/builtins/bitcode/src/lib.rs
Normal file
65
crates/compiler/builtins/bitcode/src/lib.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use tempfile::NamedTempFile;
|
||||
|
||||
const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-wasm32.o"));
|
||||
// TODO: in the future, we should use Zig's cross-compilation to generate and store these
|
||||
// for all targets, so that we can do cross-compilation!
|
||||
#[cfg(unix)]
|
||||
const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-host.o"));
|
||||
#[cfg(windows)]
|
||||
const HOST_WINDOWS: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/builtins-windows-x86_64.obj"));
|
||||
|
||||
pub fn host_wasm_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
let tempfile = tempfile::Builder::new()
|
||||
.prefix("host_bitcode")
|
||||
.suffix(".wasm")
|
||||
.rand_bytes(8)
|
||||
.tempfile()?;
|
||||
|
||||
std::fs::write(tempfile.path(), HOST_WASM)?;
|
||||
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn host_unix_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
let tempfile = tempfile::Builder::new()
|
||||
.prefix("host_bitcode")
|
||||
.suffix(".o")
|
||||
.rand_bytes(8)
|
||||
.tempfile()?;
|
||||
|
||||
std::fs::write(tempfile.path(), HOST_UNIX)?;
|
||||
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn host_windows_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
let tempfile = tempfile::Builder::new()
|
||||
.prefix("host_bitcode")
|
||||
.suffix(".obj")
|
||||
.rand_bytes(8)
|
||||
.tempfile()?;
|
||||
|
||||
std::fs::write(tempfile.path(), HOST_WINDOWS)?;
|
||||
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
pub fn host_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
host_unix_tempfile()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
host_windows_tempfile()
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
{
|
||||
unreachable!()
|
||||
}
|
||||
}
|
|
@ -67,6 +67,8 @@ const NUMBERS = INTEGERS ++ FLOATS;
|
|||
comptime {
|
||||
exportNumFn(num.bytesToU16C, "bytes_to_u16");
|
||||
exportNumFn(num.bytesToU32C, "bytes_to_u32");
|
||||
exportNumFn(num.bytesToU64C, "bytes_to_u64");
|
||||
exportNumFn(num.bytesToU128C, "bytes_to_u128");
|
||||
|
||||
inline for (INTEGERS) |T, i| {
|
||||
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
|
||||
|
@ -86,6 +88,10 @@ comptime {
|
|||
num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
|
||||
num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic.");
|
||||
num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated.");
|
||||
|
||||
num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits.");
|
||||
num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits.");
|
||||
num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits.");
|
||||
}
|
||||
|
||||
inline for (INTEGERS) |FROM| {
|
||||
|
|
|
@ -236,6 +236,24 @@ fn bytesToU32(arg: RocList, position: usize) u32 {
|
|||
return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] });
|
||||
}
|
||||
|
||||
pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 {
|
||||
return @call(.{ .modifier = always_inline }, bytesToU64, .{ arg, position });
|
||||
}
|
||||
|
||||
fn bytesToU64(arg: RocList, position: usize) u64 {
|
||||
const bytes = @ptrCast([*]const u8, arg.bytes);
|
||||
return @bitCast(u64, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] });
|
||||
}
|
||||
|
||||
pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 {
|
||||
return @call(.{ .modifier = always_inline }, bytesToU128, .{ arg, position });
|
||||
}
|
||||
|
||||
fn bytesToU128(arg: RocList, position: usize) u128 {
|
||||
const bytes = @ptrCast([*]const u8, arg.bytes);
|
||||
return @bitCast(u128, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] });
|
||||
}
|
||||
|
||||
fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
|
||||
switch (@typeInfo(T)) {
|
||||
.Int => {
|
||||
|
@ -460,3 +478,30 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
|
|||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(self: T) callconv(.C) usize {
|
||||
return @as(usize, @clz(T, self));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(self: T) callconv(.C) usize {
|
||||
return @as(usize, @ctz(T, self));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(self: T) callconv(.C) usize {
|
||||
return @as(usize, @popCount(T, self));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
#[cfg(test)]
|
||||
mod bitcode {
|
||||
use roc_builtins_bitcode::{count_segments_, str_split_};
|
||||
|
||||
#[test]
|
||||
fn count_segments() {
|
||||
assert_eq!(
|
||||
count_segments_((&"hello there").as_bytes(), (&"hello").as_bytes()),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
count_segments_((&"a\nb\nc").as_bytes(), (&"\n").as_bytes()),
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
count_segments_((&"str").as_bytes(), (&"delimiter").as_bytes()),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_split() {
|
||||
fn splits_to(string: &str, delimiter: &str, expectation: &[&[u8]]) {
|
||||
assert_eq!(
|
||||
str_split_(
|
||||
&mut [(&"").as_bytes()].repeat(expectation.len()),
|
||||
&string.as_bytes(),
|
||||
&delimiter.as_bytes()
|
||||
),
|
||||
expectation
|
||||
);
|
||||
}
|
||||
|
||||
splits_to(
|
||||
"a!b!c",
|
||||
"!",
|
||||
&[(&"a").as_bytes(), (&"b").as_bytes(), (&"c").as_bytes()],
|
||||
);
|
||||
|
||||
splits_to(
|
||||
"a!?b!?c!?",
|
||||
"!?",
|
||||
&[
|
||||
(&"a").as_bytes(),
|
||||
(&"b").as_bytes(),
|
||||
(&"c").as_bytes(),
|
||||
(&"").as_bytes(),
|
||||
],
|
||||
);
|
||||
|
||||
splits_to("abc", "!", &[(&"abc").as_bytes()]);
|
||||
|
||||
splits_to(
|
||||
"tttttghittttt",
|
||||
"ttttt",
|
||||
&[(&"").as_bytes(), (&"ghi").as_bytes(), (&"").as_bytes()],
|
||||
);
|
||||
|
||||
splits_to("def", "!!!!!!", &[(&"def").as_bytes()]);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -191,16 +191,42 @@ encodeTag = \name, payload ->
|
|||
List.append bytesWithPayload (Num.toU8 ']')
|
||||
|> List.append (Num.toU8 '}')
|
||||
|
||||
isEscapeSequence : U8, U8 -> Bool
|
||||
isEscapeSequence = \a, b ->
|
||||
when P a b is
|
||||
P '\\' 'b' -> Bool.true # Backspace
|
||||
P '\\' 'f' -> Bool.true # Form feed
|
||||
P '\\' 'n' -> Bool.true # Newline
|
||||
P '\\' 'r' -> Bool.true # Carriage return
|
||||
P '\\' 't' -> Bool.true # Tab
|
||||
P '\\' '"' -> Bool.true # Double quote
|
||||
P '\\' '\\' -> Bool.true # Backslash
|
||||
_ -> Bool.false
|
||||
|
||||
takeWhile = \list, predicate ->
|
||||
helper = \{ taken, rest } ->
|
||||
when List.first rest is
|
||||
Ok elem ->
|
||||
if predicate elem then
|
||||
helper { taken: List.append taken elem, rest: List.split rest 1 |> .others }
|
||||
when rest is
|
||||
[a, b, ..] ->
|
||||
if isEscapeSequence a b then
|
||||
helper {
|
||||
taken: taken |> List.append a |> List.append b,
|
||||
rest: List.drop rest 2,
|
||||
}
|
||||
else if predicate a then
|
||||
helper {
|
||||
taken: List.append taken a,
|
||||
rest: List.dropFirst rest,
|
||||
}
|
||||
else
|
||||
{ taken, rest }
|
||||
|
||||
Err _ -> { taken, rest }
|
||||
[a, ..] if predicate a ->
|
||||
helper {
|
||||
taken: List.append taken a,
|
||||
rest: List.dropFirst rest,
|
||||
}
|
||||
|
||||
_ -> { taken, rest }
|
||||
|
||||
helper { taken: [], rest: list }
|
||||
|
||||
|
@ -341,7 +367,6 @@ jsonString = \bytes ->
|
|||
if
|
||||
before == ['"']
|
||||
then
|
||||
# TODO: handle escape sequences
|
||||
{ taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"'
|
||||
|
||||
when Str.fromUtf8 strSequence is
|
||||
|
@ -358,42 +383,30 @@ decodeString = Decode.custom \bytes, @Json {} ->
|
|||
jsonString bytes
|
||||
|
||||
decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
|
||||
|
||||
decodeElems = \chunk, accum ->
|
||||
when Decode.decodeWith chunk decodeElem (@Json {}) is
|
||||
{ result, rest } ->
|
||||
when result is
|
||||
Ok val ->
|
||||
# TODO: handle spaces before ','
|
||||
{ before: afterElem, others } = List.split rest 1
|
||||
|
||||
if
|
||||
afterElem == [',']
|
||||
then
|
||||
decodeElems others (List.append accum val)
|
||||
else
|
||||
Done (List.append accum val) rest
|
||||
|
||||
Err e -> Errored e rest
|
||||
Ok val ->
|
||||
restWithoutWhitespace = eatWhitespace rest
|
||||
when restWithoutWhitespace is
|
||||
[',', ..] -> decodeElems (eatWhitespace (List.dropFirst restWithoutWhitespace)) (List.append accum val)
|
||||
_ -> Done (List.append accum val) restWithoutWhitespace
|
||||
|
||||
{ before, others: afterStartingBrace } = List.split bytes 1
|
||||
when bytes is
|
||||
['[', ']'] -> { result: Ok [], rest: List.drop bytes 2 }
|
||||
['[', ..] ->
|
||||
when decodeElems (eatWhitespace (List.dropFirst bytes)) [] is
|
||||
Errored e rest -> { result: Err e, rest }
|
||||
Done vals rest ->
|
||||
when rest is
|
||||
[']', ..] -> { result: Ok vals, rest: List.dropFirst rest }
|
||||
_ -> { result: Err TooShort, rest }
|
||||
|
||||
if
|
||||
before == ['[']
|
||||
then
|
||||
# TODO: empty lists
|
||||
when decodeElems afterStartingBrace [] is
|
||||
Errored e rest -> { result: Err e, rest }
|
||||
Done vals rest ->
|
||||
{ before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1
|
||||
|
||||
if
|
||||
maybeEndingBrace == [']']
|
||||
then
|
||||
{ result: Ok vals, rest: afterEndingBrace }
|
||||
else
|
||||
{ result: Err TooShort, rest }
|
||||
else
|
||||
{ result: Err TooShort, rest: bytes }
|
||||
_ ->
|
||||
{ result: Err TooShort, rest: bytes }
|
||||
|
||||
parseExactChar : List U8, U8 -> DecodeResult {}
|
||||
parseExactChar = \bytes, char ->
|
||||
|
@ -463,3 +476,82 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
|
|||
when finalizer endStateResult is
|
||||
Ok val -> { result: Ok val, rest: afterRecordBytes }
|
||||
Err e -> { result: Err e, rest: afterRecordBytes }
|
||||
|
||||
# Helper to eat leading Json whitespace characters
|
||||
eatWhitespace = \input ->
|
||||
when input is
|
||||
[' ', ..] -> eatWhitespace (List.dropFirst input)
|
||||
['\n', ..] -> eatWhitespace (List.dropFirst input)
|
||||
['\r', ..] -> eatWhitespace (List.dropFirst input)
|
||||
['\t', ..] -> eatWhitespace (List.dropFirst input)
|
||||
_ -> input
|
||||
|
||||
# Test eating Json whitespace
|
||||
expect
|
||||
input = Str.toUtf8 " \n\r\tabc"
|
||||
actual = eatWhitespace input
|
||||
expected = Str.toUtf8 "abc"
|
||||
actual == expected
|
||||
|
||||
# Test json string decoding with escapes
|
||||
expect
|
||||
input = Str.toUtf8 "\"a\r\nbc\\\"xz\""
|
||||
expected = Ok "a\r\nbc\\\"xz"
|
||||
actual = Decode.fromBytes input fromUtf8
|
||||
actual == expected
|
||||
|
||||
# Test json string encoding with escapes
|
||||
expect
|
||||
input = "a\r\nbc\\\"xz"
|
||||
expected = Str.toUtf8 "\"a\r\nbc\\\"xz\""
|
||||
actual = Encode.toBytes input toUtf8
|
||||
actual == expected
|
||||
|
||||
# Test json array decode empty list
|
||||
expect
|
||||
input = Str.toUtf8 "[ ]"
|
||||
expected = []
|
||||
|
||||
actual : List U8
|
||||
actual = Decode.fromBytes input fromUtf8 |> Result.withDefault []
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test json array decoding into integers
|
||||
expect
|
||||
input = Str.toUtf8 "[ 1,\n2,\t3]"
|
||||
expected = [1, 2, 3]
|
||||
|
||||
actual : List U8
|
||||
actual = Decode.fromBytes input fromUtf8 |> Result.withDefault []
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test json array decoding into strings ignoring whitespace around values
|
||||
expect
|
||||
input = Str.toUtf8 "[\r\"one\" ,\t\"two\"\n,\n\"3\"\t]"
|
||||
expected = ["one", "two", "3"]
|
||||
|
||||
actual : List Str
|
||||
actual =
|
||||
Decode.fromBytes input fromUtf8
|
||||
|> Result.onErr handleJsonDecodeError
|
||||
|> Result.withDefault []
|
||||
|
||||
actual == expected
|
||||
|
||||
# Helper for tests to handle Json decoding errors
|
||||
handleJsonDecodeError = \err ->
|
||||
when err is
|
||||
Leftover bytes ->
|
||||
when Str.fromUtf8 bytes is
|
||||
Ok bs -> crash "ERROR: bytes left \(bs)"
|
||||
Err _ ->
|
||||
ls =
|
||||
bytes
|
||||
|> List.map Num.toStr
|
||||
|> Str.joinWith ","
|
||||
|
||||
crash "ERROR: bytes left \(ls)"
|
||||
|
||||
TooShort -> crash "ERROR: input too short"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -68,6 +68,9 @@ interface Num
|
|||
compare,
|
||||
pow,
|
||||
powInt,
|
||||
countLeadingZeroBits,
|
||||
countTrailingZeroBits,
|
||||
countOneBits,
|
||||
addWrap,
|
||||
addChecked,
|
||||
addSaturated,
|
||||
|
@ -86,6 +89,8 @@ interface Num
|
|||
intCast,
|
||||
bytesToU16,
|
||||
bytesToU32,
|
||||
bytesToU64,
|
||||
bytesToU128,
|
||||
divCeil,
|
||||
divCeilChecked,
|
||||
divTrunc,
|
||||
|
@ -151,9 +156,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 +196,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 +264,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 +343,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 +493,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 `"-∞"`.
|
||||
##
|
||||
|
@ -510,6 +510,8 @@ intCast : Int a -> Int b
|
|||
|
||||
bytesToU16Lowlevel : List U8, Nat -> U16
|
||||
bytesToU32Lowlevel : List U8, Nat -> U32
|
||||
bytesToU64Lowlevel : List U8, Nat -> U64
|
||||
bytesToU128Lowlevel : List U8, Nat -> U128
|
||||
|
||||
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
|
||||
bytesToU16 = \bytes, index ->
|
||||
|
@ -531,6 +533,26 @@ bytesToU32 = \bytes, index ->
|
|||
else
|
||||
Err OutOfBounds
|
||||
|
||||
bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds]
|
||||
bytesToU64 = \bytes, index ->
|
||||
# we need at least 7 more bytes
|
||||
offset = 7
|
||||
|
||||
if index + offset < List.len bytes then
|
||||
Ok (bytesToU64Lowlevel bytes index)
|
||||
else
|
||||
Err OutOfBounds
|
||||
|
||||
bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds]
|
||||
bytesToU128 = \bytes, index ->
|
||||
# we need at least 15 more bytes
|
||||
offset = 15
|
||||
|
||||
if index + offset < List.len bytes then
|
||||
Ok (bytesToU128Lowlevel bytes index)
|
||||
else
|
||||
Err OutOfBounds
|
||||
|
||||
compare : Num a, Num a -> [LT, EQ, GT]
|
||||
|
||||
## Returns `Bool.true` if the first number is less than the second.
|
||||
|
@ -539,9 +561,10 @@ compare : Num a, Num a -> [LT, EQ, GT]
|
|||
##
|
||||
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
|
||||
## 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 +573,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 +625,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 +644,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 +669,16 @@ neg : Num a -> Num a
|
|||
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `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 +690,16 @@ add : Num a, Num a -> Num a
|
|||
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `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 +711,18 @@ sub : Num a, Num a -> Num a
|
|||
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `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 +757,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 +775,7 @@ sqrtChecked = \x ->
|
|||
else
|
||||
Ok (Num.sqrt x)
|
||||
|
||||
## Natural logarithm
|
||||
log : Frac a -> Frac a
|
||||
|
||||
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]
|
||||
|
@ -778,15 +806,16 @@ logChecked = \x ->
|
|||
##
|
||||
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
|
||||
## 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 +841,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 +862,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 +890,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 +915,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,21 +942,60 @@ 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
|
||||
## so large it causes an overflow.
|
||||
powInt : Int a, Int a -> Int a
|
||||
|
||||
## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
|
||||
##
|
||||
## ```
|
||||
## Num.countLeadingZeroBits 0b0001_1100u8
|
||||
##
|
||||
## 3
|
||||
##
|
||||
## Num.countLeadingZeroBits 0b0000_0000u8
|
||||
##
|
||||
## 8
|
||||
## ```
|
||||
countLeadingZeroBits : Int a -> Nat
|
||||
|
||||
## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
|
||||
##
|
||||
## ```
|
||||
## Num.countTrailingZeroBits 0b0001_1100u8
|
||||
##
|
||||
## 2
|
||||
##
|
||||
## Num.countTrailingZeroBits 0b0000_0000u8
|
||||
##
|
||||
## 8
|
||||
## ```
|
||||
countTrailingZeroBits : Int a -> Nat
|
||||
|
||||
## Counts the number of set bits in an integer.
|
||||
##
|
||||
## ```
|
||||
## Num.countOneBits 0b0001_1100u8
|
||||
##
|
||||
## 3
|
||||
##
|
||||
## Num.countOneBits 0b0000_0000u8
|
||||
##
|
||||
## 0
|
||||
## ```
|
||||
countOneBits : Int a -> Nat
|
||||
|
||||
addWrap : Int range, Int range -> Int range
|
||||
|
||||
## Add two numbers, clamping on the maximum representable number rather than
|
||||
|
@ -1261,164 +1330,3 @@ toU128Checked : Int * -> Result U128 [OutOfBounds]
|
|||
toNatChecked : Int * -> Result Nat [OutOfBounds]
|
||||
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,73 +1,6 @@
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_target::TargetInfo;
|
||||
use std::ops::Index;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
pub const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-wasm32.o"));
|
||||
// TODO: in the future, we should use Zig's cross-compilation to generate and store these
|
||||
// for all targets, so that we can do cross-compilation!
|
||||
#[cfg(unix)]
|
||||
pub const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-host.o"));
|
||||
#[cfg(windows)]
|
||||
pub const HOST_WINDOWS: &[u8] = include_bytes!(concat!(
|
||||
env!("OUT_DIR"),
|
||||
"/bitcode/builtins-windows-x86_64.obj"
|
||||
));
|
||||
|
||||
pub fn host_wasm_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
let tempfile = tempfile::Builder::new()
|
||||
.prefix("host_bitcode")
|
||||
.suffix(".wasm")
|
||||
.rand_bytes(8)
|
||||
.tempfile()?;
|
||||
|
||||
std::fs::write(tempfile.path(), HOST_WASM)?;
|
||||
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn host_unix_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
let tempfile = tempfile::Builder::new()
|
||||
.prefix("host_bitcode")
|
||||
.suffix(".o")
|
||||
.rand_bytes(8)
|
||||
.tempfile()?;
|
||||
|
||||
std::fs::write(tempfile.path(), HOST_UNIX)?;
|
||||
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn host_windows_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
let tempfile = tempfile::Builder::new()
|
||||
.prefix("host_bitcode")
|
||||
.suffix(".obj")
|
||||
.rand_bytes(8)
|
||||
.tempfile()?;
|
||||
|
||||
std::fs::write(tempfile.path(), HOST_WINDOWS)?;
|
||||
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
pub fn host_tempfile() -> std::io::Result<NamedTempFile> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
host_unix_tempfile()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
host_windows_tempfile()
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
{
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct IntrinsicName {
|
||||
|
@ -356,8 +289,16 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.
|
|||
pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName =
|
||||
float_intrinsic!("roc_builtins.num.mul_with_overflow");
|
||||
|
||||
pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
|
||||
int_intrinsic!("roc_builtins.num.count_leading_zero_bits");
|
||||
pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
|
||||
int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
|
||||
pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
|
||||
|
||||
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
|
||||
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
|
||||
pub const NUM_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64";
|
||||
pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128";
|
||||
|
||||
pub const STR_INIT: &str = "roc_builtins.str.init";
|
||||
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -187,6 +187,8 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
NumAsin; NUM_ASIN; 1,
|
||||
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
|
||||
NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2,
|
||||
NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2,
|
||||
NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2,
|
||||
NumBitwiseAnd; NUM_BITWISE_AND; 2,
|
||||
NumBitwiseXor; NUM_BITWISE_XOR; 2,
|
||||
NumBitwiseOr; NUM_BITWISE_OR; 2,
|
||||
|
@ -194,6 +196,9 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
NumShiftRightBy; NUM_SHIFT_RIGHT; 2,
|
||||
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
|
||||
NumToStr; NUM_TO_STR; 1,
|
||||
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
|
||||
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
|
||||
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
|
||||
|
||||
Eq; BOOL_STRUCTURAL_EQ; 2,
|
||||
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
|
||||
|
|
|
@ -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" }
|
||||
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,26 @@
|
|||
[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_bitcode_bc = { path = "../builtins/bitcode/bc" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
inkwell.workspace = true
|
||||
target-lexicon.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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -848,9 +848,24 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
|
||||
| NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin
|
||||
| NumToIntChecked => {
|
||||
NumAbs
|
||||
| NumNeg
|
||||
| NumRound
|
||||
| NumSqrtUnchecked
|
||||
| NumLogUnchecked
|
||||
| NumSin
|
||||
| NumCos
|
||||
| NumCeiling
|
||||
| NumFloor
|
||||
| NumToFrac
|
||||
| NumIsFinite
|
||||
| NumAtan
|
||||
| NumAcos
|
||||
| NumAsin
|
||||
| NumToIntChecked
|
||||
| NumCountLeadingZeroBits
|
||||
| NumCountTrailingZeroBits
|
||||
| NumCountOneBits => {
|
||||
arguments_with_layouts!((arg, arg_layout));
|
||||
|
||||
match layout_interner.get(arg_layout) {
|
||||
|
@ -914,6 +929,28 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
|
|||
bitcode::NUM_BYTES_TO_U32,
|
||||
)
|
||||
}
|
||||
NumBytesToU64 => {
|
||||
arguments!(list, position);
|
||||
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[list.into_struct_value()],
|
||||
&[position],
|
||||
BitcodeReturns::Basic,
|
||||
bitcode::NUM_BYTES_TO_U64,
|
||||
)
|
||||
}
|
||||
NumBytesToU128 => {
|
||||
arguments!(list, position);
|
||||
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[list.into_struct_value()],
|
||||
&[position],
|
||||
BitcodeReturns::Basic,
|
||||
bitcode::NUM_BYTES_TO_U128,
|
||||
)
|
||||
}
|
||||
NumCompare => {
|
||||
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
||||
|
||||
|
@ -2045,6 +2082,19 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
|||
complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked")
|
||||
}
|
||||
}
|
||||
NumCountLeadingZeroBits => call_bitcode_fn(
|
||||
env,
|
||||
&[arg.into()],
|
||||
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width],
|
||||
),
|
||||
NumCountTrailingZeroBits => call_bitcode_fn(
|
||||
env,
|
||||
&[arg.into()],
|
||||
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width],
|
||||
),
|
||||
NumCountOneBits => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width])
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Unrecognized int unary operation: {:?}", op);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1489,6 +1489,40 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
_ => panic_ret_type(),
|
||||
},
|
||||
|
||||
NumCountLeadingZeroBits => match backend
|
||||
.layout_interner
|
||||
.get(backend.storage.symbol_layouts[&self.arguments[0]])
|
||||
{
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
self.load_args_and_call_zig(
|
||||
backend,
|
||||
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[width],
|
||||
);
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
},
|
||||
NumCountTrailingZeroBits => match backend
|
||||
.layout_interner
|
||||
.get(backend.storage.symbol_layouts[&self.arguments[0]])
|
||||
{
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
self.load_args_and_call_zig(
|
||||
backend,
|
||||
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[width],
|
||||
);
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
},
|
||||
NumCountOneBits => match backend
|
||||
.layout_interner
|
||||
.get(backend.storage.symbol_layouts[&self.arguments[0]])
|
||||
{
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_COUNT_ONE_BITS[width]);
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
},
|
||||
NumRound => {
|
||||
self.load_args(backend);
|
||||
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
||||
|
@ -1577,6 +1611,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
},
|
||||
NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16),
|
||||
NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32),
|
||||
NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64),
|
||||
NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128),
|
||||
NumBitwiseAnd => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
|
|
|
@ -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."
|
||||
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[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_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 = []
|
||||
|
|
|
@ -90,6 +90,8 @@ pub enum LowLevel {
|
|||
NumAsin,
|
||||
NumBytesToU16,
|
||||
NumBytesToU32,
|
||||
NumBytesToU64,
|
||||
NumBytesToU128,
|
||||
NumBitwiseAnd,
|
||||
NumBitwiseXor,
|
||||
NumBitwiseOr,
|
||||
|
@ -101,6 +103,9 @@ pub enum LowLevel {
|
|||
NumToIntChecked,
|
||||
NumToFloatChecked,
|
||||
NumToStr,
|
||||
NumCountLeadingZeroBits,
|
||||
NumCountTrailingZeroBits,
|
||||
NumCountOneBits,
|
||||
Eq,
|
||||
NotEq,
|
||||
And,
|
||||
|
@ -305,6 +310,8 @@ map_symbol_to_lowlevel! {
|
|||
NumAsin <= NUM_ASIN,
|
||||
NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL,
|
||||
NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL,
|
||||
NumBytesToU64 <= NUM_BYTES_TO_U64_LOWLEVEL,
|
||||
NumBytesToU128 <= NUM_BYTES_TO_U128_LOWLEVEL,
|
||||
NumBitwiseAnd <= NUM_BITWISE_AND,
|
||||
NumBitwiseXor <= NUM_BITWISE_XOR,
|
||||
NumBitwiseOr <= NUM_BITWISE_OR,
|
||||
|
@ -312,6 +319,9 @@ map_symbol_to_lowlevel! {
|
|||
NumShiftRightBy <= NUM_SHIFT_RIGHT,
|
||||
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
|
||||
NumToStr <= NUM_TO_STR,
|
||||
NumCountLeadingZeroBits <= NUM_COUNT_LEADING_ZERO_BITS,
|
||||
NumCountTrailingZeroBits <= NUM_COUNT_TRAILING_ZERO_BITS,
|
||||
NumCountOneBits <= NUM_COUNT_ONE_BITS,
|
||||
Eq <= BOOL_STRUCTURAL_EQ,
|
||||
NotEq <= BOOL_STRUCTURAL_NOT_EQ,
|
||||
And <= BOOL_AND,
|
||||
|
|
|
@ -1189,63 +1189,70 @@ define_builtins! {
|
|||
88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias
|
||||
89 NUM_BYTES_TO_U16: "bytesToU16"
|
||||
90 NUM_BYTES_TO_U32: "bytesToU32"
|
||||
91 NUM_CAST_TO_NAT: "#castToNat"
|
||||
92 NUM_DIV_CEIL: "divCeil"
|
||||
93 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
|
||||
94 NUM_TO_STR: "toStr"
|
||||
95 NUM_MIN_I8: "minI8"
|
||||
96 NUM_MAX_I8: "maxI8"
|
||||
97 NUM_MIN_U8: "minU8"
|
||||
98 NUM_MAX_U8: "maxU8"
|
||||
99 NUM_MIN_I16: "minI16"
|
||||
100 NUM_MAX_I16: "maxI16"
|
||||
101 NUM_MIN_U16: "minU16"
|
||||
102 NUM_MAX_U16: "maxU16"
|
||||
103 NUM_MIN_I32: "minI32"
|
||||
104 NUM_MAX_I32: "maxI32"
|
||||
105 NUM_MIN_U32: "minU32"
|
||||
106 NUM_MAX_U32: "maxU32"
|
||||
107 NUM_MIN_I64: "minI64"
|
||||
108 NUM_MAX_I64: "maxI64"
|
||||
109 NUM_MIN_U64: "minU64"
|
||||
110 NUM_MAX_U64: "maxU64"
|
||||
111 NUM_MIN_I128: "minI128"
|
||||
112 NUM_MAX_I128: "maxI128"
|
||||
113 NUM_MIN_U128: "minU128"
|
||||
114 NUM_MAX_U128: "maxU128"
|
||||
115 NUM_TO_I8: "toI8"
|
||||
116 NUM_TO_I8_CHECKED: "toI8Checked"
|
||||
117 NUM_TO_I16: "toI16"
|
||||
118 NUM_TO_I16_CHECKED: "toI16Checked"
|
||||
119 NUM_TO_I32: "toI32"
|
||||
120 NUM_TO_I32_CHECKED: "toI32Checked"
|
||||
121 NUM_TO_I64: "toI64"
|
||||
122 NUM_TO_I64_CHECKED: "toI64Checked"
|
||||
123 NUM_TO_I128: "toI128"
|
||||
124 NUM_TO_I128_CHECKED: "toI128Checked"
|
||||
125 NUM_TO_U8: "toU8"
|
||||
126 NUM_TO_U8_CHECKED: "toU8Checked"
|
||||
127 NUM_TO_U16: "toU16"
|
||||
128 NUM_TO_U16_CHECKED: "toU16Checked"
|
||||
129 NUM_TO_U32: "toU32"
|
||||
130 NUM_TO_U32_CHECKED: "toU32Checked"
|
||||
131 NUM_TO_U64: "toU64"
|
||||
132 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
133 NUM_TO_U128: "toU128"
|
||||
134 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
135 NUM_TO_NAT: "toNat"
|
||||
136 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
137 NUM_TO_F32: "toF32"
|
||||
138 NUM_TO_F32_CHECKED: "toF32Checked"
|
||||
139 NUM_TO_F64: "toF64"
|
||||
140 NUM_TO_F64_CHECKED: "toF64Checked"
|
||||
141 NUM_MAX_F64: "maxF64"
|
||||
142 NUM_MIN_F64: "minF64"
|
||||
143 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
|
||||
144 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
|
||||
145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
|
||||
146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
|
||||
147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
|
||||
91 NUM_BYTES_TO_U64: "bytesToU64"
|
||||
92 NUM_BYTES_TO_U128: "bytesToU128"
|
||||
93 NUM_CAST_TO_NAT: "#castToNat"
|
||||
94 NUM_DIV_CEIL: "divCeil"
|
||||
95 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
|
||||
96 NUM_TO_STR: "toStr"
|
||||
97 NUM_MIN_I8: "minI8"
|
||||
98 NUM_MAX_I8: "maxI8"
|
||||
99 NUM_MIN_U8: "minU8"
|
||||
100 NUM_MAX_U8: "maxU8"
|
||||
101 NUM_MIN_I16: "minI16"
|
||||
102 NUM_MAX_I16: "maxI16"
|
||||
103 NUM_MIN_U16: "minU16"
|
||||
104 NUM_MAX_U16: "maxU16"
|
||||
105 NUM_MIN_I32: "minI32"
|
||||
106 NUM_MAX_I32: "maxI32"
|
||||
107 NUM_MIN_U32: "minU32"
|
||||
108 NUM_MAX_U32: "maxU32"
|
||||
109 NUM_MIN_I64: "minI64"
|
||||
110 NUM_MAX_I64: "maxI64"
|
||||
111 NUM_MIN_U64: "minU64"
|
||||
112 NUM_MAX_U64: "maxU64"
|
||||
113 NUM_MIN_I128: "minI128"
|
||||
114 NUM_MAX_I128: "maxI128"
|
||||
115 NUM_MIN_U128: "minU128"
|
||||
116 NUM_MAX_U128: "maxU128"
|
||||
117 NUM_TO_I8: "toI8"
|
||||
118 NUM_TO_I8_CHECKED: "toI8Checked"
|
||||
119 NUM_TO_I16: "toI16"
|
||||
120 NUM_TO_I16_CHECKED: "toI16Checked"
|
||||
121 NUM_TO_I32: "toI32"
|
||||
122 NUM_TO_I32_CHECKED: "toI32Checked"
|
||||
123 NUM_TO_I64: "toI64"
|
||||
124 NUM_TO_I64_CHECKED: "toI64Checked"
|
||||
125 NUM_TO_I128: "toI128"
|
||||
126 NUM_TO_I128_CHECKED: "toI128Checked"
|
||||
127 NUM_TO_U8: "toU8"
|
||||
128 NUM_TO_U8_CHECKED: "toU8Checked"
|
||||
129 NUM_TO_U16: "toU16"
|
||||
130 NUM_TO_U16_CHECKED: "toU16Checked"
|
||||
131 NUM_TO_U32: "toU32"
|
||||
132 NUM_TO_U32_CHECKED: "toU32Checked"
|
||||
133 NUM_TO_U64: "toU64"
|
||||
134 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
135 NUM_TO_U128: "toU128"
|
||||
136 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
137 NUM_TO_NAT: "toNat"
|
||||
138 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
139 NUM_TO_F32: "toF32"
|
||||
140 NUM_TO_F32_CHECKED: "toF32Checked"
|
||||
141 NUM_TO_F64: "toF64"
|
||||
142 NUM_TO_F64_CHECKED: "toF64Checked"
|
||||
143 NUM_MAX_F64: "maxF64"
|
||||
144 NUM_MIN_F64: "minF64"
|
||||
145 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
|
||||
146 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
|
||||
147 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
|
||||
148 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
|
||||
149 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
|
||||
150 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel"
|
||||
151 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel"
|
||||
152 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits"
|
||||
153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits"
|
||||
154 NUM_COUNT_ONE_BITS: "countOneBits"
|
||||
}
|
||||
4 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
|
||||
|
@ -1319,6 +1326,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 +1409,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;
|
||||
|
@ -984,13 +1026,33 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
| NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
|
||||
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||
|
||||
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
|
||||
| NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos
|
||||
| NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => {
|
||||
arena.alloc_slice_copy(&[irrelevant])
|
||||
}
|
||||
NumToStr
|
||||
| NumAbs
|
||||
| NumNeg
|
||||
| NumSin
|
||||
| NumCos
|
||||
| NumSqrtUnchecked
|
||||
| NumLogUnchecked
|
||||
| NumRound
|
||||
| NumCeiling
|
||||
| NumFloor
|
||||
| NumToFrac
|
||||
| Not
|
||||
| NumIsFinite
|
||||
| NumAtan
|
||||
| NumAcos
|
||||
| NumAsin
|
||||
| NumIntCast
|
||||
| NumToIntChecked
|
||||
| NumToFloatCast
|
||||
| NumToFloatChecked
|
||||
| NumCountLeadingZeroBits
|
||||
| NumCountTrailingZeroBits
|
||||
| NumCountOneBits => arena.alloc_slice_copy(&[irrelevant]),
|
||||
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
NumBytesToU128 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
|
||||
StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -120,6 +120,8 @@ enum FirstOrder {
|
|||
NumShiftRightBy,
|
||||
NumBytesToU16,
|
||||
NumBytesToU32,
|
||||
NumBytesToU64,
|
||||
NumBytesToU128,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumFloatCast,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
661
crates/compiler/parse/src/highlight.rs
Normal file
661
crates/compiler/parse/src/highlight.rs
Normal file
|
@ -0,0 +1,661 @@
|
|||
use encode_unicode::CharExt;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
use crate::{
|
||||
ast::CommentOrNewline,
|
||||
blankspace::loc_spaces,
|
||||
keyword::KEYWORDS,
|
||||
number_literal::positive_number_literal,
|
||||
parser::{EExpr, ParseResult, Parser},
|
||||
state::State,
|
||||
string_literal::{parse_str_like_literal, StrLikeLiteral},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Token {
|
||||
LineComment,
|
||||
DocComment,
|
||||
Error,
|
||||
SingleQuote,
|
||||
String,
|
||||
UnicodeEscape,
|
||||
EscapedChar,
|
||||
Interpolated,
|
||||
Keyword,
|
||||
UpperIdent,
|
||||
LowerIdent,
|
||||
Number,
|
||||
QuestionMark,
|
||||
Percent,
|
||||
Caret,
|
||||
Other,
|
||||
Minus,
|
||||
Bang,
|
||||
BangEquals,
|
||||
Plus,
|
||||
Colon,
|
||||
ColonEquals,
|
||||
Bar,
|
||||
DoubleBar,
|
||||
And,
|
||||
DoubleAnd,
|
||||
Equals,
|
||||
DoubleEquals,
|
||||
GreaterThan,
|
||||
GreaterThanEquals,
|
||||
LessThan,
|
||||
LessThanEquals,
|
||||
Comma,
|
||||
Backslash,
|
||||
Slash,
|
||||
DoubleSlash,
|
||||
Pizza,
|
||||
Brace,
|
||||
Bracket,
|
||||
AtSign,
|
||||
Paren,
|
||||
Arrow,
|
||||
Pipe,
|
||||
Backpass,
|
||||
Decimal,
|
||||
Multiply,
|
||||
Underscore,
|
||||
}
|
||||
|
||||
pub fn highlight(text: &str) -> Vec<Loc<Token>> {
|
||||
let mut tokens = Vec::new();
|
||||
let state = State::new(text.as_bytes());
|
||||
|
||||
let arena = Bump::new();
|
||||
|
||||
let header_keywords = HEADER_KEYWORDS.iter().copied().collect::<HashSet<_>>();
|
||||
let body_keywords = KEYWORDS.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
if let Ok((_prog, _, new_state)) = crate::module::header().parse(&arena, state.clone(), 0) {
|
||||
let inner_state =
|
||||
State::new(text[..state.bytes().len() - new_state.bytes().len()].as_bytes());
|
||||
highlight_inner(&arena, inner_state, &mut tokens, &header_keywords);
|
||||
highlight_inner(&arena, new_state, &mut tokens, &body_keywords);
|
||||
} else {
|
||||
highlight_inner(&arena, state, &mut tokens, &body_keywords);
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
fn highlight_inner<'a>(
|
||||
arena: &'a Bump,
|
||||
mut state: State<'a>,
|
||||
tokens: &mut Vec<Loc<Token>>,
|
||||
keywords: &HashSet<&str>,
|
||||
) {
|
||||
loop {
|
||||
let start = state.pos();
|
||||
if let Ok((b, _width)) = char::from_utf8_slice_start(state.bytes()) {
|
||||
match b {
|
||||
' ' | '\n' | '\t' | '\r' | '#' => {
|
||||
let res: ParseResult<'a, _, EExpr<'a>> =
|
||||
loc_spaces().parse(arena, state.clone(), 0);
|
||||
if let Ok((_, spaces, new_state)) = res {
|
||||
state = new_state;
|
||||
for space in spaces {
|
||||
let token = match space.value {
|
||||
CommentOrNewline::Newline => {
|
||||
continue;
|
||||
}
|
||||
CommentOrNewline::LineComment(_) => Token::LineComment,
|
||||
CommentOrNewline::DocComment(_) => Token::DocComment,
|
||||
};
|
||||
tokens.push(Loc::at(space.region, token));
|
||||
}
|
||||
} else {
|
||||
fast_forward_to(&mut state, tokens, start, |c| c == b'\n');
|
||||
}
|
||||
}
|
||||
'"' | '\'' => {
|
||||
if let Ok((_, item, new_state)) =
|
||||
parse_str_like_literal().parse(arena, state.clone(), 0)
|
||||
{
|
||||
state = new_state;
|
||||
match item {
|
||||
StrLikeLiteral::SingleQuote(_) => {
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
Token::SingleQuote,
|
||||
));
|
||||
}
|
||||
StrLikeLiteral::Str(_) => {
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
Token::String,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fast_forward_to(&mut state, tokens, start, |c| c == b'\n');
|
||||
}
|
||||
}
|
||||
c if c.is_alphabetic() => {
|
||||
let buffer = state.bytes();
|
||||
let mut chomped = 0;
|
||||
|
||||
let is_upper = c.is_uppercase();
|
||||
|
||||
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
if ch.is_alphabetic() || ch.is_ascii_digit() {
|
||||
chomped += width;
|
||||
} else {
|
||||
// we're done
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let ident = std::str::from_utf8(&buffer[..chomped]).unwrap();
|
||||
state.advance_mut(chomped);
|
||||
|
||||
if keywords.contains(ident) {
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Keyword));
|
||||
} else {
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
if is_upper {
|
||||
Token::UpperIdent
|
||||
} else {
|
||||
Token::LowerIdent
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
'.' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Decimal));
|
||||
}
|
||||
'0'..='9' => {
|
||||
if let Ok((_, _item, new_state)) =
|
||||
positive_number_literal().parse(arena, state.clone(), 0)
|
||||
{
|
||||
state = new_state;
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Number));
|
||||
} else {
|
||||
fast_forward_to(&mut state, tokens, start, |b| !b.is_ascii_digit());
|
||||
}
|
||||
}
|
||||
':' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'=') {
|
||||
state.advance_mut(1);
|
||||
Token::ColonEquals
|
||||
} else {
|
||||
Token::Colon
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'|' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'>') {
|
||||
state.advance_mut(1);
|
||||
Token::Pizza
|
||||
} else if state.bytes().first() == Some(&b'|') {
|
||||
state.advance_mut(1);
|
||||
Token::DoubleBar
|
||||
} else {
|
||||
Token::Bar
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'&' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'&') {
|
||||
state.advance_mut(1);
|
||||
Token::DoubleAnd
|
||||
} else {
|
||||
Token::And
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'-' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'>') {
|
||||
state.advance_mut(1);
|
||||
Token::Arrow
|
||||
} else {
|
||||
Token::Minus
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'+' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Plus));
|
||||
}
|
||||
'=' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'=') {
|
||||
state.advance_mut(1);
|
||||
Token::DoubleEquals
|
||||
} else {
|
||||
Token::Equals
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'>' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'=') {
|
||||
state.advance_mut(1);
|
||||
Token::GreaterThanEquals
|
||||
} else {
|
||||
Token::GreaterThan
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'<' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'=') {
|
||||
state.advance_mut(1);
|
||||
Token::LessThanEquals
|
||||
} else if state.bytes().first() == Some(&b'-') {
|
||||
state.advance_mut(1);
|
||||
Token::Backpass
|
||||
} else {
|
||||
Token::LessThan
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'!' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'=') {
|
||||
state.advance_mut(1);
|
||||
Token::BangEquals
|
||||
} else {
|
||||
Token::Bang
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
',' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Comma));
|
||||
}
|
||||
'_' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
Token::Underscore,
|
||||
));
|
||||
}
|
||||
'?' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
Token::QuestionMark,
|
||||
));
|
||||
}
|
||||
'%' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Percent));
|
||||
}
|
||||
'*' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
Token::Multiply,
|
||||
));
|
||||
}
|
||||
'^' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Caret));
|
||||
}
|
||||
'\\' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(
|
||||
Region::between(start, state.pos()),
|
||||
Token::Backslash,
|
||||
));
|
||||
}
|
||||
'/' => {
|
||||
state.advance_mut(1);
|
||||
let tok = if state.bytes().first() == Some(&b'/') {
|
||||
state.advance_mut(1);
|
||||
Token::DoubleSlash
|
||||
} else {
|
||||
Token::Slash
|
||||
};
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), tok));
|
||||
}
|
||||
'@' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::AtSign));
|
||||
}
|
||||
'{' | '}' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Brace));
|
||||
}
|
||||
'[' | ']' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Bracket));
|
||||
}
|
||||
'(' | ')' => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Paren));
|
||||
}
|
||||
_ => {
|
||||
state.advance_mut(1);
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Other));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fast_forward_to(
|
||||
state: &mut State,
|
||||
tokens: &mut Vec<Loc<Token>>,
|
||||
start: roc_region::all::Position,
|
||||
end: impl Fn(u8) -> bool,
|
||||
) {
|
||||
while let Some(b) = state.bytes().first() {
|
||||
if end(*b) {
|
||||
break;
|
||||
}
|
||||
state.advance_mut(1);
|
||||
}
|
||||
tokens.push(Loc::at(Region::between(start, state.pos()), Token::Error));
|
||||
}
|
||||
|
||||
pub const HEADER_KEYWORDS: [&str; 14] = [
|
||||
"interface",
|
||||
"app",
|
||||
"package",
|
||||
"platform",
|
||||
"hosted",
|
||||
"exposes",
|
||||
"imports",
|
||||
"with",
|
||||
"generates",
|
||||
"package",
|
||||
"packages",
|
||||
"requires",
|
||||
"provides",
|
||||
"to",
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use roc_region::all::Position;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_highlight_comments() {
|
||||
let text = "# a\n#b\n#c";
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(3)),
|
||||
Token::LineComment
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(4), Position::new(6)),
|
||||
Token::LineComment
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(7), Position::new(9)),
|
||||
Token::LineComment
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_doc_comments() {
|
||||
let text = "## a\n##b\n##c";
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(4)),
|
||||
Token::DocComment
|
||||
),
|
||||
// the next two are line comments because there's not a space at the beginning
|
||||
Loc::at(
|
||||
Region::between(Position::new(5), Position::new(8)),
|
||||
Token::LineComment
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(9), Position::new(12)),
|
||||
Token::LineComment
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_strings() {
|
||||
let text = r#""a""#;
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![Loc::at(
|
||||
Region::between(Position::new(0), Position::new(3)),
|
||||
Token::String
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_single_quotes() {
|
||||
let text = r#"'a'"#;
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![Loc::at(
|
||||
Region::between(Position::new(0), Position::new(3)),
|
||||
Token::SingleQuote
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_header() {
|
||||
let text = r#"app "test-app" provides [] to "./blah""#;
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(3)),
|
||||
Token::Keyword
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(4), Position::new(14)),
|
||||
Token::String
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(15), Position::new(23)),
|
||||
Token::Keyword
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(24), Position::new(25)),
|
||||
Token::Bracket
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(25), Position::new(26)),
|
||||
Token::Bracket
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(27), Position::new(29)),
|
||||
Token::Keyword
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(30), Position::new(38)),
|
||||
Token::String
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_numbers() {
|
||||
let text = "123.0 123 123. 123.0e10 123e10 123e-10 0x123";
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(5)),
|
||||
Token::Number
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(6), Position::new(9)),
|
||||
Token::Number
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(10), Position::new(14)),
|
||||
Token::Number
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(15), Position::new(23)),
|
||||
Token::Number
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(24), Position::new(30)),
|
||||
Token::Number
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(31), Position::new(38)),
|
||||
Token::Number
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(39), Position::new(44)),
|
||||
Token::Number
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combine_tokens() {
|
||||
let text = "-> := <- |> || >= <= ==";
|
||||
let actual = highlight(text);
|
||||
|
||||
let expected = vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(2)),
|
||||
Token::Arrow,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(3), Position::new(5)),
|
||||
Token::ColonEquals,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(6), Position::new(8)),
|
||||
Token::Backpass,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(9), Position::new(11)),
|
||||
Token::Pizza,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(12), Position::new(14)),
|
||||
Token::DoubleBar,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(15), Position::new(17)),
|
||||
Token::GreaterThanEquals,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(18), Position::new(20)),
|
||||
Token::LessThanEquals,
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(21), Position::new(23)),
|
||||
Token::DoubleEquals,
|
||||
),
|
||||
];
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_pattern_matching() {
|
||||
let text = "Green | Yellow -> \"not red\"";
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(5)),
|
||||
Token::UpperIdent
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(6), Position::new(7)),
|
||||
Token::Bar
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(8), Position::new(14)),
|
||||
Token::UpperIdent
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(15), Position::new(17)),
|
||||
Token::Arrow
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(18), Position::new(27)),
|
||||
Token::String
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_question_mark() {
|
||||
let text = "title? Str";
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(5)),
|
||||
Token::LowerIdent
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(5), Position::new(6)),
|
||||
Token::QuestionMark
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(7), Position::new(10)),
|
||||
Token::UpperIdent
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_slash() {
|
||||
let text = "first / second";
|
||||
let tokens = highlight(text);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Loc::at(
|
||||
Region::between(Position::new(0), Position::new(5)),
|
||||
Token::LowerIdent
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(6), Position::new(7)),
|
||||
Token::Slash
|
||||
),
|
||||
Loc::at(
|
||||
Region::between(Position::new(8), Position::new(14)),
|
||||
Token::LowerIdent
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue