Merge branch 'trunk' of github.com:rtfeldman/roc into nix_flake_M1

This commit is contained in:
Anton-4 2022-05-11 16:39:10 +02:00
commit 8cd68a05e4
No known key found for this signature in database
GPG Key ID: C954D6E0F9C0ABFD
417 changed files with 28878 additions and 15849 deletions

View File

@ -8,3 +8,17 @@ test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --featur
# 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"]
[env]
# Debug flags. Keep this up-to-date with compiler/debug_flags/src/lib.rs.
# Set = "1" to turn a debug flag on.
ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0"
ROC_PRINT_UNIFICATIONS = "0"
ROC_PRINT_MISMATCHES = "0"
ROC_VERIFY_RIGID_LET_GENERALIZED = "0"
ROC_PRINT_IR_AFTER_SPECIALIZATION = "0"
ROC_PRINT_IR_AFTER_RESET_REUSE = "0"
ROC_PRINT_IR_AFTER_REFCOUNT = "0"
ROC_DEBUG_ALIAS_ANALYSIS = "0"
ROC_PRINT_LLVM_FN_VERIFICATION = "0"
ROC_PRINT_LOAD_LOG = "0"

View File

@ -1 +1 @@
12.0.0
13.0.0

View File

@ -77,3 +77,6 @@ Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
Jared Cone <jared.cone@gmail.com>
Sean Hagstrom <sean@seanhagstrom.com>
Kas Buunk <kasbuunk@icloud.com>
Oskar Hahn <mail@oshahn.de>
Nuno Ferreira <nunogcferreira@gmail.com>
Mfon Eti-mfon <mfonetimfon@gmail.com>

View File

@ -124,7 +124,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
```
### Zig
**version: 0.8.0**
**version: 0.9.1**
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
@ -136,14 +136,14 @@ If you prefer a package manager, you can try the following:
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
### LLVM
**version: 12.0.x**
**version: 13.0.x**
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
For macOS, you can install LLVM 13 using `brew install llvm@13` and then adding
`$(brew --prefix llvm@13)/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 13.0.0" at the top.
You may also need to manually specify a prefix env var like so:
```
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
export LLVM_SYS_130_PREFIX=/usr/local/opt/llvm@13
```
For Ubuntu and Debian:
@ -151,19 +151,15 @@ For Ubuntu and Debian:
sudo apt -y install lsb-release software-properties-common gnupg
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 12
./llvm.sh 13
```
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
By default, the script installs them as `clang-12` and `llvm-as-12`,
respectively. You can address this with symlinks like so:
If you use this script, you'll need to add `clang` to your `PATH`.
By default, the script installs it as `clang-13`. You can address this with symlinks like so:
```
sudo ln -s /usr/bin/clang-12 /usr/bin/clang
sudo ln -s /usr/bin/clang-13 /usr/bin/clang
```
```
sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
````
There are also alternative installation options at http://releases.llvm.org/download.html
@ -187,9 +183,9 @@ If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`.
If you encounter:
```
error: No suitable version of LLVM was found system-wide or pointed
to by LLVM_SYS_120_PREFIX.
to by LLVM_SYS_130_PREFIX.
```
Add `export LLVM_SYS_120_PREFIX=/usr/lib/llvm-12` to your `~/.bashrc` or equivalent file for your shell.
Add `export LLVM_SYS_130_PREFIX=/usr/lib/llvm-13` to your `~/.bashrc` or equivalent file for your shell.
### LLVM installation on macOS
@ -208,14 +204,14 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to follow the steps below:
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work too; the Build Tools are just the CLI tools, which is all I wanted)
1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v12.0.1).
1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v13.0.1).
1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive.
1. Extract the 7z file to where you want to permanently keep the folder.
1. In powershell, set the `LLVM_SYS_120_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-changes-to-environment-variables) to make this a permanent environment variable):
1. In powershell, set the `LLVM_SYS_130_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-changes-to-environment-variables) to make this a permanent environment variable):
```
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-12.0.1-win64\bin",
[Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-13.0.1-win64\bin",
"User"
)
```
@ -242,8 +238,8 @@ Create `~/.cargo/config.toml` if it does not exist and add this to it:
rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "target-cpu=native"]
```
Then install `lld` version 12 (e.g. with `$ sudo apt-get install lld-12`)
Then install `lld` version 13 (e.g. with `$ sudo apt-get install lld-13`)
and add make sure there's a `ld.lld` executable on your `PATH` which
is symlinked to `lld-12`.
is symlinked to `lld-13`.
That's it! Enjoy the faster builds.

1677
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,10 +26,12 @@ members = [
"compiler/arena_pool",
"compiler/test_gen",
"compiler/roc_target",
"compiler/debug_flags",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",
"vendor/pretty",
"bindgen",
"editor",
"ast",
"cli",
@ -44,7 +46,9 @@ members = [
"test_utils",
"utils",
"docs",
"docs_cli",
"linker",
"wasi-libc-sys",
]
exclude = [
"ci/bench-runner",
@ -52,7 +56,7 @@ exclude = [
# The tests will still correctly build them.
"cli_utils",
"compiler/test_mono_macros",
# `cargo build` would cause roc_std to be built with default features which errors on windows
# `cargo build` would cause roc_std to be built with default features which errors on windows
"roc_std",
]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -

View File

@ -1,4 +1,4 @@
FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages
FROM rust:1.60.0-slim-bullseye # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
@ -10,15 +10,16 @@ install-other-libs:
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libasound2-dev # for editor sounds
RUN apt -y install libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install unzip # for www/build.sh
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
# editor
RUN apt -y install libxkbcommon-dev
# zig
RUN wget -c https://ziglang.org/download/0.8.0/zig-linux-x86_64-0.8.0.tar.xz --no-check-certificate
RUN tar -xf zig-linux-x86_64-0.8.0.tar.xz
RUN ln -s /earthbuild/zig-linux-x86_64-0.8.0/zig /usr/bin/zig
RUN wget -c https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz --no-check-certificate
RUN tar -xf zig-linux-x86_64-0.9.1.tar.xz
RUN ln -s /earthbuild/zig-linux-x86_64-0.9.1/zig /bin/zig
# zig builtins wasm tests
RUN apt -y install build-essential
RUN cargo install wasmer-cli --features "singlepass"
@ -26,11 +27,10 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN apt -y install lsb-release software-properties-common gnupg
RUN wget https://apt.llvm.org/llvm.sh
RUN chmod +x llvm.sh
RUN ./llvm.sh 12
RUN ln -s /usr/bin/clang-12 /usr/bin/clang
RUN ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
RUN ./llvm.sh 13
RUN ln -s /usr/bin/clang-13 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-12 /usr/bin/ld.lld
RUN ln -s /usr/bin/lld-13 /usr/bin/ld.lld
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
# valgrind
RUN apt -y install valgrind
@ -53,21 +53,26 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
COPY --dir bindgen cli cli_utils compiler docs docs_cli editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm repl_www roc_std vendor examples linker Cargo.toml Cargo.lock version.txt www wasi-libc-sys ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh && ./run-wasm-tests.sh
check-clippy:
build-rust-test:
FROM +copy-dirs
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats
check-clippy:
FROM +build-rust-test
RUN cargo clippy -V
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs
FROM +build-rust-test
RUN cargo fmt --version
RUN cargo fmt --all -- --check
@ -77,7 +82,7 @@ check-typos:
RUN typos
test-rust:
FROM +copy-dirs
FROM +build-rust-test
ENV RUST_BACKTRACE=1
# for race condition problem with cli test
ENV ROC_NUM_WORKERS=1
@ -100,6 +105,9 @@ test-rust:
# RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
# RUN --mount=type=cache,target=$SCCACHE_DIR \
# cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
# make sure doc generation works (that is, make sure build.sh returns status code 0)
RUN bash www/build.sh
verify-no-git-changes:
FROM +test-rust

View File

@ -230,232 +230,6 @@ THE SOFTWARE.
===========================================================
* LLVM - https://llvm.org
This source code can be found in ci/install-ci-libraries.sh and is licensed under the following terms:
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
===========================================================
* Volta - https://github.com/volta-cli/volta
This source code can be found in cli/tests/helpers.rs and is licensed under the following terms:

View File

@ -81,7 +81,7 @@ The core Roc language and standard library include no I/O operations, which give
## Project Goals
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself.
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/roc) is in even earlier stages than the compiler itself.
Besides the above language design, a separate goal is for Roc to ship with an ambitiously boundary-pushing graphical editor. Not like "an IDE," but rather something that makes people say "I have never seen anything remotely like this outside of Bret Victor demos."

View File

@ -115,18 +115,19 @@ Create a new file called `Hello.roc` and put this inside it:
```coffee
app "hello"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides [ main ] to pf
main = Stdout.line "I'm a Roc application!"
```
> **NOTE:** This assumes you've put Hello.roc in the root directory of the
> Roc source code. If you'd like to put it somewhere else, you'll need to replace
> `"examples/cli/"` with the path to the `examples/cli/` folder in
> that source code. In the future, Roc will have the tutorial built in, and this
> aside will no longer be necessary!
> **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc
> source code. If you'd like to put it somewhere else, you'll need to replace
> `"examples/interactive/cli-platform"` with the path to the
> `examples/interactive/cli-platform` folder in that source code. In the future,
> Roc will have the tutorial built in, and this aside will no longer be
> necessary!
Try running this with:
@ -1202,6 +1203,24 @@ and also `Num.cos 1` and have them all work as expected; the number literal `1`
`Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason,
you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`.
### Typed Number Literals
When writing a number literal in Roc you can specify the numeric type as a suffix of the literal.
`1u8` specifies `1` as an unsigned 8-bit integer, `5i32` specifies `5` as a signed 32-bit integer, etc.
The full list of possible suffixes includes:
`i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `nat`, `f32`, `f64`, `dec`
### Hexadecimal Integer Literals
Integer literals can be written in hexadecimal form by prefixing with `0x` followed by hexadecimal characters.
`0xFE` evaluates to decimal `254`
The integer type can be specified as a suffix to the hexadecimal literal,
so `0xC8u8` evaluates to decimal `200` as an unsigned 8-bit integer.
### Binary Integer Literals
Integer literals can be written in binary form by prefixing with `0b` followed by the 1's and 0's representing
each bit. `0b0000_1000` evaluates to decimal `8`
The integer type can be specified as a suffix to the binary literal,
so `0b0100u8` evaluates to decimal `4` as an unsigned 8-bit integer.
## Interface modules
[ This part of the tutorial has not been written yet. Coming soon! ]
@ -1236,7 +1255,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee
app "hello"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides main to pf
```
@ -1254,14 +1273,14 @@ without running it by running `roc build Hello.roc`.
The remaining lines all involve the *platform* this application is built on:
```coffee
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides main to pf
```
The `packages { pf: "examples/cli/platform" }` part says two things:
The `packages { pf: "examples/interactive/cli-platform" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"`
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"`
- We're going to name that package `pf` so we can refer to it more concisely in the future.
The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module
@ -1281,17 +1300,18 @@ calling a function named `line` which is exposed by a module named
When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout`
module comes from the `pf` package.
Since `pf` was the name we chose for the `examples/cli/platform` package
(when we wrote `packages { pf: "examples/cli/platform" }`), this `imports` line
tells the Roc compiler that when we call `Stdout.line`, it should look for that
`line` function in the `Stdout` module of the `examples/cli/platform` package.
Since `pf` was the name we chose for the `examples/interactive/cli-platform`
package (when we wrote `packages { pf: "examples/interactive/cli-platform" }`),
this `imports` line tells the Roc compiler that when we call `Stdout.line`, it
should look for that `line` function in the `Stdout` module of the
`examples/interactive/cli-platform` package.
# Building a Command-Line Interface (CLI)
## Tasks
Tasks are technically not part of the Roc language, but they're very common in
platforms. Let's use the CLI platform in `examples/cli` as an example!
platforms. Let's use the CLI platform in `examples/interactive/cli-platform` as an example!
In the CLI platform, we have four operations we can do:
@ -1306,7 +1326,7 @@ First, let's do a basic "Hello World" using the tutorial app.
```coffee
app "cli-tutorial"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides [ main ] to pf
@ -1343,7 +1363,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
```swift
app "cli-tutorial"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout, pf.Stdin, pf.Task ]
provides [ main ] to pf
@ -1393,7 +1413,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```haskell
app "cli-tutorial"
packages { pf: "examples/cli/platform" }
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ]
provides [ main ] to pf
@ -1944,7 +1964,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
| `a // b` | `Num.divTrunc a b` |
| `a ^ b` | `Num.pow a b` |
| `a % b` | `Num.rem a b` |
| `a %% b` | `Num.mod a b` |
| `a >> b` | `Num.shr a b` |
| `a << b` | `Num.shl a b` |
| `-a` | `Num.neg a` |

View File

@ -7,6 +7,7 @@ use roc_can::operator::desugar_def;
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::symbol::IdentIdsByModule;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::pattern::PatternType;
@ -48,7 +49,7 @@ pub fn canonicalize_module_defs<'a>(
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: MutMap<ModuleId, IdentIds>,
dep_idents: IdentIdsByModule,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
mut exposed_symbols: MutSet<Symbol>,

View File

@ -27,7 +27,7 @@ use crate::{
},
env::Env,
},
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
/// A presence constraint is an additive constraint that defines the lower bound
@ -245,32 +245,13 @@ pub fn constrain_expr<'a>(
exists(arena, field_vars, And(constraints))
}
}
Expr2::GlobalTag {
Expr2::Tag {
variant_var,
ext_var,
name,
arguments,
} => {
let tag_name = TagName::Global(name.as_str(env.pool).into());
constrain_tag(
arena,
env,
expected,
region,
tag_name,
arguments,
*ext_var,
*variant_var,
)
}
Expr2::PrivateTag {
name,
arguments,
ext_var,
variant_var,
} => {
let tag_name = TagName::Private(*name);
let tag_name = TagName::Tag(name.as_str(env.pool).into());
constrain_tag(
arena,
@ -678,24 +659,28 @@ pub fn constrain_expr<'a>(
for (index, when_branch_id) in branches.iter_node_ids().enumerate() {
let when_branch = env.pool.get(when_branch_id);
let pattern_region = region;
// let pattern_region = Region::across_all(
// when_branch.patterns.iter(env.pool).map(|v| &v.region),
// );
let pattern_expected = |sub_pattern, sub_region| {
PExpected::ForReason(
PReason::WhenMatch {
index: HumanIndex::zero_based(index),
sub_pattern,
},
cond_type.shallow_clone(),
sub_region,
)
};
let branch_con = constrain_when_branch(
arena,
env,
// TODO: when_branch.value.region,
region,
when_branch,
PExpected::ForReason(
PReason::WhenMatch {
index: HumanIndex::zero_based(index),
},
cond_type.shallow_clone(),
pattern_region,
),
pattern_expected,
Expected::FromAnnotation(
name.clone(),
*arity,
@ -722,22 +707,26 @@ pub fn constrain_expr<'a>(
for (index, when_branch_id) in branches.iter_node_ids().enumerate() {
let when_branch = env.pool.get(when_branch_id);
let pattern_region = region;
// let pattern_region =
// Region::across_all(when_branch.patterns.iter().map(|v| &v.region));
let pattern_expected = |sub_pattern, sub_region| {
PExpected::ForReason(
PReason::WhenMatch {
index: HumanIndex::zero_based(index),
sub_pattern,
},
cond_type.shallow_clone(),
sub_region,
)
};
let branch_con = constrain_when_branch(
arena,
env,
region,
when_branch,
PExpected::ForReason(
PReason::WhenMatch {
index: HumanIndex::zero_based(index),
},
cond_type.shallow_clone(),
pattern_region,
),
pattern_expected,
Expected::ForReason(
Reason::WhenBranch {
index: HumanIndex::zero_based(index),
@ -1296,7 +1285,7 @@ fn constrain_when_branch<'a>(
env: &mut Env,
region: Region,
when_branch: &WhenBranch,
pattern_expected: PExpected<Type2>,
pattern_expected: impl Fn(HumanIndex, Region) -> PExpected<Type2>,
expr_expected: Expected<Type2>,
) -> Constraint<'a> {
let when_expr = env.pool.get(when_branch.body);
@ -1311,16 +1300,22 @@ fn constrain_when_branch<'a>(
// TODO investigate for error messages, is it better to unify all branches with a variable,
// then unify that variable with the expectation?
for pattern_id in when_branch.patterns.iter_node_ids() {
for (sub_pattern, pattern_id) in when_branch.patterns.iter_node_ids().enumerate() {
let pattern = env.pool.get(pattern_id);
let pattern_expected = pattern_expected(
HumanIndex::zero_based(sub_pattern),
// TODO: use the proper subpattern region. Not available to us right now.
region,
);
constrain_pattern(
arena,
env,
pattern,
// loc_pattern.region,
region,
pattern_expected.shallow_clone(),
pattern_expected,
&mut state,
true,
);
@ -1609,34 +1604,13 @@ pub fn constrain_pattern<'a>(
state.constraints.push(whole_con);
state.constraints.push(record_con);
}
GlobalTag {
Tag {
whole_var,
ext_var,
tag_name: name,
arguments,
} => {
let tag_name = TagName::Global(name.as_str(env.pool).into());
constrain_tag_pattern(
arena,
env,
region,
expected,
state,
*whole_var,
*ext_var,
arguments,
tag_name,
destruct_position,
);
}
PrivateTag {
whole_var,
ext_var,
tag_name: name,
arguments,
} => {
let tag_name = TagName::Private(*name);
let tag_name = TagName::Tag(name.as_str(env.pool).into());
constrain_tag_pattern(
arena,
@ -1871,8 +1845,8 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 {
let num_num_id = pool.add(num_num_type);
Type2::Alias(
Symbol::NUM_FLOAT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
Symbol::NUM_FRAC,
PoolVec::new(vec![range].into_iter(), pool),
num_num_id,
)
}
@ -1881,21 +1855,11 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 {
fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 {
let range_type = pool.get(range);
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = range_type.shallow_clone();
Type2::Alias(
Type2::Opaque(
Symbol::NUM_FLOATINGPOINT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
pool.add(alias_content),
)
}
@ -1910,44 +1874,23 @@ fn num_int(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Alias(
Symbol::NUM_INT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
num_num_id,
)
}
#[inline(always)]
fn _num_signed64(pool: &mut Pool) -> Type2 {
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_SIGNED64),
PoolVec::empty(pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
Type2::Alias(
Symbol::NUM_SIGNED64,
PoolVec::empty(pool),
pool.add(alias_content),
pool.add(Type2::EmptyTagUnion),
)
}
#[inline(always)]
fn num_unsigned32(pool: &mut Pool) -> Type2 {
let alias_content = Type2::TagUnion(
PoolVec::new(
std::iter::once((
TagName::Private(Symbol::NUM_UNSIGNED32),
PoolVec::empty(pool),
)),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = Type2::EmptyTagUnion;
Type2::Alias(
Symbol::NUM_UNSIGNED32,
@ -1960,21 +1903,11 @@ fn num_unsigned32(pool: &mut Pool) -> Type2 {
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
let range_type = pool.get(range);
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_INTEGER),
PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = range_type.shallow_clone();
Type2::Alias(
Type2::Opaque(
Symbol::NUM_INTEGER,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
pool.add(alias_content),
)
}
@ -1983,24 +1916,11 @@ fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 {
let range_type = pool.get(type_id);
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_NUM),
PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = range_type.shallow_clone();
Type2::Alias(
Type2::Opaque(
Symbol::NUM_NUM,
PoolVec::new(
vec![(PoolStr::new("range", pool), type_id)].into_iter(),
pool,
),
PoolVec::new(vec![type_id].into_iter(), pool),
pool.add(alias_content),
)
}
@ -2017,7 +1937,7 @@ pub mod test_constrain {
use roc_parse::parser::{SourceError, SyntaxError};
use roc_region::all::Region;
use roc_types::{
pretty_print::{content_to_string, name_all_type_vars},
pretty_print::name_and_print_var,
solved_types::Solved,
subs::{Subs, VarStore, Variable},
};
@ -2130,11 +2050,6 @@ pub mod test_constrain {
let subs = solved.inner_mut();
// name type vars
name_all_type_vars(var, subs);
let content = subs.get_content_without_compacting(var);
// Connect the ModuleId to it's IdentIds
dep_idents.insert(mod_id, env.ident_ids);
@ -2143,7 +2058,7 @@ pub mod test_constrain {
all_ident_ids: dep_idents,
};
let actual_str = content_to_string(content, subs, mod_id, &interns);
let actual_str = name_and_print_var(var, subs, mod_id, &interns);
assert_eq!(actual_str, expected_str);
}
@ -2275,7 +2190,7 @@ pub mod test_constrain {
}
#[test]
fn constrain_global_tag() {
fn constrain_tag() {
infer_eq(
indoc!(
r#"
@ -2286,18 +2201,6 @@ pub mod test_constrain {
)
}
#[test]
fn constrain_private_tag() {
infer_eq(
indoc!(
r#"
@Foo
"#
),
"[ @Foo ]*",
)
}
#[test]
fn constrain_call_and_accessor() {
infer_eq(

View File

@ -148,18 +148,12 @@ pub enum Expr2 {
},
// Sum Types
GlobalTag {
Tag {
name: PoolStr, // 4B
variant_var: Variable, // 4B
ext_var: Variable, // 4B
arguments: PoolVec<(Variable, ExprId)>, // 8B
},
PrivateTag {
name: Symbol, // 8B
variant_var: Variable, // 4B
ext_var: Variable, // 4B
arguments: PoolVec<(Variable, ExprId)>, // 8B
},
Blank, // Rendered as empty box in editor
// Compiles, but will crash if reached

View File

@ -173,10 +173,10 @@ pub fn expr_to_expr2<'a>(
(expr, output)
}
GlobalTag(tag) => {
// a global tag without any arguments
Tag(tag) => {
// a tag without any arguments
(
Expr2::GlobalTag {
Expr2::Tag {
name: PoolStr::new(tag, env.pool),
variant_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
@ -185,20 +185,6 @@ pub fn expr_to_expr2<'a>(
Output::default(),
)
}
PrivateTag(name) => {
// a private tag without any arguments
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
let name = Symbol::new(env.home, ident_id);
(
Expr2::PrivateTag {
name,
variant_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
arguments: PoolVec::empty(env.pool),
},
Output::default(),
)
}
RecordUpdate {
fields,
@ -557,23 +543,12 @@ pub fn expr_to_expr2<'a>(
// We can't call a runtime error; bail out by propagating it!
return (fn_expr, output);
}
Expr2::GlobalTag {
Expr2::Tag {
variant_var,
ext_var,
name,
..
} => Expr2::GlobalTag {
variant_var,
ext_var,
name,
arguments: args,
},
Expr2::PrivateTag {
variant_var,
ext_var,
name,
..
} => Expr2::PrivateTag {
} => Expr2::Tag {
variant_var,
ext_var,
name,

View File

@ -41,18 +41,12 @@ pub enum Pattern2 {
StrLiteral(PoolStr), // 8B
CharacterLiteral(char), // 4B
Underscore, // 0B
GlobalTag {
Tag {
whole_var: Variable, // 4B
ext_var: Variable, // 4B
tag_name: PoolStr, // 8B
arguments: PoolVec<(Variable, PatternId)>, // 8B
},
PrivateTag {
whole_var: Variable, // 4B
ext_var: Variable, // 4B
tag_name: Symbol, // 8B
arguments: PoolVec<(Variable, PatternId)>, // 8B
},
RecordDestructure {
whole_var: Variable, // 4B
ext_var: Variable, // 4B
@ -271,26 +265,15 @@ pub fn to_pattern2<'a>(
ptype => unsupported_pattern(env, ptype, region),
},
GlobalTag(name) => {
Tag(name) => {
// Canonicalize the tag's name.
Pattern2::GlobalTag {
Pattern2::Tag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: PoolStr::new(name, env.pool),
arguments: PoolVec::empty(env.pool),
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
// Canonicalize the tag's name.
Pattern2::PrivateTag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: Symbol::new(env.home, ident_id),
arguments: PoolVec::empty(env.pool),
}
}
OpaqueRef(..) => todo_opaques!(),
@ -313,22 +296,12 @@ pub fn to_pattern2<'a>(
}
match tag.value {
GlobalTag(name) => Pattern2::GlobalTag {
Tag(name) => Pattern2::Tag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: PoolStr::new(name, env.pool),
arguments: can_patterns,
},
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
Pattern2::PrivateTag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: Symbol::new(env.home, ident_id),
arguments: can_patterns,
}
}
_ => unreachable!("Other patterns cannot be applied"),
}
}
@ -506,7 +479,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
symbols.push(*symbol);
}
GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => {
Tag { arguments, .. } => {
for (_, pat_id) in arguments.iter(pool) {
let pat = pool.get(*pat_id);
stack.push(pat);
@ -543,7 +516,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult<String> {
match pattern {
Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()),
Pattern2::Identifier(symbol) => Ok(symbol.as_str(interns).to_string()),
other => UnexpectedPattern2Variant {
required_pattern2: "Identifier".to_string(),
encountered_pattern2: format!("{:?}", other),
@ -567,7 +540,7 @@ pub fn symbols_and_variables_from_pattern(
symbols.push((*symbol, variable));
}
GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => {
Tag { arguments, .. } => {
for (var, pat_id) in arguments.iter(pool) {
let pat = pool.get(*pat_id);
stack.push((*var, pat));

View File

@ -19,11 +19,14 @@ use crate::mem_pool::shallow_clone::ShallowClone;
pub type TypeId = NodeId<Type2>;
const TYPE2_SIZE: () = assert!(std::mem::size_of::<Type2>() == 3 * 8 + 4);
#[derive(Debug)]
pub enum Type2 {
Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
Alias(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
Opaque(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 24B
@ -46,11 +49,6 @@ pub enum Type2 {
Erroneous(Problem2), // 24B
}
#[test]
fn type2_size() {
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
}
#[derive(Debug)]
pub enum Problem2 {
CanonicalizationProblem,
@ -74,6 +72,9 @@ impl ShallowClone for Type2 {
Self::Alias(symbol, args, alias_type_id) => {
Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone())
}
Self::Opaque(symbol, args, alias_type_id) => {
Self::Opaque(*symbol, args.shallow_clone(), alias_type_id.clone())
}
Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()),
Self::Function(args, closure_type_id, ret_type_id) => Self::Function(
args.shallow_clone(),
@ -101,7 +102,7 @@ impl Type2 {
Variable(v) => {
result.insert(*v);
}
Alias(_, _, actual) | AsAlias(_, _, actual) => {
Alias(_, _, actual) | AsAlias(_, _, actual) | Opaque(_, _, actual) => {
stack.push(pool.get(*actual));
}
HostExposedAlias {
@ -690,29 +691,14 @@ fn can_tags<'a>(
// a duplicate
let new_name = 'inner: loop {
match tag {
Tag::Global { name, args } => {
Tag::Apply { name, args } => {
let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool);
for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) {
as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region);
}
let tag_name = TagName::Global(name.value.into());
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
}
Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
let symbol = Symbol::new(env.home, ident_id);
let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool);
for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) {
as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region);
}
let tag_name = TagName::Private(symbol);
let tag_name = TagName::Tag(name.value.into());
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
@ -747,7 +733,7 @@ fn can_tags<'a>(
enum TypeApply {
Apply(Symbol, PoolVec<Type2>),
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId),
Alias(Symbol, PoolVec<TypeId>, TypeId),
Erroneous(roc_types::types::Problem),
}
@ -849,7 +835,17 @@ fn to_type_apply<'a>(
// instantiate variables
Type2::substitute(env.pool, &substitutions, actual);
TypeApply::Alias(symbol, arguments, actual)
let type_arguments = PoolVec::with_capacity(arguments.len() as u32, env.pool);
for (node_id, type_id) in arguments
.iter_node_ids()
.zip(type_arguments.iter_node_ids())
{
let typ = env.pool[node_id].1;
env.pool[type_id] = typ;
}
TypeApply::Alias(symbol, type_arguments, actual)
}
None => TypeApply::Apply(symbol, argument_type_ids),
}

View File

@ -2,7 +2,7 @@ use crate::mem_pool::pool::{NodeId, Pool};
use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::VarStore;
@ -19,7 +19,7 @@ pub struct Env<'a> {
pub problems: BumpVec<'a, Problem>,
pub dep_idents: MutMap<ModuleId, IdentIds>,
pub dep_idents: IdentIdsByModule,
pub module_ids: &'a ModuleIds,
pub ident_ids: IdentIds,
pub exposed_ident_ids: IdentIds,
@ -41,7 +41,7 @@ impl<'a> Env<'a> {
arena: &'a Bump,
pool: &'a mut Pool,
var_store: &'a mut VarStore,
dep_idents: MutMap<ModuleId, IdentIds>,
dep_idents: IdentIdsByModule,
module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds,
) -> Env<'a> {
@ -129,8 +129,8 @@ impl<'a> Env<'a> {
region,
},
self.ident_ids
.idents()
.map(|(_, string)| string.as_ref().into())
.ident_strs()
.map(|(_, string)| string.into())
.collect(),
)),
}
@ -146,11 +146,11 @@ impl<'a> Env<'a> {
}
None => {
let exposed_values = exposed_ids
.idents()
.ident_strs()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
ident.starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.map(|(_, ident)| Lowercase::from(ident))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,

View File

@ -12,7 +12,8 @@ use crate::mem_pool::shallow_clone::ShallowClone;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{
get_module_ident_ids, get_module_ident_ids_mut, IdentIds, Interns, ModuleId, Symbol,
get_module_ident_ids, get_module_ident_ids_mut, IdentIds, IdentIdsByModule, Interns, ModuleId,
Symbol,
};
use roc_problem::can::RuntimeError;
use roc_region::all::{Loc, Region};
@ -47,7 +48,7 @@ fn to_type2(
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => {
let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool);
for (type_variable_node_id, (lowercase, solved_arg)) in type_variables
for (type_variable_node_id, solved_arg) in type_variables
.iter_node_ids()
.zip(solved_type_variables.iter())
{
@ -55,7 +56,7 @@ fn to_type2(
let node = pool.add(typ2);
pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node);
pool[type_variable_node_id] = node;
}
let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store);
@ -245,7 +246,7 @@ impl Scope {
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone().into()),
None => all_ident_ids.add_str(ident.as_str()),
};
let symbol = Symbol::new(self.home, ident_id);
@ -262,7 +263,7 @@ impl Scope {
///
/// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol {
let ident_id = all_ident_ids.add(ident.into());
let ident_id = all_ident_ids.add_str(ident.as_str());
Symbol::new(self.home, ident_id)
}
@ -320,16 +321,12 @@ impl Scope {
self.aliases.contains_key(&name)
}
pub fn fill_scope(
&mut self,
env: &Env,
all_ident_ids: &mut MutMap<ModuleId, IdentIds>,
) -> ASTResult<()> {
pub fn fill_scope(&mut self, env: &Env, all_ident_ids: &mut IdentIdsByModule) -> ASTResult<()> {
let ident_ids = get_module_ident_ids(all_ident_ids, &env.home)?.clone();
for (_, ident_ref) in ident_ids.idents() {
for (_, ident_ref) in ident_ids.ident_strs() {
self.introduce(
ident_ref.as_inline_str().as_str().into(),
ident_ref.into(),
&env.exposed_ident_ids,
get_module_ident_ids_mut(all_ident_ids, &env.home)?,
Region::zero(),

View File

@ -1,10 +1,9 @@
use bumpalo::Bump;
use roc_load::{LoadedModule, Threading};
use roc_target::TargetInfo;
use std::path::Path;
use bumpalo::Bump;
use roc_load::LoadedModule;
use roc_target::TargetInfo;
pub fn load_module(src_file: &Path) -> LoadedModule {
pub fn load_module(src_file: &Path, threading: Threading) -> LoadedModule {
let subs_by_module = Default::default();
let arena = Bump::new();
@ -20,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
subs_by_module,
TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::ColorTerminal,
threading,
);
match loaded {

View File

@ -3,6 +3,7 @@
use bumpalo::Bump;
use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
@ -868,7 +869,7 @@ fn type_to_variable<'a>(
register(subs, rank, pools, content)
}
Alias(symbol, args, alias_type_id) => {
Alias(symbol, args, alias_type_id) | Opaque(symbol, args, alias_type_id) => {
// TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var!
// Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n)
// different variables (once for each occurrence). The recursion restriction is required
@ -898,7 +899,7 @@ fn type_to_variable<'a>(
let mut arg_vars = Vec::with_capacity(args.len());
for (_, arg_type_id) in args.iter(mempool) {
for arg_type_id in args.iter(mempool) {
let arg_type = mempool.get(*arg_type_id);
let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type);
@ -910,8 +911,12 @@ fn type_to_variable<'a>(
let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type);
// TODO(opaques): take opaques into account
let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural);
let kind = match typ {
Alias(..) => AliasKind::Structural,
Opaque(..) => AliasKind::Opaque,
_ => internal_error!(),
};
let content = Content::Alias(*symbol, arg_vars, alias_var, kind);
let result = register(subs, rank, pools, content);

36
bindgen/Cargo.toml Normal file
View File

@ -0,0 +1,36 @@
[package]
name = "roc-bindgen"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
edition = "2018"
description = "A CLI for roc-bindgen"
[[bin]]
name = "roc-bindgen"
path = "src/main.rs"
test = false
bench = false
[dependencies]
roc_std = { path = "../roc_std" }
roc_can = { path = "../compiler/can" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_reporting = { path = "../reporting" }
roc_types = { path = "../compiler/types" }
roc_builtins = { path = "../compiler/builtins" }
roc_module = { path = "../compiler/module" }
roc_collections = { path = "../compiler/collections" }
roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] }
ven_graph = { path = "../vendor/pathfinding" }
target-lexicon = "0.12.3"
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions", "derive"] }
[dev-dependencies]
pretty_assertions = "1.0.0"
indoc = "1.0.3"
tempfile = "3.2.0"

340
bindgen/src/bindgen.rs Normal file
View File

@ -0,0 +1,340 @@
use std::convert::TryInto;
use crate::structs::Structs;
use crate::types::{TypeId, Types};
use crate::{enums::Enums, types::RocType};
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth::*, IntWidth::*};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::layout::{cmp_fields, ext_var_is_empty_tag_union, Builtin, Layout, LayoutCache};
use roc_types::subs::UnionTags;
use roc_types::{
subs::{Content, FlatType, Subs, Variable},
types::RecordField,
};
pub struct Env<'a> {
pub arena: &'a Bump,
pub subs: &'a Subs,
pub layout_cache: &'a mut LayoutCache<'a>,
pub interns: &'a Interns,
pub struct_names: Structs,
pub enum_names: Enums,
}
pub fn add_type<'a>(env: &mut Env<'a>, var: Variable, types: &mut Types) -> TypeId {
let layout = env
.layout_cache
.from_var(env.arena, var, env.subs)
.expect("Something weird ended up in the content");
add_type_help(env, layout, var, None, types)
}
pub fn add_type_help<'a>(
env: &mut Env<'a>,
layout: Layout<'a>,
var: Variable,
opt_name: Option<Symbol>,
types: &mut Types,
) -> TypeId {
let subs = env.subs;
match subs.get_content_without_compacting(var) {
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::RecursionVar { .. } => {
todo!("TODO give a nice error message for a non-concrete type being passed to the host")
}
Content::Structure(FlatType::Record(fields, ext)) => {
let it = fields
.unsorted_iterator(subs, *ext)
.expect("something weird in content")
.flat_map(|(label, field)| {
match field {
RecordField::Required(field_var) | RecordField::Demanded(field_var) => {
Some((label.clone(), field_var))
}
RecordField::Optional(_) => {
// drop optional fields
None
}
}
});
let name = match opt_name {
Some(sym) => sym.as_str(env.interns).to_string(),
None => env.struct_names.get_name(var),
};
add_struct(env, name, it, types)
}
Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
debug_assert!(ext_var_is_empty_tag_union(subs, *ext_var));
add_tag_union(env, opt_name, tags, var, types)
}
Content::Structure(FlatType::Apply(symbol, _)) => {
if symbol.is_builtin() {
match layout {
Layout::Builtin(builtin) => {
add_builtin_type(env, builtin, var, opt_name, types)
}
_ => {
unreachable!()
}
}
} else {
todo!("Handle non-builtin Apply")
}
}
Content::Structure(FlatType::Func(_, _, _)) => {
todo!()
}
Content::Structure(FlatType::FunctionOrTagUnion(_, _, _)) => {
todo!()
}
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!()
}
Content::Structure(FlatType::Erroneous(_)) => todo!(),
Content::Structure(FlatType::EmptyRecord) => todo!(),
Content::Structure(FlatType::EmptyTagUnion) => {
// This can happen when unwrapping a tag union; don't do anything.
todo!()
}
Content::Alias(name, _, real_var, _) => {
if name.is_builtin() {
match layout {
Layout::Builtin(builtin) => {
add_builtin_type(env, builtin, var, opt_name, types)
}
_ => {
unreachable!()
}
}
} else {
// If this was a non-builtin type alias, we can use that alias name
// in the generated bindings.
add_type_help(env, layout, *real_var, Some(*name), types)
}
}
Content::RangedNumber(_, _) => todo!(),
Content::Error => todo!(),
}
}
pub fn add_builtin_type<'a>(
env: &mut Env<'a>,
builtin: Builtin<'a>,
var: Variable,
opt_name: Option<Symbol>,
types: &mut Types,
) -> TypeId {
match builtin {
Builtin::Int(width) => match width {
U8 => types.add(RocType::U8),
U16 => types.add(RocType::U16),
U32 => types.add(RocType::U32),
U64 => types.add(RocType::U64),
U128 => types.add(RocType::U128),
I8 => types.add(RocType::I8),
I16 => types.add(RocType::I16),
I32 => types.add(RocType::I32),
I64 => types.add(RocType::I64),
I128 => types.add(RocType::I128),
},
Builtin::Float(width) => match width {
F32 => types.add(RocType::F32),
F64 => types.add(RocType::F64),
F128 => types.add(RocType::F128),
},
Builtin::Bool => types.add(RocType::Bool),
Builtin::Decimal => types.add(RocType::RocDec),
Builtin::Str => types.add(RocType::RocStr),
Builtin::Dict(key_layout, val_layout) => {
// TODO FIXME this `var` is wrong - should have a different `var` for key and for val
let key_id = add_type_help(env, *key_layout, var, opt_name, types);
let val_id = add_type_help(env, *val_layout, var, opt_name, types);
let dict_id = types.add(RocType::RocDict(key_id, val_id));
types.depends(dict_id, key_id);
types.depends(dict_id, val_id);
dict_id
}
Builtin::Set(elem_layout) => {
let elem_id = add_type_help(env, *elem_layout, var, opt_name, types);
let set_id = types.add(RocType::RocSet(elem_id));
types.depends(set_id, elem_id);
set_id
}
Builtin::List(elem_layout) => {
let elem_id = add_type_help(env, *elem_layout, var, opt_name, types);
let list_id = types.add(RocType::RocList(elem_id));
types.depends(list_id, elem_id);
list_id
}
}
}
fn add_struct<I: IntoIterator<Item = (Lowercase, Variable)>>(
env: &mut Env<'_>,
name: String,
fields: I,
types: &mut Types,
) -> TypeId {
let subs = env.subs;
let fields_iter = fields.into_iter();
let mut sortables = bumpalo::collections::Vec::with_capacity_in(
fields_iter.size_hint().1.unwrap_or_default(),
env.arena,
);
for (label, field_var) in fields_iter {
sortables.push((
label,
field_var,
env.layout_cache
.from_var(env.arena, field_var, subs)
.unwrap(),
));
}
sortables.sort_by(|(label1, _, layout1), (label2, _, layout2)| {
cmp_fields(
label1,
layout1,
label2,
layout2,
env.layout_cache.target_info,
)
});
let fields = sortables
.into_iter()
.map(|(label, field_var, field_layout)| {
(
label.to_string(),
add_type_help(env, field_layout, field_var, None, types),
)
})
.collect();
types.add(RocType::Struct { name, fields })
}
fn add_tag_union(
env: &mut Env<'_>,
opt_name: Option<Symbol>,
union_tags: &UnionTags,
var: Variable,
types: &mut Types,
) -> TypeId {
let subs = env.subs;
let mut tags: Vec<(String, Vec<Variable>)> = union_tags
.iter_from_subs(subs)
.map(|(tag_name, payload_vars)| {
let name_str = match tag_name {
TagName::Tag(uppercase) => uppercase.as_str().to_string(),
TagName::Closure(_) => unreachable!(),
};
(name_str, payload_vars.to_vec())
})
.collect();
if tags.len() == 1 {
let (tag_name, payload_vars) = tags.pop().unwrap();
// If there was a type alias name, use that. Otherwise use the tag name.
let name = match opt_name {
Some(sym) => sym.as_str(env.interns).to_string(),
None => tag_name,
};
return match payload_vars.len() {
0 => {
// This is a single-tag union with no payload, e.g. `[ Foo ]`
// so just generate an empty record
types.add(RocType::Struct {
name,
fields: Vec::new(),
})
}
1 => {
// This is a single-tag union with 1 payload field, e.g.`[ Foo Str ]`.
// We'll just wrap that.
let var = *payload_vars.get(0).unwrap();
let content = add_type(env, var, types);
types.add(RocType::TransparentWrapper { name, content })
}
_ => {
// This is a single-tag union with multiple payload field, e.g.`[ Foo Str U32 ]`.
// Generate a record.
let fields = payload_vars.iter().enumerate().map(|(index, payload_var)| {
let field_name = format!("f{}", index).into();
(field_name, *payload_var)
});
add_struct(env, name, fields, types)
}
};
}
let name = match opt_name {
Some(sym) => sym.as_str(env.interns).to_string(),
None => env.enum_names.get_name(var),
};
// Sort tags alphabetically by tag name
tags.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
let tags = tags
.into_iter()
.map(|(tag_name, payload_vars)| {
let payloads = payload_vars
.iter()
.map(|payload_var| add_type(env, *payload_var, types))
.collect::<Vec<TypeId>>();
(tag_name, payloads)
})
.collect();
let typ = match env.layout_cache.from_var(env.arena, var, subs).unwrap() {
Layout::Struct { .. } => {
// a single-tag union with multiple payload values, e.g. [ Foo Str Str ]
unreachable!()
}
Layout::Union(_) => todo!(),
Layout::Builtin(builtin) => match builtin {
Builtin::Int(int_width) => RocType::TagUnion {
tag_bytes: int_width.stack_size().try_into().unwrap(),
name,
tags,
},
Builtin::Bool => RocType::Bool,
Builtin::Float(_)
| Builtin::Decimal
| Builtin::Str
| Builtin::Dict(_, _)
| Builtin::Set(_)
| Builtin::List(_) => unreachable!(),
},
Layout::Boxed(_) | Layout::LambdaSet(_) | Layout::RecursivePointer => {
unreachable!()
}
};
types.add(typ)
}

40
bindgen/src/bindgen_c.rs Normal file
View File

@ -0,0 +1,40 @@
use std::io;
static TEMPLATE: &[u8] = include_bytes!("../templates/template.c");
pub fn write_template(writer: &mut impl io::Write) -> io::Result<()> {
writer.write_all(TEMPLATE)?;
Ok(())
}
// pub fn write_bindings(_writer: &mut impl io::Write) -> io::Result<()> {
// extern struct RocStr roc__mainForHost_1_exposed();
// int main() {
// struct RocStr str = roc__mainForHost_1_exposed();
// // Determine str_len and the str_bytes pointer,
// // taking into account the small string optimization.
// size_t str_len = roc_str_len(str);
// char* str_bytes;
// if (is_small_str(str)) {
// str_bytes = (char*)&str;
// } else {
// str_bytes = str.bytes;
// }
// // Write to stdout
// if (write(1, str_bytes, str_len) >= 0) {
// // Writing succeeded!
// return 0;
// } else {
// printf("Error writing to stdout: %s\n", strerror(errno));
// return 1;
// }
// }
// Ok(())
// }

186
bindgen/src/bindgen_rs.rs Normal file
View File

@ -0,0 +1,186 @@
use crate::types::{RocType, TypeId, Types};
use std::fmt::{self, Write};
pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs");
pub static HEADER: &[u8] = include_bytes!("../templates/header.rs");
static INDENT: &str = " ";
pub fn write_types(types: &Types, buf: &mut String) -> fmt::Result {
for id in types.sorted_ids() {
match types.get(id) {
RocType::Struct { name, fields } => write_struct(name, fields, id, types, buf)?,
RocType::TagUnion {
tags,
name,
tag_bytes,
} => {
let is_enumeration = tags.iter().all(|(_, payloads)| payloads.is_empty());
match tags.len() {
0 => {
// Empty tag unions can never come up at runtime,
// and so don't need declared types.
}
1 => {
if is_enumeration {
// A tag union with one tag is a zero-sized unit type, so
// represent it as a zero-sized struct (e.g. "struct Foo()").
write_deriving(id, types, buf)?;
buf.write_str("\nstruct ")?;
write_type_name(id, types, buf)?;
buf.write_str("();\n")?;
} else {
// if it wasn't an enumeration
// this is a newtype wrapper around something,
// so write an alias for its contents
todo!();
}
}
_ => {
if is_enumeration {
write_deriving(id, types, buf)?;
write_enum(name, tags.iter().map(|(name, _)| name), *tag_bytes, buf)?;
} else {
todo!();
}
}
}
}
RocType::RecursiveTagUnion { .. } => {
todo!();
}
// These types don't need to be declared in Rust.
RocType::U8
| RocType::U16
| RocType::U32
| RocType::U64
| RocType::U128
| RocType::I8
| RocType::I16
| RocType::I32
| RocType::I64
| RocType::I128
| RocType::F32
| RocType::F64
| RocType::F128
| RocType::Bool
| RocType::RocDec
| RocType::RocStr
| RocType::RocDict(_, _)
| RocType::RocSet(_)
| RocType::RocList(_)
| RocType::RocBox(_) => {}
RocType::TransparentWrapper { name, content } => {
write_deriving(id, types, buf)?;
write!(buf, "#[repr(transparent)]\npub struct {}(", name)?;
write_type_name(*content, types, buf)?;
buf.write_str(");\n")?;
}
}
}
Ok(())
}
fn write_enum<I: IntoIterator<Item = S>, S: AsRef<str>>(
name: &str,
tags: I,
tag_bytes: u8,
buf: &mut String,
) -> fmt::Result {
// e.g. "#[repr(u8)]\npub enum Foo {\n"
writeln!(buf, "#[repr(u{})]\npub enum {} {{", tag_bytes * 8, name)?;
for name in tags {
writeln!(buf, "{}{},", INDENT, name.as_ref())?;
}
buf.write_str("}\n")
}
fn write_struct(
name: &str,
fields: &[(String, TypeId)],
struct_id: TypeId,
types: &Types,
buf: &mut String,
) -> fmt::Result {
write_deriving(struct_id, types, buf)?;
writeln!(buf, "#[repr(C)]\npub struct {} {{", name)?;
for (label, field_id) in fields {
write!(buf, "{}{}: ", INDENT, label.as_str())?;
write_type_name(*field_id, types, buf)?;
buf.write_str(",\n")?;
}
buf.write_str("}\n")
}
fn write_type_name(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result {
match types.get(id) {
RocType::U8 => buf.write_str("u8"),
RocType::U16 => buf.write_str("u16"),
RocType::U32 => buf.write_str("u32"),
RocType::U64 => buf.write_str("u64"),
RocType::U128 => buf.write_str("u128"),
RocType::I8 => buf.write_str("i8"),
RocType::I16 => buf.write_str("i16"),
RocType::I32 => buf.write_str("i32"),
RocType::I64 => buf.write_str("i64"),
RocType::I128 => buf.write_str("i128"),
RocType::F32 => buf.write_str("f32"),
RocType::F64 => buf.write_str("f64"),
RocType::F128 => buf.write_str("f128"),
RocType::Bool => buf.write_str("bool"),
RocType::RocDec => buf.write_str("roc_std::RocDec"),
RocType::RocStr => buf.write_str("roc_std::RocStr"),
RocType::RocDict(key_id, val_id) => {
buf.write_str("roc_std::RocDict<")?;
write_type_name(*key_id, types, buf)?;
buf.write_str(", ")?;
write_type_name(*val_id, types, buf)?;
buf.write_char('>')
}
RocType::RocSet(elem_id) => {
buf.write_str("roc_std::RocSet<")?;
write_type_name(*elem_id, types, buf)?;
buf.write_char('>')
}
RocType::RocList(elem_id) => {
buf.write_str("roc_std::RocList<")?;
write_type_name(*elem_id, types, buf)?;
buf.write_char('>')
}
RocType::RocBox(elem_id) => {
buf.write_str("roc_std::RocBox<")?;
write_type_name(*elem_id, types, buf)?;
buf.write_char('>')
}
RocType::Struct { name, .. }
| RocType::TagUnion { name, .. }
| RocType::TransparentWrapper { name, .. }
| RocType::RecursiveTagUnion { name, .. } => buf.write_str(name),
}
}
fn write_deriving(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result {
let typ = types.get(id);
buf.write_str("\n#[derive(Clone, PartialEq, PartialOrd, ")?;
if !typ.has_pointer(types) {
buf.write_str("Copy, ")?;
}
if !typ.has_tag_union(types) {
buf.write_str("Default, ")?;
}
if !typ.has_float(types) {
buf.write_str("Eq, Ord, Hash, ")?;
}
buf.write_str("Debug)]\n")
}

View File

@ -0,0 +1,18 @@
use std::io;
static TEMPLATE: &[u8] = include_bytes!("../templates/template.zig");
pub fn write_template(writer: &mut impl io::Write) -> io::Result<()> {
writer.write_all(TEMPLATE)?;
Ok(())
}
pub fn write_bindings(_writer: &mut impl io::Write) -> io::Result<()> {
// extern "C" {
// #[link_name = "roc__mainForHost_1_exposed"]
// fn roc_main() -> RocStr;
// }
Ok(())
}

36
bindgen/src/enums.rs Normal file
View File

@ -0,0 +1,36 @@
use roc_collections::MutMap;
use roc_types::subs::Variable;
#[derive(Copy, Clone, Debug, Default)]
struct EnumId(u64);
impl EnumId {
pub fn to_name(self) -> String {
format!("U{}", self.0)
}
}
/// Whenever we register a new tag union type,
/// give it a unique and short name (e.g. U1, U2, U3...)
/// and then from then on, whenever we ask for that
/// same record type, return the same name.
#[derive(Default)]
pub struct Enums {
by_variable: MutMap<Variable, EnumId>,
next_id: EnumId,
}
impl Enums {
pub fn get_name(&mut self, var: Variable) -> String {
match self.by_variable.get(&var) {
Some(struct_id) => struct_id.to_name(),
None => self.next_id().to_name(),
}
}
fn next_id(&mut self) -> EnumId {
self.next_id.0 += 1;
self.next_id
}
}

8
bindgen/src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
pub mod bindgen;
pub mod bindgen_c;
pub mod bindgen_rs;
pub mod bindgen_zig;
pub mod enums;
pub mod load;
pub mod structs;
pub mod types;

105
bindgen/src/load.rs Normal file
View File

@ -0,0 +1,105 @@
use crate::bindgen::{self, Env};
use crate::types::Types;
use bumpalo::Bump;
use roc_can::{
def::{Declaration, Def},
pattern::Pattern,
};
use roc_load::{LoadedModule, Threading};
use roc_mono::layout::LayoutCache;
use roc_reporting::report::RenderTarget;
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
pub fn load_types(
full_file_path: PathBuf,
dir: &Path,
threading: Threading,
) -> Result<Types, io::Error> {
// TODO: generate both 32-bit and 64-bit #[cfg] macros if structs are different
// depending on 32-bit vs 64-bit targets.
let target_info = (&Triple::host()).into();
let arena = &Bump::new();
let subs_by_module = Default::default();
let LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
mut declarations_by_id,
mut solved,
interns,
..
} = roc_load::load_and_typecheck(
arena,
full_file_path,
dir,
subs_by_module,
target_info,
RenderTarget::Generic,
threading,
)
.expect("Problem loading platform module");
let decls = declarations_by_id.remove(&home).unwrap();
let subs = solved.inner_mut();
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
if !can_problems.is_empty() || !type_problems.is_empty() {
todo!(
"Gracefully report compilation problems during bindgen: {:?}, {:?}",
can_problems,
type_problems
);
}
let mut layout_cache = LayoutCache::new(target_info);
let mut env = Env {
arena,
layout_cache: &mut layout_cache,
interns: &interns,
struct_names: Default::default(),
enum_names: Default::default(),
subs,
};
let mut types = Types::default();
for decl in decls.into_iter() {
let defs = match decl {
Declaration::Declare(def) => {
vec![def]
}
Declaration::DeclareRec(defs) => defs,
Declaration::Builtin(..) => {
unreachable!("Builtin decl in userspace module?")
}
Declaration::InvalidCycle(..) => {
vec![]
}
};
for Def {
loc_pattern,
pattern_vars,
..
} in defs.into_iter()
{
if let Pattern::Identifier(sym) = loc_pattern.value {
let var = pattern_vars
.get(&sym)
.expect("Indetifier known but it has no var?");
bindgen::add_type(&mut env, *var, &mut types);
} else {
// figure out if we need to export non-identifier defs - when would that
// happen?
}
}
}
Ok(types)
}

124
bindgen/src/main.rs Normal file
View File

@ -0,0 +1,124 @@
use clap::Parser;
use roc_bindgen::bindgen_rs;
use roc_bindgen::load::load_types;
use roc_load::Threading;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{ErrorKind, Write};
use std::path::PathBuf;
use std::process;
/// Printed in error messages if you try to use an unsupported extension.
const SUPPORTED_EXTENSIONS: &str = ".c, .rs, .zig, and .json";
// TODO add an option for --targets so that you can specify
// e.g. 64-bit, 32-bit, *and* 16-bit (which can matter for alignment because of pointers)
#[derive(Debug, Parser)]
#[clap(about)]
struct Opts {
/// The path to the platform's Package-Config.roc file
platform_module: PathBuf,
/// The output file, e.g. `test.rs`
dest: PathBuf,
}
enum OutputType {
Rust,
C,
Zig,
Json,
}
pub fn main() {
let opts = Opts::parse();
let input_path = opts.platform_module;
let cwd = std::env::current_dir().unwrap();
let output_path = opts.dest;
let output_type = match output_path.extension().and_then(OsStr::to_str) {
Some("rs") => OutputType::Rust,
Some("c") => OutputType::C,
Some("zig") => OutputType::Zig,
Some("json") => OutputType::Json,
Some(other) => {
eprintln!(
"Unsupported output file extension: \".{}\" - currently supported extensions are {}",
other,
SUPPORTED_EXTENSIONS
);
process::exit(1);
}
None => {
eprintln!("The output file path needs to have a file extension in order to tell what output format to use. Currently supported extensions are {}", SUPPORTED_EXTENSIONS);
process::exit(1);
}
};
match load_types(input_path.clone(), &cwd, Threading::AllAvailable) {
Ok(types) => {
let mut buf;
let result = match output_type {
OutputType::Rust => {
buf = std::str::from_utf8(bindgen_rs::HEADER).unwrap().to_string();
bindgen_rs::write_types(&types, &mut buf)
}
OutputType::C => todo!("TODO: Generate bindings for C"),
OutputType::Zig => todo!("TODO: Generate bindings for Zig"),
OutputType::Json => todo!("TODO: Generate bindings for JSON"),
};
if let Err(err) = result {
eprintln!(
"Unable to generate binding string {} - {:?}",
output_path.display(),
err
);
process::exit(1);
}
let mut file = File::create(output_path.clone()).unwrap_or_else(|err| {
eprintln!(
"Unable to create output file {} - {:?}",
output_path.display(),
err
);
process::exit(1);
});
file.write_all(buf.as_bytes()).unwrap_or_else(|err| {
eprintln!(
"Unable to write bindings to output file {} - {:?}",
output_path.display(),
err
);
process::exit(1);
});
println!(
"🎉 Generated type declarations in:\n\n\t{}",
output_path.display()
);
}
Err(err) => match err.kind() {
ErrorKind::NotFound => {
eprintln!("Platform module file not found: {}", input_path.display());
process::exit(1);
}
error => {
eprintln!(
"Error loading platform module file {} - {:?}",
input_path.display(),
error
);
process::exit(1);
}
},
}
}

36
bindgen/src/structs.rs Normal file
View File

@ -0,0 +1,36 @@
use roc_collections::MutMap;
use roc_types::subs::Variable;
#[derive(Copy, Clone, Debug, Default)]
struct StructId(u64);
impl StructId {
pub fn to_name(self) -> String {
format!("R{}", self.0)
}
}
/// Whenever we register a new Roc record type,
/// give it a unique and short name (e.g. R1, R2, R3...)
/// and then from then on, whenever we ask for that
/// same record type, return the same name.
#[derive(Default)]
pub struct Structs {
by_variable: MutMap<Variable, StructId>,
next_id: StructId,
}
impl Structs {
pub fn get_name(&mut self, var: Variable) -> String {
match self.by_variable.get(&var) {
Some(struct_id) => struct_id.to_name(),
None => self.next_id().to_name(),
}
}
fn next_id(&mut self) -> StructId {
self.next_id.0 += 1;
self.next_id
}
}

319
bindgen/src/types.rs Normal file
View File

@ -0,0 +1,319 @@
use core::mem::align_of;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::VecMap;
use roc_std::RocDec;
use roc_target::TargetInfo;
use ven_graph::topological_sort;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TypeId(usize);
#[derive(Default, Debug)]
pub struct Types {
by_id: Vec<RocType>,
/// Dependencies - that is, which type depends on which other type.
/// This is important for declaration order in C; we need to output a
/// type declaration earlier in the file than where it gets referenced by another type.
deps: VecMap<TypeId, Vec<TypeId>>,
}
impl Types {
pub fn with_capacity(cap: usize) -> Self {
Self {
by_id: Vec::with_capacity(cap),
deps: VecMap::with_capacity(cap),
}
}
pub fn add(&mut self, typ: RocType) -> TypeId {
let id = TypeId(self.by_id.len());
self.by_id.push(typ);
id
}
pub fn depends(&mut self, id: TypeId, depends_on: TypeId) {
self.deps.get_or_insert(id, Vec::new).push(depends_on);
}
pub fn get(&self, id: TypeId) -> &RocType {
match self.by_id.get(id.0) {
Some(typ) => typ,
None => unreachable!(),
}
}
pub fn ids(&self) -> impl ExactSizeIterator<Item = TypeId> {
(0..self.by_id.len()).map(TypeId)
}
pub fn sorted_ids(&self) -> Vec<TypeId> {
// TODO: instead use the bitvec matrix type we use in the Roc compiler -
// it's more efficient and also would bring us one step closer to dropping
// the dependency on this topological_sort implementation!
topological_sort(self.ids(), |id| match self.deps.get(id) {
Some(dep_ids) => dep_ids.to_vec(),
None => Vec::new(),
})
.unwrap_or_else(|err| {
unreachable!("Cyclic type definitions: {:?}", err);
})
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &RocType> {
TypesIter {
types: self.by_id.as_slice(),
len: self.by_id.len(),
}
}
}
struct TypesIter<'a> {
types: &'a [RocType],
len: usize,
}
impl<'a> ExactSizeIterator for TypesIter<'a> {
fn len(&self) -> usize {
self.len
}
}
impl<'a> Iterator for TypesIter<'a> {
type Item = &'a RocType;
fn next(&mut self) -> Option<Self::Item> {
let len = self.len;
let answer = self.types.get(self.types.len() - len);
self.len = len.saturating_sub(1);
answer
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RocType {
RocStr,
Bool,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
I128,
U128,
F32,
F64,
F128,
RocDec,
RocList(TypeId),
RocDict(TypeId, TypeId),
RocSet(TypeId),
RocBox(TypeId),
RecursiveTagUnion {
name: String,
tags: Vec<(String, Vec<TypeId>)>,
},
TagUnion {
tag_bytes: u8,
name: String,
tags: Vec<(String, Vec<TypeId>)>,
},
Struct {
name: String,
fields: Vec<(String, TypeId)>,
},
/// Either a single-tag union or a single-field record
TransparentWrapper {
name: String,
content: TypeId,
},
}
impl RocType {
/// Useful when determining whether to derive Copy in a Rust type.
pub fn has_pointer(&self, types: &Types) -> bool {
match self {
RocType::Bool
| RocType::I8
| RocType::U8
| RocType::I16
| RocType::U16
| RocType::I32
| RocType::U32
| RocType::I64
| RocType::U64
| RocType::I128
| RocType::U128
| RocType::F32
| RocType::F64
| RocType::F128
| RocType::RocDec => false,
RocType::RocStr
| RocType::RocList(_)
| RocType::RocDict(_, _)
| RocType::RocSet(_)
| RocType::RocBox(_)
| RocType::RecursiveTagUnion { .. } => true,
RocType::TagUnion { tags, .. } => tags
.iter()
.any(|(_, payloads)| payloads.iter().any(|id| types.get(*id).has_pointer(types))),
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, id)| types.get(*id).has_pointer(types)),
RocType::TransparentWrapper { content, .. } => types.get(*content).has_pointer(types),
}
}
/// Useful when determining whether to derive Eq, Ord, and Hash in a Rust type.
pub fn has_float(&self, types: &Types) -> bool {
match self {
RocType::F32 | RocType::F64 | RocType::F128 => true,
RocType::RocStr
| RocType::Bool
| RocType::I8
| RocType::U8
| RocType::I16
| RocType::U16
| RocType::I32
| RocType::U32
| RocType::I64
| RocType::U64
| RocType::I128
| RocType::U128
| RocType::RocDec => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
types.get(*id).has_float(types)
}
RocType::RocDict(key_id, val_id) => {
types.get(*key_id).has_float(types) || types.get(*val_id).has_float(types)
}
RocType::RecursiveTagUnion { tags, .. } | RocType::TagUnion { tags, .. } => tags
.iter()
.any(|(_, payloads)| payloads.iter().any(|id| types.get(*id).has_float(types))),
RocType::Struct { fields, .. } => {
fields.iter().any(|(_, id)| types.get(*id).has_float(types))
}
RocType::TransparentWrapper { content, .. } => types.get(*content).has_float(types),
}
}
/// Useful when determining whether to derive Default in a Rust type.
pub fn has_tag_union(&self, types: &Types) -> bool {
match self {
RocType::RecursiveTagUnion { .. } | RocType::TagUnion { .. } => true,
RocType::RocStr
| RocType::Bool
| RocType::I8
| RocType::U8
| RocType::I16
| RocType::U16
| RocType::I32
| RocType::U32
| RocType::I64
| RocType::U64
| RocType::I128
| RocType::U128
| RocType::F32
| RocType::F64
| RocType::F128
| RocType::RocDec => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
types.get(*id).has_tag_union(types)
}
RocType::RocDict(key_id, val_id) => {
types.get(*key_id).has_tag_union(types) || types.get(*val_id).has_tag_union(types)
}
RocType::Struct { fields, .. } => fields
.iter()
.any(|(_, id)| types.get(*id).has_tag_union(types)),
RocType::TransparentWrapper { content, .. } => types.get(*content).has_tag_union(types),
}
}
pub fn alignment(&self, types: &Types, target_info: TargetInfo) -> usize {
match self {
RocType::RocStr
| RocType::RocList(_)
| RocType::RocDict(_, _)
| RocType::RocSet(_)
| RocType::RocBox(_) => target_info.ptr_alignment_bytes(),
RocType::RocDec => align_of::<RocDec>(),
RocType::Bool => align_of::<bool>(),
RocType::TagUnion { tags, .. } => {
// The smallest alignment this could possibly have is based on the number of tags - e.g.
// 0 tags is an empty union (so, alignment 0), 1-255 tags has a u8 tag (so, alignment 1), etc.
let mut align = align_for_tag_count(tags.len());
for (_, payloads) in tags {
for id in payloads {
align = align.max(types.get(*id).alignment(types, target_info));
}
}
align
}
RocType::RecursiveTagUnion { tags, .. } => {
// The smallest alignment this could possibly have is based on the number of tags - e.g.
// 0 tags is an empty union (so, alignment 0), 1-255 tags has a u8 tag (so, alignment 1), etc.
//
// Unlike a regular tag union, a recursive one also includes a pointer.
let ptr_align = target_info.ptr_alignment_bytes();
let mut align = ptr_align.max(align_for_tag_count(tags.len()));
for (_, payloads) in tags {
for id in payloads {
align = align.max(types.get(*id).alignment(types, target_info));
}
}
align
}
RocType::Struct { fields, .. } => fields.iter().fold(0, |align, (_, id)| {
align.max(types.get(*id).alignment(types, target_info))
}),
RocType::I8 => IntWidth::I8.alignment_bytes(target_info) as usize,
RocType::U8 => IntWidth::U8.alignment_bytes(target_info) as usize,
RocType::I16 => IntWidth::I16.alignment_bytes(target_info) as usize,
RocType::U16 => IntWidth::U16.alignment_bytes(target_info) as usize,
RocType::I32 => IntWidth::I32.alignment_bytes(target_info) as usize,
RocType::U32 => IntWidth::U32.alignment_bytes(target_info) as usize,
RocType::I64 => IntWidth::I64.alignment_bytes(target_info) as usize,
RocType::U64 => IntWidth::U64.alignment_bytes(target_info) as usize,
RocType::I128 => IntWidth::I128.alignment_bytes(target_info) as usize,
RocType::U128 => IntWidth::U128.alignment_bytes(target_info) as usize,
RocType::F32 => FloatWidth::F32.alignment_bytes(target_info) as usize,
RocType::F64 => FloatWidth::F64.alignment_bytes(target_info) as usize,
RocType::F128 => FloatWidth::F128.alignment_bytes(target_info) as usize,
RocType::TransparentWrapper { content, .. } => {
types.get(*content).alignment(types, target_info)
}
}
}
}
fn align_for_tag_count(num_tags: usize) -> usize {
if num_tags == 0 {
// empty tag union
0
} else if num_tags < u8::MAX as usize {
align_of::<u8>()
} else if num_tags < u16::MAX as usize {
align_of::<u16>()
} else if num_tags < u32::MAX as usize {
align_of::<u32>()
} else if num_tags < u64::MAX as usize {
align_of::<u64>()
} else {
panic!(
"Too many tags. You can't have more than {} tags in a tag union!",
u64::MAX
);
}
}

View File

@ -0,0 +1,6 @@
// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc-bindgen` CLI
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]

View File

@ -0,0 +1,59 @@
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
void* roc_realloc(void* ptr, size_t new_size, size_t old_size, unsigned int alignment) {
return realloc(ptr, new_size);
}
void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); }
void roc_panic(void* ptr, unsigned int alignment) {
char* msg = (char*)ptr;
fprintf(stderr,
"Application crashed with message\n\n %s\n\nShutting down\n", msg);
exit(0);
}
void* roc_memcpy(void* dest, const void* src, size_t n) {
return memcpy(dest, src, n);
}
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
///////////////////////////////////////////////////////////////////////////
//
// roc_std
//
///////////////////////////////////////////////////////////////////////////
struct RocStr {
char* bytes;
size_t len;
};
bool is_small_str(struct RocStr str) { return ((ssize_t)str.len) < 0; }
// Determine the length of the string, taking into
// account the small string optimization
size_t roc_str_len(struct RocStr str) {
char* bytes = (char*)&str;
char last_byte = bytes[sizeof(str) - 1];
char last_byte_xored = last_byte ^ 0b10000000;
size_t small_len = (size_t)(last_byte_xored);
size_t big_len = str.len;
// Avoid branch misprediction costs by always
// determining both small_len and big_len,
// so this compiles to a cmov instruction.
if (is_small_str(str)) {
return small_len;
} else {
return big_len;
}
}

View File

@ -0,0 +1,79 @@
#![allow(non_snake_case)]
use core::ffi::c_void;
// TODO don't have these depend on the libc crate; instead, use default
// allocator, built-in memset, etc.
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use std::ffi::CStr;
use std::os::raw::c_char;
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
////////////////////////////////////////////////////////////////////////////
//
// TODO: rust_main should be removed once we use surgical linking everywhere.
// It's just a workaround to get cargo to build an object file the way
// the non-surgical linker needs it to. The surgical linker works on
// executables, not object files, so this workaround is not needed there.
//
////////////////////////////////////////////////////////////////////////////
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
use roc_std::RocStr;
unsafe {
let roc_str = roc_main();
let len = roc_str.len();
let str_bytes = roc_str.as_bytes().as_ptr() as *const libc::c_void;
if libc::write(1, str_bytes, len) < 0 {
panic!("Writing to stdout failed!");
}
}
// Exit code
0
}

View File

@ -0,0 +1,71 @@
const std = @import("std");
const str = @import("str");
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
}
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
std.process.exit(0);
}
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}

402
bindgen/tests/gen_rs.rs Normal file
View File

@ -0,0 +1,402 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
use roc_bindgen::bindgen_rs;
use roc_bindgen::load::load_types;
use roc_load::Threading;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn generate_bindings(decl_src: &str) -> String {
use tempfile::tempdir;
let mut src = indoc!(
r#"
platform "main"
requires {} { nothing : {} }
exposes []
packages {}
imports []
provides [ main ]
"#
)
.to_string();
src.push_str(decl_src);
let types = {
let dir = tempdir().expect("Unable to create tempdir");
let filename = PathBuf::from("Package-Config.roc");
let file_path = dir.path().join(filename);
let full_file_path = file_path.clone();
let mut file = File::create(file_path).unwrap();
writeln!(file, "{}", &src).unwrap();
let result = load_types(full_file_path, dir.path(), Threading::Single);
dir.close().expect("Unable to close tempdir");
result.expect("had problems loading")
};
// Reuse the `src` allocation since we're done with it.
let mut buf = src;
buf.clear();
bindgen_rs::write_types(&types, &mut buf).expect("I/O error when writing bindgen string");
buf
}
#[test]
fn record_aliased() {
let module = indoc!(
r#"
MyRcd : { a : U64, b : U128 }
main : MyRcd
main = { a: 1u64, b: 2u128 }
"#
);
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Copy, Default, Eq, Ord, Hash, Debug)]
#[repr(C)]
pub struct MyRcd {
b: u128,
a: u64,
}
"#
)
);
}
#[test]
fn nested_record_aliased() {
let module = indoc!(
r#"
Outer : { x : Inner, y : Str, z : List U8 }
Inner : { a : U16, b : F32 }
main : Outer
main = { x: { a: 5, b: 24 }, y: "foo", z: [ 1, 2 ] }
"#
);
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Default, Debug)]
#[repr(C)]
pub struct Outer {
y: roc_std::RocStr,
z: roc_std::RocList<u8>,
x: Inner,
}
#[derive(Clone, PartialEq, PartialOrd, Copy, Default, Debug)]
#[repr(C)]
pub struct Inner {
b: f32,
a: u16,
}
"#
)
);
}
#[test]
fn record_anonymous() {
let module = "main = { a: 1u64, b: 2u128 }";
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Copy, Default, Eq, Ord, Hash, Debug)]
#[repr(C)]
pub struct R1 {
b: u128,
a: u64,
}
"#
)
);
}
#[test]
fn nested_record_anonymous() {
let module = r#"main = { x: { a: 5u16, b: 24f32 }, y: "foo", z: [ 1u8, 2 ] }"#;
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Default, Debug)]
#[repr(C)]
pub struct R1 {
y: roc_std::RocStr,
z: roc_std::RocList<u8>,
x: R2,
}
#[derive(Clone, PartialEq, PartialOrd, Copy, Default, Debug)]
#[repr(C)]
pub struct R2 {
b: f32,
a: u16,
}
"#
)
);
}
#[test]
#[ignore]
fn tag_union_aliased() {
let module = indoc!(
r#"
MyTagUnion : [ Foo U64, Bar U128 ]
main : MyTagUnion
main = Foo 123
"#
);
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[repr(C)]
pub struct MyTagUnion {
tag: tag_MyTagUnion,
variant: variant_MyTagUnion,
}
#[repr(C)]
union variant_MyTagUnion {
Bar: u128,
Foo: std::mem::ManuallyDrop<Payload2<roc_std::RocStr, i32>>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct Payload2<V0, V1> {
_0: V0,
_1: V1,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(u8)]
pub enum tag_MyTagUnion {
Bar,
Foo,
}
impl MyTagUnion {
pub fn tag(&self) -> tag_MyTagUnion {
self.tag
}
/// Assume this is the tag named Foo, and return a reference to its payload.
pub unsafe fn as_Foo(&self) -> &Payload2<roc_std::RocStr, i32> {
&*self.variant.Foo
}
/// Assume this is the tag named Foo, and return a mutable reference to its payload.
pub unsafe fn as_mut_Foo(&mut self) -> &mut Payload2<roc_std::RocStr, i32> {
&mut *self.variant.Foo
}
/// Assume this is the tag named Bar, and return a reference to its payload.
pub unsafe fn as_Bar(&self) -> u128 {
self.variant.Bar
}
/// Assume this is the tag named Bar, and return a mutable reference to its payload.
pub unsafe fn as_mut_Bar(&mut self) -> &mut u128 {
&mut self.variant.Bar
}
/// Construct a tag named Foo, with the appropriate payload
pub fn Foo(_0: roc_std::RocStr, _1: i32) -> Self {
Self {
tag: tag_MyTagUnion::Foo,
variant: variant_MyTagUnion {
Foo: std::mem::ManuallyDrop::new(Payload2 { _0, _1 }),
},
}
}
/// Construct a tag named Bar, with the appropriate payload
pub fn Bar(arg0: u128) -> Self {
Self {
tag: tag_MyTagUnion::Bar,
variant: variant_MyTagUnion { Bar: arg0 },
}
}
}
impl Drop for MyTagUnion {
fn drop(&mut self) {
match self.tag {
tag_MyTagUnion::Bar => {}
tag_MyTagUnion::Foo => unsafe { std::mem::ManuallyDrop::drop(&mut self.variant.Foo) },
}
}
}
impl PartialEq for MyTagUnion {
fn eq(&self, other: &Self) -> bool {
if self.tag != other.tag {
return false;
}
unsafe {
match self.tag {
tag_MyTagUnion::Bar => self.variant.Bar == other.variant.Bar,
tag_MyTagUnion::Foo => self.variant.Foo == other.variant.Foo,
}
}
}
}
impl Eq for MyTagUnion {}
impl PartialOrd for MyTagUnion {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match self.tag.partial_cmp(&other.tag) {
Some(core::cmp::Ordering::Equal) => {}
not_eq => return not_eq,
}
unsafe {
match self.tag {
tag_MyTagUnion::Bar => self.variant.Bar.partial_cmp(&other.variant.Bar),
tag_MyTagUnion::Foo => self.variant.Foo.partial_cmp(&other.variant.Foo),
}
}
}
}
impl Ord for MyTagUnion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.tag.cmp(&other.tag) {
core::cmp::Ordering::Equal => {}
not_eq => return not_eq,
}
unsafe {
match self.tag {
tag_MyTagUnion::Bar => self.variant.Bar.cmp(&other.variant.Bar),
tag_MyTagUnion::Foo => self.variant.Foo.cmp(&other.variant.Foo),
}
}
}
}
"#
)
);
}
#[test]
fn tag_union_enumeration() {
let module = indoc!(
r#"
MyTagUnion : [ Blah, Foo, Bar, ]
main : MyTagUnion
main = Foo
"#
);
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Copy, Eq, Ord, Hash, Debug)]
#[repr(u8)]
pub enum MyTagUnion {
Bar,
Blah,
Foo,
}
"#
)
);
}
#[test]
fn single_tag_union_with_payloads() {
let module = indoc!(
r#"
UserId : [ Id U32 Str ]
main : UserId
main = Id 42 "blah"
"#
);
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash, Debug)]
#[repr(C)]
pub struct UserId {
f1: roc_std::RocStr,
f0: u32,
}
"#
)
);
}
#[test]
fn single_tag_union_with_one_payload_field() {
let module = indoc!(
r#"
UserId : [ Id Str ]
main : UserId
main = Id "blah"
"#
);
assert_eq!(
generate_bindings(module)
.strip_prefix('\n')
.unwrap_or_default(),
indoc!(
r#"
#[derive(Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash, Debug)]
#[repr(transparent)]
pub struct UserId(roc_std::RocStr);
"#
)
);
}

View File

@ -65,28 +65,26 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.0.0-beta.2"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.2"
version = "3.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
dependencies = [
"heck",
"proc-macro-error",
@ -95,6 +93,15 @@ dependencies = [
"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"
@ -109,12 +116,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
@ -188,9 +192,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "os_str_bytes"
version = "2.4.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]]
name = "proc-macro-error"
@ -300,24 +304,9 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.12.1"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "unicode-xid"
@ -331,17 +320,11 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"

View File

@ -6,7 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "3.0.0-beta.2"
clap = { version = "3.1.15", features = ["derive"] }
regex = "1.5.4"
is_executable = "1.0.1"
ring = "0.16.20"

View File

@ -1,4 +1,4 @@
use clap::{AppSettings, Clap};
use clap::Parser;
use data_encoding::HEXUPPER;
use is_executable::IsExecutable;
use regex::Regex;
@ -160,8 +160,7 @@ fn remove(file_or_folder: &str) {
.unwrap_or_else(|_| panic!("Something went wrong trying to remove {}", file_or_folder));
}
#[derive(Clap)]
#[clap(setting = AppSettings::ColoredHelp)]
#[derive(Parser)]
struct OptionalArgs {
/// How many times to repeat the benchmarks. A single benchmark has to fail every for a regression to be reported.
#[clap(long, default_value = "3")]

View File

@ -1,86 +0,0 @@
#!/bin/bash
################################################################################
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
################################################################################
#
# This script will install the llvm toolchain on the different
# Debian and Ubuntu versions
set -eux
# read optional command line argument
LLVM_VERSION=10
if [ "$#" -eq 1 ]; then
LLVM_VERSION=$1
fi
DISTRO=$(lsb_release -is)
VERSION=$(lsb_release -sr)
DIST_VERSION="${DISTRO}_${VERSION}"
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root!"
exit 1
fi
declare -A LLVM_VERSION_PATTERNS
LLVM_VERSION_PATTERNS[9]="-9"
LLVM_VERSION_PATTERNS[10]="-10"
LLVM_VERSION_PATTERNS[11]=""
if [ ! ${LLVM_VERSION_PATTERNS[$LLVM_VERSION]+_} ]; then
echo "This script does not support LLVM version $LLVM_VERSION"
exit 3
fi
LLVM_VERSION_STRING=${LLVM_VERSION_PATTERNS[$LLVM_VERSION]}
# find the right repository name for the distro and version
case "$DIST_VERSION" in
Debian_9* ) REPO_NAME="deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch$LLVM_VERSION_STRING main" ;;
Debian_10* ) REPO_NAME="deb http://apt.llvm.org/buster/ llvm-toolchain-buster$LLVM_VERSION_STRING main" ;;
Debian_unstable ) REPO_NAME="deb http://apt.llvm.org/unstable/ llvm-toolchain$LLVM_VERSION_STRING main" ;;
Debian_testing ) REPO_NAME="deb http://apt.llvm.org/unstable/ llvm-toolchain$LLVM_VERSION_STRING main" ;;
Ubuntu_16.04 ) REPO_NAME="deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial$LLVM_VERSION_STRING main" ;;
Ubuntu_18.04 ) REPO_NAME="deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic$LLVM_VERSION_STRING main" ;;
Ubuntu_18.10 ) REPO_NAME="deb http://apt.llvm.org/cosmic/ llvm-toolchain-cosmic$LLVM_VERSION_STRING main" ;;
Ubuntu_19.04 ) REPO_NAME="deb http://apt.llvm.org/disco/ llvm-toolchain-disco$LLVM_VERSION_STRING main" ;;
Ubuntu_19.10 ) REPO_NAME="deb http://apt.llvm.org/eoan/ llvm-toolchain-eoan$LLVM_VERSION_STRING main" ;;
Ubuntu_20.04 ) REPO_NAME="deb http://apt.llvm.org/focal/ llvm-toolchain-focal$LLVM_VERSION_STRING main" ;;
* )
echo "Distribution '$DISTRO' in version '$VERSION' is not supported by this script (${DIST_VERSION})."
exit 2
esac
# install everything
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}"
apt-get update
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc6-dbg libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
tar -xf valgrind-3.16.1.tar.bz2
mv valgrind-3.16.1 ~
pushd ~/valgrind-3.16.1
apt-get install -y autotools-dev automake
./autogen.sh
./configure
make -j`nproc`
sudo make install
popd
# Report current valgrind version, to confirm it installed properly
valgrind --version
# install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0)
wget -c https://ziglang.org/download/0.7.1/zig-linux-x86_64-0.7.1.tar.xz --no-check-certificate
tar -xf zig-linux-x86_64-0.7.1.tar.xz
ln -s "$PWD/zig-linux-x86_64-0.7.1/zig" /usr/local/bin/zig
# test sccache
./ci/sccache -V
# copy sccache to prevent current working dir problems
cp ./ci/sccache /usr/local/bin/sccache

View File

@ -15,14 +15,11 @@ test = false
bench = false
[features]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor", "llvm"]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
i386-cli-run = ["target-x86"]
# TODO: change to roc_repl_cli/llvm once roc_repl can run without llvm.
llvm = ["roc_build/llvm", "roc_repl_cli"]
editor = ["roc_editor"]
run-wasm32 = ["wasmer", "wasmer-wasi"]
@ -61,37 +58,39 @@ roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22"
bumpalo = { version = "3.8.0", features = ["collections"] }
mimalloc = { version = "0.1.26", default-features = false }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
tempfile = "3.2.0"
wasmer-wasi = { version = "2.0.0", optional = true }
wasmer-wasi = { version = "2.2.1", optional = true }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] }
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["cranelift", "universal"] }
[dev-dependencies]
wasmer-wasi = "2.0.0"
wasmer-wasi = "2.2.1"
pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3"
serial_test = "0.5.1"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" }
strum = "0.24.0"
strum_macros = "0.24"
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] }
wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
[[bench]]
name = "time_bench"

View File

@ -4,7 +4,7 @@ use roc_build::{
program::{self, Problems},
};
use roc_builtins::bitcode;
use roc_load::LoadingProblem;
use roc_load::{LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
@ -40,6 +40,7 @@ pub fn build_file<'a>(
surgically_link: bool,
precompiled: bool,
target_valgrind: bool,
threading: Threading,
) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now();
let target_info = TargetInfo::from(target);
@ -55,6 +56,7 @@ pub fn build_file<'a>(
target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
threading,
)?;
use target_lexicon::Architecture;
@ -348,6 +350,7 @@ pub fn check_file(
src_dir: PathBuf,
roc_file_path: PathBuf,
emit_timings: bool,
threading: Threading,
) -> Result<(program::Problems, Duration), LoadingProblem> {
let compilation_start = SystemTime::now();
@ -366,6 +369,7 @@ pub fn check_file(
target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
threading,
)?;
let buf = &mut String::with_capacity(1024);

View File

@ -2,29 +2,17 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use crate::FormatMode;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{
AbilityMember, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral,
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
};
use roc_parse::header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_fmt::spaces::RemoveSpaces;
use roc_fmt::{Ast, Buf};
use roc_parse::{
ast::{Def, Module},
ident::UppercaseIdent,
module::{self, module_defs},
parser::{Parser, SyntaxError},
state::State,
};
use roc_region::all::{Loc, Region};
fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> {
let mut to_flatten = files;
@ -166,12 +154,6 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
Ok(())
}
#[derive(Debug, PartialEq)]
struct Ast<'a> {
module: Module<'a>,
defs: Vec<'a, Loc<Def<'a>>>,
}
fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?;
@ -189,581 +171,3 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) {
buf.fmt_end_of_file();
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
for d in &self.defs {
defs.push(d.remove_spaces(arena))
}
defs
},
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {
Module::Interface { header } => Module::Interface {
header: InterfaceHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_header: &[],
after_interface_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
},
},
Module::App { header } => Module::App {
header: AppHeader {
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
},
},
Module::Platform { header } => Module::Platform {
header: PlatformHeader {
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
},
},
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
}
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for Def<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Has<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Has::Has
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::GlobalTag(a) => Expr::GlobalTag(a),
Expr::PrivateTag(a) => Expr::PrivateTag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Defs(a, b) => {
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::GlobalTag(a) => Pattern::GlobalTag(a),
Pattern::PrivateTag(a) => Pattern::PrivateTag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => {
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
}
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
HasClause {
var: self.var.remove_spaces(arena),
ability: self.ability.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Global { name, args } => Tag::Global {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Private { name, args } => Tag::Private {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}

View File

@ -3,17 +3,16 @@ extern crate const_format;
use build::BuiltFile;
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use clap::{Arg, ArgMatches, Command};
use roc_build::link::LinkType;
use roc_error_macros::user_error;
use roc_load::LoadingProblem;
use roc_load::{LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use std::env;
use std::ffi::OsStr;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use std::process::Command;
use target_lexicon::BinaryFormat;
use target_lexicon::{
Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture,
@ -35,12 +34,12 @@ pub const CMD_FORMAT: &str = "format";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_MAX_THREADS: &str = "max-threads";
pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
@ -52,39 +51,87 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
const VERSION: &str = include_str!("../../version.txt");
pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc")
pub fn build_app<'a>() -> Command<'a> {
let flag_optimize = Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false);
let flag_max_threads = Arg::new(FLAG_MAX_THREADS)
.long(FLAG_MAX_THREADS)
.help("Limit the number of threads (and hence cores) used during compilation.")
.requires(ROC_FILE)
.takes_value(true)
.validator(|s| s.parse::<usize>())
.required(false);
let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
.required(false);
let flag_dev = Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation finish as soon as possible, at the expense of runtime performance.")
.required(false);
let flag_debug = Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program.")
.requires(ROC_FILE)
.required(false);
let flag_valgrind = Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false);
let flag_time = Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
.required(false);
let flag_linker = Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.help("Sets which linker to use. The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false);
let flag_precompiled = Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
.possible_values(["true", "false"])
.required(false);
let roc_file_to_run = Arg::new(ROC_FILE)
.help("The .roc file of an app to run")
.allow_invalid_utf8(true);
let args_for_app = Arg::new(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.requires(ROC_FILE)
.allow_invalid_utf8(true)
.multiple_values(true);
let app = Command::new("roc")
.version(concatcp!(VERSION, "\n"))
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD)
.subcommand(Command::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file to build")
.required(true),
)
.arg(
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.help("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
@ -92,181 +139,99 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::new(FLAG_LIB)
.long(FLAG_LIB)
.about("Build a C library instead of an executable.")
.help("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::new(FLAG_NO_LINK)
.long(FLAG_NO_LINK)
.about("Does not link. Instead just outputs the `.o` file")
.help("Does not link. Instead just outputs the `.o` file")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Deprecated in favor of --linker")
.required(false),
)
.arg(
Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false),
)
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
.possible_values(["true", "false"])
.required(false),
)
.arg(
Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.about("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false),
)
)
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.help("The .roc file to build")
.allow_invalid_utf8(true)
.required(true),
)
)
.subcommand(App::new(CMD_FORMAT)
.subcommand(Command::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(Command::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(roc_file_to_run.clone().required(true))
.arg(args_for_app.clone())
)
.subcommand(Command::new(CMD_FORMAT)
.about("Format a .roc file using standard Roc formatting")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false))
.required(false)
.allow_invalid_utf8(true))
.arg(
Arg::new(FLAG_CHECK)
.long(FLAG_CHECK)
.about("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
.help("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
.required(false),
)
)
.subcommand(App::new(CMD_VERSION)
.subcommand(Command::new(CMD_VERSION)
.about(concatcp!("Print the Roc compilers version, which is currently ", VERSION)))
.subcommand(App::new(CMD_CHECK)
.subcommand(Command::new(CMD_CHECK)
.about("Check the code for problems, but doesnt build or run it")
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(flag_time.clone())
.arg(flag_max_threads.clone())
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to check")
.help("The .roc file of an app to check")
.allow_invalid_utf8(true)
.required(true),
)
)
.subcommand(
App::new(CMD_DOCS)
Command::new(CMD_DOCS)
.about("Generate documentation for Roc modules (Work In Progress)")
.arg(Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.about("The directory or files to build documentation for")
.help("The directory or files to build documentation for")
.allow_invalid_utf8(true)
)
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation finish as soon as possible, at the expense of runtime performance.")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program.")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Deprecated in favor of --linker")
.required(false),
)
.arg(
Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false),
)
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
.possible_values(["true", "false"])
.required(false),
)
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::new(ARGS_FOR_APP)
.about("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple_values(true),
);
.trailing_var_arg(true)
.arg(flag_optimize)
.arg(flag_max_threads.clone())
.arg(flag_opt_size)
.arg(flag_dev)
.arg(flag_debug)
.arg(flag_time)
.arg(flag_linker)
.arg(flag_precompiled)
.arg(flag_valgrind)
.arg(roc_file_to_run.required(false))
.arg(args_for_app);
if cfg!(feature = "editor") {
app.subcommand(
App::new(CMD_EDIT)
Command::new(CMD_EDIT)
.about("Launch the Roc editor (Work In Progress)")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.about("(optional) The directory or files to open on launch."),
.help("(optional) The directory or files to open on launch."),
),
)
} else {
@ -274,15 +239,11 @@ pub fn build_app<'a>() -> App<'a> {
}
}
pub fn docs(files: Vec<PathBuf>) {
roc_docs::generate_docs_html(files, Path::new("./generated-docs"))
}
#[derive(Debug, PartialEq, Eq)]
pub enum BuildConfig {
BuildOnly,
BuildAndRun { roc_file_arg_index: usize },
BuildAndRunIfNoErrors { roc_file_arg_index: usize },
BuildAndRun,
BuildAndRunIfNoErrors,
}
pub enum FormatMode {
@ -290,20 +251,17 @@ pub enum FormatMode {
CheckOnly,
}
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
pub fn build(
matches: &ArgMatches,
config: BuildConfig,
triple: Triple,
link_type: LinkType,
) -> io::Result<i32> {
use build::build_file;
use std::str::FromStr;
use BuildConfig::*;
let target = match matches.value_of(FLAG_TARGET) {
Some(name) => Target::from_str(name).unwrap(),
None => Target::default(),
};
let triple = target.to_triple();
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
let filename = matches.value_of_os(ROC_FILE).unwrap();
let original_cwd = std::env::current_dir()?;
let opt_level = match (
@ -320,22 +278,16 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
let threading = match matches
.value_of(FLAG_MAX_THREADS)
.and_then(|s| s.parse::<usize>().ok())
{
None => Threading::AllAvailable,
Some(0) => user_error!("cannot build with at most 0 threads"),
Some(1) => Threading::Single,
Some(n) => Threading::AtMost(n),
};
// TODO remove FLAG_LINK from the code base anytime after the end of May 2022
if matches.is_present(FLAG_LINK) {
eprintln!("ERROR: The --roc-linker flag has been deprecated because the roc linker is now used automatically where it's supported. (Currently that's only x64 Linux.) No need to use --roc-linker anymore, but you can use the --linker flag to switch linkers.");
process::exit(1);
}
// Use surgical linking when supported, or when explicitly requested with --linker surgical
let surgically_link = if matches.is_present(FLAG_LINKER) {
matches.value_of(FLAG_LINKER) == Some("surgical")
@ -348,7 +300,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
} else {
// When compiling for a different target, default to assuming a precompiled host.
// Otherwise compilation would most likely fail!
target != Target::System
triple != Triple::host()
};
let path = Path::new(filename);
@ -385,6 +337,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
surgically_link,
precompiled,
target_valgrind,
threading,
);
match res_binary_path {
@ -435,7 +388,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
// Return a nonzero exit code if there were problems
Ok(problems.exit_code())
}
BuildAndRun { roc_file_arg_index } => {
BuildAndRun => {
if problems.errors > 0 || problems.warnings > 0 {
println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
@ -466,15 +419,11 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
);
}
roc_run(
arena,
&original_cwd,
triple,
roc_file_arg_index,
&binary_path,
)
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
roc_run(arena, &original_cwd, triple, args, &binary_path)
}
BuildAndRunIfNoErrors { roc_file_arg_index } => {
BuildAndRunIfNoErrors => {
if problems.errors == 0 {
if problems.warnings > 0 {
println!(
@ -490,13 +439,9 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
);
}
roc_run(
arena,
&original_cwd,
triple,
roc_file_arg_index,
&binary_path,
)
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
roc_run(arena, &original_cwd, triple, args, &binary_path)
} else {
println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with: \x1B[32mroc run {}\x1B[39m",
@ -523,7 +468,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
"warnings"
},
total_time.as_millis(),
filename
filename.to_string_lossy()
);
Ok(problems.exit_code())
@ -542,17 +487,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
}
}
#[cfg(target_family = "unix")]
fn roc_run(
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
cwd: &Path,
triple: Triple,
roc_file_arg_index: usize,
args: I,
binary_path: &Path,
) -> io::Result<i32> {
use std::os::unix::process::CommandExt;
let mut cmd = match triple.architecture {
match triple.architecture {
Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
@ -563,19 +505,44 @@ fn roc_run(
// since the process is about to exit anyway.
std::mem::forget(arena);
let args = std::env::args()
.skip(roc_file_arg_index)
.collect::<Vec<_>>();
if cfg!(target_family = "unix") {
use std::os::unix::ffi::OsStrExt;
run_with_wasmer(generated_filename, &args);
return Ok(0);
run_with_wasmer(
generated_filename,
args.into_iter().map(|os_str| os_str.as_bytes()),
);
} else {
run_with_wasmer(
generated_filename,
args.into_iter().map(|os_str| {
os_str.to_str().expect(
"Roc does not currently support passing non-UTF8 arguments to Wasmer.",
)
}),
);
}
Ok(0)
}
_ => {
if cfg!(target_family = "unix") {
roc_run_unix(cwd, args, binary_path)
} else {
roc_run_non_unix(arena, cwd, args, binary_path)
}
}
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = triple.architecture {
cmd.arg(binary_path);
}
}
fn roc_run_unix<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
cwd: &Path,
args: I,
binary_path: &Path,
) -> io::Result<i32> {
use std::os::unix::process::CommandExt;
let mut cmd = std::process::Command::new(&binary_path);
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
@ -584,10 +551,8 @@ fn roc_run(
//
// ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments.
for (index, arg) in std::env::args().enumerate() {
if index > roc_file_arg_index {
cmd.arg(arg);
}
for arg in args {
cmd.arg(arg);
}
// This is much faster than spawning a subprocess if we're on a UNIX system!
@ -599,29 +564,36 @@ fn roc_run(
Err(err)
}
#[cfg(not(target_family = "unix"))]
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
// Run the compiled app
let exit_status = cmd
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
fn roc_run_non_unix<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
_arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
_cwd: &Path,
_args: I,
_binary_path: &Path,
) -> io::Result<i32> {
todo!("TODO support running roc programs on non-UNIX targets");
// let mut cmd = std::process::Command::new(&binary_path);
// `roc [FILE]` exits with the same status code as the app it ran.
//
// If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead!
match exit_status.code() {
Some(code) => Ok(code),
None => {
todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
}
}
// // Run the compiled app
// let exit_status = cmd
// .spawn()
// .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
// .wait()
// .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
// // `roc [FILE]` exits with the same status code as the app it ran.
// //
// // If you want to know whether there were compilation problems
// // via status code, use either `roc build` or `roc check` instead!
// match exit_status.code() {
// Some(code) => Ok(code),
// None => {
// todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
// }
// }
}
#[cfg(feature = "run-wasm32")]
fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(wasm_path: &std::path::Path, args: I) {
use wasmer::{Instance, Module, Store};
let store = Store::default();
@ -652,12 +624,12 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
}
#[cfg(not(feature = "run-wasm32"))]
fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
println!("Running wasm files not support");
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) {
println!("Running wasm files is not supported on this target.");
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Target {
pub enum Target {
System,
Linux32,
Linux64,
@ -690,7 +662,7 @@ impl Target {
Target::Wasm32.as_str(),
];
fn to_triple(self) -> Triple {
pub fn to_triple(self) -> Triple {
use Target::*;
match self {
@ -733,15 +705,15 @@ impl std::fmt::Display for Target {
}
impl std::str::FromStr for Target {
type Err = ();
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"system" => Ok(Target::System),
"linux32" => Ok(Target::Linux32),
"linux64" => Ok(Target::Linux64),
"wasm32" => Ok(Target::Wasm32),
_ => Err(()),
_ => Err(format!("Roc does not know how to compile to {}", string)),
}
}
}

View File

@ -1,13 +1,17 @@
use roc_build::link::LinkType;
use roc_cli::build::check_file;
use roc_cli::{
build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME,
ROC_FILE,
build_app, format, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB,
FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE,
};
use roc_load::LoadingProblem;
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
use roc_load::{LoadingProblem, Threading};
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
#[macro_use]
extern crate const_format;
@ -24,48 +28,72 @@ fn main() -> io::Result<()> {
let exit_code = match matches.subcommand() {
None => {
match matches.index_of(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
if matches.is_present(ROC_FILE) {
build(
&matches,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
LinkType::Executable,
)
} else {
launch_editor(None)?;
build(
&matches,
BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index },
)
}
None => {
launch_editor(None)?;
Ok(0)
}
Ok(0)
}
}
Some((CMD_RUN, matches)) => {
match matches.index_of(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
if matches.is_present(ROC_FILE) {
build(
matches,
BuildConfig::BuildAndRun,
Triple::host(),
LinkType::Executable,
)
} else {
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
build(matches, BuildConfig::BuildAndRun { roc_file_arg_index })
}
None => {
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
Ok(1)
}
Ok(1)
}
}
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
Some((CMD_BUILD, matches)) => {
let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default();
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
};
Ok(build(
matches,
BuildConfig::BuildOnly,
target.to_triple(),
link_type,
)?)
}
Some((CMD_CHECK, matches)) => {
let arena = bumpalo::Bump::new();
let emit_timings = matches.is_present(FLAG_TIME);
let filename = matches.value_of(ROC_FILE).unwrap();
let filename = matches.value_of_os(ROC_FILE).unwrap();
let roc_file_path = PathBuf::from(filename);
let src_dir = roc_file_path.parent().unwrap().to_owned();
match check_file(&arena, src_dir, roc_file_path, emit_timings) {
let threading = match matches
.value_of(roc_cli::FLAG_MAX_THREADS)
.and_then(|s| s.parse::<usize>().ok())
{
None => Threading::AllAvailable,
Some(0) => user_error!("cannot build with at most 0 threads"),
Some(1) => Threading::Single,
Some(n) => Threading::AtMost(n),
};
match check_file(&arena, src_dir, roc_file_path, emit_timings, threading) {
Ok((problems, total_time)) => {
println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",
@ -108,16 +136,12 @@ fn main() -> io::Result<()> {
}
}
Some((CMD_REPL, _)) => {
#[cfg(feature = "llvm")]
{
roc_repl_cli::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
#[cfg(not(feature = "llvm"))]
todo!("enable roc repl without llvm");
}
Some((CMD_EDIT, matches)) => {
match matches
@ -166,7 +190,7 @@ fn main() -> io::Result<()> {
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
}
docs(roc_files);
generate_docs_html(roc_files);
Ok(0)
}

View File

@ -14,10 +14,29 @@ mod cli_run {
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat,
};
use const_format::concatcp;
use indoc::indoc;
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN};
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial;
use std::iter;
use std::path::{Path, PathBuf};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
const VALGRIND_FLAG: &str = concatcp!("--", roc_cli::FLAG_VALGRIND);
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
#[allow(dead_code)]
const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET);
#[derive(Debug, EnumIter)]
enum CliMode {
RocBuild,
RocRun,
Roc,
}
#[cfg(not(debug_assertions))]
use roc_collections::all::MutMap;
@ -65,7 +84,7 @@ mod cli_run {
}
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat());
let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]);
let err = compile_out.stdout.trim();
let err = strip_colors(err);
@ -77,19 +96,41 @@ mod cli_run {
}
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
let flags = &["--check"];
let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
if expects_success_exit_code {
assert!(out.status.success());
} else {
assert!(!out.status.success());
}
let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]);
assert_eq!(out.status.success(), expects_success_exit_code);
}
fn build_example(file: &Path, flags: &[&str]) -> Out {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() {
panic!("roc build had stderr: {}", compile_out.stderr);
fn run_roc_on<'a, I: IntoIterator<Item = &'a str>>(
file: &'a Path,
args: I,
stdin: &[&str],
input_file: Option<PathBuf>,
) -> Out {
let compile_out = match input_file {
Some(input_file) => run_roc(
// converting these all to String avoids lifetime issues
args.into_iter().map(|arg| arg.to_string()).chain([
file.to_str().unwrap().to_string(),
input_file.to_str().unwrap().to_string(),
]),
stdin,
),
None => run_roc(
args.into_iter().chain(iter::once(file.to_str().unwrap())),
stdin,
),
};
if !compile_out.stderr.is_empty() &&
// If there is any stderr, it should be reporting the runtime and that's it!
!(compile_out.stderr.starts_with("runtime: ")
&& compile_out.stderr.ends_with("ms\n"))
{
panic!(
"`roc` command had unexpected stderr: {}",
compile_out.stderr
);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
@ -106,86 +147,104 @@ mod cli_run {
expected_ending: &str,
use_valgrind: bool,
) {
let mut all_flags = vec![];
all_flags.extend_from_slice(flags);
for cli_mode in CliMode::iter() {
let flags = {
let mut vec = flags.to_vec();
if use_valgrind {
all_flags.extend_from_slice(&["--valgrind"]);
}
if use_valgrind {
vec.push(VALGRIND_FLAG);
}
build_example(file, &all_flags[..]);
let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
run_with_valgrind(
stdin,
&[
file.with_file_name(executable_filename).to_str().unwrap(),
input_file.to_str().unwrap(),
],
)
} else {
run_with_valgrind(
stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()],
)
vec.into_iter()
};
if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
});
let out = match cli_mode {
CliMode::RocBuild => {
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None);
if !memory_errors.is_empty() {
for error in memory_errors {
let ValgrindError {
kind,
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file {
run_with_valgrind(
stdin.clone().iter().copied(),
&[
file.with_file_name(executable_filename).to_str().unwrap(),
input_file.clone().to_str().unwrap(),
],
)
} else {
run_with_valgrind(
stdin.clone().iter().copied(),
&[file.with_file_name(executable_filename).to_str().unwrap()],
)
};
if let Some(ValgrindErrorXWhat {
text,
leakedbytes: _,
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
});
if !memory_errors.is_empty() {
for error in memory_errors {
let ValgrindError {
kind,
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
if let Some(ValgrindErrorXWhat {
text,
leakedbytes: _,
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
}
}
panic!("Valgrind reported memory errors");
}
} else {
let exit_code = match valgrind_out.status.code() {
Some(code) => format!("exit code {}", code),
None => "no exit code".to_string(),
};
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
}
}
panic!("Valgrind reported memory errors");
}
} else {
let exit_code = match valgrind_out.status.code() {
Some(code) => format!("exit code {}", code),
None => "no exit code".to_string(),
};
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
valgrind_out
} else if let Some(ref input_file) = input_file {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin.iter().copied(),
&[input_file.to_str().unwrap()],
)
} else {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin.iter().copied(),
&[],
)
}
}
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, input_file.clone()),
CliMode::RocRun => run_roc_on(
file,
iter::once(CMD_RUN).chain(flags.clone()),
stdin,
input_file.clone(),
),
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr
);
}
valgrind_out
} else if let Some(input_file) = input_file {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[input_file.to_str().unwrap()],
)
} else {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[],
)
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr
);
assert!(out.status.success());
}
assert!(out.status.success());
}
#[cfg(feature = "wasm32-cli-run")]
@ -199,9 +258,13 @@ mod cli_run {
) {
assert_eq!(input_file, None, "Wasm does not support input files");
let mut flags = flags.to_vec();
flags.push("--target=wasm32");
flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat());
let compile_out = run_roc(
[CMD_BUILD, file.to_str().unwrap()]
.iter()
.chain(flags.as_slice()),
);
if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr);
}
@ -256,9 +319,9 @@ mod cli_run {
return;
}
}
"hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it.
build_example(&file_name, &["--optimize"]);
"hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them.
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None);
return;
}
@ -283,7 +346,7 @@ mod cli_run {
&file_name,
example.stdin,
example.executable_filename,
&["--optimize"],
&[OPTIMIZE_FLAG],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending,
example.use_valgrind,
@ -296,7 +359,7 @@ mod cli_run {
&file_name,
example.stdin,
example.executable_filename,
&["--linker", "legacy"],
&[LINKER_FLAG, "legacy"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending,
example.use_valgrind,
@ -394,6 +457,14 @@ mod cli_run {
expected_ending: "",
use_valgrind: false,
},
breakout:"breakout" => Example {
filename: "breakout.roc",
executable_filename: "breakout",
stdin: &[],
input_file: None,
expected_ending: "",
use_valgrind: false,
},
quicksort:"algorithms" => Example {
filename: "quicksort.roc",
executable_filename: "quicksort",
@ -505,7 +576,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--optimize"],
&[OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -547,7 +618,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--optimize"],
&[OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
);
@ -579,7 +650,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--target=x86_32"],
[concatcp!(TARGET_FLAG, "=x86_32")],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -589,7 +660,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--target=x86_32", "--optimize"],
[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -685,7 +756,7 @@ mod cli_run {
stdin: &[],
input_file: None,
expected_ending: "",
use_valgrind: true,
use_valgrind: false,
},
issue2279 => Example {
filename: "Issue2279.roc",
@ -808,7 +879,7 @@ mod cli_run {
&fixture_file("multi-dep-str", "Main.roc"),
&[],
"multi-dep-str",
&["--optimize"],
&[OPTIMIZE_FLAG],
None,
"I am Dep2.str2\n",
true,
@ -836,7 +907,7 @@ mod cli_run {
&fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"multi-dep-thunk",
&["--optimize"],
&[OPTIMIZE_FLAG],
None,
"I am Dep2.value2\n",
true,

View File

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
@ -14,7 +15,7 @@ comptime {
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
@ -22,23 +23,28 @@ comptime {
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed() RocStr;
extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = old_size;
_ = alignment;
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
_ = alignment;
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
}
@ -50,7 +56,9 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
@ -65,14 +73,15 @@ pub export fn main() i32 {
// start time
var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
var callresult = RocStr.empty();
roc__mainForHost_1_exposed_generic(&callresult);
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;

View File

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
@ -14,7 +15,7 @@ comptime {
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
@ -22,23 +23,27 @@ comptime {
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed() RocStr;
extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn malloc(size: usize) callconv(.C) ?*anyopaque;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = old_size;
_ = alignment;
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
_ = alignment;
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
}
@ -50,7 +55,9 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
@ -65,14 +72,15 @@ pub export fn main() i32 {
// start time
var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
var callresult = RocStr.empty();
roc__mainForHost_1_exposed_generic(&callresult);
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;

332
cli_utils/Cargo.lock generated
View File

@ -124,9 +124,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
@ -188,10 +188,22 @@ version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527"
dependencies = [
"funty",
"radium",
"funty 1.2.0",
"radium 0.6.2",
"tap",
"wyz",
"wyz 0.4.0",
]
[[package]]
name = "bitvec"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b"
dependencies = [
"funty 2.0.0",
"radium 0.7.0",
"tap",
"wyz 0.5.0",
]
[[package]]
@ -346,17 +358,26 @@ dependencies = [
[[package]]
name = "clap"
version = "3.0.0-beta.5"
version = "3.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"os_str_bytes",
"strsim 0.10.0",
"termcolor",
"textwrap 0.14.2",
"textwrap 0.15.0",
]
[[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]]
@ -958,7 +979,7 @@ checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d"
dependencies = [
"cfg-if 1.0.0",
"libc",
"windows-sys",
"windows-sys 0.28.0",
]
[[package]]
@ -1015,6 +1036,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.17"
@ -1297,7 +1324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f"
dependencies = [
"lazy_static",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -1348,26 +1375,26 @@ dependencies = [
name = "inkwell"
version = "0.1.0"
dependencies = [
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1)",
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?branch=master)",
]
[[package]]
name = "inkwell"
version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
source = "git+https://github.com/rtfeldman/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e"
dependencies = [
"either",
"inkwell_internals",
"libc",
"llvm-sys",
"once_cell",
"parking_lot",
"parking_lot 0.12.0",
]
[[package]]
name = "inkwell_internals"
version = "0.5.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
source = "git+https://github.com/rtfeldman/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e"
dependencies = [
"proc-macro2",
"quote",
@ -1496,9 +1523,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "llvm-sys"
version = "120.2.1"
version = "130.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4a810627ac62b396f5fd2214ba9bbd8748d4d6efdc4d2c1c1303ea7a75763ce"
checksum = "95eb03b4f7ae21f48ef7c565a3e3aa22c50616aea64645fb1fd7f6f56b51c274"
dependencies = [
"cc",
"lazy_static",
@ -1509,10 +1536,11 @@ dependencies = [
[[package]]
name = "lock_api"
version = "0.4.5"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
@ -1566,9 +1594,9 @@ dependencies = [
[[package]]
name = "memmap2"
version = "0.5.0"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e"
checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
dependencies = [
"libc",
]
@ -1949,12 +1977,9 @@ dependencies = [
[[package]]
name = "os_str_bytes"
version = "4.2.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
dependencies = [
"memchr",
]
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]]
name = "owned_ttf_parser"
@ -1980,7 +2005,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1"
dependencies = [
"bitvec",
"bitvec 0.22.3",
"packed_struct_codegen",
"serde",
]
@ -2038,7 +2063,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
"parking_lot_core 0.8.5",
]
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core 0.9.2",
]
[[package]]
@ -2055,6 +2090,46 @@ dependencies = [
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.34.0",
]
[[package]]
name = "peg"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c"
dependencies = [
"peg-macros",
"peg-runtime",
]
[[package]]
name = "peg-macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16"
dependencies = [
"peg-runtime",
"proc-macro2",
"quote",
]
[[package]]
name = "peg-runtime"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -2280,6 +2355,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "radix_trie"
version = "0.2.1"
@ -2451,6 +2532,17 @@ dependencies = [
"libc",
]
[[package]]
name = "roc_alias_analysis"
version = "0.1.0"
dependencies = [
"morphic_lib",
"roc_collections",
"roc_debug_flags",
"roc_module",
"roc_mono",
]
[[package]]
name = "roc_ast"
version = "0.1.0"
@ -2468,11 +2560,13 @@ dependencies = [
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
"roc_unify",
"snafu",
"ven_graph",
"winapi",
]
[[package]]
@ -2486,6 +2580,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_error_macros",
"roc_gen_dev",
"roc_gen_llvm",
"roc_gen_wasm",
@ -2504,6 +2599,7 @@ dependencies = [
"serde_json",
"target-lexicon",
"tempfile",
"wasi_libc_sys",
]
[[package]]
@ -2511,6 +2607,7 @@ name = "roc_builtins"
version = "0.1.0"
dependencies = [
"dunce",
"lazy_static",
"roc_collections",
"roc_module",
"roc_region",
@ -2522,16 +2619,17 @@ dependencies = [
name = "roc_can"
version = "0.1.0"
dependencies = [
"bitvec 1.0.0",
"bumpalo",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_exhaustive",
"roc_module",
"roc_parse",
"roc_problem",
"roc_region",
"roc_types",
"ven_graph",
"static_assertions 1.1.0",
]
[[package]]
@ -2539,7 +2637,7 @@ name = "roc_cli"
version = "0.1.0"
dependencies = [
"bumpalo",
"clap 3.0.0-beta.5",
"clap 3.1.17",
"const_format",
"mimalloc",
"roc_build",
@ -2592,6 +2690,7 @@ dependencies = [
name = "roc_constrain"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.2",
"roc_builtins",
"roc_can",
"roc_collections",
@ -2602,21 +2701,28 @@ dependencies = [
"roc_types",
]
[[package]]
name = "roc_debug_flags"
version = "0.1.0"
[[package]]
name = "roc_docs"
version = "0.1.0"
dependencies = [
"bumpalo",
"peg",
"pulldown-cmark",
"roc_ast",
"roc_builtins",
"roc_can",
"roc_code_markup",
"roc_collections",
"roc_highlight",
"roc_load",
"roc_module",
"roc_parse",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
"snafu",
@ -2671,6 +2777,16 @@ dependencies = [
name = "roc_error_macros"
version = "0.1.0"
[[package]]
name = "roc_exhaustive"
version = "0.1.0"
dependencies = [
"roc_collections",
"roc_module",
"roc_region",
"roc_std",
]
[[package]]
name = "roc_fmt"
version = "0.1.0"
@ -2696,7 +2812,6 @@ dependencies = [
"roc_mono",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
@ -2711,8 +2826,10 @@ dependencies = [
"bumpalo",
"inkwell 0.1.0",
"morphic_lib",
"roc_alias_analysis",
"roc_builtins",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_mono",
@ -2735,6 +2852,14 @@ dependencies = [
"roc_target",
]
[[package]]
name = "roc_highlight"
version = "0.1.0"
dependencies = [
"peg",
"roc_code_markup",
]
[[package]]
name = "roc_ident"
version = "0.1.0"
@ -2745,9 +2870,9 @@ version = "0.1.0"
dependencies = [
"bincode",
"bumpalo",
"clap 3.0.0-beta.5",
"clap 3.1.17",
"iced-x86",
"memmap2 0.5.0",
"memmap2 0.5.3",
"object 0.26.2",
"roc_build",
"roc_collections",
@ -2760,16 +2885,31 @@ dependencies = [
[[package]]
name = "roc_load"
version = "0.1.0"
dependencies = [
"bumpalo",
"roc_builtins",
"roc_collections",
"roc_constrain",
"roc_load_internal",
"roc_module",
"roc_reporting",
"roc_target",
"roc_types",
]
[[package]]
name = "roc_load_internal"
version = "0.1.0"
dependencies = [
"bumpalo",
"crossbeam",
"morphic_lib",
"num_cpus",
"parking_lot",
"parking_lot 0.12.0",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_constrain",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_mono",
@ -2795,7 +2935,7 @@ dependencies = [
"roc_ident",
"roc_region",
"snafu",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -2804,11 +2944,12 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"hashbrown 0.11.2",
"morphic_lib",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_exhaustive",
"roc_module",
"roc_problem",
"roc_region",
@ -2817,7 +2958,7 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
"static_assertions",
"static_assertions 1.1.0",
"ven_graph",
"ven_pretty",
]
@ -2841,13 +2982,14 @@ dependencies = [
"roc_module",
"roc_parse",
"roc_region",
"roc_types",
]
[[package]]
name = "roc_region"
version = "0.1.0"
dependencies = [
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -2866,6 +3008,8 @@ dependencies = [
"roc_mono",
"roc_parse",
"roc_repl_eval",
"roc_reporting",
"roc_std",
"roc_target",
"roc_types",
"rustyline",
@ -2888,6 +3032,7 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_reporting",
"roc_std",
"roc_target",
"roc_types",
]
@ -2900,8 +3045,8 @@ dependencies = [
"distance",
"roc_can",
"roc_collections",
"roc_exhaustive",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
@ -2918,6 +3063,9 @@ dependencies = [
"bumpalo",
"roc_can",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_exhaustive",
"roc_module",
"roc_region",
"roc_types",
@ -2927,6 +3075,9 @@ dependencies = [
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"static_assertions 0.1.1",
]
[[package]]
name = "roc_target"
@ -2941,10 +3092,11 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_region",
"static_assertions",
"static_assertions 1.1.0",
"ven_ena",
]
@ -2954,6 +3106,8 @@ version = "0.1.0"
dependencies = [
"bitflags",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_types",
]
@ -2999,7 +3153,7 @@ dependencies = [
[[package]]
name = "rustyline"
version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
@ -3022,7 +3176,7 @@ dependencies = [
[[package]]
name = "rustyline-derive"
version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"quote",
"syn",
@ -3285,6 +3439,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -3337,9 +3497,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff"
checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1"
[[package]]
name = "tempfile"
@ -3375,9 +3535,9 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.14.2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
@ -3447,7 +3607,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e"
dependencies = [
"cfg-if 1.0.0",
"rand",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -3567,6 +3727,10 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasi_libc_sys"
version = "0.1.0"
[[package]]
name = "wasm-bindgen"
version = "0.2.79"
@ -3798,7 +3962,7 @@ dependencies = [
"arrayvec 0.7.2",
"js-sys",
"log",
"parking_lot",
"parking_lot 0.11.2",
"raw-window-handle",
"smallvec",
"wasm-bindgen",
@ -3822,7 +3986,7 @@ dependencies = [
"fxhash",
"log",
"naga",
"parking_lot",
"parking_lot 0.11.2",
"profiling",
"raw-window-handle",
"smallvec",
@ -3857,7 +4021,7 @@ dependencies = [
"metal",
"naga",
"objc",
"parking_lot",
"parking_lot 0.11.2",
"profiling",
"range-alloc",
"raw-window-handle",
@ -3927,11 +4091,24 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
"windows_aarch64_msvc 0.28.0",
"windows_i686_gnu 0.28.0",
"windows_i686_msvc 0.28.0",
"windows_x86_64_gnu 0.28.0",
"windows_x86_64_msvc 0.28.0",
]
[[package]]
name = "windows-sys"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825"
dependencies = [
"windows_aarch64_msvc 0.34.0",
"windows_i686_gnu 0.34.0",
"windows_i686_msvc 0.34.0",
"windows_x86_64_gnu 0.34.0",
"windows_x86_64_msvc 0.34.0",
]
[[package]]
@ -3940,30 +4117,60 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
[[package]]
name = "windows_aarch64_msvc"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
[[package]]
name = "windows_i686_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
[[package]]
name = "windows_i686_gnu"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
[[package]]
name = "windows_i686_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
[[package]]
name = "windows_i686_msvc"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
[[package]]
name = "windows_x86_64_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
[[package]]
name = "windows_x86_64_gnu"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
[[package]]
name = "windows_x86_64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
[[package]]
name = "windows_x86_64_msvc"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"
[[package]]
name = "winit"
version = "0.25.0"
@ -3986,7 +4193,7 @@ dependencies = [
"ndk-glue",
"ndk-sys",
"objc",
"parking_lot",
"parking_lot 0.11.2",
"percent-encoding",
"raw-window-handle",
"scopeguard",
@ -4014,6 +4221,15 @@ dependencies = [
"tap",
]
[[package]]
name = "wyz"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
dependencies = [
"tap",
]
[[package]]
name = "x11-clipboard"
version = "0.5.3"

View File

@ -20,6 +20,7 @@ serde = { version = "1.0.130", features = ["derive"] }
serde-xml-rs = "0.5.1"
strip-ansi-escapes = "0.1.1"
tempfile = "3.2.0"
const_format = "0.2.22"
[target.'cfg(unix)'.dependencies]
rlimit = "0.6.2"

View File

@ -1,9 +1,13 @@
use crate::helpers::{example_file, run_cmd, run_roc};
use const_format::concatcp;
use criterion::{black_box, measurement::Measurement, BenchmarkGroup};
use roc_cli::CMD_BUILD;
use std::{path::Path, thread};
const CFOLD_STACK_SIZE: usize = 8192 * 100000;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
fn exec_bench_w_input<T: Measurement>(
file: &Path,
stdin_str: &'static str,
@ -11,9 +15,10 @@ fn exec_bench_w_input<T: Measurement>(
expected_ending: &str,
bench_group_opt: Option<&mut BenchmarkGroup<T>>,
) {
let flags: &[&str] = &["--optimize"];
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
let compile_out = run_roc(
[CMD_BUILD, OPTIMIZE_FLAG, file.to_str().unwrap()],
&[stdin_str],
);
if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr);
@ -45,12 +50,12 @@ fn check_cmd_output(
let out = if cmd_str.contains("cfold") {
let child = thread::Builder::new()
.stack_size(CFOLD_STACK_SIZE)
.spawn(move || run_cmd(&cmd_str, &[stdin_str], &[]))
.spawn(move || run_cmd(&cmd_str, [stdin_str], &[]))
.unwrap();
child.join().unwrap()
} else {
run_cmd(&cmd_str, &[stdin_str], &[])
run_cmd(&cmd_str, [stdin_str], &[])
};
if !&out.stdout.ends_with(expected_ending) {
@ -93,12 +98,12 @@ fn bench_cmd<T: Measurement>(
if let Some(bench_group) = bench_group_opt {
bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| {
b.iter(|| run_cmd(black_box(&cmd_str), black_box(&[stdin_str]), &[]))
b.iter(|| run_cmd(black_box(&cmd_str), black_box([stdin_str]), &[]))
});
} else {
run_cmd(
black_box(file.with_file_name(executable_filename).to_str().unwrap()),
black_box(&[stdin_str]),
black_box([stdin_str]),
&[],
);
}

View File

@ -7,6 +7,7 @@ extern crate tempfile;
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::env;
use std::ffi::OsStr;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
@ -44,18 +45,34 @@ pub fn path_to_roc_binary() -> PathBuf {
path
}
#[allow(dead_code)]
pub fn run_roc(args: &[&str]) -> Out {
pub fn run_roc<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(args: I, stdin_vals: &[&str]) -> Out {
let mut cmd = Command::new(path_to_roc_binary());
for arg in args {
cmd.arg(arg);
}
let output = cmd
.output()
let mut child = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to execute compiled `roc` binary in CLI test");
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
for stdin_str in stdin_vals.iter() {
stdin
.write_all(stdin_str.as_bytes())
.expect("Failed to write to stdin");
}
}
let output = child
.wait_with_output()
.expect("failed to get output for compiled `roc` binary in CLI test");
Out {
stdout: String::from_utf8(output.stdout).unwrap(),
stderr: String::from_utf8(output.stderr).unwrap(),
@ -63,8 +80,11 @@ pub fn run_roc(args: &[&str]) -> Out {
}
}
#[allow(dead_code)]
pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out {
pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>>(
cmd_name: &str,
stdin_vals: I,
args: &[&str],
) -> Out {
let mut cmd = Command::new(cmd_name);
for arg in args {
@ -99,8 +119,10 @@ pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out {
}
}
#[allow(dead_code)]
pub fn run_with_valgrind(stdin_vals: &[&str], args: &[&str]) -> (Out, String) {
pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
stdin_vals: I,
args: &[&str],
) -> (Out, String) {
//TODO: figure out if there is a better way to get the valgrind executable.
let mut cmd = Command::new("valgrind");
let named_tempfile =

View File

@ -1,4 +1,4 @@
use palette::{FromColor, Hsv, Srgb};
use palette::{FromColor, Hsv, LinSrgb, Srgb};
pub type RgbaTup = (f32, f32, f32, f32);
pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0);
@ -12,11 +12,11 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup {
}
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup {
let rgb = Srgb::from_color(Hsv::new(
let rgb = LinSrgb::from(Srgb::from_color(Hsv::new(
hue as f32,
(saturation as f32) / 100.0,
(brightness as f32) / 100.0,
));
)));
(rgb.red, rgb.green, rgb.blue, alpha)
}

View File

@ -88,7 +88,7 @@ pub fn expr2_to_markup<'a>(
mark_id_ast_id_map,
)
}
Expr2::GlobalTag { name, .. } => new_markup_node(
Expr2::Tag { name, .. } => new_markup_node(
with_indent(indent_level, &get_string(env, name)),
ast_node_id,
HighlightStyle::Type,

View File

@ -165,9 +165,19 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`.
## Debugging intermediate representations
## Debugging the compiler
### The mono IR
Please see the [debug flags](./debug_flags/src/lib.rs) for information on how to
ask the compiler to emit debug information during various stages of compilation.
There are some goals for more sophisticated debugging tools:
- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486.
Any interest in helping out here is greatly appreciated.
### General Tips
#### Miscompilations
If you observe a miscomplication, you may first want to check the generated mono
IR for your code - maybe there was a problem during specialization or layout
@ -175,13 +185,16 @@ generation. One way to do this is to add a test to `test_mono/src/tests.rs`
and run the tests with `cargo test -p test_mono`; this will write the mono
IR to a file.
You may also want to set some or all of the following environment variables:
#### Typechecking errors
- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function
specialization to stdout
- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of
reset/reuse isntructions to stdout
- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference
counting instructions to stdout
- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the
information it knows about the mono IR whenever it is printed
First, try to minimize your reproduction into a test that fits in
[`solve_expr`](./solve/tests/solve_expr.rs).
Once you've done this, check out the `ROC_PRINT_UNIFICATIONS` debug flag. It
will show you where type unification went right and wrong. This is usually
enough to figure out a fix for the bug.
If that doesn't work and you know your error has something to do with ranks,
you may want to instrument `deep_copy_var_help` in [solve](./solve/src/solve.rs).
If that doesn't work, chatting on Zulip is always a good strategy.

View File

@ -10,4 +10,4 @@ morphic_lib = {path = "../../vendor/morphic_lib"}
roc_collections = {path = "../collections"}
roc_module = {path = "../module"}
roc_mono = {path = "../mono"}
roc_debug_flags = {path = "../debug_flags"}

View File

@ -26,8 +26,16 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout)
}
const DEBUG: bool = false;
const SIZE: usize = if DEBUG { 50 } else { 16 };
#[inline(always)]
fn debug() -> bool {
use roc_debug_flags::{dbg_do, ROC_DEBUG_ALIAS_ANALYSIS};
dbg_do!(ROC_DEBUG_ALIAS_ANALYSIS, {
return true;
});
false
}
const SIZE: usize = 16;
#[derive(Debug, Clone, Copy, Hash)]
struct TagUnionId(u64);
@ -87,7 +95,7 @@ where
*target = *source;
}
if DEBUG {
if debug() {
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
name_bytes[25 + i] = c as u8;
}
@ -175,7 +183,7 @@ where
}
}
if DEBUG {
if debug() {
eprintln!(
"{:?}: {:?} with {:?} args",
proc.name,
@ -239,7 +247,7 @@ where
p.build()?
};
if DEBUG {
if debug() {
eprintln!("{}", program.to_source_string());
}
@ -279,7 +287,8 @@ fn build_entry_point(
let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
let argument_type =
build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?;
// does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
@ -308,7 +317,11 @@ fn build_entry_point(
let block = builder.add_block();
let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?;
let type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(layouts),
&WhenRecursive::Unreachable,
)?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -352,8 +365,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
let arg_type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(&argument_layouts),
&WhenRecursive::Unreachable,
)?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -457,10 +471,14 @@ fn stmt_spec<'a>(
let mut type_ids = Vec::new();
for p in parameters.iter() {
type_ids.push(layout_spec(builder, &p.layout)?);
type_ids.push(layout_spec(
builder,
&p.layout,
&WhenRecursive::Unreachable,
)?);
}
let ret_type_id = layout_spec(builder, layout)?;
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
@ -500,14 +518,14 @@ fn stmt_spec<'a>(
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
Jump(id, symbols) => {
let ret_type_id = layout_spec(builder, layout)?;
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
}
@ -556,11 +574,15 @@ fn build_recursive_tuple_type(
builder.add_tuple_type(&field_types)
}
fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result<TypeId> {
fn build_tuple_type(
builder: &mut impl TypeContext,
layouts: &[Layout],
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
let mut field_types = Vec::new();
for field in layouts.iter() {
field_types.push(layout_spec(builder, field)?);
field_types.push(layout_spec(builder, field, when_recursive)?);
}
builder.add_tuple_type(&field_types)
@ -691,7 +713,7 @@ fn call_spec(
.map(|symbol| env.symbols[symbol])
.collect();
let result_type = layout_spec(builder, ret_layout)?;
let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -761,7 +783,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -782,7 +805,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -806,7 +830,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -828,10 +853,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -851,10 +878,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -879,7 +908,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -903,10 +933,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -936,10 +968,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -975,10 +1009,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1010,7 +1046,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -1087,11 +1124,13 @@ fn call_spec(
)
};
let output_element_type = layout_spec(builder, &output_element_layout)?;
let output_element_type =
layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
let state_layout = Layout::Builtin(Builtin::List(&output_element_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
@ -1108,7 +1147,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::Bool);
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_num(builder, block)?;
@ -1127,7 +1167,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::Bool);
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_num(builder, block)?;
@ -1139,7 +1180,8 @@ fn call_spec(
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
let output_layout = Layout::struct_no_name_order(&output_layouts);
let output_type = layout_spec(builder, &output_layout)?;
let output_type =
layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?;
let loop_body = |builder: &mut FuncDefBuilder, block, output| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
@ -1201,7 +1243,7 @@ fn lowlevel_spec(
) -> Result<ValueId> {
use LowLevel::*;
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
@ -1230,7 +1272,7 @@ fn lowlevel_spec(
builder.add_sub_block(block, sub_block)
}
NumToFloat => {
NumToFrac => {
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
@ -1323,8 +1365,8 @@ fn lowlevel_spec(
}
DictEmpty => match layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let key_id = layout_spec(builder, key_layout)?;
let value_id = layout_spec(builder, value_layout)?;
let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?;
let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?;
new_dict(builder, block, key_id, value_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1367,7 +1409,7 @@ fn lowlevel_spec(
// TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
let result_type = layout_spec(builder, layout)?;
let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -1478,7 +1520,8 @@ fn expr_spec<'a>(
let value_id = match tag_layout {
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let variant_types =
non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
}
@ -1592,7 +1635,7 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Array { elem_layout, elems } => {
let type_id = layout_spec(builder, elem_layout)?;
let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?;
let list = new_list(builder, block, type_id)?;
@ -1619,19 +1662,19 @@ fn expr_spec<'a>(
EmptyArray => match layout {
Layout::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(builder, element_layout)?;
let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
},
Reset { symbol, .. } => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let value_id = env.symbols[symbol];
builder.add_unknown_with(block, &[value_id], type_id)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
}
@ -1658,18 +1701,24 @@ fn literal_spec(
}
}
fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId> {
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
fn layout_spec(
builder: &mut impl TypeContext,
layout: &Layout,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
layout_spec_help(builder, layout, when_recursive)
}
fn non_recursive_variant_types(
builder: &mut impl TypeContext,
tags: &[&[Layout]],
// If there is a recursive pointer latent within this layout, coming from a containing layout.
when_recursive: &WhenRecursive,
) -> Result<Vec<TypeId>> {
let mut result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
result.push(build_tuple_type(builder, tag)?);
result.push(build_tuple_type(builder, tag, when_recursive)?);
}
Ok(result)
@ -1701,7 +1750,7 @@ fn layout_spec_help(
builder.add_tuple_type(&[])
}
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?;
builder.add_union_type(&variant_types)
}
UnionLayout::Recursive(_)

View File

@ -20,8 +20,8 @@ roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_target = { path = "../roc_target" }
roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_llvm = { path = "../gen_llvm" }
roc_gen_wasm = { path = "../gen_wasm" }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" }
@ -29,8 +29,9 @@ roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
tempfile = "3.2.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.3"
wasi_libc_sys = { path = "../../wasi-libc-sys" }
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69"
@ -40,8 +41,4 @@ target-arm = []
target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"]
target-wasm32 = ["roc_gen_wasm"]
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen_llvm"]
target-wasm32 = []

View File

@ -1,9 +1,7 @@
use crate::target::{arch_str, target_zig_str};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_builtins::bitcode;
use roc_error_macros::internal_error;
// #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
use std::env;
@ -72,16 +70,12 @@ fn find_zig_str_path() -> PathBuf {
}
fn find_wasi_libc_path() -> PathBuf {
let wasi_libc_path = PathBuf::from("compiler/builtins/bitcode/wasi-libc.a");
use wasi_libc_sys::WASI_LIBC_PATH;
if std::path::Path::exists(&wasi_libc_path) {
return wasi_libc_path;
}
// when running the tests, we start in the /cli directory
let wasi_libc_path = PathBuf::from("../compiler/builtins/bitcode/wasi-libc.a");
if std::path::Path::exists(&wasi_libc_path) {
return wasi_libc_path;
// Environment variable defined in wasi-libc-sys/build.rs
let wasi_libc_pathbuf = PathBuf::from(WASI_LIBC_PATH);
if std::path::Path::exists(&wasi_libc_pathbuf) {
return wasi_libc_pathbuf;
}
panic!("cannot find `wasi-libc.a`")
@ -1065,14 +1059,13 @@ fn link_windows(
todo!("Add windows support to the surgical linker. See issue #2608.")
}
#[cfg(feature = "llvm")]
pub fn module_to_dylib(
module: &inkwell::module::Module,
target: &Triple,
opt_level: OptLevel,
) -> Result<Library, Error> {
use crate::target::{self, convert_opt_level};
use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::targets::{FileType, RelocMode};
let dir = tempfile::tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
@ -1083,9 +1076,8 @@ pub fn module_to_dylib(
// Emit the .o file using position-independent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model).unwrap();
target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap();
target_machine
.write_to_file(module, FileType::Object, &app_o_file)
@ -1105,6 +1097,21 @@ pub fn module_to_dylib(
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
if matches!(target.architecture, Architecture::Aarch64(_)) {
// On AArch64 darwin machines, calling `ldopen` on Roc-generated libs from multiple threads
// sometimes fails with
// cannot dlopen until fork() handlers have completed
// This may be due to codesigning. In any case, spinning until we are able to dlopen seems
// to be okay.
loop {
match unsafe { Library::new(path) } {
Ok(lib) => return Ok(lib),
Err(Error::DlOpen { .. }) => continue,
Err(other) => return Err(other),
}
}
}
unsafe { Library::new(path) }
}

View File

@ -1,6 +1,4 @@
#[cfg(feature = "llvm")]
use roc_gen_llvm::llvm::build::module_from_builtins;
#[cfg(feature = "llvm")]
pub use roc_gen_llvm::llvm::build::FunctionIterator;
use roc_load::{LoadedModule, MonomorphizedModule};
use roc_module::symbol::{Interns, ModuleId};
@ -19,15 +17,6 @@ pub struct CodeGenTiming {
pub emit_o_file: Duration,
}
// TODO: If modules besides this one start needing to know which version of
// llvm we're using, consider moving me somewhere else.
#[cfg(feature = "llvm")]
const LLVM_VERSION: &str = "12";
// TODO instead of finding exhaustiveness problems in monomorphization, find
// them after type checking (like Elm does) so we can complete the entire
// `roc check` process without needing to monomorphize.
/// Returns the number of problems reported.
pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Problems {
report_problems_help(
loaded.total_problems(),
@ -35,7 +24,6 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Proble
&loaded.interns,
&mut loaded.can_problems,
&mut loaded.type_problems,
&mut loaded.mono_problems,
)
}
@ -46,7 +34,6 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> Problems {
&loaded.interns,
&mut loaded.can_problems,
&mut loaded.type_problems,
&mut Default::default(),
)
}
@ -73,11 +60,9 @@ fn report_problems_help(
interns: &Interns,
can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
type_problems: &mut MutMap<ModuleId, Vec<roc_solve::solve::TypeError>>,
mono_problems: &mut MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
) -> Problems {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*,
DEFAULT_PALETTE,
can_problem, type_problem, Report, RocDocAllocator, Severity::*, DEFAULT_PALETTE,
};
let palette = DEFAULT_PALETTE;
@ -134,25 +119,6 @@ fn report_problems_help(
}
}
}
let problems = mono_problems.remove(home).unwrap_or_default();
for problem in problems {
let report = mono_problem(&alloc, &lines, module_path.clone(), problem);
let severity = report.severity;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
match severity {
Warning => {
warnings.push(buf);
}
RuntimeError => {
errors.push(buf);
}
}
}
}
let problems_reported;
@ -188,27 +154,6 @@ fn report_problems_help(
}
}
#[cfg(not(feature = "llvm"))]
pub fn gen_from_mono_module(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
_roc_file_path: &Path,
target: &target_lexicon::Triple,
app_o_file: &Path,
opt_level: OptLevel,
_emit_debug_info: bool,
) -> CodeGenTiming {
match opt_level {
OptLevel::Optimize | OptLevel::Size => {
todo!("Return this error message in a better way: optimized builds not supported without llvm backend");
}
OptLevel::Normal | OptLevel::Development => {
gen_from_mono_module_dev(arena, loaded, target, app_o_file)
}
}
}
#[cfg(feature = "llvm")]
pub fn gen_from_mono_module(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
@ -235,7 +180,6 @@ pub fn gen_from_mono_module(
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[cfg(feature = "llvm")]
pub fn gen_from_mono_module_llvm(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
@ -249,7 +193,7 @@ pub fn gen_from_mono_module_llvm(
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::targets::{FileType, RelocMode};
let code_gen_start = SystemTime::now();
@ -384,9 +328,11 @@ pub fn gen_from_mono_module_llvm(
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => {
// assemble the .ll into a .bc
let _ = Command::new("llvm-as")
Architecture::X86_64
| Architecture::X86_32(_)
| Architecture::Aarch64(_)
| Architecture::Wasm32 => {
let ll_to_bc = Command::new("llvm-as")
.args(&[
app_ll_dbg_file.to_str().unwrap(),
"-o",
@ -395,6 +341,8 @@ pub fn gen_from_mono_module_llvm(
.output()
.unwrap();
assert!(ll_to_bc.stderr.is_empty(), "{:#?}", ll_to_bc);
let llc_args = &[
"-relocation-model=pic",
"-filetype=obj",
@ -408,26 +356,9 @@ pub fn gen_from_mono_module_llvm(
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let _: Result<std::process::Output, std::io::Error> =
Command::new(format!("llc-{}", LLVM_VERSION))
.args(llc_args)
.output()
.or_else(|_| Command::new("llc").args(llc_args).output())
.map_err(|_| {
panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION);
});
}
let bc_to_object = Command::new("llc").args(llc_args).output().unwrap();
Architecture::Wasm32 => {
// assemble the .ll into a .bc
let _ = Command::new("llvm-as")
.args(&[
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(bc_to_object.stderr.is_empty(), "{:#?}", bc_to_object);
}
_ => unreachable!(),
}
@ -437,10 +368,8 @@ pub fn gen_from_mono_module_llvm(
match target.architecture {
Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => {
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model)
.unwrap();
target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap();
target_machine
.write_to_file(env.module, FileType::Object, app_o_file)

View File

@ -1,9 +1,7 @@
#[cfg(feature = "llvm")]
use inkwell::{
targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple},
OptimizationLevel,
};
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
@ -98,7 +96,6 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
}
}
#[cfg(feature = "llvm")]
pub fn init_arch(target: &Triple) {
match target.architecture {
Architecture::X86_64 | Architecture::X86_32(_)
@ -142,28 +139,35 @@ pub fn arch_str(target: &Triple) -> &'static str {
}
}
#[cfg(feature = "llvm")]
pub fn target_machine(
target: &Triple,
opt: OptimizationLevel,
reloc: RelocMode,
model: CodeModel,
) -> Option<TargetMachine> {
let arch = arch_str(target);
init_arch(target);
let code_model = match target.architecture {
// LLVM 12 will not compile our programs without a large code model.
// The reason is not totally clear to me, but my guess is a few special-cases in
// llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions)
// llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables)
// Revisit when upgrading to LLVM 13.
Architecture::Aarch64(..) => CodeModel::Large,
_ => CodeModel::Default,
};
Target::from_name(arch).unwrap().create_target_machine(
&TargetTriple::create(target_triple_str(target)),
"generic",
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
opt,
reloc,
model,
code_model,
)
}
#[cfg(feature = "llvm")]
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
match level {
OptLevel::Development | OptLevel::Normal => OptimizationLevel::None,

View File

@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m
### module/src/symbol.rs
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.

View File

@ -5,7 +5,7 @@ const CrossTarget = std.zig.CrossTarget;
const Arch = std.Target.Cpu.Arch;
pub fn build(b: *Builder) void {
// b.setPreferredReleaseMode(builtin.Mode.Debug
// b.setPreferredReleaseMode(.Debug);
b.setPreferredReleaseMode(.ReleaseFast);
const mode = b.standardReleaseOptions();
@ -57,8 +57,9 @@ fn generateLlvmIrFile(
const obj = b.addObject(object_name, main_path);
obj.setBuildMode(mode);
obj.strip = true;
obj.emit_llvm_ir = true;
obj.emit_bin = false;
obj.emit_llvm_ir = .emit;
obj.emit_llvm_bc = .emit;
obj.emit_bin = .no_emit;
obj.target = target;
const ir = b.step(step_name, "Build LLVM ir");

View File

@ -4,6 +4,8 @@ set -euxo pipefail
# Test failures will always point at the _start function
# Make sure to look at the rest of the stack trace!
warning_about_non_native_binary=$(zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig 2>&1)
wasm_test_binary=$(echo $warning_about_non_native_binary | cut -d' ' -f 3)
wasmer $wasm_test_binary dummyArgForZigTestBinary
# Zig will try to run the test binary it produced, but it is a wasm object and hence your OS won't
# know how to run it. In the error message, it prints the binary it tried to run. We use some fun
# unix tools to get that path, then feed it to wasmer
zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd wasmer --test-cmd-bin

View File

@ -139,7 +139,7 @@ pub const RocDec = extern struct {
// Format the backing i128 into an array of digit (ascii) characters (u8s)
var digit_bytes_storage: [max_digits + 1]u8 = undefined;
var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, false, .{});
var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, .lower, .{});
var digit_bytes: [*]u8 = digit_bytes_storage[0..];
// space where we assemble all the characters that make up the final string

View File

@ -27,18 +27,8 @@ pub fn expectFailed(
// Lock the failures mutex before reading from any of the failures globals,
// and then release the lock once we're done modifying things.
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
failures_mutex.lock();
defer failures_mutex.unlock();
// If we don't have enough capacity to add a failure, allocate a new failures pointer.
if (failure_length >= failure_capacity) {
@ -87,17 +77,8 @@ pub fn expectFailedC(
}
pub fn getExpectFailures() []Failure {
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
failures_mutex.lock();
defer failures_mutex.unlock();
if (failure_length > 0) {
// defensively clone failures, in case someone modifies the originals after the mutex has been released.
@ -116,23 +97,14 @@ pub fn getExpectFailures() []Failure {
}
pub fn getExpectFailuresC() callconv(.C) CSlice {
var bytes = @ptrCast(*c_void, failures);
var bytes = @ptrCast(*anyopaque, failures);
return .{ .pointer = bytes, .len = failure_length };
}
pub fn deinitFailures() void {
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
failures_mutex.lock();
defer failures_mutex.unlock();
utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure));
failure_length = 0;

View File

@ -1326,7 +1326,6 @@ pub fn listFindUnsafe(
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
element_width: usize,
inc: Inc,
dec: Dec,
@ -1355,3 +1354,9 @@ pub fn listFindUnsafe(
return .{ .value = null, .found = false };
}
}
pub fn listIsUnique(
list: RocList,
) callconv(.C) bool {
return list.isEmpty() or list.isUnique();
}

View File

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const math = std.math;
const utils = @import("utils.zig");
const expect = @import("expect.zig");
@ -55,6 +56,7 @@ comptime {
exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
exportListFn(list.listFindUnsafe, "find_unsafe");
exportListFn(list.listIsUnique, "is_unique");
}
// Dict Module
@ -104,6 +106,9 @@ comptime {
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
num.exportRoundF32(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32.");
num.exportRoundF64(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64.");
}
inline for (FLOATS) |T| {
@ -112,7 +117,6 @@ comptime {
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
}
}
@ -161,6 +165,31 @@ comptime {
exportExpectFn(expect.deinitFailuresC, "deinit_failures");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
if (builtin.target.cpu.arch == .aarch64) {
@export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
}
}
// Utils continued - SJLJ
// For tests (in particular test_gen), roc_panic is implemented in terms of
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965),
// so instead we ask Zig to please provide implementations for us, which is does
// (seemingly via musl).
pub extern fn setjmp([*c]c_int) c_int;
pub extern fn longjmp([*c]c_int, c_int) noreturn;
pub extern fn _setjmp([*c]c_int) c_int;
pub extern fn _longjmp([*c]c_int, c_int) noreturn;
pub extern fn sigsetjmp([*c]c_int, c_int) c_int;
pub extern fn siglongjmp([*c]c_int, c_int) noreturn;
pub extern fn longjmperror() void;
// Zig won't expose the externs (and hence link correctly) unless we force them to be used.
fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int {
return setjmp(it);
}
fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
longjmp(a0, a1);
}
// Export helpers - Must be run inside a comptime
@ -193,7 +222,6 @@ fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void {
// Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
const builtin = @import("builtin");
if (builtin.is_test) {
std.debug.print("{s}: {?}", .{ message, stacktrace });
} else {

View File

@ -24,7 +24,7 @@ pub fn exportParseInt(comptime T: type, comptime name: []const u8) void {
const radix = 0;
if (std.fmt.parseInt(T, buf.asSlice(), radix)) |success| {
return .{ .errorcode = 0, .value = success };
} else |err| {
} else |_| {
return .{ .errorcode = 1, .value = 0 };
}
}
@ -37,7 +37,7 @@ pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
fn func(buf: RocStr) callconv(.C) NumParseResult(T) {
if (std.fmt.parseFloat(T, buf.asSlice())) |success| {
return .{ .errorcode = 0, .value = success };
} else |err| {
} else |_| {
return .{ .errorcode = 1, .value = 0 };
}
}
@ -90,10 +90,19 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) i64 {
return @floatToInt(i64, (@round(input)));
fn func(input: f32) callconv(.C) T {
return @floatToInt(T, (@round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: f64) callconv(.C) T {
return @floatToInt(T, (@round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View File

@ -2041,7 +2041,7 @@ test "ReverseUtf8View: empty" {
const original_bytes = "";
var iter = ReverseUtf8View.initUnchecked(original_bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
while (iter.nextCodepoint()) |_| {
try expect(false);
}
}

View File

@ -7,17 +7,17 @@ pub fn WithOverflow(comptime T: type) type {
}
// If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void;
extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque;
// This should never be passed a null pointer.
// If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void;
extern fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque;
// This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
extern fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void;
// Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
extern fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void;
// should work just like libc memcpy (we can't assume libc is present)
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
@ -34,31 +34,31 @@ comptime {
}
}
fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*c_void {
return @ptrCast(?*c_void, std.testing.allocator.alloc(u8, size) catch unreachable);
fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*anyopaque {
return @ptrCast(?*anyopaque, std.testing.allocator.alloc(u8, size) catch unreachable);
}
fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*c_void {
fn testing_roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*anyopaque {
const ptr = @ptrCast([*]u8, @alignCast(2 * @alignOf(usize), c_ptr));
const slice = ptr[0..old_size];
return @ptrCast(?*c_void, std.testing.allocator.realloc(slice, new_size) catch unreachable);
return @ptrCast(?*anyopaque, std.testing.allocator.realloc(slice, new_size) catch unreachable);
}
fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void {
fn testing_roc_dealloc(c_ptr: *anyopaque, _: u32) callconv(.C) void {
const ptr = @ptrCast([*]u8, @alignCast(2 * @alignOf(usize), c_ptr));
std.testing.allocator.destroy(ptr);
}
fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
fn testing_roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
_ = c_ptr;
_ = tag_id;
@panic("Roc panicked");
}
fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void {
fn testing_roc_memcpy(dest: *anyopaque, src: *anyopaque, bytes: usize) callconv(.C) ?*anyopaque {
const zig_dest = @ptrCast([*]u8, dest);
const zig_src = @ptrCast([*]u8, src);
@ -79,7 +79,7 @@ pub fn dealloc(c_ptr: [*]u8, alignment: u32) void {
}
// must export this explicitly because right now it is not used from zig code
pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
pub fn panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
}
@ -89,7 +89,7 @@ pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
// indirection because otherwise zig creates an alias to the panic function which our LLVM code
// does not know how to deal with
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
pub fn test_panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
_ = c_ptr;
_ = alignment;
// const cstr = @ptrCast([*:0]u8, c_ptr);
@ -240,7 +240,7 @@ pub fn allocateWithRefcount(
}
pub const CSlice = extern struct {
pointer: *c_void,
pointer: *anyopaque,
len: usize,
};

View File

@ -36,30 +36,15 @@ fn main() {
// LLVM .bc FILES
generate_bc_file(&bitcode_path, &build_script_dir_path, "ir", "builtins-host");
generate_bc_file(&bitcode_path, "ir", "builtins-host");
if !DEBUG {
generate_bc_file(
&bitcode_path,
&build_script_dir_path,
"ir-wasm32",
"builtins-wasm32",
);
generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
}
generate_bc_file(
&bitcode_path,
&build_script_dir_path,
"ir-i386",
"builtins-i386",
);
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(
&bitcode_path,
&build_script_dir_path,
"ir-x86_64",
"builtins-x86_64",
);
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
// OBJECT FILES
#[cfg(windows)]
@ -131,35 +116,23 @@ fn generate_object_file(
}
}
fn generate_bc_file(
bitcode_path: &Path,
build_script_dir_path: &Path,
zig_object: &str,
file_name: &str,
) {
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);
run_command(
&bitcode_path,
&zig_executable(),
&["build", zig_object, "-Drelease=true"],
);
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);
run_command(
&build_script_dir_path,
"llvm-as",
&[dest_ir_host, "-o", dest_bc_64bit],
);
}
fn run_command<S, I, P: AsRef<Path>>(path: P, command_str: &str, args: I)

View File

@ -1,90 +0,0 @@
interface Bool
exposes [ and, isEq, isNotEq, not, or, xor ]
imports []
## Returns `False` when given `True`, and vice versa.
not : [True, False] -> [True, False]
## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
##
## `a && b` is shorthand for `Bool.and a b`
##
## >>> True && True
##
## >>> True && False
##
## >>> False && True
##
## >>> False && False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## For example, in some languages, `enablePets && likesDogs user` would compile
## to the equivalent of:
##
## if enablePets then
## likesDogs user
## else
## False
##
## In Roc, however, `&&` and `||` are not special. They work the same way as
## other functions. Conditionals like `if` and `when` have a performance cost,
## and sometimes calling a function like `likesDogs user` can be faster across
## the board than doing an `if` to decide whether to skip calling it.
##
## (Naturally, if you expect the `if` to improve performance, you can always add
## one explicitly!)
and : Bool, Bool -> Bool
## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
##
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
##
## >>> True || False
##
## >>> False || True
##
## >>> False || False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
## Exclusive or
xor : Bool, Bool -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isEq : 'val, 'val -> Bool`
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 1. Global tags are equal if they are the same tag, and also their contents (if any) are equal.
## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
## 3. Records are equal if all their fields are equal.
## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
isEq : val, val -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isNotEq : 'val, 'val -> Bool`
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : val, val -> Bool

View File

@ -1,210 +0,0 @@
interface Dict
exposes
[
Dict,
contains,
difference,
empty,
get,
keys,
insert,
intersection,
len,
remove,
single,
union,
values,
walk
]
imports []
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##
## ### Inserting
##
## The most basic way to use a dictionary is to start with an empty one and then:
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
##
## 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
##
## ### Converting to a [List]
##
## We can call [Dict.toList] on `populationByCity` to turn it into a list of key-value pairs:
##
## Dict.toList populationByCity == [
## { k: "London", v: 8961989 },
## { k: "Philadelphia", v: 1603797 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## { k: "Amsterdam", v: 872680 },
## ]
##
## We can use the similar [Dict.keyList] and [Dict.values] functions to get only the keys or only the values,
## instead of getting these `{ k, v }` records that contain both.
##
## You may notice that these lists have the same order as the original insertion order. This will be true if
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
## Let's see how that looks.
##
## ### Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.toList
## ==
## [
## { k: "London", v: 8961989 },
## { k: "Amsterdam", v: 872680 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## ]
##
## Notice that the order changed! Philadelphia has been 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] does: it removes an element and moves the most recent insertion into the vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). If you need a removal
## operation which preserves ordering, [Dict.removeShift] will remove the element and then shift everything after it
## over one spot. Be aware that this shifting requires copying every single entry after the removed element, though,
## so it can be massively more costly than [remove]! This makes [remove] the recommended default choice;
## [removeShift] should only be used if maintaining original insertion order is absolutely necessary.
##
##
## ### Removing
##
## ### Equality
##
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering (for example, if
## `fn` is `Dict.toList` or calls it internally.)
##
## The [Dict.hasSameContents] function gives an alternative to `==` which ignores ordering
## and returns `True` if both dictionaries have the same keys and associated values.
Dict k v : [ @Dict k v ] # TODO k should require a hashing and equating constraint
## An empty dictionary.
empty : Dict * *
size : Dict * * -> Nat
isEmpty : Dict * * -> Bool
## Returns a [List] of the dictionary's key/value pairs.
##
## See [walk] to walk over the key/value pairs without creating an intermediate data structure.
toList : Dict k v -> List { k, v }
## Returns a [List] of the dictionary's keys.
##
## See [keySet] to get a [Set] of keys instead, or [walkKeys] to walk over the keys without creating
## an intermediate data structure.
keyList : Dict key * -> List key
## Returns a [Set] of the dictionary's keys.
##
## See [keyList] to get a [List] of keys instead, or [walkKeys] to walk over the keys without creating
## an intermediate data structure.
keySet : Dict key * -> Set key
## Returns a [List] of the dictionary's values.
##
## See [walkValues] to walk over the values without creating an intermediate data structure.
values : Dict * value -> List value
walk : Dict k v, state, (state, k, v -> state) -> state
walkKeys : Dict key *, state, (state, key -> state) -> state
walkValues : Dict * value, state, (state, value -> state) -> state
## Convert each key and value in the #Dict to something new, by calling a conversion
## function on each of them. Then return a new #Map of the converted keys and values.
##
## >>> Dict.map {{ 3.14 => "pi", 1.0 => "one" }} \{ key, value } -> { key:
##
## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], [Result.map], and `Set.map`.
map :
Dict beforeKey beforeVal,
({ k: beforeKey, v: beforeVal } -> { k: afterKey, v: afterVal })
-> Dict afterKey afterVal
# DESIGN NOTES: The reason for panicking when given NaN is that:
# * If we allowed NaN in, Dict.insert would no longer be idempotent.
# * If we allowed NaN but overrode its semantics to make it feel like "NaN == NaN" we'd need isNaN checks in all hashing operations as well as all equality checks (during collision detection), not just insert. This would be much worse for performance than panicking on insert, which only requires one extra conditional on insert.
# * It's obviously invalid; the whole point of NaN is that an error occurred. Giving a runtime error notifies you when this problem happens. Giving it only on insert is the best for performance, because it means you aren't paying for isNaN checks on lookups as well.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: insert : Dict 'key val, 'key, val -> Dict 'key val
## Make sure never to insert a key of *NaN* into a [Dict]! Because *NaN* is
## defined to be unequal to *NaN*, inserting a *NaN* key results in an entry
## that can never be retrieved or removed from the [Dict].
insert : Dict key val, key, val -> Dict key val
## Removes a key from the dictionary in [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), without preserving insertion order.
##
## Since the internal [List] which determines the order of operations like [toList] and [walk] cannot have gaps in it,
## whenever an element is removed from the middle of that list, something must be done to eliminate the resulting gap.
##
## * [removeShift] eliminates the gap by shifting over every element after the removed one. This takes [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time),
## and preserves the original ordering.
## * [remove] eliminates the gap by replacing the removed element with the one at the end of the list - that is, the most recent insertion. This takes [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), but does not preserve the original ordering.
##
## For example, suppose we have a `populationByCity` with these contents:
##
## Dict.toList populationByCity == [
## { k: "London", v: 8961989 },
## { k: "Philadelphia", v: 1603797 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## { k: "Amsterdam", v: 872680 },
## ]
##
## Using `Dict.remove "Philadelphia"` on this will replace the `"Philadelphia"` entry with the most recent insertion,
## which is `"Amsterdam"` in this case.
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.toList
## ==
## [
## { k: "London", v: 8961989 },
## { k: "Amsterdam", v: 872680 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## ]
##
## Both [remove] and [removeShift] leave the dictionary with the same contents; they only differ in ordering and in
## performance. Since ordering only affects operations like [toList] and [walk], [remove] is the better default
## choice because it has much better performance characteristics; [removeShift] should only be used when it's
## absolutely necessary for operations like [toList] and [walk] to preserve the exact original insertion order.
remove : Dict k v, k -> Dict k v
## Removes a key from the dictionary in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time), while preserving insertion order.
##
## It's better to use [remove] than this by default, since [remove] has [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time),
## which commonly leads [removeShift] to take many times as long to run as [remove] does. However, [remove] does not
## preserve insertion order, so the slower [removeShift] exists only for use cases where it's abolutely necessary for
## ordering-sensitive functions like [toList] and [walk] to preserve the exact original insertion order.
##
## See the [remove] documentation for more details about the differences between [remove] and [removeShift].
removeShift : Dict k v, k -> Dict k v
## Returns whether both dictionaries have the same keys, and the same values associated with those keys.
## This is different from `==` in that it disregards the ordering of the keys and values.
hasSameContents : Dict k v, Dict k v -> Bool

View File

@ -1,705 +0,0 @@
interface List
exposes
[
List,
append,
concat,
contains,
drop,
dropAt,
dropLast,
first,
get,
isEmpty,
join,
keepErrs,
keepIf,
keepOks,
last,
len,
map,
map2,
map3,
map4,
mapJoin,
mapOrDrop,
mapWithIndex,
prepend,
product,
range,
repeat,
reverse,
set,
single,
sortWith,
split,
sublist,
sum,
swap,
walk,
walkBackwards,
walkUntil
]
imports []
## 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
##
## The list `[ 1, "a" ]` gives an error, because each element in a list must have
## the same type. If you want to put a mix of [I64] and [Str] values into a list, try this:
##
## ```
## mixedList : List [ IntElem I64, StrElem Str ]*
## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ]
## ```
##
## 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)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field as well
## as a pointer to a reference count and a flat array of bytes. Unique lists
## store a capacity #Nat instead of a reference count.
##
## ## Shared Lists
##
## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting).
##
## Each time a given list gets referenced, its reference count ("refcount" for short)
## gets incremented. Each time a list goes out of scope, its refcount count gets
## decremented. Once a refcount, has been decremented more times than it has been
## incremented, we know nothing is referencing it anymore, and the list's memory
## will be immediately freed.
##
## Let's look at an example.
##
## ratings = [ 5, 4, 3 ]
##
## { 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.
##
## The second line alters this refcount. `{ foo: ratings` references
## the `ratings` list, which will result in its refcount getting incremented
## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list,
## which will result in its refcount getting incremented from 1 to 2.
##
## Let's turn this example into a function.
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { 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
## `getRatings` function would be an error!)
##
## Since `ratings` represented a way to reference the list, and that way is no
## longer accessible, the list's refcount gets decremented when `ratings` goes
## out of scope. It will decrease from 2 back down to 1.
##
## Putting these together, when we call `getRatings 5`, what we get back is
## a record with two fields, `foo`, and `bar`, each of which refers to the same
## list, and that list has a refcount of 1.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { 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
##
## 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
## anymore, and its memory will get freed.
##
## Things are different if this is a list of lists instead of a list of numbers.
## Let's look at a simpler example using [List.first] - first with a list of numbers,
## 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 ]
##
## 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 ] ]
##
## 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
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
## In the `lists` example, `lists = [ ... ]` also creates a list with an initial
## refcount of 1. Separately, it also creates several other lists - each with
## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.)
##
## At the end, we once again call [List.first] on the list, but this time
##
## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold.
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * 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!
List elem : [ @List elem ]
## Initialize
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
single : elem -> List elem
## An empty list.
empty : List *
## Returns a list with the given length, where every element is the given value.
##
##
repeat : elem, Nat -> List elem
## Returns a list of all the integers between one and another,
## including both of the given numbers.
##
## >>> List.range 2 8
range : Int a, Int a -> List (Int a)
## Transform
## Returns the list with its elements reversed.
##
## >>> List.reverse [ 1, 2, 3 ]
reverse : List elem -> List elem
## Sorts a list using a function which specifies how two elements are ordered.
##
## When sorting by numeric values, it's more efficient to use [sortAsc] or
## [sortDesc] instead.
sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List elem, (elem -> Num *) -> List elem
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List elem, (elem -> Num *) -> List elem
## 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 [ "", "a", "bc" ] Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example `Set.map`, `Dict.map`, and [Result.map].
map : List before, (before -> after) -> List after
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
##
## 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
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after
## This works like [List.map], except at any time you can return `Err` to
## cancel the entire operation immediately, and return that #Err.
mapOrCancel : List before, (before -> Result after err) -> Result (List after) err
## Like [List.map], except the transformation function specifies whether to
## `Keep` or `Drop` each element from the final [List].
##
## You may know a similar function named `filterMap` in other languages.
mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
mapJoin : List before, (before -> List after) -> List after
## 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.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ]
mapOks : List before, (before -> Result after *) -> List after
## Returns a list with the element at the given index having been transformed by
## the given function.
##
## For a version of this which gives you more control over when to perform
## the transformation, see `List.updater`
##
## ## Performance notes
##
## In particular when updating nested collections, this is potentially much more
## efficient than using [List.get] to obtain the element, transforming it,
## and then putting it back in the same place.
update : List elem, Nat, (elem -> elem) -> List elem
## A more flexible version of `List.update`, which returns an "updater" function
## that lets you delay performing the update until later.
updater : List elem, Nat -> { elem, new : (elem -> List elem) }
## If all the elements in the list are #Ok, return a new list containing the
## contents of those #Ok tags. If any elements are #Err, return #Err.
allOks : List (Result ok err) -> Result (List ok) err
## Add a single element to the end of a list.
##
## >>> List.append [ 1, 2, 3 ] 4
##
## >>> [ 0, 1, 2 ]
## >>> |> List.append 3
append : List elem, elem -> List elem
## Add a single element to the beginning of a list.
##
## >>> List.prepend [ 1, 2, 3 ] 0
##
## >>> [ 2, 3, 4 ]
## >>> |> List.prepend 1
prepend : List elem, elem -> List elem
## Put two lists together.
##
## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ]
##
## >>> [ 0, 1, 2 ]
## >>> |> List.concat [ 3, 4 ]
concat : List elem, List elem -> List elem
## Join the given lists together into one list.
##
## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ]
##
## >>> List.join [ [], [] ]
##
## >>> List.join []
join : List (List elem) -> List elem
## Like [List.join], but only keeps elements tagged with `Ok`. Elements
## tagged with `Err` are dropped.
##
## This can be useful after using an operation that returns a #Result
## on each element of a list, for example [List.first]:
##
## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ]
## >>> |> List.map List.first
## >>> |> List.joinOks
##
## Eventually, `oks` type signature will be `List [Ok elem]* -> List elem`.
## The implementation for that is a lot tricker then `List (Result elem *)`
## so we're sticking with `Result` for now.
oks : List (Result elem *) -> List elem
## Filter
## Run the given function on each element of a list, and return all the
## elements for which the function returned `True`.
##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List elem, (elem -> Bool) -> List elem
## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`.
##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
## See its documentation for details on those characteristics!
dropIf : List elem, (elem -> Bool) -> List elem
## Access
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List elem -> Result elem [ ListWasEmpty ]*
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
last : List elem -> Result elem [ ListWasEmpty ]*
get : List elem, Nat -> Result elem [ OutOfBounds ]*
max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
## Modify
## Replaces the element at the given index with a replacement.
##
## >>> List.set [ "a", "b", "c" ] 1 "B"
##
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List elem, Nat, elem -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list.
##
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"
##
## ## Performance Details
##
## When given a Unique list, this adds the new element in-place if possible.
## This is only possible if the list has enough capacity. Otherwise, it will
## have to *clone and grow*. See the section on [capacity](#capacity) in this
## module's documentation.
append : List elem, elem -> List elem
## Adds a new element to the beginning of the list.
##
## >>> List.prepend [ "b", "c" ] "a"
##
## ## Performance Details
##
## This always clones the entire list, even when given a Unique list. That means
## it runs about as fast as `List.addLast` when both are given a Shared list.
##
## If you have a Unique list instead, [List.append] will run much faster than
## [List.append] except in the specific case where the list has no excess capacity,
## and needs to *clone and grow*. In that uncommon case, both [List.append] and
## [List.append] will run at about the same speed—since [List.append] always
## has to clone and grow.
##
## | Unique list | Shared list |
##---------+--------------------------------+----------------+
## append | in-place given enough capacity | clone and grow |
## prepend | clone and grow | clone and grow |
prepend : List elem, elem -> List elem
## Remove the last element from the list.
##
## Returns both the removed element as well as the new list (with the removed
## element missing), or `Err ListWasEmpty` if the list was empty.
##
## Here's one way you can use this:
##
## when List.pop list is
## Ok { others, last } -> ...
## Err ListWasEmpty -> ...
##
## ## Performance Details
##
## Calling `List.pop` on a Unique list runs extremely fast. It's essentially
## the same as a [List.last] except it also returns the [List] it was given,
## with its length decreased by 1.
##
## In contrast, calling `List.pop` on a Shared list creates a new list, then
## copies over every element in the original list except the last one. This
## takes much longer.
dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]*
##
## Here's one way you can use this:
##
## when List.pop list is
## Ok { others, last } -> ...
## Err ListWasEmpty -> ...
##
## ## Performance Details
##
## When calling either `List.dropFirst` or `List.dropLast` on a Unique list, `List.dropLast`
## runs *much* faster. This is because for `List.dropLast`, removing the last element
## in-place is as easy as reducing the length of the list by 1. In contrast,
## removing the first element from the list involves copying every other element
## in the list into the index before it - which is massively more costly.
##
## In the case of a Shared list,
##
## | Unique list | Shared list |
##-----------+----------------------------------+---------------------------------+
## dropFirst | [List.last] + length change | [List.last] + clone rest of list |
## dropLast | [List.last] + clone rest of list | [List.last] + clone rest of list |
dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]*
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst 5 [ 1, 2 ]
##
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast 5 [ 1, 2 ]
##
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
## Deconstruct
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
## contain all the elements whose index in the original list was **less than**
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before: List elem, others: List elem }
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ]
##
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ]
##
## > 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.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
## Build a value using each element in the list.
##
## Starting with a given `state` value, this walks through each element in the
## list from first to last, running a given `step` function on that element
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [ 2, 4, 8 ]
## |> List.walk { start: 0, step: Num.add }
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
##
## [ 1, 2, 3 ]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, state, (state, elem -> state) -> state
## Same as [List.walk], except you can stop walking early.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Done` earlier than the last element is expected to be common.
walkUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state
# Same as [List.walk]Backwards, except you can stop walking early.
walkBackwardsUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state
## Check
## Returns the length of the list - the number of elements it contains.
##
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List * -> Nat
isEmpty : List * -> Bool
contains : List elem, elem -> Bool
startsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Apply a function that returns a Result on a list, only successful
## Results are kept and returned unwrapped.
keepOks : List before, (before -> Result after *) -> List after
## Apply a function that returns a Result on a list, only unsuccessful
## Results are kept and returned unwrapped.
keepErrs : List before, (before -> Result * after) -> List after

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
interface Result
exposes
[
Result,
after,
isOk,
isErr,
map,
mapErr,
withDefault
]
imports []
## The result of an operation that could fail: either the operation went
## okay, or else there was an error of some sort.
Result ok err : [ @Result ok err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result * * -> bool
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result * * -> bool
## 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
withDefault : Result ok err, ok -> ok
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.)
##
## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
after : Result before err, (before -> Result after err) -> Result after err
## If the result is `Ok`, transform the value it holds by running a conversion
## 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
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
map : Result before err, (before -> after) -> Result after err
## If the result is `Err`, transform the value it holds by running a conversion
## 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
mapErr : Result ok before, (before -> after) -> Result ok after

View File

@ -1,59 +0,0 @@
interface Set
exposes
[
Set,
contains,
difference,
empty,
fromList,
insert,
intersection,
len,
remove,
single,
toList,
union,
walk
]
imports []
## A Set is an unordered collection of unique elements.
Set elem : [ @Set elem ]
## An empty set.
empty : Set *
## Check
isEmpty : Set * -> Bool
len : Set * -> Nat
## Modify
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `add : Set 'elem, 'elem -> Set 'elem`
## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
add : Set elem, elem -> Set elem
## Drops the given element from the set.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `drop : Set 'elem, 'elem -> Set 'elem`
drop : Set elem, elem -> Set elem
## Transform
## Convert each element in the set to something new, by calling a conversion
## function on each of them. Then return a new set of the converted values.
##
## >>> Set.map {: -1, 1, 3 :} Num.negate
##
## >>> Set.map {: "", "a", "bc" :} Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Dict.map`, and [Result.map].
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after`
map : Set elem, (before -> after) -> Set after

View File

@ -1,470 +0,0 @@
interface Str
exposes
[
Str,
append,
concat,
countGraphemes,
endsWith,
fromUtf8,
isEmpty,
joinWith,
split,
startsWith,
startsWithCodePt,
toUtf8,
Utf8Problem,
Utf8ByteProblem
]
imports []
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics.
##
## _For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point),
## see the [roc/unicode](roc/unicode) package. For locale-specific text
## functions (including uppercasing strings, as capitalization rules vary by locale;
## in English, `"i"` capitalizes to `"I"`, but [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing),
## the same `"i"` capitalizes to `"İ"` - as well as sorting strings, which also varies
## by locale; `"ö"` is sorted differently in German and Swedish) see the [roc/locale](roc/locale) package._
##
## ### Unicode
##
## 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 "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
##
## 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`.
##
## ### Escape sequences
##
## 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."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
##
## ### Encoding
##
## Roc strings are not coupled to any particular
## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens,
## they are currently encoded in UTF-8, but this module is intentionally designed
## not to rely on that implementation detail so that a future release of Roc can
## potentially change it without breaking existing Roc applications. (UTF-8
## seems pretty great today, but so did UTF-16 at an earlier point in history.)
##
## This module has functions to can convert a [Str] to a [List] of raw [code unit](https://unicode.org/glossary/#code_unit)
## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point)
## mentioned earlier) in a particular encoding. If you need encoding-specific functions,
## you should take a look at the [roc/unicode](roc/unicode) package.
## It has many more tools than this module does!
## A [Unicode](https://unicode.org) text value.
Str : [ @Str ]
## Convert
## Convert a [Float] to a decimal string, rounding off to the given number of decimal places.
##
## If you want to keep all the digits, use [Str.num] instead.
decimal : Float *, Nat -> Str
## Convert a [Num] to a string.
num : Float *, Nat -> Str
## Split a string around a separator.
##
## >>> Str.split "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.split "1,2,3" ""
##
## To split a string into its individual graphemes, use `Str.graphemes`
split : Str, Str -> List Str
## Split a string around newlines.
##
## On strings that use `"\n"` for their line endings, this gives the same answer
## as passing `"\n"` to [Str.split]. However, on strings that use `"\n\r"` (such
## as [in Windows files](https://en.wikipedia.org/wiki/Newline#History)), this
## will consume the entire `"\n\r"` instead of just the `"\n"`.
##
## >>> Str.lines "Hello, World!\nNice to meet you!"
##
## >>> Str.lines "Hello, World!\n\rNice to meet you!"
##
## To split a string using a custom separator, use [Str.split]. For more advanced
## string splitting, use a #Parser.
lines : Str, Str -> List Str
## Check
## Returns `True` if the string is empty, and `False` otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool
startsWith : Str, Str -> Bool
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `True`. Otherwise return `False`.
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `False`.
##
## **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.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePt : Str, U32 -> Bool
endsWith : Str, Str -> Bool
contains : Str, Str -> Bool
anyGraphemes : Str, (Str -> Bool) -> Bool
allGraphemes : Str, (Str -> Bool) -> Bool
## Combine
## Combine a list of strings into a single string.
##
## >>> Str.join [ "a", "bc", "def" ]
join : List Str -> Str
## Combine a list of strings into a single string, with a separator
## string in between each.
##
## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str
## Add to the start of a string until it has at least the given number of
## graphemes.
##
## >>> Str.padGraphemesStart "0" 5 "36"
##
## >>> Str.padGraphemesStart "0" 1 "36"
##
## >>> Str.padGraphemesStart "0" 5 "12345"
##
## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padGraphemesStart : Str, Nat, Str -> Str
## Add to the end of a string until it has at least the given number of
## graphemes.
##
## >>> Str.padGraphemesStart "0" 5 "36"
##
## >>> Str.padGraphemesStart "0" 1 "36"
##
## >>> Str.padGraphemesStart "0" 5 "12345"
##
## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padGraphemesEnd : Str, Nat, Str -> Str
## Graphemes
## Split a string into its individual graphemes.
##
## >>> Str.graphemes "1,2,3"
##
## >>> Str.graphemes "👍👍👍"
##
graphemes : Str -> List Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Str.countGraphemes "Roc!" # 4
## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "🕊" # 1
countGraphemes : Str -> Nat
## Reverse the order of the string's individual graphemes.
##
## >>> Str.reverseGraphemes "1-2-3"
##
## >>> Str.reverseGraphemes "🐦✈️"👩‍👩‍👦‍👦"
##
## >>> Str.reversegraphemes "Crème Brûlée"
reverseGraphemes : Str -> Str
## Returns `True` if the two strings are equal when ignoring case.
##
## >>> Str.caseInsensitiveEq "hi" "Hi"
isCaseInsensitiveEq : Str, Str -> Bool
isCaseInsensitiveNeq : Str, Str -> Bool
walkGraphemes : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state
walkGraphemesBackwards : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state
## Returns `True` if the string begins with an uppercase letter.
##
## >>> Str.isCapitalized "Hi"
##
## >>> Str.isCapitalized " Hi"
##
## >>> Str.isCapitalized "hi"
##
## >>> Str.isCapitalized "Česká"
##
## >>> Str.isCapitalized "Э"
##
## >>> Str.isCapitalized "東京"
##
## >>> Str.isCapitalized "🐦"
##
## >>> Str.isCapitalized ""
##
## Since the rules for how to capitalize a string vary by locale,
## (for example, in English, `"i"` capitalizes to `"I"`, but
## [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing),
## the same `"i"` capitalizes to `"İ"`) see the [roc/locale](roc/locale) package
## package for functions which capitalize strings.
isCapitalized : Str -> Bool
## Returns `True` if the string consists entirely of uppercase letters.
##
## >>> Str.isAllUppercase "hi"
##
## >>> Str.isAllUppercase "Hi"
##
## >>> Str.isAllUppercase "HI"
##
## >>> Str.isAllUppercase " Hi"
##
## >>> Str.isAllUppercase "Česká"
##
## >>> Str.isAllUppercase "Э"
##
## >>> Str.isAllUppercase "東京"
##
## >>> Str.isAllUppercase "🐦"
##
## >>> Str.isAllUppercase ""
isAllUppercase : Str -> Bool
## Returns `True` if the string consists entirely of lowercase letters.
##
## >>> Str.isAllLowercase "hi"
##
## >>> Str.isAllLowercase "Hi"
##
## >>> Str.isAllLowercase "HI"
##
## >>> Str.isAllLowercase " Hi"
##
## >>> Str.isAllLowercase "Česká"
##
## >>> Str.isAllLowercase "Э"
##
## >>> Str.isAllLowercase "東京"
##
## >>> Str.isAllLowercase "🐦"
##
## >>> Str.isAllLowercase ""
isAllLowercase : Str -> Bool
## Return the string with any blank spaces removed from both the beginning
## as well as the end.
trim : Str -> Str
## If the given [U32] is a valid [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value),
## return a [Str] containing only that scalar.
fromScalar : U32 -> Result Str [ BadScalar ]*
fromCodePts : List U32 -> Result Str [ BadCodePt U32 ]*
fromUtf8 : List U8 -> Result Str [ BadUtf8 ]*
## Create a [Str] from bytes encoded as [UTF-16LE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes).
# fromUtf16Le : List U8 -> Result Str [ BadUtf16Le Endi ]*
# ## Create a [Str] from bytes encoded as [UTF-16BE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes).
# fromUtf16Be : List U8 -> Result Str [ BadUtf16Be Endi ]*
# ## Create a [Str] from bytes encoded as UTF-16 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark).
# fromUtf16Bom : List U8 -> Result Str [ BadUtf16 Endi, NoBom ]*
# ## Create a [Str] from bytes encoded as [UTF-32LE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html)
# fromUtf32Le : List U8 -> Result Str [ BadUtf32Le Endi ]*
# ## Create a [Str] from bytes encoded as [UTF-32BE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html)
# fromUtf32Be : List U8 -> Result Str [ BadUtf32Be Endi ]*
# ## Create a [Str] from bytes encoded as UTF-32 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark).
# fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]*
# ## Convert from UTF-8, substituting the replacement character ("<22>") for any
# ## invalid sequences encountered.
# fromUtf8Sub : List U8 -> Str
# fromUtf16Sub : List U8, Endi -> Str
# fromUtf16BomSub : List U8 -> Result Str [ NoBom ]*
## Return 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] and `Str.graphemes`.)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
##
## For a more flexible function that walks through each of these [U8] code units
## without creating a [List], see `Str.walkUtf8` and `Str.walkRevUtf8`.
toUtf8 : Str -> List U8
toUtf16Be : Str -> List U8
toUtf16Le : Str -> List U8
# toUtf16Bom : Str, Endi -> List U8
toUtf32Be : Str -> List U8
toUtf32Le : Str -> List U8
# toUtf32Bom : Str, Endi -> List U8
# Parsing
## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the bytes do not begin with a valid grapheme, for example because the list was
## empty or began with an invalid grapheme, return `Err`.
parseUtf8Grapheme : List U8 -> Result { grapheme : Str, bytesParsed: Nat } [ InvalidGrapheme ]*
## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the string does not begin with a valid code point, for example because the list was
## empty or began with an invalid code point, return an `Err`.
parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]*
## If the string represents a valid [U8] number, return that number.
##
## For more advanced options, see [parseU8].
toU8 : Str -> Result U8 [ InvalidU8 ]*
toI8 : Str -> Result I8 [ InvalidI8 ]*
toU16 : Str -> Result U16 [ InvalidU16 ]*
toI16 : Str -> Result I16 [ InvalidI16 ]*
toU32 : Str -> Result U32 [ InvalidU32 ]*
toI32 : Str -> Result I32 [ InvalidI32 ]*
toU64 : Str -> Result U64 [ InvalidU64 ]*
toI64 : Str -> Result I64 [ InvalidI64 ]*
toU128 : Str -> Result U128 [ InvalidU128 ]*
toI128 : Str -> Result I128 [ InvalidI128 ]*
toF64 : Str -> Result U128 [ InvalidF64 ]*
toF32 : Str -> Result I128 [ InvalidF32 ]*
toDec : Str -> Result Dec [ InvalidDec ]*
## If the string represents a valid number, return that number.
##
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of I64 in the type signature will require that type instead of (Num *).
##
## >>> strToI64 : Str -> Result I64 [ InvalidNumStr ]*
## >>> strToI64 = \inputStr ->
## >>> Str.toNum inputStr
##
## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted
## only when converting to [F64] or [F32] numbers, and will be translated accordingly.
##
## This never accepts numbers with underscores or commas in them. For more
## advanced options, see [parseNum].
toNum : Str -> Result (Num *) [ InvalidNumStr ]*
## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return
## that number along with the rest of the string after it.
##
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of Float64 in the type signature will require that type instead of (Num *).
##
## >>> parseFloat64 : Str -> Result { val: Float64, rest: Str } [ InvalidNumStr ]*
## >>> Str.parseNum input {}
##
## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [F64] or [F32] numbers, and translated accordingly.
# parseNum : Str, NumParseConfig -> Result { val : Num *, rest : Str } [ InvalidNumStr ]*
## Notes:
## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0.
## * For `wholeSep`, `Required` has a payload for how many digits (e.g. "required every 3 digits")
## * For `wholeSep`, `Allowed` allows the separator to appear anywhere.
# NumParseConfig :
# {
# base ? [ Decimal, Hexadecimal, Octal, Binary ],
# notation ? [ Standard, Scientific, Any ],
# decimalMark ? [ Allowed Str, Required Str, Disallowed ],
# decimalDigits ? [ Any, AtLeast U16, Exactly U16 ],
# wholeDigits ? [ Any, AtLeast U16, Exactly U16 ],
# leadingZeroes ? [ Allowed, Disallowed ],
# trailingZeroes ? [ Allowed, Disallowed ],
# wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] }
# }

View File

@ -4,11 +4,81 @@ interface Bool
Bool : [ True, False ]
## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
##
## `a && b` is shorthand for `Bool.and a b`
##
## >>> True && True
##
## >>> True && False
##
## >>> False && True
##
## >>> False && False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## For example, in some languages, `enablePets && likesDogs user` would compile
## to the equivalent of:
##
## if enablePets then
## likesDogs user
## else
## False
##
## In Roc, however, `&&` and `||` are not special. They work the same way as
## other functions. Conditionals like `if` and `when` have a performance cost,
## and sometimes calling a function like `likesDogs user` can be faster across
## the board than doing an `if` to decide whether to skip calling it.
##
## (Naturally, if you expect the `if` to improve performance, you can always add
## one explicitly!)
and : Bool, Bool -> Bool
## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
##
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
##
## >>> True || False
##
## >>> False || True
##
## >>> False || False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
# xor : Bool, Bool -> Bool # currently unimplemented
## Returns `False` when given `True`, and vice versa.
not : Bool -> Bool
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
## 2. Records are equal if all their fields are equal.
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
isEq : a, a -> Bool
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : a, a -> Bool

View File

@ -20,6 +20,57 @@ interface Dict
Bool.{ Bool }
]
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##
## ### Inserting
##
## The most basic way to use a dictionary is to start with an empty one and then:
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
##
## 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
##
## ### Accessing keys or values
##
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or only the values.
##
## You may notice that these lists have the same order as the original insertion order. This will be true if
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
## Let's see how that looks.
##
## ### Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.keys
## ==
## [ "London", "Amsterdam", "Shanghai", "Delhi" ]
##
## Notice that the order changed! Philadelphia has been 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] does: it removes an element and moves the most recent insertion into the vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). ##
##
## ### Equality
##
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering.
## An empty dictionary.
empty : Dict k v
single : k, v -> Dict k v
get : Dict k v, k -> Result v [ KeyNotFound ]*
@ -28,7 +79,11 @@ insert : Dict k v, k, v -> Dict k v
len : Dict k v -> Nat
remove : Dict k v, k -> Dict k v
contains : Dict k v, k -> Bool
## Returns a [List] of the dictionary's keys.
keys : Dict k v -> List k
## Returns a [List] of the dictionary's values.
values : Dict k v -> List v
union : Dict k v, Dict k v -> Dict k v
intersection : Dict k v, Dict k v -> Dict k v

View File

@ -56,6 +56,149 @@ interface List
Bool.{ Bool }
]
## 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
##
## 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)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field as well
## as a pointer to a reference count and a flat array of bytes. Unique lists
## store a capacity #Nat instead of a reference count.
##
## ## Shared Lists
##
## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting).
##
## Each time a given list gets referenced, its reference count ("refcount" for short)
## gets incremented. Each time a list goes out of scope, its refcount count gets
## decremented. Once a refcount, has been decremented more times than it has been
## incremented, we know nothing is referencing it anymore, and the list's memory
## will be immediately freed.
##
## Let's look at an example.
##
## ratings = [ 5, 4, 3 ]
##
## { 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.
##
## The second line alters this refcount. `{ foo: ratings` references
## the `ratings` list, which will result in its refcount getting incremented
## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list,
## which will result in its refcount getting incremented from 1 to 2.
##
## Let's turn this example into a function.
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { 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
## `getRatings` function would be an error!)
##
## Since `ratings` represented a way to reference the list, and that way is no
## longer accessible, the list's refcount gets decremented when `ratings` goes
## out of scope. It will decrease from 2 back down to 1.
##
## Putting these together, when we call `getRatings 5`, what we get back is
## a record with two fields, `foo`, and `bar`, each of which refers to the same
## list, and that list has a refcount of 1.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { 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
##
## 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
## anymore, and its memory will get freed.
##
## Things are different if this is a list of lists instead of a list of numbers.
## Let's look at a simpler example using [List.first] - first with a list of numbers,
## 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 ]
##
## 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 ] ]
##
## 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
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
## In the `lists` example, `lists = [ ... ]` also creates a list with an initial
## refcount of 1. Separately, it also creates several other lists - each with
## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.)
##
## At the end, we once again call [List.first] on the list, but this time
##
## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold.
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * 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 []
isEmpty : List a -> Bool
isEmpty = \list ->
List.len list == 0
@ -63,22 +206,134 @@ isEmpty = \list ->
get : List a, Nat -> Result a [ OutOfBounds ]*
replace : List a, Nat, a -> { list : List a, value : a }
## Replaces the element at the given index with a replacement.
##
## >>> List.set [ "a", "b", "c" ] 1 "B"
##
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List a, Nat, a -> List a
set = \list, index, value ->
(List.replace list index value).list
## Add a single element to the end of a list.
##
## >>> List.append [ 1, 2, 3 ] 4
##
## >>> [ 0, 1, 2 ]
## >>> |> List.append 3
append : List a, a -> List a
## Add a single element to the beginning of a list.
##
## >>> List.prepend [ 1, 2, 3 ] 0
##
## >>> [ 2, 3, 4 ]
## >>> |> List.prepend 1
prepend : List a, a -> List a
## Returns the length of the list - the number of elements it contains.
##
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List a -> Nat
## Put two lists together.
##
## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ]
##
## >>> [ 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.
last : List a -> Result a [ ListWasEmpty ]*
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
single : a -> List a
## Returns a list with the given length, where every element is the given value.
##
##
repeat : a, Nat -> List a
## Returns the list with its elements reversed.
##
## >>> List.reverse [ 1, 2, 3 ]
reverse : List a -> List a
## Join the given lists together into one list.
##
## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ]
##
## >>> List.join [ [], [] ]
##
## >>> List.join []
join : List (List a) -> List a
contains : List a, a -> Bool
## Build a value using each element in the list.
##
## Starting with a given `state` value, this walks through each element in the
## list from first to last, running a given `step` function on that element
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [ 2, 4, 8 ]
## |> List.walk { start: 0, step: Num.add }
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
##
## [ 1, 2, 3 ]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, state, (state, elem -> state) -> state
## Same as [List.walk], except you can stop walking early.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Done` earlier than the last element is expected to be common.
walkUntil : List elem, state, (state, elem -> [ Continue state, Stop state ]) -> state
sum : List (Num a) -> Num a
@ -89,40 +344,201 @@ product : List (Num a) -> Num a
product = \list ->
List.walk list 1 Num.mul
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List a, (a -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List a, (a -> Bool) -> Bool
## Run the given function on each element of a list, and return all the
## elements for which the function returned `True`.
##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List a, (a -> Bool) -> List a
## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`.
##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
## See its documentation for details on those characteristics!
dropIf : List a, (a -> Bool) -> List a
dropIf = \list, predicate ->
List.keepIf list (\e -> Bool.not (predicate e))
## 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
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepOks [ "", "a", "bc", "", "d", "ef", "" ]
keepOks : List before, (before -> Result after *) -> List after
## 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
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepErrs [ "", "a", "bc", "", "d", "ef", "" ]
keepErrs: List before, (before -> Result * after) -> List after
## 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 [ "", "a", "bc" ] Str.isEmpty
map : List a, (a -> b) -> List b
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
##
## 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
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List a, (a, Nat -> b) -> List b
## Returns a list of all the integers between one and another,
## including both of the given numbers.
##
## >>> List.range 2 8
range : Int a, Int a -> List (Int a)
sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List (Num a) -> List (Num a)
sortAsc = \list -> List.sortWith list Num.compare
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List (Num a) -> List (Num a)
sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a)
swap : List a, Nat, Nat -> List a
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List a -> Result a [ ListWasEmpty ]*
## Remove the first element from the list.
##
## Returns the new list (with the removed element missing).
dropFirst : List elem -> List elem
## Remove the last element from the list.
##
## Returns the new list (with the removed element missing).
dropLast : List elem -> List elem
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst 5 [ 1, 2 ]
##
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast 5 [ 1, 2 ]
##
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list.
##
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
@ -163,11 +579,41 @@ maxHelp = \list, initial ->
else
bestSoFar
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : List a, (a -> List b) -> List b
joinMap = \list, mapper ->
List.walk list [] (\state, elem -> List.concat state (mapper elem))
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ]
##
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ]
##
## > 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.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
intersperse : List elem, elem -> List elem
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
## contain all the elements whose index in the original list was **less than**
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before: List elem, others: List elem }

File diff suppressed because it is too large Load Diff

View File

@ -2,38 +2,79 @@ interface Result
exposes [ Result, isOk, isErr, map, mapErr, after, withDefault ]
imports [ Bool.{ Bool } ]
## The result of an operation that could fail: either the operation went
## okay, or else there was an error of some sort.
Result ok err : [ Ok ok, Err err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result ok err -> Bool
isOk = \result ->
when result is
Ok _ -> True
Err _ -> False
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result ok err -> Bool
isErr = \result ->
when result is
Ok _ -> False
Err _ -> True
## 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
withDefault : Result ok err, ok -> ok
withDefault = \result, default ->
when result is
Ok value -> value
Err _ -> default
## If the result is `Ok`, transform the value it holds by running a conversion
## 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
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
map : Result a err, (a -> b) -> Result b err
map = \result, transform ->
when result is
Ok v -> Ok (transform v)
Err e -> Err e
## If the result is `Err`, transform the value it holds by running a conversion
## 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
mapErr : Result ok a, (a -> b) -> Result ok b
mapErr = \result, transform ->
when result is
Ok v -> Ok v
Err e -> Err (transform e)
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.)
##
## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
after : Result a err, (a -> Result b err) -> Result b err
after = \result, transform ->
when result is

View File

@ -16,10 +16,17 @@ interface Set
]
imports [ List, Bool.{ Bool }, Dict.{ values } ]
## An empty set.
empty : Set k
single : k -> Set k
## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
insert : Set k, k -> Set k
len : Set k -> Nat
## Drops the given element from the set.
remove : Set k, k -> Set k
contains : Set k, k -> Bool

View File

@ -36,6 +36,81 @@ interface Str
]
imports [ Bool.{ Bool }, Result.{ Result } ]
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics.
##
## ### Unicode
##
## 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 "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
##
## 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`.
##
## ### Escape sequences
##
## 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."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
Utf8ByteProblem :
@ -50,15 +125,68 @@ Utf8ByteProblem :
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns `True` if the string is empty, and `False` otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool
concat : Str, Str -> Str
## Combine a list of strings into a single string, with a separator
## string in between each.
##
## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str
## Split a string around a separator.
##
## >>> Str.split "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.split "1,2,3" ""
##
## To split a string into its individual graphemes, use `Str.graphemes`
split : Str, Str -> List Str
repeat : Str, Nat -> Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Str.countGraphemes "Roc!" # 4
## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "üïä" # 1
countGraphemes : Str -> Nat
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `True`. Otherwise return `False`.
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `False`.
##
## **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.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePt : Str, U32 -> Bool
## Return 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].)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
toUtf8 : Str -> List U8
# fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
@ -70,6 +198,8 @@ fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 Ut
startsWith : Str, Str -> Bool
endsWith : Str, Str -> Bool
## Return the string with any blank spaces removed from both the beginning
## as well as the end.
trim : Str -> Str
trimLeft : Str -> Str
trimRight : Str -> Str

View File

@ -40,6 +40,9 @@ impl FloatWidth {
pub const fn stack_size(&self) -> u32 {
use FloatWidth::*;
// NOTE: this must never use mem::size_of, because that returns the size
// for the target of *the compiler itself* (e.g. this Rust code), not what
// the compiler is targeting (e.g. what the Roc code will be compiled to).
match self {
F32 => 4,
F64 => 8,
@ -49,33 +52,27 @@ impl FloatWidth {
pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
use roc_target::Architecture;
use std::mem::align_of;
use FloatWidth::*;
// TODO actually alignment is architecture-specific
// NOTE: this must never use mem::align_of, because that returns the alignment
// for the target of *the compiler itself* (e.g. this Rust code), not what
// the compiler is targeting (e.g. what the Roc code will be compiled to).
match self {
F32 => align_of::<f32>() as u32,
F64 => match target_info.architecture {
F32 => 4,
F64 | F128 => match target_info.architecture {
Architecture::X86_64
| Architecture::Aarch64
| Architecture::Arm
| Architecture::Wasm32 => 8,
Architecture::X86_32 => 4,
},
F128 => align_of::<i128>() as u32,
}
}
pub const fn try_from_symbol(symbol: Symbol) -> Option<Self> {
match symbol {
Symbol::NUM_F64 | Symbol::NUM_BINARY64 | Symbol::NUM_AT_BINARY64 => {
Some(FloatWidth::F64)
}
Symbol::NUM_F32 | Symbol::NUM_BINARY32 | Symbol::NUM_AT_BINARY32 => {
Some(FloatWidth::F32)
}
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(FloatWidth::F64),
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(FloatWidth::F32),
_ => None,
}
}
@ -105,6 +102,9 @@ impl IntWidth {
pub const fn stack_size(&self) -> u32 {
use IntWidth::*;
// NOTE: this must never use mem::size_of, because that returns the size
// for the target of *the compiler itself* (e.g. this Rust code), not what
// the compiler is targeting (e.g. what the Roc code will be compiled to).
match self {
U8 | I8 => 1,
U16 | I16 => 2,
@ -116,13 +116,15 @@ impl IntWidth {
pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
use roc_target::Architecture;
use std::mem::align_of;
use IntWidth::*;
// NOTE: this must never use mem::align_of, because that returns the alignment
// for the target of *the compiler itself* (e.g. this Rust code), not what
// the compiler is targeting (e.g. what the Roc code will be compiled to).
match self {
U8 | I8 => align_of::<i8>() as u32,
U16 | I16 => align_of::<i16>() as u32,
U32 | I32 => align_of::<i32>() as u32,
U8 | I8 => 1,
U16 | I16 => 2,
U32 | I32 => 4,
U64 | I64 => match target_info.architecture {
Architecture::X86_64
| Architecture::Aarch64
@ -130,32 +132,26 @@ impl IntWidth {
| Architecture::Wasm32 => 8,
Architecture::X86_32 => 4,
},
U128 | I128 => align_of::<i128>() as u32,
U128 | I128 => {
// the C ABI defines 128-bit integers to always be 16B aligned,
// according to https://reviews.llvm.org/D28990#655487
16
}
}
}
pub const fn try_from_symbol(symbol: Symbol) -> Option<Self> {
match symbol {
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 | Symbol::NUM_AT_SIGNED128 => {
Some(IntWidth::I128)
}
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 | Symbol::NUM_AT_SIGNED64 => Some(IntWidth::I64),
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 | Symbol::NUM_AT_SIGNED32 => Some(IntWidth::I32),
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 | Symbol::NUM_AT_SIGNED16 => Some(IntWidth::I16),
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 | Symbol::NUM_AT_SIGNED8 => Some(IntWidth::I8),
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 | Symbol::NUM_AT_UNSIGNED128 => {
Some(IntWidth::U128)
}
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 | Symbol::NUM_AT_UNSIGNED64 => {
Some(IntWidth::U64)
}
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 | Symbol::NUM_AT_UNSIGNED32 => {
Some(IntWidth::U32)
}
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 | Symbol::NUM_AT_UNSIGNED16 => {
Some(IntWidth::U16)
}
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 | Symbol::NUM_AT_UNSIGNED8 => Some(IntWidth::U8),
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(IntWidth::I128),
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(IntWidth::I64),
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(IntWidth::I32),
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(IntWidth::I16),
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(IntWidth::I8),
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(IntWidth::U128),
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(IntWidth::U64),
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(IntWidth::U32),
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(IntWidth::U16),
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(IntWidth::U8),
_ => None,
}
}
@ -283,7 +279,9 @@ pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round");
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
@ -359,6 +357,7 @@ pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
@ -379,6 +378,9 @@ pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";
#[derive(Debug, Default)]
pub struct IntToIntrinsicName {
pub options: [IntrinsicName; 10],

View File

@ -3,7 +3,7 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::builtin_aliases::{
bool_type, box_type, dec_type, dict_type, f32_type, f64_type, float_type, i128_type, i16_type,
bool_type, box_type, dec_type, dict_type, f32_type, f64_type, frac_type, i128_type, i16_type,
i32_type, i64_type, i8_type, int_type, list_type, nat_type, num_type, ordering_type,
result_type, set_type, str_type, str_utf8_byte_problem_type, u128_type, u16_type, u32_type,
u64_type, u8_type,
@ -131,7 +131,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
fn overflow() -> SolvedType {
SolvedType::TagUnion(
vec![(TagName::Global("Overflow".into()), vec![])],
vec![(TagName::Tag("Overflow".into()), vec![])],
Box::new(SolvedType::Wildcard),
)
}
@ -269,11 +269,11 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(ordering_type()),
);
// toFloat : Num * -> Float *
// toFrac : Num * -> Frac *
add_top_level_function_type!(
Symbol::NUM_TO_FLOAT,
Symbol::NUM_TO_FRAC,
vec![num_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR2))),
Box::new(frac_type(flex(TVAR2))),
);
// isNegative : Num a -> Bool
@ -312,7 +312,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let div_by_zero = SolvedType::TagUnion(
vec![(TagName::Global("DivByZero".into()), vec![])],
vec![(TagName::Tag("DivByZero".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -393,16 +393,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(int_type(flex(TVAR2)))
);
// rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
// rem : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_REM,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
Box::new(int_type(flex(TVAR1))),
);
// mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
// remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_MOD_INT,
Symbol::NUM_REM_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
@ -476,7 +476,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -551,7 +551,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -667,98 +667,99 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type())
);
// Float module
// Frac module
// div : Float a, Float a -> Float a
// div : Frac a, Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1)))
Symbol::NUM_DIV_FRAC,
vec![frac_type(flex(TVAR1)), frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1)))
);
// divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
// divChecked : Frac a, Frac a -> Result (Frac a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT_CHECKED,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
);
// mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_MOD_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)),
);
// sqrt : Float a -> Float a
let sqrt_of_negative = SolvedType::TagUnion(
vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
Box::new(SolvedType::Wildcard),
Symbol::NUM_DIV_FRAC_CHECKED,
vec![frac_type(flex(TVAR1)), frac_type(flex(TVAR1))],
Box::new(result_type(frac_type(flex(TVAR1)), div_by_zero)),
);
// sqrt : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_SQRT,
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// log : Float a -> Float a
let log_needs_positive = SolvedType::TagUnion(
vec![(TagName::Global("LogNeedsPositive".into()), vec![])],
// sqrtChecked : Frac a -> Result (Frac a) [ SqrtOfNegative ]*
let sqrt_of_negative = SolvedType::TagUnion(
vec![(TagName::Tag("SqrtOfNegative".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
Symbol::NUM_LOG,
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)),
Symbol::NUM_SQRT_CHECKED,
vec![frac_type(flex(TVAR1))],
Box::new(result_type(frac_type(flex(TVAR1)), sqrt_of_negative)),
);
// round : Float a -> Int b
// log : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_LOG,
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// logChecked : Frac a -> Result (Frac a) [ LogNeedsPositive ]*
let log_needs_positive = SolvedType::TagUnion(
vec![(TagName::Tag("LogNeedsPositive".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
Symbol::NUM_LOG_CHECKED,
vec![frac_type(flex(TVAR1))],
Box::new(result_type(frac_type(flex(TVAR1)), log_needs_positive)),
);
// round : Frac a -> Int b
add_top_level_function_type!(
Symbol::NUM_ROUND,
vec![float_type(flex(TVAR1))],
vec![frac_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR2))),
);
// sin : Float a -> Float a
// sin : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_SIN,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// cos : Float a -> Float a
// cos : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_COS,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// tan : Float a -> Float a
// tan : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_TAN,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// maxFloat : Float a
add_type!(Symbol::NUM_MAX_FLOAT, float_type(flex(TVAR1)));
// minFloat : Float a
add_type!(Symbol::NUM_MIN_FLOAT, float_type(flex(TVAR1)));
// pow : Float a, Float a -> Float a
// pow : Frac a, Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_POW,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1)), frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// ceiling : Float a -> Int b
// ceiling : Frac a -> Int b
add_top_level_function_type!(
Symbol::NUM_CEILING,
vec![float_type(flex(TVAR1))],
vec![frac_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR2))),
);
@ -769,38 +770,38 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(int_type(flex(TVAR1))),
);
// floor : Float a -> Int b
// floor : Frac a -> Int b
add_top_level_function_type!(
Symbol::NUM_FLOOR,
vec![float_type(flex(TVAR1))],
vec![frac_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR2))),
);
// atan : Float a -> Float a
// atan : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_ATAN,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// acos : Float a -> Float a
// acos : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_ACOS,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// asin : Float a -> Float a
// asin : Frac a -> Frac a
add_top_level_function_type!(
Symbol::NUM_ASIN,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
vec![frac_type(flex(TVAR1))],
Box::new(frac_type(flex(TVAR1))),
);
// bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ]
{
let position_out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
@ -813,7 +814,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ]
{
let position_out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
@ -935,7 +936,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
{
let bad_utf8 = SolvedType::TagUnion(
vec![(
TagName::Global("BadUtf8".into()),
TagName::Tag("BadUtf8".into()),
vec![str_utf8_byte_problem_type(), nat_type()],
)],
Box::new(SolvedType::Wildcard),
@ -953,10 +954,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
let bad_utf8 = SolvedType::TagUnion(
vec![
(
TagName::Global("BadUtf8".into()),
TagName::Tag("BadUtf8".into()),
vec![str_utf8_byte_problem_type(), nat_type()],
),
(TagName::Global("OutOfBounds".into()), vec![]),
(TagName::Tag("OutOfBounds".into()), vec![]),
],
Box::new(SolvedType::Wildcard),
);
@ -992,7 +993,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// `str_to_num` in can `builtins.rs`
let invalid_str = || {
SolvedType::TagUnion(
vec![(TagName::Global("InvalidNumStr".into()), vec![])],
vec![(TagName::Tag("InvalidNumStr".into()), vec![])],
Box::new(SolvedType::Wildcard),
)
};
@ -1099,7 +1100,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// get : List elem, Nat -> Result elem [ OutOfBounds ]*
let index_out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -1111,7 +1112,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// first : List elem -> Result elem [ ListWasEmpty ]*
let list_was_empty = SolvedType::TagUnion(
vec![(TagName::Global("ListWasEmpty".into()), vec![])],
vec![(TagName::Tag("ListWasEmpty".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -1216,8 +1217,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// [ LT, EQ, GT ]
SolvedType::TagUnion(
vec![
(TagName::Global("Continue".into()), vec![content.clone()]),
(TagName::Global("Stop".into()), vec![content]),
(TagName::Tag("Continue".into()), vec![content.clone()]),
(TagName::Tag("Stop".into()), vec![content]),
],
Box::new(SolvedType::EmptyTagUnion),
)
@ -1578,7 +1579,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
{
let not_found = SolvedType::TagUnion(
vec![(TagName::Global("NotFound".into()), vec![])],
vec![(TagName::Tag("NotFound".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
let (elem, cvar) = (TVAR1, TVAR2);
@ -1620,7 +1621,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// get : Dict k v, k -> Result v [ KeyNotFound ]*
let key_not_found = SolvedType::TagUnion(
vec![(TagName::Global("KeyNotFound".into()), vec![])],
vec![(TagName::Tag("KeyNotFound".into()), vec![])],
Box::new(SolvedType::Wildcard),
);

View File

@ -8,13 +8,12 @@ edition = "2018"
[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_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0"
bitvec = "1"

View File

@ -6,6 +6,8 @@ use roc_types::{subs::Variable, types::Type};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemberVariables {
pub able_vars: Vec<Variable>,
/// This includes - named rigid vars, lambda sets, wildcards. See
/// [`IntroducedVariables::collect_rigid`](crate::annotation::IntroducedVariables::collect_rigid).
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
@ -13,7 +15,7 @@ pub struct MemberVariables {
/// Stores information about an ability member definition, including the parent ability, the
/// defining type, and what type variables need to be instantiated with instances of the ability.
// TODO: SoA and put me in an arena
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub struct AbilityMemberData {
pub parent_ability: Symbol,
pub signature_var: Variable,
@ -29,11 +31,22 @@ pub struct MemberSpecialization {
pub region: Region,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpecializationId(u64);
#[allow(clippy::derivable_impls)] // let's be explicit about this
impl Default for SpecializationId {
fn default() -> Self {
Self(0)
}
}
/// Stores information about what abilities exist in a scope, what it means to implement an
/// ability, and what types implement them.
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
// are only dealing with inter-module abilities for now.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
// are only dealing with intra-module abilities for now.
// TODO(abilities): many of these should be `VecMap`s. Do some benchmarking.
#[derive(Default, Debug, Clone)]
pub struct AbilitiesStore {
/// Maps an ability to the members defining it.
members_of_ability: MutMap<Symbol, Vec<Symbol>>,
@ -54,6 +67,12 @@ pub struct AbilitiesStore {
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
/// member `member`, to the exact symbol that implements the ability.
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
next_specialization_id: u64,
/// Resolved specializations for a symbol. These might be ephemeral (known due to type solving),
/// or resolved on-the-fly during mono.
resolved_specializations: MutMap<SpecializationId, Symbol>,
}
impl AbilitiesStore {
@ -168,4 +187,37 @@ impl AbilitiesStore {
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
self.members_of_ability.get(&ability).map(|v| v.as_ref())
}
pub fn fresh_specialization_id(&mut self) -> SpecializationId {
debug_assert!(self.next_specialization_id != std::u64::MAX);
let id = SpecializationId(self.next_specialization_id);
self.next_specialization_id += 1;
id
}
pub fn insert_resolved(&mut self, id: SpecializationId, specialization: Symbol) {
debug_assert!(self.is_specialization_name(specialization));
let old_specialization = self.resolved_specializations.insert(id, specialization);
debug_assert!(
old_specialization.is_none(),
"Existing resolution: {:?}",
old_specialization
);
}
pub fn remove_resolved(&mut self, id: SpecializationId) {
let old_specialization = self.resolved_specializations.remove(&id);
debug_assert!(
old_specialization.is_some(),
"Trying to remove a resolved specialization that was never there!",
);
}
pub fn get_resolved(&self, id: SpecializationId) -> Option<Symbol> {
self.resolved_specializations.get(&id).copied()
}
}

View File

@ -1,15 +1,16 @@
use crate::env::Env;
use crate::procedure::References;
use crate::scope::Scope;
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_problem::can::ShadowKind;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{
name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type,
TypeExtension,
name_type_var, Alias, AliasCommon, AliasKind, AliasVar, LambdaSet, OptAbleType, OptAbleVar,
Problem, RecordField, Type, TypeExtension,
};
#[derive(Clone, Debug)]
@ -20,6 +21,27 @@ pub struct Annotation {
pub aliases: SendMap<Symbol, Alias>,
}
impl Annotation {
pub fn add_to(
&self,
aliases: &mut VecMap<Symbol, Alias>,
references: &mut References,
introduced_variables: &mut IntroducedVariables,
) {
for symbol in self.references.iter() {
references.insert_type_lookup(*symbol);
}
introduced_variables.union(&self.introduced_variables);
for (name, alias) in self.aliases.iter() {
if !aliases.contains_key(name) {
aliases.insert(*name, alias.clone());
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NamedOrAbleVariable<'a> {
Named(&'a NamedVariable),
@ -49,6 +71,48 @@ impl<'a> NamedOrAbleVariable<'a> {
}
}
pub enum OwnedNamedOrAble {
Named(NamedVariable),
Able(AbleVariable),
}
impl OwnedNamedOrAble {
pub fn first_seen(&self) -> Region {
match self {
OwnedNamedOrAble::Named(nv) => nv.first_seen,
OwnedNamedOrAble::Able(av) => av.first_seen,
}
}
pub fn ref_name(&self) -> &Lowercase {
match self {
OwnedNamedOrAble::Named(nv) => &nv.name,
OwnedNamedOrAble::Able(av) => &av.name,
}
}
pub fn name(self) -> Lowercase {
match self {
OwnedNamedOrAble::Named(nv) => nv.name,
OwnedNamedOrAble::Able(av) => av.name,
}
}
pub fn variable(&self) -> Variable {
match self {
OwnedNamedOrAble::Named(nv) => nv.variable,
OwnedNamedOrAble::Able(av) => av.variable,
}
}
pub fn opt_ability(&self) -> Option<Symbol> {
match self {
OwnedNamedOrAble::Named(_) => None,
OwnedNamedOrAble::Able(av) => Some(av.ability),
}
}
}
/// A named type variable, not bound to an ability.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NamedVariable {
@ -277,7 +341,7 @@ fn make_apply_symbol(
}
}
} else {
match env.qualified_lookup(module_name, ident, region) {
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => Ok(symbol),
Err(problem) => {
// Either the module wasn't imported, or
@ -298,8 +362,7 @@ fn make_apply_symbol(
/// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`,
/// `U8`, and `Str`.
pub fn find_type_def_symbols(
module_id: ModuleId,
ident_ids: &mut IdentIds,
scope: &mut Scope,
initial_annotation: &roc_parse::ast::TypeAnnotation,
) -> Vec<Symbol> {
use roc_parse::ast::TypeAnnotation::*;
@ -312,9 +375,8 @@ pub fn find_type_def_symbols(
match annotation {
Apply(_module_name, ident, arguments) => {
let ident: Ident = (*ident).into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = scope.scopeless_symbol(&ident, Region::zero());
let symbol = Symbol::new(module_id, ident_id);
result.push(symbol);
for t in arguments.iter() {
@ -365,7 +427,7 @@ pub fn find_type_def_symbols(
while let Some(tag) = inner_stack.pop() {
match tag {
Tag::Global { args, .. } | Tag::Private { args, .. } => {
Tag::Apply { args, .. } => {
for t in args.iter() {
stack.push(&t.value);
}
@ -508,51 +570,28 @@ fn can_annotation_help(
return error;
}
let is_structural = alias.kind == AliasKind::Structural;
if is_structural {
let mut type_var_to_arg = Vec::new();
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args) {
let name = loc_var.value.0.clone();
type_var_to_arg.push((name, arg_ann));
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
introduced_variables.insert_lambda_set(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
Type::DelayedAlias(AliasCommon {
symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
})
} else {
let (type_arguments, lambda_set_variables, actual) =
instantiate_and_freshen_alias_type(
var_store,
introduced_variables,
&alias.type_variables,
args,
&alias.lambda_set_variables,
alias.typ.clone(),
);
Type::Alias {
symbol,
type_arguments,
lambda_set_variables,
actual: Box::new(actual),
kind: alias.kind,
}
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
type_var_to_arg.push(arg_ann);
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
introduced_variables.insert_lambda_set(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
Type::DelayedAlias(AliasCommon {
symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
})
}
None => Type::Apply(symbol, args, region),
}
@ -579,12 +618,7 @@ fn can_annotation_help(
vars: loc_vars,
},
) => {
let symbol = match scope.introduce(
name.value.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
let symbol = match scope.introduce(name.value.into(), region) {
Ok(symbol) => symbol,
Err((original_region, shadow, _new_symbol)) => {
@ -611,7 +645,7 @@ fn can_annotation_help(
references,
);
let mut vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(loc_vars.len());
references.insert(symbol);
@ -624,21 +658,36 @@ fn can_annotation_help(
};
let var_name = Lowercase::from(var);
// TODO(abilities): check that there are no abilities bound here.
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
vars.push(Type::Variable(var));
lowercase_vars.push(Loc::at(
loc_var.region,
AliasVar {
name: var_name,
var,
opt_bound_ability: None,
},
));
} else {
let var = var_store.fresh();
introduced_variables
.insert_named(var_name.clone(), Loc::at(loc_var.region, var));
vars.push((var_name.clone(), Type::Variable(var)));
vars.push(Type::Variable(var));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
lowercase_vars.push(Loc::at(
loc_var.region,
AliasVar {
name: var_name,
var,
opt_bound_ability: None,
},
));
}
}
let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::<Vec<_>>();
let alias_args = vars.clone();
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh();
@ -683,7 +732,7 @@ fn can_annotation_help(
hidden_variables.extend(alias_actual.variables());
for loc_var in lowercase_vars.iter() {
hidden_variables.remove(&loc_var.value.1);
hidden_variables.remove(&loc_var.value.var);
}
scope.add_alias(
@ -697,8 +746,6 @@ fn can_annotation_help(
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
@ -712,7 +759,13 @@ fn can_annotation_help(
} else {
Type::Alias {
symbol,
type_arguments: vars,
type_arguments: vars
.into_iter()
.map(|typ| OptAbleType {
typ,
opt_ability: None,
})
.collect(),
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
kind: alias.kind,
@ -1005,7 +1058,7 @@ fn shallow_dealias_with_scope<'a>(scope: &'a mut Scope, typ: &'a Type) -> &'a Ty
pub fn instantiate_and_freshen_alias_type(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
type_variables: &[Loc<(Lowercase, Variable)>],
type_variables: &[Loc<AliasVar>],
type_arguments: Vec<Type>,
lambda_set_variables: &[LambdaSet],
mut actual_type: Type,
@ -1014,8 +1067,8 @@ pub fn instantiate_and_freshen_alias_type(
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in type_variables.iter().zip(type_arguments.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
let name = loc_var.value.name.clone();
let var = loc_var.value.var;
substitutions.insert(var, arg_ann.clone());
type_var_to_arg.push((name.clone(), arg_ann));
@ -1050,26 +1103,37 @@ pub fn instantiate_and_freshen_alias_type(
pub fn freshen_opaque_def(
var_store: &mut VarStore,
opaque: &Alias,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
) -> (Vec<OptAbleVar>, Vec<LambdaSet>, Type) {
debug_assert!(opaque.kind == AliasKind::Opaque);
let fresh_arguments = opaque
let fresh_variables: Vec<OptAbleVar> = opaque
.type_variables
.iter()
.map(|_| Type::Variable(var_store.fresh()))
.map(|alias_var| OptAbleVar {
var: var_store.fresh(),
opt_ability: alias_var.value.opt_bound_ability,
})
.collect();
// TODO this gets ignored; is that a problem
let fresh_type_arguments = fresh_variables
.iter()
.map(|av| Type::Variable(av.var))
.collect();
// NB: We don't introduce the fresh variables here, we introduce them during constraint gen.
// NB: If there are bugs, check whether this is a problem!
let mut introduced_variables = IntroducedVariables::default();
instantiate_and_freshen_alias_type(
let (_fresh_type_arguments, fresh_lambda_set, fresh_type) = instantiate_and_freshen_alias_type(
var_store,
&mut introduced_variables,
&opaque.type_variables,
fresh_arguments,
fresh_type_arguments,
&opaque.lambda_set_variables,
opaque.typ.clone(),
)
);
(fresh_variables, fresh_lambda_set, fresh_type)
}
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
@ -1229,7 +1293,7 @@ fn can_tags<'a>(
// a duplicate
let new_name = 'inner: loop {
match tag {
Tag::Global { name, args } => {
Tag::Apply { name, args } => {
let name = name.value.into();
let mut arg_types = Vec::with_capacity(args.len());
@ -1248,32 +1312,7 @@ fn can_tags<'a>(
arg_types.push(ann);
}
let tag_name = TagName::Global(name);
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
}
Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
let symbol = Symbol::new(env.home, ident_id);
let mut arg_types = Vec::with_capacity(args.len());
for arg in args.iter() {
let ann = can_annotation_help(
env,
&arg.value,
arg.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
arg_types.push(ann);
}
let tag_name = TagName::Private(symbol);
let tag_name = TagName::Tag(name);
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,10 @@
use crate::exhaustive::{ExhaustiveContext, SketchedRows};
use crate::expected::{Expected, PExpected};
use roc_collections::soa::{EitherIndex, Index, Slice};
use roc_module::ident::TagName;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::subs::{ExhaustiveMark, Variable};
use roc_types::types::{Category, PatternCategory, Type};
#[derive(Debug)]
@ -19,6 +20,9 @@ pub struct Constraints {
pub pattern_expectations: Vec<PExpected<Type>>,
pub includes_tags: Vec<IncludesTag>,
pub strings: Vec<&'static str>,
pub sketched_rows: Vec<SketchedRows>,
pub eq: Vec<Eq>,
pub pattern_eq: Vec<PatternEq>,
}
impl Default for Constraints {
@ -40,6 +44,9 @@ impl Constraints {
let pattern_expectations = Vec::new();
let includes_tags = Vec::new();
let strings = Vec::new();
let sketched_rows = Vec::new();
let eq = Vec::new();
let pattern_eq = Vec::new();
types.extend([
Type::EmptyRec,
@ -90,6 +97,9 @@ impl Constraints {
pattern_expectations,
includes_tags,
strings,
sketched_rows,
eq,
pattern_eq,
}
}
@ -225,7 +235,7 @@ impl Constraints {
let expected_index = Index::push_new(&mut self.expectations, expected);
let category_index = Self::push_category(self, category);
Constraint::Eq(type_index, expected_index, category_index, region)
Constraint::Eq(Eq(type_index, expected_index, category_index, region))
}
#[inline(always)]
@ -240,7 +250,7 @@ impl Constraints {
let expected_index = Index::push_new(&mut self.expectations, expected);
let category_index = Self::push_category(self, category);
Constraint::Eq(type_index, expected_index, category_index, region)
Constraint::Eq(Eq(type_index, expected_index, category_index, region))
}
#[inline(always)]
@ -256,17 +266,17 @@ impl Constraints {
let expected_index = Index::push_new(&mut self.expectations, expected);
let category_index = Self::push_category(self, category);
let equal = Constraint::Eq(type_index, expected_index, category_index, region);
let equal = Constraint::Eq(Eq(type_index, expected_index, category_index, region));
let storage_type_index = Self::push_type_variable(storage_var);
let storage_category = Category::Storage(std::file!(), std::line!());
let storage_category_index = Self::push_category(self, storage_category);
let storage = Constraint::Eq(
let storage = Constraint::Eq(Eq(
storage_type_index,
expected_index,
storage_category_index,
region,
);
));
self.and_constraint([equal, storage])
}
@ -544,11 +554,6 @@ impl Constraints {
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
match constraint {
Constraint::Eq(..) => false,
Constraint::Store(..) => false,
Constraint::Lookup(..) => false,
Constraint::Pattern(..) => false,
Constraint::True => false,
Constraint::SaveTheEnvironment => true,
Constraint::Let(index, _) => {
let let_constraint = &self.let_constraints[index.index()];
@ -567,9 +572,15 @@ impl Constraints {
.iter()
.any(|c| self.contains_save_the_environment(c))
}
Constraint::IsOpenType(_) => false,
Constraint::IncludesTag(_) => false,
Constraint::PatternPresence(_, _, _, _) => false,
Constraint::Eq(..)
| Constraint::Store(..)
| Constraint::Lookup(..)
| Constraint::Pattern(..)
| Constraint::True
| Constraint::IsOpenType(_)
| Constraint::IncludesTag(_)
| Constraint::PatternPresence(_, _, _, _)
| Constraint::Exhaustive { .. } => false,
}
}
@ -597,18 +608,65 @@ impl Constraints {
Constraint::Store(type_index, variable, string_index, line_number)
}
pub fn exhaustive(
&mut self,
real_var: Variable,
real_region: Region,
category_and_expectation: Result<
(Category, Expected<Type>),
(PatternCategory, PExpected<Type>),
>,
sketched_rows: SketchedRows,
context: ExhaustiveContext,
exhaustive: ExhaustiveMark,
) -> Constraint {
let real_var = Self::push_type_variable(real_var);
let sketched_rows = Index::push_new(&mut self.sketched_rows, sketched_rows);
let equality = match category_and_expectation {
Ok((category, expected)) => {
let category = Index::push_new(&mut self.categories, category);
let expected = Index::push_new(&mut self.expectations, expected);
let equality = Eq(real_var, expected, category, real_region);
let equality = Index::push_new(&mut self.eq, equality);
Ok(equality)
}
Err((category, expected)) => {
let category = Index::push_new(&mut self.pattern_categories, category);
let expected = Index::push_new(&mut self.pattern_expectations, expected);
let equality = PatternEq(real_var, expected, category, real_region);
let equality = Index::push_new(&mut self.pattern_eq, equality);
Err(equality)
}
};
Constraint::Exhaustive(equality, sketched_rows, context, exhaustive)
}
}
roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8);
roc_error_macros::assert_sizeof_aarch64!(Constraint, 3 * 8);
#[derive(Clone)]
#[derive(Clone, Copy, Debug)]
pub struct Eq(
pub EitherIndex<Type, Variable>,
pub Index<Expected<Type>>,
pub Index<Category>,
pub Region,
);
#[derive(Clone, Copy, Debug)]
pub struct PatternEq(
pub EitherIndex<Type, Variable>,
pub Index<PExpected<Type>>,
pub Index<PatternCategory>,
pub Region,
);
#[derive(Clone, Copy)]
pub enum Constraint {
Eq(
EitherIndex<Type, Variable>,
Index<Expected<Type>>,
Index<Category>,
Region,
),
Eq(Eq),
Store(
EitherIndex<Type, Variable>,
Variable,
@ -641,6 +699,12 @@ pub enum Constraint {
Index<PatternCategory>,
Region,
),
Exhaustive(
Result<Index<Eq>, Index<PatternEq>>,
Index<SketchedRows>,
ExhaustiveContext,
ExhaustiveMark,
),
}
#[derive(Debug, Clone, Copy, Default)]
@ -670,7 +734,7 @@ pub struct IncludesTag {
impl std::fmt::Debug for Constraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Eq(arg0, arg1, arg2, arg3) => {
Self::Eq(Eq(arg0, arg1, arg2, arg3)) => {
write!(f, "Eq({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
}
Self::Store(arg0, arg1, arg2, arg3) => {
@ -695,6 +759,13 @@ impl std::fmt::Debug for Constraint {
arg0, arg1, arg2, arg3
)
}
Self::Exhaustive(arg0, arg1, arg2, arg3) => {
write!(
f,
"Exhaustive({:?}, {:?}, {:?}, {:?})",
arg0, arg1, arg2, arg3
)
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,18 @@
use crate::procedure::References;
use crate::scope::Scope;
use roc_collections::{MutMap, VecSet};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_module::symbol::{IdentIdsByModule, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
/// The canonicalization environment for a particular module.
pub struct Env<'a> {
/// The module's path. Private tags and unqualified references to identifiers
/// The module's path. Opaques and unqualified references to identifiers
/// are assumed to be relative to this path.
pub home: ModuleId,
pub dep_idents: &'a MutMap<ModuleId, IdentIds>,
pub dep_idents: &'a IdentIdsByModule,
pub module_ids: &'a ModuleIds,
@ -24,9 +25,6 @@ pub struct Env<'a> {
/// current tail-callable symbol
pub tailcallable_symbol: Option<Symbol>,
/// current closure name (if any)
pub closure_name_symbol: Option<Symbol>,
/// Symbols of values/functions which were referenced by qualified lookups.
pub qualified_value_lookups: VecSet<Symbol>,
@ -34,30 +32,23 @@ pub struct Env<'a> {
pub qualified_type_lookups: VecSet<Symbol>,
pub top_level_symbols: VecSet<Symbol>,
pub ident_ids: IdentIds,
pub exposed_ident_ids: IdentIds,
}
impl<'a> Env<'a> {
pub fn new(
home: ModuleId,
dep_idents: &'a MutMap<ModuleId, IdentIds>,
dep_idents: &'a IdentIdsByModule,
module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds,
) -> Env<'a> {
Env {
home,
dep_idents,
module_ids,
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later
exposed_ident_ids,
problems: Vec::new(),
closures: MutMap::default(),
qualified_value_lookups: VecSet::default(),
qualified_type_lookups: VecSet::default(),
tailcallable_symbol: None,
closure_name_symbol: None,
top_level_symbols: VecSet::default(),
}
}
@ -65,6 +56,7 @@ impl<'a> Env<'a> {
/// Returns Err if the symbol resolved, but it was not exposed by the given module
pub fn qualified_lookup(
&mut self,
scope: &Scope,
module_name_str: &str,
ident: &str,
region: Region,
@ -85,7 +77,7 @@ impl<'a> Env<'a> {
// You can do qualified lookups on your own module, e.g.
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
if module_id == self.home {
match self.ident_ids.get_id(&ident) {
match scope.locals.ident_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);
@ -103,9 +95,11 @@ impl<'a> Env<'a> {
value: ident,
region,
},
self.ident_ids
.idents()
.map(|(_, string)| string.as_ref().into())
scope
.locals
.ident_ids
.ident_strs()
.map(|(_, string)| string.into())
.collect(),
);
Err(error)
@ -127,11 +121,11 @@ impl<'a> Env<'a> {
}
None => {
let exposed_values = exposed_ids
.idents()
.ident_strs()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
ident.starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.map(|(_, ident)| Lowercase::from(ident))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,
@ -168,22 +162,7 @@ impl<'a> Env<'a> {
}
}
/// Generates a unique, new symbol like "$1" or "$5",
/// using the home module as the module_id.
///
/// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure.
pub fn gen_unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
Symbol::new(self.home, ident_id)
}
pub fn problem(&mut self, problem: Problem) {
self.problems.push(problem)
}
pub fn register_closure(&mut self, symbol: Symbol, references: References) {
self.closures.insert(symbol, references);
}
}

View File

@ -0,0 +1,423 @@
use crate::expr::{IntValue, WhenBranch};
use crate::pattern::DestructType;
use roc_collections::all::HumanIndex;
use roc_error_macros::internal_error;
use roc_exhaustive::{
is_useful, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union,
};
use roc_module::ident::{TagIdIntType, TagName};
use roc_region::all::{Loc, Region};
use roc_types::subs::{Content, FlatType, RedundantMark, Subs, SubsFmtContent, Variable};
use roc_types::types::AliasKind;
pub use roc_exhaustive::Context as ExhaustiveContext;
pub const GUARD_CTOR: &str = "#Guard";
pub const NONEXHAUSIVE_CTOR: &str = "#Open";
pub struct ExhaustiveSummary {
pub errors: Vec<Error>,
pub exhaustive: bool,
pub redundancies: Vec<RedundantMark>,
}
pub fn check(
subs: &Subs,
sketched_rows: SketchedRows,
context: ExhaustiveContext,
) -> ExhaustiveSummary {
let overall_region = sketched_rows.overall_region;
let mut all_errors = Vec::with_capacity(1);
let NonRedundantSummary {
non_redundant_rows,
errors,
redundancies,
} = sketched_rows.reify_to_non_redundant(subs);
all_errors.extend(errors);
let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) {
Ok(()) => true,
Err(errors) => {
all_errors.extend(errors);
false
}
};
ExhaustiveSummary {
errors: all_errors,
exhaustive,
redundancies,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum SketchedPattern {
Anything,
Literal(Literal),
Ctor(Variable, TagName, Vec<SketchedPattern>),
KnownCtor(Union, TagId, Vec<SketchedPattern>),
}
impl SketchedPattern {
fn reify(self, subs: &Subs) -> Pattern {
match self {
Self::Anything => Pattern::Anything,
Self::Literal(lit) => Pattern::Literal(lit),
Self::KnownCtor(union, tag_id, patterns) => Pattern::Ctor(
union,
tag_id,
patterns.into_iter().map(|pat| pat.reify(subs)).collect(),
),
Self::Ctor(var, tag_name, patterns) => {
let (union, tag_id) = convert_tag(subs, var, &tag_name);
Pattern::Ctor(
union,
tag_id,
patterns.into_iter().map(|pat| pat.reify(subs)).collect(),
)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct SketchedRow {
patterns: Vec<SketchedPattern>,
region: Region,
guard: Guard,
redundant_mark: RedundantMark,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SketchedRows {
rows: Vec<SketchedRow>,
overall_region: Region,
}
impl SketchedRows {
fn reify_to_non_redundant(self, subs: &Subs) -> NonRedundantSummary {
to_nonredundant_rows(subs, self)
}
}
fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedPattern {
use crate::pattern::Pattern::*;
use SketchedPattern as SP;
match pattern {
&NumLiteral(_, _, IntValue::I128(n), _) | &IntLiteral(_, _, _, IntValue::I128(n), _) => {
SP::Literal(Literal::Int(n))
}
&NumLiteral(_, _, IntValue::U128(n), _) | &IntLiteral(_, _, _, IntValue::U128(n), _) => {
SP::Literal(Literal::U128(n))
}
&FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))),
StrLiteral(v) => SP::Literal(Literal::Str(v.clone())),
&SingleQuote(c) => SP::Literal(Literal::Byte(c as u8)),
RecordDestructure { destructs, .. } => {
let tag_id = TagId(0);
let mut patterns = std::vec::Vec::with_capacity(destructs.len());
let mut field_names = std::vec::Vec::with_capacity(destructs.len());
for Loc {
value: destruct,
region: _,
} in destructs
{
field_names.push(destruct.label.clone());
match &destruct.typ {
DestructType::Required | DestructType::Optional(..) => {
patterns.push(SP::Anything)
}
DestructType::Guard(_, guard) => {
patterns.push(sketch_pattern(destruct.var, &guard.value))
}
}
}
let union = Union {
render_as: RenderAs::Record(field_names),
alternatives: vec![Ctor {
name: CtorName::Tag(TagName::Tag("#Record".into())),
tag_id,
arity: destructs.len(),
}],
};
SP::KnownCtor(union, tag_id, patterns)
}
AppliedTag {
tag_name,
arguments,
..
} => {
let simplified_args: std::vec::Vec<_> = arguments
.iter()
.map(|(var, arg)| sketch_pattern(*var, &arg.value))
.collect();
SP::Ctor(var, tag_name.clone(), simplified_args)
}
UnwrappedOpaque {
opaque, argument, ..
} => {
let (arg_var, argument) = &(**argument);
let tag_id = TagId(0);
let union = Union {
render_as: RenderAs::Opaque,
alternatives: vec![Ctor {
name: CtorName::Opaque(*opaque),
tag_id,
arity: 1,
}],
};
SP::KnownCtor(
union,
tag_id,
vec![sketch_pattern(*arg_var, &argument.value)],
)
}
// Treat this like a literal so we mark it as non-exhaustive
MalformedPattern(..) => SP::Literal(Literal::Byte(1)),
Underscore
| Identifier(_)
| AbilityMemberSpecialization { .. }
| Shadowed(..)
| OpaqueNotInScope(..)
| UnsupportedPattern(..) => SP::Anything,
}
}
pub fn sketch_when_branches(
target_var: Variable,
region: Region,
patterns: &[WhenBranch],
) -> SketchedRows {
let mut rows: Vec<SketchedRow> = Vec::with_capacity(patterns.len());
// If any of the branches has a guard, e.g.
//
// when x is
// y if y < 10 -> "foo"
// _ -> "bar"
//
// then we treat it as a pattern match on the pattern and a boolean, wrapped in the #Guard
// constructor. We can use this special constructor name to generate better error messages.
// This transformation of the pattern match only works because we only report exhaustiveness
// errors: the Pattern created in this file is not used for code gen.
//
// when x is
// #Guard y True -> "foo"
// #Guard _ _ -> "bar"
let any_has_guard = patterns.iter().any(|branch| branch.guard.is_some());
use SketchedPattern as SP;
for WhenBranch {
patterns,
guard,
value: _,
redundant,
} in patterns
{
let guard = if guard.is_some() {
Guard::HasGuard
} else {
Guard::NoGuard
};
for loc_pat in patterns {
// Decompose each pattern in the branch into its own row.
let patterns = if any_has_guard {
let guard_pattern = match guard {
Guard::HasGuard => SP::Literal(Literal::Bit(true)),
Guard::NoGuard => SP::Anything,
};
let tag_id = TagId(0);
let union = Union {
render_as: RenderAs::Guard,
alternatives: vec![Ctor {
tag_id,
name: CtorName::Tag(TagName::Tag(GUARD_CTOR.into())),
arity: 2,
}],
};
vec![SP::KnownCtor(
union,
tag_id,
// NB: ordering the guard pattern first seems to be better at catching
// non-exhaustive constructors in the second argument; see the paper to see if
// there is a way to improve this in general.
vec![guard_pattern, sketch_pattern(target_var, &loc_pat.value)],
)]
} else {
// Simple case
vec![sketch_pattern(target_var, &loc_pat.value)]
};
let row = SketchedRow {
patterns,
region: loc_pat.region,
guard,
redundant_mark: *redundant,
};
rows.push(row);
}
}
SketchedRows {
rows,
overall_region: region,
}
}
pub fn sketch_pattern_to_rows(
target_var: Variable,
region: Region,
pattern: &crate::pattern::Pattern,
) -> SketchedRows {
let row = SketchedRow {
patterns: vec![sketch_pattern(target_var, pattern)],
region,
// A single row cannot be redundant!
redundant_mark: RedundantMark::known_non_redundant(),
guard: Guard::NoGuard,
};
SketchedRows {
rows: vec![row],
overall_region: region,
}
}
/// REDUNDANT PATTERNS
struct NonRedundantSummary {
non_redundant_rows: Vec<Vec<Pattern>>,
redundancies: Vec<RedundantMark>,
errors: Vec<Error>,
}
/// INVARIANT: Produces a list of rows where (forall row. length row == 1)
fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary {
let SketchedRows {
rows,
overall_region,
} = rows;
let mut checked_rows = Vec::with_capacity(rows.len());
let mut redundancies = vec![];
let mut errors = vec![];
for SketchedRow {
patterns,
guard,
region,
redundant_mark,
} in rows.into_iter()
{
let next_row: Vec<Pattern> = patterns
.into_iter()
.map(|pattern| pattern.reify(subs))
.collect();
if matches!(guard, Guard::HasGuard) || is_useful(checked_rows.clone(), next_row.clone()) {
checked_rows.push(next_row);
} else {
redundancies.push(redundant_mark);
errors.push(Error::Redundant {
overall_region,
branch_region: region,
index: HumanIndex::zero_based(checked_rows.len()),
});
}
}
NonRedundantSummary {
non_redundant_rows: checked_rows,
redundancies,
errors,
}
}
fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, TagId) {
let content = subs.get_content_without_compacting(whole_var);
use {Content::*, FlatType::*};
match dealias_tag(subs, content) {
Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => {
let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext);
let mut num_tags = sorted_tags.len();
// DEVIATION: model openness by attaching a #Open constructor, that can never
// be matched unless there's an `Anything` pattern.
let opt_openness_tag = match subs.get_content_without_compacting(ext) {
FlexVar(_) | RigidVar(_) => {
let openness_tag = TagName::Tag(NONEXHAUSIVE_CTOR.into());
num_tags += 1;
Some((openness_tag, &[] as _))
}
Structure(EmptyTagUnion) => None,
// Anything else is erroneous and we ignore
_ => None,
};
// High tag ID if we're out-of-bounds.
let mut my_tag_id = TagId(num_tags as TagIdIntType);
let mut alternatives = Vec::with_capacity(num_tags);
let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter());
for (index, (tag, args)) in alternatives_iter.enumerate() {
let tag_id = TagId(index as TagIdIntType);
if this_tag == &tag {
my_tag_id = tag_id;
}
alternatives.push(Ctor {
name: CtorName::Tag(tag),
tag_id,
arity: args.len(),
});
}
let union = Union {
alternatives,
render_as: RenderAs::Tag,
};
(union, my_tag_id)
}
_ => internal_error!(
"Content is not a tag union: {:?}",
SubsFmtContent(content, subs)
),
}
}
pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content {
use Content::*;
let mut result = content;
loop {
match result {
Alias(_, _, real_var, AliasKind::Structural)
| RecursionVar {
structure: real_var,
..
} => result = subs.get_content_without_compacting(*real_var),
_ => return result,
}
}
}

View File

@ -1,3 +1,4 @@
use crate::abilities::SpecializationId;
use crate::annotation::{freshen_opaque_def, IntroducedVariables};
use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Def};
@ -6,10 +7,10 @@ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound,
};
use crate::pattern::{canonicalize_pattern, Pattern};
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern};
use crate::procedure::References;
use crate::scope::Scope;
use roc_collections::{MutSet, SendMap, VecMap, VecSet};
use roc_collections::{SendMap, VecMap, VecSet};
use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
@ -18,8 +19,8 @@ use roc_parse::ast::{self, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, LambdaSet, Type};
use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display};
use std::{char, u32};
@ -30,6 +31,7 @@ pub struct Output {
pub introduced_variables: IntroducedVariables,
pub aliases: VecMap<Symbol, Alias>,
pub non_closures: VecSet<Symbol>,
pub abilities_in_scope: Vec<Symbol>,
}
impl Output {
@ -82,13 +84,27 @@ pub enum Expr {
// Lookups
Var(Symbol),
AbilityMember(
/// Actual member name
Symbol,
/// Specialization to use
SpecializationId,
),
// Branching
When {
/// The actual condition of the when expression.
loc_cond: Box<Loc<Expr>>,
cond_var: Variable,
/// Result type produced by the branches.
expr_var: Variable,
region: Region,
loc_cond: Box<Loc<Expr>>,
/// The branches of the when, and the type of the condition that they expect to be matched
/// against.
branches: Vec<WhenBranch>,
branches_cond_var: Variable,
/// Whether the branches are exhaustive.
exhaustive: ExhaustiveMark,
},
If {
cond_var: Variable,
@ -98,8 +114,8 @@ pub enum Expr {
},
// Let
LetRec(Vec<Def>, Box<Loc<Expr>>, Variable),
LetNonRec(Box<Def>, Box<Loc<Expr>>, Variable),
LetRec(Vec<Def>, Box<Loc<Expr>>),
LetNonRec(Box<Def>, Box<Loc<Expr>>),
/// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it.
@ -163,8 +179,7 @@ pub enum Expr {
name: TagName,
},
/// A wrapping of an opaque type, like `$Age 21`
// TODO(opaques): $->@ above when opaques land
/// A wrapping of an opaque type, like `@Age 21`
OpaqueRef {
opaque_var: Variable,
name: Symbol,
@ -184,7 +199,7 @@ pub enum Expr {
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<OptAbleVar>,
lambda_set_variables: Vec<LambdaSet>,
},
@ -194,6 +209,74 @@ pub enum Expr {
// Compiles, but will crash if reached
RuntimeError(RuntimeError),
}
impl Expr {
pub fn category(&self) -> Category {
match self {
Self::Num(..) => Category::Num,
Self::Int(..) => Category::Int,
Self::Float(..) => Category::Float,
Self::Str(..) => Category::Str,
Self::SingleQuote(..) => Category::Character,
Self::List { .. } => Category::List,
&Self::Var(sym) => Category::Lookup(sym),
&Self::AbilityMember(sym, _) => Category::Lookup(sym),
Self::When { .. } => Category::When,
Self::If { .. } => Category::If,
Self::LetRec(_, expr) => expr.value.category(),
Self::LetNonRec(_, expr) => expr.value.category(),
&Self::Call(_, _, called_via) => Category::CallResult(None, called_via),
&Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op),
Self::ForeignCall { .. } => Category::ForeignCall,
Self::Closure(..) => Category::Lambda,
Self::Record { .. } => Category::Record,
Self::EmptyRecord => Category::Record,
Self::Access { field, .. } => Category::Access(field.clone()),
Self::Accessor(data) => Category::Accessor(data.field.clone()),
Self::Update { .. } => Category::Record,
Self::Tag {
name, arguments, ..
} => Category::TagApply {
tag_name: name.clone(),
args_count: arguments.len(),
},
Self::ZeroArgumentTag { name, .. } => Category::TagApply {
tag_name: name.clone(),
args_count: 0,
},
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
Self::Expect(..) => Category::Expect,
Self::RuntimeError(..) => Category::Unknown,
}
}
}
/// Stores exhaustiveness-checking metadata for a closure argument that may
/// have an annotated type.
#[derive(Clone, Copy, Debug)]
pub struct AnnotatedMark {
pub annotation_var: Variable,
pub exhaustive: ExhaustiveMark,
}
impl AnnotatedMark {
pub fn new(var_store: &mut VarStore) -> Self {
Self {
annotation_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
}
}
// NOTE: only ever use this if you *know* a pattern match is surely exhaustive!
// Otherwise you will get unpleasant unification errors.
pub fn known_exhaustive() -> Self {
Self {
annotation_var: Variable::EMPTY_TAG_UNION,
exhaustive: ExhaustiveMark::known_exhaustive(),
}
}
}
#[derive(Clone, Debug)]
pub struct ClosureData {
pub function_type: Variable,
@ -203,7 +286,7 @@ pub struct ClosureData {
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
pub arguments: Vec<(Variable, Loc<Pattern>)>,
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,
pub loc_body: Box<Loc<Expr>>,
}
@ -255,7 +338,11 @@ impl AccessorData {
let loc_body = Loc::at_zero(body);
let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))];
let arguments = vec![(
record_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(record_symbol)),
)];
ClosureData {
function_type: function_var,
@ -279,7 +366,7 @@ pub struct Field {
pub loc_expr: Box<Loc<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Recursive {
NotRecursive = 0,
Recursive = 1,
@ -291,6 +378,36 @@ pub struct WhenBranch {
pub patterns: Vec<Loc<Pattern>>,
pub value: Loc<Expr>,
pub guard: Option<Loc<Expr>>,
/// Whether this branch is redundant in the `when` it appears in
pub redundant: RedundantMark,
}
impl WhenBranch {
pub fn pattern_region(&self) -> Region {
Region::span_across(
&self
.patterns
.first()
.expect("when branch has no pattern?")
.region,
&self
.patterns
.last()
.expect("when branch has no pattern?")
.region,
)
}
}
impl WhenBranch {
pub fn region(&self) -> Region {
Region::across_all(
self.patterns
.iter()
.map(|p| &p.region)
.chain([self.value.region].iter()),
)
}
}
pub fn canonicalize_expr<'a>(
@ -596,139 +713,19 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default())
}
ast::Expr::Defs(loc_defs, loc_ret) => {
can_defs_with_return(
env,
var_store,
// The body expression gets a new scope for canonicalization,
// so clone it.
scope.clone(),
loc_defs,
loc_ret,
)
// The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| {
can_defs_with_return(env, var_store, inner_scope, loc_defs, loc_ret)
})
}
ast::Expr::Backpassing(_, _, _) => {
unreachable!("Backpassing should have been desugared by now")
}
ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => {
// The globally unique symbol that will refer to this closure once it gets converted
// into a top-level procedure for code gen.
//
// In the Foo module, this will look something like Foo.$1 or Foo.$2.
let symbol = env
.closure_name_symbol
.unwrap_or_else(|| env.gen_unique_symbol());
env.closure_name_symbol = None;
let (closure_data, output) =
canonicalize_closure(env, var_store, scope, loc_arg_patterns, loc_body_expr, None);
// The body expression gets a new scope for canonicalization.
// Shadow `scope` to make sure we don't accidentally use the original one for the
// rest of this block, but keep the original around for later diffing.
let original_scope = scope;
let mut scope = original_scope.clone();
let mut can_args = Vec::with_capacity(loc_arg_patterns.len());
let mut output = Output::default();
for loc_pattern in loc_arg_patterns.iter() {
let can_argument_pattern = canonicalize_pattern(
env,
var_store,
&mut scope,
&mut output,
FunctionArg,
&loc_pattern.value,
loc_pattern.region,
);
can_args.push((var_store.fresh(), can_argument_pattern));
}
let bound_by_argument_patterns: Vec<_> =
output.references.bound_symbols().copied().collect();
let (loc_body_expr, new_output) = canonicalize_expr(
env,
var_store,
&mut scope,
loc_body_expr.region,
&loc_body_expr.value,
);
let mut captured_symbols: MutSet<Symbol> =
new_output.references.value_lookups().copied().collect();
// filter out the closure's name itself
captured_symbols.remove(&symbol);
// symbols bound either in this pattern or deeper down are not captured!
captured_symbols.retain(|s| !new_output.references.bound_symbols().any(|x| x == s));
captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s));
// filter out top-level symbols
// those will be globally available, and don't need to be captured
captured_symbols.retain(|s| !env.top_level_symbols.contains(s));
// filter out imported symbols
// those will be globally available, and don't need to be captured
captured_symbols.retain(|s| s.module_id() == env.home);
// TODO any Closure that has an empty `captured_symbols` list could be excluded!
output.union(new_output);
// filter out aliases
debug_assert!(captured_symbols
.iter()
.all(|s| !output.references.references_type_def(*s)));
// captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s));
// filter out functions that don't close over anything
captured_symbols.retain(|s| !output.non_closures.contains(s));
// Now that we've collected all the references, check to see if any of the args we defined
// went unreferenced. If any did, report them as unused arguments.
for (sub_symbol, region) in scope.symbols() {
if !original_scope.contains_symbol(*sub_symbol) {
if !output.references.has_value_lookup(*sub_symbol) {
// The body never referenced this argument we declared. It's an unused argument!
env.problem(Problem::UnusedArgument(symbol, *sub_symbol, *region));
}
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
// we end up with weird conclusions like the expression (\x -> x + 1)
// references the (nonexistent) local variable x!
output.references.remove_value_lookup(sub_symbol);
}
}
env.register_closure(symbol, output.references.clone());
let mut captured_symbols: Vec<_> = captured_symbols
.into_iter()
.map(|s| (s, var_store.fresh()))
.collect();
// sort symbols, so we know the order in which they're stored in the closure record
captured_symbols.sort();
// store that this function doesn't capture anything. It will be promoted to a
// top-level function, and does not need to be captured by other surrounding functions.
if captured_symbols.is_empty() {
output.non_closures.insert(symbol);
}
(
Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: symbol,
captured_symbols,
recursive: Recursive::NotRecursive,
arguments: can_args,
loc_body: Box::new(loc_body_expr),
}),
output,
)
(Closure(closure_data), output)
}
ast::Expr::When(loc_cond, branches) => {
// Infer the condition expression's type.
@ -742,8 +739,16 @@ pub fn canonicalize_expr<'a>(
let mut can_branches = Vec::with_capacity(branches.len());
for branch in branches.iter() {
let (can_when_branch, branch_references) =
canonicalize_when_branch(env, var_store, scope, region, *branch, &mut output);
let (can_when_branch, branch_references) = scope.inner_scope(|inner_scope| {
canonicalize_when_branch(
env,
var_store,
inner_scope,
region,
*branch,
&mut output,
)
});
output.references.union_mut(&branch_references);
@ -764,6 +769,8 @@ pub fn canonicalize_expr<'a>(
region,
loc_cond: Box::new(can_cond),
branches: can_branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
(expr, output)
@ -784,7 +791,7 @@ pub fn canonicalize_expr<'a>(
}
ast::Expr::AccessorFunction(field) => (
Accessor(AccessorData {
name: env.gen_unique_symbol(),
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
@ -795,15 +802,15 @@ pub fn canonicalize_expr<'a>(
}),
Output::default(),
),
ast::Expr::GlobalTag(tag) => {
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
let symbol = env.gen_unique_symbol();
let symbol = scope.gen_unique_symbol();
(
ZeroArgumentTag {
name: TagName::Global((*tag).into()),
name: TagName::Tag((*tag).into()),
variant_var,
closure_name: symbol,
ext_var,
@ -811,23 +818,6 @@ pub fn canonicalize_expr<'a>(
Output::default(),
)
}
ast::Expr::PrivateTag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
let tag_ident = env.ident_ids.get_or_insert(&(*tag).into());
let symbol = Symbol::new(env.home, tag_ident);
let lambda_set_symbol = env.gen_unique_symbol();
(
ZeroArgumentTag {
name: TagName::Private(symbol),
variant_var,
ext_var,
closure_name: lambda_set_symbol,
},
Output::default(),
)
}
ast::Expr::OpaqueRef(opaque_ref) => {
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
// arguments are handled in the Apply branch.
@ -1016,6 +1006,133 @@ pub fn canonicalize_expr<'a>(
)
}
pub fn canonicalize_closure<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
loc_arg_patterns: &'a [Loc<ast::Pattern<'a>>],
loc_body_expr: &'a Loc<ast::Expr<'a>>,
opt_def_name: Option<Symbol>,
) -> (ClosureData, Output) {
scope.inner_scope(|inner_scope| {
canonicalize_closure_body(
env,
var_store,
inner_scope,
loc_arg_patterns,
loc_body_expr,
opt_def_name,
)
})
}
fn canonicalize_closure_body<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
loc_arg_patterns: &'a [Loc<ast::Pattern<'a>>],
loc_body_expr: &'a Loc<ast::Expr<'a>>,
opt_def_name: Option<Symbol>,
) -> (ClosureData, Output) {
// The globally unique symbol that will refer to this closure once it gets converted
// into a top-level procedure for code gen.
let symbol = opt_def_name.unwrap_or_else(|| scope.gen_unique_symbol());
let mut can_args = Vec::with_capacity(loc_arg_patterns.len());
let mut output = Output::default();
for loc_pattern in loc_arg_patterns.iter() {
let can_argument_pattern = canonicalize_pattern(
env,
var_store,
scope,
&mut output,
FunctionArg,
&loc_pattern.value,
loc_pattern.region,
);
can_args.push((
var_store.fresh(),
AnnotatedMark::new(var_store),
can_argument_pattern,
));
}
let bound_by_argument_patterns: Vec<_> =
BindingsFromPattern::new_many(can_args.iter().map(|x| &x.2)).collect();
let (loc_body_expr, new_output) = canonicalize_expr(
env,
var_store,
scope,
loc_body_expr.region,
&loc_body_expr.value,
);
let mut captured_symbols: Vec<_> = new_output
.references
.value_lookups()
.copied()
// filter out the closure's name itself
.filter(|s| *s != symbol)
// symbols bound either in this pattern or deeper down are not captured!
.filter(|s| !new_output.references.bound_symbols().any(|x| x == s))
.filter(|s| bound_by_argument_patterns.iter().all(|(k, _)| s != k))
// filter out top-level symbols those will be globally available, and don't need to be captured
.filter(|s| !env.top_level_symbols.contains(s))
// filter out imported symbols those will be globally available, and don't need to be captured
.filter(|s| s.module_id() == env.home)
// filter out functions that don't close over anything
.filter(|s| !new_output.non_closures.contains(s))
.filter(|s| !output.non_closures.contains(s))
.map(|s| (s, var_store.fresh()))
.collect();
output.union(new_output);
// Now that we've collected all the references, check to see if any of the args we defined
// went unreferenced. If any did, report them as unused arguments.
for (sub_symbol, region) in bound_by_argument_patterns {
if !output.references.has_value_lookup(sub_symbol) {
// The body never referenced this argument we declared. It's an unused argument!
env.problem(Problem::UnusedArgument(symbol, sub_symbol, region));
} else {
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
// we end up with weird conclusions like the expression (\x -> x + 1)
// references the (nonexistent) local variable x!
output.references.remove_value_lookup(&sub_symbol);
}
}
// store the references of this function in the Env. This information is used
// when we canonicalize a surrounding def (if it exists)
env.closures.insert(symbol, output.references.clone());
// sort symbols, so we know the order in which they're stored in the closure record
captured_symbols.sort();
// store that this function doesn't capture anything. It will be promoted to a
// top-level function, and does not need to be captured by other surrounding functions.
if captured_symbols.is_empty() {
output.non_closures.insert(symbol);
}
let closure_data = ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: symbol,
captured_symbols,
recursive: Recursive::NotRecursive,
arguments: can_args,
loc_body: Box::new(loc_body_expr),
};
(closure_data, output)
}
#[inline(always)]
fn canonicalize_when_branch<'a>(
env: &mut Env<'a>,
@ -1027,15 +1144,12 @@ fn canonicalize_when_branch<'a>(
) -> (WhenBranch, References) {
let mut patterns = Vec::with_capacity(branch.patterns.len());
let original_scope = scope;
let mut scope = original_scope.clone();
// TODO report symbols not bound in all patterns
for loc_pattern in branch.patterns.iter() {
let can_pattern = canonicalize_pattern(
env,
var_store,
&mut scope,
scope,
output,
WhenBranch,
&loc_pattern.value,
@ -1048,7 +1162,7 @@ fn canonicalize_when_branch<'a>(
let (value, mut branch_output) = canonicalize_expr(
env,
var_store,
&mut scope,
scope,
branch.value.region,
&branch.value.value,
);
@ -1057,35 +1171,30 @@ fn canonicalize_when_branch<'a>(
None => None,
Some(loc_expr) => {
let (can_guard, guard_branch_output) =
canonicalize_expr(env, var_store, &mut scope, loc_expr.region, &loc_expr.value);
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
branch_output.union(guard_branch_output);
Some(can_guard)
}
};
// Now that we've collected all the references for this branch, check to see if
// any of the new idents it defined were unused. If any were, report it.
for (symbol, region) in scope.symbols() {
let symbol = *symbol;
if !output.references.has_type_or_value_lookup(symbol)
&& !branch_output.references.has_type_or_value_lookup(symbol)
&& !original_scope.contains_symbol(symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
env.problem(Problem::UnusedDef(symbol, *region));
}
}
let references = branch_output.references.clone();
output.union(branch_output);
// Now that we've collected all the references for this branch, check to see if
// any of the new idents it defined were unused. If any were, report it.
for (symbol, region) in BindingsFromPattern::new_many(patterns.iter()) {
if !output.references.has_value_lookup(symbol) {
env.problem(Problem::UnusedDef(symbol, region));
}
}
(
WhenBranch {
patterns,
value,
guard,
redundant: RedundantMark::new(var_store),
},
references,
)
@ -1218,7 +1327,11 @@ fn canonicalize_var_lookup(
Ok(symbol) => {
output.references.insert_value_lookup(symbol);
Var(symbol)
if scope.abilities_store.is_ability_member_name(symbol) {
AbilityMember(symbol, scope.abilities_store.fresh_specialization_id())
} else {
Var(symbol)
}
}
Err(problem) => {
env.problem(Problem::RuntimeError(problem.clone()));
@ -1229,11 +1342,15 @@ fn canonicalize_var_lookup(
} else {
// Since module_name was nonempty, this is a qualified var.
// Look it up in the env!
match env.qualified_lookup(module_name, ident, region) {
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => {
output.references.insert_value_lookup(symbol);
Var(symbol)
if scope.abilities_store.is_ability_member_name(symbol) {
AbilityMember(symbol, scope.abilities_store.fresh_specialization_id())
} else {
Var(symbol)
}
}
Err(problem) => {
// Either the module wasn't imported, or
@ -1267,6 +1384,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Accessor { .. }
| other @ Update { .. }
| other @ Var(_)
| other @ AbilityMember(..)
| other @ RunLowLevel { .. }
| other @ ForeignCall { .. } => other,
@ -1297,6 +1415,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
region,
loc_cond,
branches,
branches_cond_var,
exhaustive,
} => {
let loc_cond = Box::new(Loc {
region: loc_cond.region,
@ -1321,6 +1441,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
patterns: branch.patterns,
value,
guard,
redundant: RedundantMark::new(var_store),
};
new_branches.push(new_branch);
@ -1332,6 +1453,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
region,
loc_cond,
branches: new_branches,
branches_cond_var,
exhaustive,
}
}
If {
@ -1383,7 +1506,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
Expect(Box::new(loc_condition), Box::new(loc_expr))
}
LetRec(defs, loc_expr, var) => {
LetRec(defs, loc_expr) => {
let mut new_defs = Vec::with_capacity(defs.len());
for def in defs {
@ -1404,10 +1527,10 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
value: inline_calls(var_store, scope, loc_expr.value),
};
LetRec(new_defs, Box::new(loc_expr), var)
LetRec(new_defs, Box::new(loc_expr))
}
LetNonRec(def, loc_expr, var) => {
LetNonRec(def, loc_expr) => {
let def = Def {
loc_pattern: def.loc_pattern,
loc_expr: Loc {
@ -1424,7 +1547,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
value: inline_calls(var_store, scope, loc_expr.value),
};
LetNonRec(Box::new(def), Box::new(loc_expr), var)
LetNonRec(Box::new(def), Box::new(loc_expr))
}
Closure(ClosureData {
@ -1561,7 +1684,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
// Wrap the body in one LetNonRec for each argument,
// such that at the end we have all the arguments in
// scope with the values the caller provided.
for ((_param_var, loc_pattern), (expr_var, loc_expr)) in
for ((_param_var, _exhaustive_mark, loc_pattern), (expr_var, loc_expr)) in
params.iter().cloned().zip(args.into_iter()).rev()
{
// TODO get the correct vars into here.
@ -1578,11 +1701,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
loc_answer = Loc {
region: Region::zero(),
value: LetNonRec(
Box::new(def),
Box::new(loc_answer),
var_store.fresh(),
),
value: LetNonRec(Box::new(def), Box::new(loc_answer)),
};
}

View File

@ -8,6 +8,7 @@ pub mod constraint;
pub mod def;
pub mod effect_module;
pub mod env;
pub mod exhaustive;
pub mod expected;
pub mod expr;
pub mod module;
@ -15,6 +16,6 @@ pub mod num;
pub mod operator;
pub mod pattern;
pub mod procedure;
mod reference_matrix;
pub mod scope;
pub mod string;
pub mod traverse;

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