Merge remote-tracking branch 'origin/trunk' into build-nix

This commit is contained in:
Anton-4 2022-07-19 13:47:00 +02:00
commit 9a3998a10b
No known key found for this signature in database
GPG Key ID: A13F4A6E21141925
1562 changed files with 214270 additions and 109918 deletions

37
.cargo/config Normal file
View File

@ -0,0 +1,37 @@
[alias]
test-gen-llvm = "test -p test_gen"
test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev"
test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm"
test-gen-llvm-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-llvm-wasm"
[target.wasm32-unknown-unknown]
# Rust compiler flags for minimum-sized .wasm binary in the web REPL
# opt-level=s Optimizations should focus more on size than speed
# lto=fat Spend extra effort on link-time optimization across crates
rustflags = ["-Copt-level=s", "-Clto=fat"]
[target.'cfg(not(target = "wasm32-unknown-unknown"))']
# Sets the avx, avx2, sse2 and sse4.2 target-features correctly based on your CPU.
rustflags = ["-Ctarget-cpu=native"]
[env]
# Gives us the path of the workspace root for use in cargo tests without having
# to compute it per-package.
# https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993
ROC_WORKSPACE_DIR = { value = "", relative = true }
# 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_TRACE_COMPACTION = "0"
ROC_PRINT_UNIFICATIONS_DERIVED = "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_PRINT_RUNTIME_ERROR_GEN = "0"
ROC_DEBUG_ALIAS_ANALYSIS = "0"
ROC_PRINT_LLVM_FN_VERIFICATION = "0"
ROC_PRINT_LOAD_LOG = "0"

View File

@ -1,10 +1,11 @@
on:
pull_request:
paths-ignore:
- '**.md'
on: [pull_request]
name: Benchmarks
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
ROC_NUM_WORKERS: 1
@ -20,13 +21,13 @@ jobs:
- uses: actions/checkout@v2
with:
ref: "trunk"
clean: "true"
clean: "true"
- name: Earthly version
run: earthly --version
- name: on trunk; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder
- uses: actions/checkout@v2
with:

View File

@ -1,10 +1,11 @@
on:
pull_request:
paths-ignore:
- '**.md'
on: [pull_request]
name: CI
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1

View File

@ -1,33 +1,31 @@
on:
schedule:
- cron: '0 0 * * *'
- cron: '0 9 * * *'
name: Nightly Release Build
name: Nightly Release Linux x86_64
jobs:
build:
name: Test and Build
name: Rust tests, build and package nightly release
runs-on: [self-hosted, i5-4690K]
timeout-minutes: 90
env:
FORCE_COLOR: 1 # for earthly logging
steps:
- uses: actions/checkout@v2
- name: install earthly
run: "sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'"
- name: Earthly print version
run: earthly --version
- name: install dependencies, build, run tests, build release
run: ./ci/safe-earthly.sh +build-nightly-release
- name: Create pre-release with test_archive.tar.gz
uses: WebFreak001/deploy-nightly@v1.1.0
uses: Anton-4/deploy-nightly@1609d8dfe211b078674801113ab7a2ec2938b2a9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
release_id: 51880579
asset_path: ./roc_linux_x86_64.tar.gz
asset_name: roc_nightly-linux_x86_64-$$.tar.gz # $$ to inserts date (YYYYMMDD) and 6 letter commit hash
asset_name: roc_nightly-linux_x86_64-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)
asset_content_type: application/gzip
max_releases: 3

View File

@ -0,0 +1,37 @@
on:
schedule:
- cron: '0 9 * * *'
name: Nightly Release macOS Apple Silicon
jobs:
test-and-build:
name: Rust tests, build and package nightly release
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v2
- name: zig version
run: zig version
- name: llvm version
run: llc --version | grep LLVM
- name: run tests
run: cargo test --locked --release
- name: write version to file
run: ./ci/write_version.sh
- name: build nightly release
run: cargo build --locked --release
- name: package release
run: ./ci/package_release.sh roc_darwin_apple_silicon.tar.gz
- name: Create pre-release with test_archive.tar.gz
uses: Anton-4/deploy-nightly@1609d8dfe211b078674801113ab7a2ec2938b2a9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
release_id: 51880579
asset_path: ./roc_darwin_apple_silicon.tar.gz
asset_name: roc_nightly-macos_apple_silicon-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)
asset_content_type: application/gzip
max_releases: 3

View File

@ -0,0 +1,52 @@
on:
schedule:
- cron: '0 9 * * 1' # 9=9am utc+0, 1=monday
name: Nightly Release macOS x86_64
env:
ZIG_VERSION: 0.9.1
LLVM_SYS_130_PREFIX: /usr/local/opt/llvm
jobs:
test-and-build:
name: Rust tests, build and package nightly release
runs-on: [macos-12]
timeout-minutes: 90
steps:
- uses: actions/checkout@v2
- name: Install zig
run: |
curl -L -o zig.tar.xz https://ziglang.org/download/${ZIG_VERSION}/zig-macos-x86_64-${ZIG_VERSION}.tar.xz && tar -xf zig.tar.xz
echo "${GITHUB_WORKSPACE}/zig-macos-x86_64-${ZIG_VERSION}" >> $GITHUB_PATH
- name: zig version
run: zig version
- name: Install LLVM
run: brew install llvm@13
- name: execute rust tests
uses: actions-rs/cargo@v1
with:
command: test
args: --locked # no --release yet until #3166 is fixed
- name: write version to file
run: ./ci/write_version.sh
- name: build release
uses: actions-rs/cargo@v1
with:
command: build
args: --release --locked
- name: package release
run: ./ci/package_release.sh roc_darwin_x86_64.tar.gz
- name: Create pre-release with test_archive.tar.gz
uses: Anton-4/deploy-nightly@1609d8dfe211b078674801113ab7a2ec2938b2a9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
release_id: 51880579
asset_path: ./roc_darwin_x86_64.tar.gz
asset_name: roc_nightly-macos_x86_64-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)
asset_content_type: application/gzip
max_releases: 3

23
.github/workflows/nix_linux_x86_64.yml vendored Normal file
View File

@ -0,0 +1,23 @@
on: [pull_request]
name: Nix linux x86_64 cargo test
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
jobs:
nix-linux-x86:
name: nix-linux-x86
runs-on: [self-hosted, i7-6700K]
timeout-minutes: 90
steps:
- uses: actions/checkout@v2
with:
clean: "true"
- name: execute tests with --release
run: /home/big-ci-user/.nix-profile/bin/nix develop -c cargo test --locked --release

View File

@ -0,0 +1,33 @@
on: [pull_request]
name: Nix apple silicon cargo test
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
jobs:
nix-apple-silicon:
name: nix-apple-silicon
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v2
with:
clean: "true"
- name: check code style with clippy
run: nix develop -c cargo clippy --workspace --tests -- --deny warnings
- name: check code style with clippy --release
run: cargo clippy --workspace --tests --release -- --deny warnings
# this needs to be done after clippy because of code generation in wasi-libc-sys
- name: check formatting with rustfmt
run: nix develop -c cargo fmt --all -- --check
- name: execute tests with --release
run: nix develop -c cargo test --locked --release

View File

@ -2,13 +2,17 @@ on: [pull_request]
name: SpellCheck
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
jobs:
spell-check:
name: spell check
runs-on: [self-hosted]
runs-on: [self-hosted, linux]
timeout-minutes: 10
env:
FORCE_COLOR: 1

21
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: 'Close stale PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/stale@v5
with:
delete-branch: true
exempt-pr-labels: 'blocked'
days-before-issue-close: -1
days-before-pr-stale: 30
days-before-pr-close: 30
stale-pr-message: 'Thank you for your contribution! Sometimes PRs end up staying open for a long time without activity, which can make the list of open PRs get long and time-consuming to review. To keep things manageable for reviewers, this bot automatically closes PRs that havent had activity in 60 days. This PR hasnt had activity in 30 days, so it will be automatically closed if there is no more activity in the next 30 days. Keep in mind that PRs marked `Closed` are not deleted, so no matter what, the PR will still be right here in the repo. You can always access it and reopen it anytime you like!'
stale-pr-label: 'inactive for 30 days'
close-pr-label: 'auto-closed'

View File

@ -9,7 +9,7 @@ on:
jobs:
deploy:
name: 'Deploy to Netlify'
runs-on: [self-hosted]
runs-on: [self-hosted, linux]
steps:
- uses: jsmrcaga/action-netlify-deploy@v1.6.0
with:

15
.gitignore vendored
View File

@ -5,9 +5,11 @@ zig-cache
.envrc
*.rs.bk
*.o
*.obj
*.tmp
*.wasm
# llvm human-readable output
# llvm human-readable output
*.ll
*.bc
@ -18,6 +20,9 @@ vgcore.*
.idea/
.vscode/
.ignore
.exrc
.vimrc
.nvimrc
#files too big to track in git
editor/benches/resources/100000_lines.roc
@ -42,3 +47,11 @@ earthly_log.txt
# created to test release
roc_linux_x86_64.tar.gz
# macOS .DS_Store files
.DS_Store
# files geneated when formatting fails
*.roc-format-failed
*.roc-format-failed-ast-after
*.roc-format-failed-ast-before

View File

@ -1 +1 @@
12.0.0
13.0.0

8
.reuse/dep5 Normal file
View File

@ -0,0 +1,8 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Roc
Upstream-Contact: Richard Feldman <oss@rtfeldman.com>
Source: https://github.com/rtfeldman/roc
Files: *
Copyright: © The Roc Contributors
License: UPL-1.0

44
AUTHORS
View File

@ -42,4 +42,46 @@ Viktor Fröberg <vikfroberg@gmail.com>
Locria Cyber <locriacyber@noreply.users.github.com>
Matthias Beyer <mail@beyermatthias.de>
Tim Whiting <tim@whitings.org>
Logan Lowder <logan.lowder@logikcull.com>
Joshua Warner <joshuawarner32@gmail.com>
Luiz Carlos L. G. de Oliveira <luizcarlos1405@gmail.com>
Oleksii Skidan <al.skidan@gmail.com>
Martin Janiczek <martin@janiczek.cz>
Eric Newbury <enewbury@users.noreply.github.com>
Ayaz Hafiz <ayaz.hafiz.1@gmail.com>
Johannes Maas <github@j-maas.de>
Takeshi Sato <doublequotation@gmail.com>
Joost Baas <joost@joostbaas.eu>
Callum Dunster <cdunster@users.noreply.github.com>
Martin Stewart <MartinSStewart@gmail.com>
James Hegedus <jthegedus@hey.com>
Cristiano Piemontese <cristiano.piemontese@vidiemme.it>
Yann Simon <yann.simon.fr@gmail.com>
Shahn Hogan <shahnhogan@hotmail.com>
Tankor Smash <tankorsmash+github@gmail.com>
Matthias Devlamynck <matthias.devlamynck@mailoo.org>
Jan Van Bruggen <JanCVanB@users.noreply.github.com>
Mats Sigge <<mats.sigge@gmail.com>>
Drew Lazzeri <dlazzeri1@gmail.com>
Tom Dohrmann <erbse.13@gmx.de>
Elijah Schow <elijah.schow@gmail.com>
Derek Gustafson <degustaf@gmail.com>
Philippe Vinchon <p.vinchon@gmail.com>
Pierre-Henri Trivier <phtrivier@yahoo.fr>
Elliot Waite <1767836+elliotwaite@users.noreply.github.com>
zimt28 <1764689+zimt28@users.noreply.github.com>
Ananda Umamil <zweimach@zweimach.org>
SylvanSign <jake.d.bray@gmail.com>
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
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>
Drake Bennion <drake.bennion@gmail.com>
Hashi364 <49736221+Kiyoshi364@users.noreply.github.com>
Jared Forsyth <jared@jaredforsyth.com>
Patrick Kilgore <git@pck.email>
Marten/Qqwy <w-m@wmcode.nl>

View File

@ -1,11 +1,70 @@
# Building the Roc compiler from source
Installation should be a smooth process, let us now if anything does not work perfectly on [Roc Zulip](https://roc.zulipchat.com) or by creating an issue.
## Installing LLVM, Zig, valgrind, and Python 2.7
## Using Nix
We highly recommend Using [nix](https://nixos.org/download.html) to quickly install all dependencies necessary to build roc.
### On Linux x86_64/aarch64 or MacOS aarch64/arm64/x86_64
#### Install
If you are running ArchLinux or a derivative like Manjaro, you'll need to run `sudo sysctl -w kernel.unprivileged_userns_clone=1` before installing nix.
Install nix (not necessary on NixOS):
```
sh <(curl -L https://nixos.org/nix/install) --daemon
```
Open a new terminal and install nixFlakes in your environment:
```
nix-env -iA nixpkgs.nixFlakes
```
Edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add:
```
experimental-features = nix-command flakes
```
If Nix was installed in multi-user mode, make sure to restart the nix-daemon.
If you don't know how to do this, restarting your computer will also do the job.
#### Usage
Now with nix set up, you just need to run one command from the roc project root directory:
```
nix develop
```
You should be in a shell with everything needed to build already installed.
Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
Use `cargo build` to build the whole project.
#### Extra tips
If you want to load all dependencies automatically whenever you `cd` into `roc`, check out [direnv](https://direnv.net/).
Then you will no longer need to execute `nix develop` first.
### Editor
The editor is a :construction:WIP:construction: and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
`cargo run edit` should work on NixOS and MacOS. If you use Linux x86_64, follow the instructions below.
If you're not already in a nix shell, execute `nix develop` at the the root of the repo folder and then execute:
```
nixVulkanIntel cargo run edit
```
## Troubleshooting
Create an issue if you run into problems not listed here.
That will help us improve this document for everyone who reads it in the future!
## Manual Install
To build the compiler, you need these installed:
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* [Zig](https://ziglang.org/), see below for version
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
* On Debian/Ubuntu `sudo apt-get install pkg-config`
@ -16,9 +75,9 @@ To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal development you should be fine without it.
### libcxb libraries
### libxcb libraries
You may see an error like this during builds:
@ -35,7 +94,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.
@ -47,107 +106,40 @@ 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
`/usr/local/opt/llvm/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_130_PREFIX=/usr/local/opt/llvm@13
```
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 `llvm-as-12` and `clang-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
[Troubleshooting](#troubleshooting)
## Using Nix
### Building
:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation:
### Install
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.
Anyone having trouble installing the proper version of LLVM themselves might also prefer this method.
First, install nix:
`curl -L https://nixos.org/nix/install | sh`
If MacOS and using a version >= 10.15:
`sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume`
You may prefer to setup up the volume manually by following nix documentation.
> You may need to restart your terminal
### Usage
Now with nix installed you just need to run one command:
`nix-shell`
> This may not output anything for a little while. This is normal, hang in there. Also make sure you are in the roc project root.
> Also, if you're on NixOS you'll need to enable opengl at the system-wide level. You can do this in configuration.nix with `hardware.opengl.enable = true;`. If you don't do this, nix-shell will fail!
You should be in a shell with everything needed to build already installed. Next run:
`cargo run repl`
You should be in a repl now. Have fun!
### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/target/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
### Editor
When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well:
```bash
nix-shell
git clone https://github.com/guibou/nixGL
cd nixGL
```
If you have an Nvidia graphics card, run:
```
nix-env -f ./ -iA nixVulkanNvidia
```
If you have integrated Intel graphics, run:
```
nix-env -f ./ -iA nixVulkanIntel
```
Check the [nixGL repo](https://github.com/guibou/nixGL) for other configurations.
Now you should be able to run the editor:
```bash
cd roc
nixVulkanNvidia cargo run edit `# replace Nvidia with the config you chose in the previous step`
```
## Troubleshooting
Create an issue if you run into problems not listed here.
That will help us improve this document for everyone who reads it in the future!
Use `cargo build` to build the whole project.
Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
### LLVM installation on Linux
@ -161,9 +153,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
@ -178,20 +170,29 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
### LLVM installation on Windows
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source
on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me:
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, the editor and many tests will not work on windows.
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 tool; the Build Tools are just the CLI tools, which is all I wanted)
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools" [note: as of September 2021 this should no longer be necessary - the next time anyone tries this, please try it without this step and make a PR to delete this step if it's no longer needed!]
1. I launched the "x64 Native Tools Command Prompt for Visual Studio 2019" application (note: not the similarly-named "x86" one!)
1. Make sure [Python 2.7](https://www.python.org/) and [CMake 3.17](http://cmake.org/) are installed on your system.
1. I followed most of the steps under LLVM's [building from source instructions](https://github.com/llvm/llvm-project#getting-the-source-code-and-building-llvm) up to the `cmake -G ...` command, which didn't work for me. Instead, at that point I did the following step.
1. I ran `cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm` to generate a NMake makefile.
1. Once that completed, I ran `nmake` to build LLVM. (This took about 2 hours on my laptop.)
1. Finally, I set an environment variable `LLVM_SYS_100_PREFIX` to point to the `build` directory where I ran the `cmake` command.
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/v13.0.0).
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_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-13.0.0-win64\bin",
"User"
)
```
Once all that was done, `cargo` ran successfully for Roc!
Once all that was done, `cargo build` ran successfully for Roc!
### Build speed on WSL/WSL2
If your Roc project folder is in the Windows filesystem but you're compiling from Linux, rebuilds may be as much as 20x slower than they should be!
Disk access during linking seems to be the bottleneck. It's recommended to move your folder to the Linux filesystem.
## Use LLD for the linker
@ -207,8 +208,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.

44
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,44 @@
# Code of Conduct for the Roc GitHub Repository and Zulip Chat
## Our Pledge
In the interest of fostering an open and welcoming environment, we as participants in the Roc GitHub repository and Zulip Chat pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Kindly giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Telling others to be less sensitive, or that they should not feel hurt or offended by something
## Enforcement Responsibilities
Moderators are responsible for clarifying and enforcing the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior, including by project leaders or otherwise prominent community members.
Moderators have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. If a moderator is the subject of a reported code of conduct breach, or has a major conflict of interest, they should recuse themselves from participation in the resolution of that report. An exception to this is if more than 50% of the moderators would recuse themselves for the same reported breach; in such a case, none of them need to.
## Scope
This Code of Conduct applies within the Roc GitHub repository as well as the Roc Zulip Chat. It also applies when an individual is officially representing the project or its community in public spaces. Examples of representing the project or community include using an official roc-lang.org e-mail address, posting via an official social media account, or acting as the project's appointed representative at an online or offline event. Representation of the project may be further defined and clarified by moderators.
## Reporting
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [conduct@roc-lang.org](mailto:conduct@roc-lang.org). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The moderation team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Moderators who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of project leadership.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

View File

@ -10,7 +10,15 @@ Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions.
## Running Tests
To run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
Most contributors execute the following commands befor pushing their code:
```
cargo test
cargo fmt --all -- --check
cargo clippy --workspace --tests -- --deny warnings
```
Execute `cargo fmt --all` to fix the formatting.
If you want to run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
```
earthly +test-all
```
@ -19,9 +27,19 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
## Contribution Tips
- Create an issue if the purpose of a struct/field/type/function/... is not immediately clear from its name or nearby comments.
- You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security.
- All your commits need to be signed to prevent impersonation:
1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key).
3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
4. Make git sign your commits automatically:
```
git config --global commit.gpgsign true
```
## Can we do better?

4207
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,69 @@
[workspace]
members = [
"compiler/ident",
"compiler/region",
"compiler/collections",
"compiler/module",
"compiler/parse",
"compiler/can",
"compiler/problem",
"compiler/types",
"compiler/builtins",
"compiler/constrain",
"compiler/unify",
"compiler/solve",
"compiler/reporting",
"compiler/fmt",
"compiler/mono",
"compiler/test_mono_macros",
"compiler/test_mono",
"compiler/load",
"compiler/gen_llvm",
"compiler/gen_dev",
"compiler/gen_wasm",
"compiler/build",
"compiler/arena_pool",
"compiler/test_gen",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",
"vendor/pretty",
"editor",
"ast",
"cli",
"cli/cli_utils",
"code_markup",
"roc_std",
"utils",
"docs",
"linker",
"crates/compiler/ident",
"crates/compiler/region",
"crates/compiler/collections",
"crates/compiler/exhaustive",
"crates/compiler/module",
"crates/compiler/parse",
"crates/compiler/can",
"crates/compiler/problem",
"crates/compiler/types",
"crates/compiler/builtins",
"crates/compiler/constrain",
"crates/compiler/unify",
"crates/compiler/solve",
"crates/compiler/late_solve",
"crates/compiler/fmt",
"crates/compiler/derive_key",
"crates/compiler/mono",
"crates/compiler/alias_analysis",
"crates/compiler/test_mono",
"crates/compiler/test_derive",
"crates/compiler/load",
"crates/compiler/load_internal",
"crates/compiler/gen_llvm",
"crates/compiler/gen_dev",
"crates/compiler/gen_wasm",
"crates/compiler/build",
"crates/compiler/arena_pool",
"crates/compiler/test_gen",
"crates/compiler/roc_target",
"crates/compiler/debug_flags",
"crates/vendor/inkwell",
"crates/vendor/pathfinding",
"crates/vendor/pretty",
"crates/bindgen",
"crates/editor",
"crates/ast",
"crates/cli",
"crates/code_markup",
"crates/highlight",
"crates/error_macros",
"crates/reporting",
"crates/repl_cli",
"crates/repl_eval",
"crates/repl_test",
"crates/repl_wasm",
"crates/repl_expect",
"crates/test_utils",
"crates/utils",
"crates/docs",
"crates/docs_cli",
"crates/linker",
"crates/wasi-libc-sys",
]
exclude = [
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
"examples",
"ci/bench-runner",
# Ignore building these normally. They are only imported by tests.
# The tests will still correctly build them.
"crates/cli_utils",
"crates/compiler/test_mono_macros",
# `cargo build` would cause roc_std to be built with default features which errors on windows
"crates/roc_std",
]
exclude = [ "ci/bench-runner" ]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more.
#

View File

@ -1,41 +0,0 @@
# The Roc Code of Conduct
A version of this document [can be found online](https://www.roc-lang.org/conduct).
It is based on the Rust Code of Conduct, which [can also be found online](https://www.rust-lang.org/conduct).
## Conduct
**Contact**: [roc-mods@roc-lang.org](mailto:roc-mods@roc-lang.org)
* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
* Please be kind and courteous. There's no need to be mean or rude.
: Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the <a href="http://citizencodeofconduct.org/">Citizen Code of Conduct</a>; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups.
* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [Roc moderation team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back.
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
## Moderation
These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, please contact the [Roc moderation team][mod_team].
1. Remarks that violate the Roc standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
3. Moderators will first respond to such remarks with a warning.
4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off.
5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed.
8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.
In the Roc community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
The enforcement policies listed above apply to all official Roc venues; including official Zulip chat (https://roc.zulipchat.com); and GitHub repositories under the roc-lang organization. If you wish to use this code of conduct (or the Rust code of conduct, on which it is based) for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
[mod_team]: https://www.roc-lang.org/moderation

108
Earthfile
View File

@ -1,4 +1,4 @@
FROM rust:1.54-slim-bullseye
FROM rust:1.61.0-slim-bullseye # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
@ -10,35 +10,38 @@ 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:
install-zig-llvm-valgrind:
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"
RUN cargo install bindgen
# llvm
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
# clippy
RUN rustup component add clippy
# rustfmt
RUN rustup component add rustfmt
# wasm repl & tests
RUN rustup target add wasm32-unknown-unknown wasm32-wasi
RUN apt -y install libssl-dev
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
# criterion
RUN cargo install cargo-criterion
# editor
RUN apt -y install libxkbcommon-dev
# sccache
RUN apt -y install libssl-dev
RUN cargo install sccache
RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
@ -46,44 +49,58 @@ install-zig-llvm-valgrind-clippy-rustfmt:
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
FROM +install-zig-llvm-valgrind
COPY --dir crates examples Cargo.toml Cargo.lock version.txt www ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh
FROM +install-zig-llvm-valgrind
COPY --dir crates/compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh && ./run-wasm-tests.sh
check-clippy:
build-rust-test:
FROM +copy-dirs
RUN cargo clippy -V
RUN echo "deb http://deb.debian.org/debian testing main contrib non-free" >> /etc/apt/sources.list # to get gcc 10.3
RUN apt -y update
RUN apt -y install gcc-10 g++-10 && rm /usr/bin/gcc && ln -s /usr/bin/gcc-10 /usr/bin/gcc # gcc-9 maybe causes segfault
RUN gcc --version
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs
RUN cargo fmt --version
RUN cargo fmt --all -- --check
cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats
check-typos:
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix version.txt ./
COPY --dir .github ci crates examples nightly_benches www *.md LEGAL_DETAILS flake.nix version.txt ./
RUN typos
test-rust:
FROM +copy-dirs
FROM +build-rust-test
ENV ROC_WORKSPACE_DIR=/earthbuild
ENV RUST_BACKTRACE=1
# for race condition problem with cli test
ENV ROC_NUM_WORKERS=1
# run one of the benchmarks to make sure the host is compiled
# not pre-compiling the host can cause race conditions
RUN gcc --version
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release && sccache --show-stats
# run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --release -- --backend=x86_32 examples/benchmarks/NQueens.roc
cargo test --locked --release --features with_sound --workspace && sccache --show-stats
# test the dev and wasm backend: they require an explicit feature flag.
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
# repl_test: build the compiler for wasm target, then run the tests on native target
RUN --mount=type=cache,target=$SCCACHE_DIR \
crates/repl_test/test_wasm.sh && sccache --show-stats
# run i386 (32-bit linux) cli tests
# NOTE: disabled until zig 0.9
# 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 website deployment works (that is, make sure build.sh returns status code 0)
ENV REPL_DEBUG=1
RUN bash www/build.sh
verify-no-git-changes:
FROM +test-rust
@ -98,33 +115,32 @@ verify-no-git-changes:
test-all:
BUILD +test-zig
BUILD +check-rustfmt
BUILD +check-clippy
BUILD +test-rust
BUILD +verify-no-git-changes
build-nightly-release:
FROM +test-rust
COPY --dir .git ./
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
# version.txt is used by the CLI: roc --version
RUN printf "nightly pre-release, built from commit " > version.txt
RUN git log --pretty=format:'%h' -n 1 >> version.txt
RUN cargo build --release
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
RUN ./ci/write_version.sh
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release
RUN ./ci/package_release.sh roc_linux_x86_64.tar.gz
SAVE ARTIFACT ./roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
prep-bench-folder:
FROM +copy-dirs
# to make use of avx, avx2, sse2, sse4.2... instructions
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
ARG BENCH_SUFFIX=branch
RUN cargo criterion -V
RUN --mount=type=cache,target=$SCCACHE_DIR cd cli && cargo criterion --no-run
RUN mkdir -p bench-folder/compiler/builtins/bitcode/src
RUN --mount=type=cache,target=$SCCACHE_DIR cd crates/cli && cargo criterion --no-run
RUN mkdir -p bench-folder/crates/compiler/builtins/bitcode/src
RUN mkdir -p bench-folder/target/release/deps
RUN mkdir -p bench-folder/examples/benchmarks
RUN cp examples/benchmarks/*.roc bench-folder/examples/benchmarks/
RUN cp -r examples/benchmarks/platform bench-folder/examples/benchmarks/
RUN cp compiler/builtins/bitcode/src/str.zig bench-folder/compiler/builtins/bitcode/src
RUN cp crates/compiler/builtins/bitcode/src/str.zig bench-folder/crates/compiler/builtins/bitcode/src
RUN cp target/release/roc bench-folder/target/release
# copy the most recent time bench to bench-folder
RUN cp target/release/deps/`ls -t target/release/deps/ | grep time_bench | head -n 1` bench-folder/target/release/deps/time_bench

405
FAQ.md Normal file
View File

@ -0,0 +1,405 @@
# Frequently Asked Questions
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
A key part of our editor will be the use of plugins that are shipped with libraries. Think of a regex visualizer, parser debugger, or color picker. For library authors, it would be most convenient to write these plugins in Roc. Trying to dynamically load library plugins (written in Roc) in for example VSCode seems very difficult.
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious
effort to focus on the Roc Editor *instead of* adding Roc support to other editors - specifically in order to give the Roc
Editor the best possible chance at kickstarting a virtuous cycle of plugin authorship.
This is an unusual approach, but there are more details in [this 2021 interview](https://youtu.be/ITrDd6-PbvY?t=212).
In the meantime, using CoffeeScript syntax highlighting for .roc files turns out to work surprisingly well!
## Why is there no way to specify "import everything this module exposes" in `imports`?
In [Elm](https://elm-lang.org), it's possible to import a module in a way that brings everything that module
exposes into scope. It can be convenient, but like all programming language features, it has downsides.
A minor reason Roc doesn't have this feature is that exposing everything can make it more difficult
outside the editor (e.g. on a website) to tell where something comes from, especially if multiple imports are
using this. ("I don't see `blah` defined in this module, so it must be coming from an import...but which of
these several import-exposing-everything modules could it be? I'll have to check all of them, or
download this code base and open it up in the editor so I can jump to definition!")
The main reason for this design, though, is compiler performance.
Currently, the name resolution step in compilation can be parallelized across modules, because it's possible to
tell if there's a naming error within a module using only the contents of that module. If "expose everything" is
allowed, then it's no longer clear whether anything is a naming error or not, until all the "expose everything"
modules have been processed, so we know exactly which names they expose. Because that feature doesn't exist in Roc,
all modules can do name resolution in parallel.
Of note, allowing this feature would only slow down modules that used it; modules that didn't use it would still be
parallelizable. However, when people find out ways to speed up their builds (in any language), advice starts to
circulate about how to unlock those speed boosts. If Roc had this feature, it's predictable that a commonly-accepted
piece of advice would eventually circulate: "don't use this feature because it slows down your builds."
If a feature exists in a language, but the common recommendation is never to use it, that's cause for reconsidering
whether the feature should be in the language at all. In the case of this feature, I think it's simpler if the
language doesn't have it; that way nobody has to learn (or spend time spreading the word) about the
performance-boosting advice not to use it.
## Why can't functions be compared for equality using the `==` operator?
Function equality has been proven to be undecidable in the general case because of the [halting problem](https://en.wikipedia.org/wiki/Halting_problem).
So while we as humans might be able to look at `\x -> x + 1` and `\x -> 1 + x` and know that they're equivalent,
in the general case it's not possible for a computer to do this reliably.
There are some other potential ways to define function equality, but they all have problems.
One way would be to have two functions be considered equal if their source code is equivalent. (Perhaps disregarding
comments and spaces.) This sounds reasonable, but it means that now revising a function to do
exactly the same thing as before (say, changing `\x -> x + 1` to `\x -> 1 + x`) can cause a bug in a
distant part of the code base. Defining function equality this way means that revising a function's internals
is no longer a safe, local operation - even if it gives all the same outputs for all the same inputs.
Another option would be to define it using "reference equality." This is what JavaScript does, for example.
However, Roc does not use reference equality anywhere else in the language, and it would mean that (for example)
passing `\x -> x + 1` to a function compared to defining `fn = \x -> x + 1` elsewhere and then passing `fn` into
the function might give different answers.
Both of these would make revising code riskier across the entire language, which is very undesirable.
Another option would be to define that function equality always returns `False`. So both of these would evaluate
to `False`:
* `(\x -> x + 1) == (\x -> 1 + x)`
* `(\x -> x + 1) == (\x -> x + 1)`
This makes function equality effectively useless, while still technically allowing it. It has some other downsides:
* Now if you put a function inside a record, using `==` on that record will still type-check, but it will then return `False`. This could lead to bugs if you didn't realize you had accidentally put a function in there - for example, because you were actually storing a different type (e.g. an opaque type) and didn't realize it had a function inside it.
* If you put a function (or a value containing a function) into a `Dict` or `Set`, you'll never be able to get it out again. This is a common problem with [NaN](https://en.wikipedia.org/wiki/NaN), which is also defined not to be equal to itself.
The first of these problems could be addressed by having function equality always return `True` instead of `False` (since that way it would not affect other fields' equality checks in a record), but that design has its own problems:
* Although function equality is still useless, `(\x -> x + 1) == (\x -> x)` returns `True`. Even if it didn't lead to bugs in practice, this would certainly be surprising and confusing to beginners.
* Now if you put several different functions into a `Dict` or `Set`, only one of them will be kept; the others will be discarded or overwritten. This could cause bugs if a value stored a function internally, and then other functions relied on that internal function for correctness.
Each of these designs makes Roc a language that's some combination of more error-prone, more confusing, and more
brittle to change. Disallowing function equality at compile time eliminates all of these drawbacks.
## Why doesn't Roc have a `Maybe` or `Option` or `Optional` type, or `null` or `nil` or `undefined`?
It's common for programming languages to have a [null reference](https://en.wikipedia.org/wiki/Null_pointer)
(e.g. `null` in C, `nil` in Ruby, `None` in Python, or `undefined` in JavaScript).
The inventor of the null reference refers to it as his "[billion dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)" because it "has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years."
For this and other reasons, many languages do not include a null reference, but instead have a standard library
data type which can be used in situations where a null reference would otherwise be used. Common names for this
null reference alternative type include `Maybe` (like in Haskell or Elm), `Option` (like in OCaml or Rust),
and `Optional` (like in Java).
By design, Roc does not have one of these. There are several reasons for this.
First, if a function returns a potential error, Roc has the convention to use `Result` with an error type that
has a single tag describing what went wrong. (For example, `List.first : List a -> Result a [ListWasEmpty]*`
instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with
other operations that can fail; there's no need to have functions like `Result.toMaybe` or `Maybe.toResult`,
because in Roc, the convention is that operations that can fail always use `Result`.
Second, optional record fields can be handled using Roc's Optional Record Field language feature, so using a type like `Maybe` there would be less ergonomic.
To describe something that's neither an optional field nor an operation that can fail, an explicit tag union can be
more descriptive than something like `Maybe`. For example, if a record type has an `artist` field, but the artist
information may not be available, compare these three alternative ways to represent that:
* `artist : Maybe Artist`
* `artist : [Loading, Loaded Artist]`
* `artist : [Unspecified, Specified Artist]`
All three versions tell us that we might not have access to an `Artist`. However, the `Maybe` version doesn't
tell us why that might be. The `Loading`/`Loaded` version tells us we don't have one *yet*, because we're
still loading it, whereas the `Unspecified`/`Specified` version tells us we don't have one and shouldn't expect
to have one later if we wait, because it wasn't specified.
Naming aside, using explicit tag unions also makes it easier to transition to richer data models. For example,
after using `[Loading, Loaded Artist]` for awhile, we might realize that there's another possible state: loading
failed due to an error. If we modify this to be `[Loading, Loaded Artist, Errored LoadingErr]`, all
of our code for the `Loading` and `Loaded` states will still work.
In contrast, if we'd had `Maybe Artist` and were using helper functions like `Maybe.isNone` (a common argument
for using `Maybe` even when it's less self-descriptive), we'd have to rewrite all the code which used those
helper functions. As such, a subtle downside of these helper functions is that they discourage any change to
the data model that would break their call sites, even if that change would improve the data model overall.
On a historical note, `Maybe` may have been thought of as a substitute for null references—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed. That said, in languages that do not have an equivalent of Roc's tag unions, it's much less ergonomic to write something like `Result a [ListWasEmpty]*`, so that design would not fit those languages as well as it fits Roc.
## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types?
_Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._
A valuable aspect of Roc's type system is that it has decidable [principal](https://en.wikipedia.org/wiki/Principal_type)
type inference. This means that:
* At compile time, Roc can correctly infer the types for every expression in a program, even if you don't annotate any of the types.
* This inference always infers the most general type possible; you couldn't possibly add a valid type annotation that would make the type more flexible than the one that Roc would infer if you deleted the annotation.
It's been proven that any type system which supports either [higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf) or [arbitrary-rank types](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf) cannot have decidable
principal type inference. With either of those features in the language, there will be situations where the compiler
would be unable to infer a type—and you'd have to write a type annotation. This also means there would be
situations where the editor would not be able to reliably tell you the type of part of your program, unlike today
where it can accurately tell you the type of anything, even if you have no type annotations in your entire code base.
### Arbitrary-rank types
Unlike arbitrary-rank (aka "Rank-N") types, both Rank-1 and Rank-2 type systems are compatible with principal
type inference. Roc currently uses Rank-1 types, and the benefits of Rank-N over Rank-2 don't seem worth
sacrificing principal type inference to attain, so let's focus on the trade-offs between Rank-1 and Rank-2.
Supporting Rank-2 types in Roc has been discussed before, but it has several important downsides:
* It would increase the complexity of the language.
* It would make some compiler error messages more confusing (e.g. they might mention `forall` because that was the most general type that could be inferred, even if that wasn't helpful or related to the actual problem).
* It would substantially increase the complexity of the type checker, which would necessarily slow it down.
No implementation of Rank-2 types can remove any of these downsides. Thus far, we've been able to come up
with sufficiently nice APIs that only require Rank-1 types, and we haven't seen a really compelling use case
where the gap between the Rank-2 and Rank-1 designs was big enough to justify switching to Rank-2.
Since I prefer Roc being simpler and having a faster compiler with nicer error messages, my hope is that Roc
will never get Rank-2 types. However, it may turn out that in the future we learn about currently-unknown
upsides that somehow outweigh these downsides, so I'm open to considering the possibility - while rooting against it.
### Higher-kinded polymorphism
I want to be really clear about this one: the explicit plan is that Roc will never support higher-kinded polymorphism.
On the technical side, the reasons for this are ordinary: I understand the practical benefits and
drawbacks of HKP, and I think the drawbacks outweigh the benefits when it comes to Roc. (Those who come to a
different conclusion may think HKP's drawbacks would be less of a big a deal in Roc than I do. That's reasonable;
we programmers often weigh the same trade-offs differently.) To be clear, I think this in the specific context of
Roc; there are plenty of other languages where HKP seems like a great fit. For example, it's hard to imagine Haskell
without it. Similarly, I think lifetime annotations are a great fit for Rust, but don't think they'd be right
for Roc either.
I also think it's important to consider the cultural implications of deciding whether or not to support HKP.
To illustrate what I mean, imagine this conversation:
**Programmer 1:** "How do you feel about higher-kinded polymorphism?"
**Programmer 2:** "I have no idea what that is."
**Programmer 1:** "Okay, how do you feel about monads?"
**Programmer 2:** "OH NO."
I've had several variations of this conversation: I'm talking about higher-kinded types,
another programmer asks what that means, I give monads as an example, and their reaction is strongly negative.
I've also had plenty of conversations with programmers who love HKP and vigorously advocate for its addition
to languages they use which don't have it. Feelings about HKP seem strongly divided, maybe more so
than any other type system feature besides static and dynamic types.
It's impossible for a programming language to be neutral on this. If the language doesn't support HKP, nobody can
implement a Monad typeclass (or equivalent) in any way that can be expected to catch on. Advocacy to add HKP to the
language will inevitably follow. If the language does support HKP, one or more alternate standard libraries built
around monads will inevitably follow, along with corresponding cultural changes. (See Scala for example.)
Culturally, to support HKP is to take a side, and to decline to support it is also to take a side.
Given this, language designers have three options:
* Have HKP and have Monad in the standard library. Embrace them and build a culture and ecosystem around them.
* Have HKP and don't have Monad in the standard library. An alternate standard lbirary built around monads will inevitably emerge, and both the community and ecosystem will divide themselves along pro-monad and anti-monad lines.
* Don't have HKP; build a culture and ecosystem around other things.
Considering that these are the only three options, I think the best choice for Roc—not only on a technical
level, but on a cultural level as well—is to make it clear that the plan is for Roc never to support HKP.
I hope this clarity can save a lot of community members' time that would otherwise be spent on advocacy or
arguing between the two sides of the divide. Again, I think it's completely reasonable for anyone to have a
different preference, but given that language designers can only choose one of these options, I'm confident
I've made the right choice for Roc by designing it never to have higher-kinded polymorphism.
## Why do Roc's syntax and standard library differ from Elm's?
Roc is a direct descendant of [Elm](https://elm-lang.org/). However, there are some differences between the two languages.
Syntactic differences are among these. This is a feature, not a bug; if Roc had identical syntax to Elm, then it's
predictable that people would write code that was designed to work in both languages - and would then rely on
that being true, for example by making a package which advertised "Works in both Elm and Roc!" This in turn
would mean that later if either language were to change its syntax in a way that didn't make sense for the other,
the result would be broken code and sadness.
So why does Roc have the specific syntax changes it does? Here are some brief explanations:
* `#` instead of `--` for comments - this allows [hashbang](https://senthilnayagan.medium.com/shebang-hashbang-10966b8f28a8)s to work without needing special syntax. That isn't a use case Elm supports, but it is one Roc is designed to support.
* `{}` instead of `()` for the unit type - Elm has both, and they can both be used as a unit type. Since `{}` has other uses in the type system, but `()` doesn't, I consider it redundant and took it out.
* `when`...`is` instead of `case`...`of` - I predict it will be easier for beginners to pick up, because usually the way I explain `case`...`of` to beginners is by saying the words "when" and "is" out loud - e.g. "when `color` is `Red`, it runs this first branch; when `color` is `Blue`, it runs this other branch..."
* `:` instead of `=` for record field definitions (e.g. `{ foo: bar }` where Elm syntax would be `{ foo = bar }`): I like `=` being reserved for definitions, and `:` is the most popular alternative.
* Backpassing syntax - since Roc is designed to be used for use cases like command-line apps, shell scripts, and servers, I expect chained effects to come up a lot more often than they do in Elm. I think backpassing is nice for those use cases, similarly to how `do` notation is nice for them in Haskell.
* Tag unions instead of Elm's custom types (aka algebraic data types). This isn't just a syntactic change; tag unions are mainly in Roc because they can facilitate errors being accumulated across chained effects, which (as noted a moment ago) I expect to be a lot more common in Roc than in Elm. If you have tag unions, you don't really need a separate language feature for algebraic data types, since closed tag unions essentially work the same way - aside from not giving you a way to selectively expose variants or define phantom types. Roc's opaque types language feature covers those use cases instead.
* No `::` operator, or `::` pattern matching for lists. Both of these are for the same reason: an Elm `List` is a linked list, so both prepending to it and removing an element from the front are very cheap operations. In contrast, a Roc `List` is a flat array, so both prepending to it and removing an element from the front are among the most expensive operations you can possibly do with it! To get good performance, this usage pattern should be encouraged in Elm and discouraged in Roc. Since having special syntax would encourage it, it would not be good for Roc to have that syntax!
* No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens.
* The `|>` operator passes the expression before the `|>` as the *first* argument to the function after the `|>` instead of as the last argument. See the section on currying for details on why this works this way.
* `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.)
* No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.)
* Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas.
* The `!` unary prefix operator. I didn't want to have a `Basics` module (more on that in a moment), and without `Basics`, this would either need to be called fully-qualified (`Bool.not`) or else a module import of `Bool.{ not }` would be necessary. Both seemed less nice than supporting the `!` prefix that's common to so many widely-used languages, especially when we already have a unary prefix operator of `-` for negation (e.g. `-x`).
* `!=` for the inequality operator (instead of Elm's `/=`) - this one pairs more naturally with the `!` prefix operator and is also very common in other languages.
Roc also has a different standard library from Elm. Some of the differences come down to platforms and applications (e.g. having `Task` in Roc's standard library wouldn't make sense), but others do not. Here are some brief explanations:
* No `Basics` module. I wanted to have a simple rule of "all modules in the standard library are imported by default, and so are their exposed types," and that's it. Given that I wanted the comparison operators (e.g. `<`) to work only on numbers, it ended up that having `Num` and `Bool` modules meant that almost nothing would be left for a `Basics` equivalent in Roc except `identity` and `Never`. The Roc type `[]` (empty tag union) is equivalent to `Never`, so that wasn't necessary, and I generally think that `identity` is a good concept but a sign of an incomplete API whenever its use comes up in practice. For example, instead of calling `|> List.filterMap identity` I'd rather have access to a more self-descriptive function like `|> List.dropNothings`. With `Num` and `Bool`, and without `identity` and `Never`, there was nothing left in `Basics`.
* `Str` instead of `String` - after using the `str` type in Rust, I realized I had no issue whatsoever with the more concise name, especially since it was used in so many places (similar to `Msg` and `Cmd` in Elm) - so I decided to save a couple of letters.
* No function composition operators - I stopped using these in Elm so long ago, at one point I forgot they were in the language! See the FAQ entry on currying for details about why.
* No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value).
* No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail.
* No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places.
* No `Maybe`. See the "Why doesn't Roc have a `Maybe`/`Option`/`Optional` type" FAQ question
## Why aren't Roc functions curried by default?
Although technically any language with first-class functions makes it possible to curry
any function (e.g. I can manually curry a Roc function `\x, y, z ->` by writing `\x -> \y -> \z ->` instead),
typically what people mean when they say Roc isn't a curried language is that Roc functions aren't curried
by default. For the rest of this section, I'll use "currying" as a shorthand for "functions that are curried
by default" for the sake of brevity.
As I see it, currying has one major upside and several major downsides. The upside:
* It makes function calls more concise in some cases.
The downsides:
* It lowers error message quality, because there can no longer be an error for "function called with too few arguments." (Calling a function with fewer arguments is always valid in curried functions; the error you get instead will unavoidably be some other sort of type mismatch, and it will be up to you to figure out that the real problem was that you forgot an argument.)
* It makes the `|>` operator more error-prone in some cases.
* It makes higher-order function calls need more parentheses in some cases.
* It significantly increases the language's learning curve. (More on this later.)
* It facilitates pointfree function composition. (More on why this is listed as a downside later.)
There's also a downside that it would make runtime performance of compiled programs worse by default,
but I assume it would be possible to optimize that away at the cost of slightly longer compile times.
I consider the one upside (conciseness in some places) extremely minor, and have almost never missed it in Roc.
Here are some more details about the downsides as I see them.
### Currying and the `|>` operator
In Roc, this code produces `"Hello, World!"`
```elm
"Hello, World"
|> Str.concat "!"
```
This is because Roc's `|>` operator uses the expression before the `|>` as the *first* argument to the function
after it. For functions where both arguments have the same type, but it's obvious which argument goes where (e.g.
`Str.concat "Hello, " "World!"`, `List.concat [1, 2] [3, 4]`), this works out well. Another example would
be `|> Num.sub 1`, which subtracts 1 from whatever came before the `|>`.
For this reason, "pipeline-friendliness" in Roc means that the first argument to each function is typically
the one that's most likely to be built up using a pipeline. For example, `List.map`:
```elm
numbers
|> List.map Num.abs
```
This argument ordering convention also often makes it possible to pass anonymous functions to higher-order
functions without needing parentheses, like so:
```elm
List.map numbers \num -> Num.abs (num - 1)
```
(If the arguments were reversed, this would be `List.map (\num -> Num.abs (num - 1)) numbers` and the
extra parentheses would be required.)
Neither of these benefits is compatible with the argument ordering currying encourages. Currying encourages
`List.map` to take the `List` as its second argument instead of the first, so that you can partially apply it
like `(List.map Num.abs)`; if Roc introduced currying but kept the order of `List.map` the same way it is today,
then partially applying `List.map` (e.g. `(List.map numbers)`) would be much less useful than if the arguments
were swapped - but that in turn would make it less useful with `|>` and would require parentheses when passing
it an anonymous function.
This is a fundamental design tension. One argument order works well with `|>` (at least the way it works in Roc
today) and with passing anonymous functions to higher-order functions, and the other works well with currying.
It's impossible to have both.
Of note, one possible design is to have currying while also having `|>` pass the *last* argument instead of the first.
This is what Elm does, and it makes pipeline-friendliness and curry-friendliness the same thing. However, it also
means that either `|> Str.concat "!"` would add the `"!"` to the front of the string, or else `Str.concat`'s
arguments would have to be flipped - meaning that `Str.concat "Hello, World" "!"` would evaluate to `"!Hello, World"`.
The only way to have `Str.concat` work the way it does in Roc today (where both pipelines and non-pipeline calling
do what you'd want them to) is to order function arguments in a way that is not conducive to currying. This design
tension only exists if there's currying in the language; without it, you can order arguments for pipeline-friendliness
without concern.
### Currying and learning curve
Prior to designing Roc, I taught a lot of beginner [Elm](https://elm-lang.org/) workshops. Sometimes at
conferences, sometimes for [Frontend Masters](https://frontendmasters.com/courses/intro-elm/),
sometimes for free at local coding bootcamps or meetup groups.
In total I've spent well over 100 hours standing in front of a class, introducing the students to their
first pure functional programming language.
Here was my experience teaching currying:
* The only way to avoid teaching it is to refuse to explain why multi-argument functions have multiple `->`s in them. (If you don't explain it, at least one student will ask about it - and many if not all of the others will wonder.)
* Teaching currying properly takes a solid chunk of time, because it requires explaining partial application, explaining how curried functions facilitate partial application, how function signatures accurately reflect that they're curried, and going through examples for all of these.
* Even after doing all this, and iterating on my approach each time to try to explain it more effectively than I had the time before, I'd estimate that under 50% of the class ended up actually understanding currying. I consistently heard that in practice it only "clicked" for most people after spending significantly more time writing code with it.
This is not the end of the world, especially because it's easy enough to think "okay, I still don't totally get this
even after that explanation, but I can remember that function arguments are separated by `->` in this language
and maybe I'll understand the rest later." (Which they almost always do, if they stick with the language.)
Clearly currying doesn't preclude a language from being easy to learn, because Elm has currying, and Elm's learning
curve is famously gentle.
That said, beginners who feel confused while learning the language are less likely to continue with it.
And however easy Roc would be to learn if it had currying, the language is certainly easier to learn without it.
### Pointfree function composition
[Pointfree function composition](https://en.wikipedia.org/wiki/Tacit_programming) is where you define
a new function by composing together two existing functions without naming intermediate arguments.
Here's an example:
```elm
reverseSort : List elem -> List elem
reverseSort = compose List.reverse List.sort
compose : (a -> b), (c -> a) -> (c -> b)
compose = \f, g, x -> f (g x)
```
Here's how I would instead write this:
```elm
reverseSort : List elem -> List elem
reverseSort = \list -> List.reverse (List.sort list)
```
I've consistently found that I can more quickly and accurately understand function definitions that use
named arguments, even though the code is longer. I suspect this is because I'm faster at reading than I am at
desugaring, and whenever I read the top version I end up needing to mentally desugar it into the bottom version.
In more complex examples (this is among the tamest pointfree function composition examples I've seen), I make
a mistake in my mental desugaring, and misunderstand what the function is doing - which can cause bugs.
I assumed I would get faster and more accurate at this over time. However, by now it's been about a decade
since I first learned about the technique, and I'm still slower and less accurate at reading code that uses
pointfree function composition (including if I wrote it - but even moreso if I didn't) than code written with
with named arguments. I've asked a lot of other programmers about their experiences with pointfree function
composition over the years, and the overwhelming majority of responses have been consistent with my experience.
As such, my opinion about pointfree function composition has gotten less and less nuanced over time. I've now moved
past "it's the right tool for the job, sometimes" to concluding it's best thought of as an antipattern. This is
because I realized how much time I was spending evaluating on a case-by-case basis whether it might be the
right fit for a given situation. The time spent on this analysis alone vastly outweighed the sum of all the
benefits I got in the rare cases where I concluded it was a fit. So I've found the way to get the most out of
pointfree function composition is to never even think about using it; every other strategy leads to a worse outcome.
Currying facilitates the antipattern of pointfree function composition, which I view as a downside of currying.
Stacking up all these downsides of currying against the one upside of making certain function calls more concise,
I concluded that it would be a mistake to have it in Roc.
## Why are both rust and zig used?
At the start of the project, we did not know zig well and it was not production ready. The reason zig entered the project because it has many different backends (wasm, various assembly formats, llvm IR) and can create code with minimal dependencies
Rust has much more overhead in terms of code size. It's objectively not a lot, but it's less with zig.
We think rust is a nicer language to work in for a project of this size. It has a type system that we're more familiar with, it has a package ecosystem and excellent tooling.

View File

@ -203,7 +203,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
* Zig - https://ziglang.org
This source code can be found in compiler/builtins/bitcode/src/hash.zig and is licensed under the following terms:
This source code can be found in compiler/builtins/bitcode/src/hash.zig, highlight/tests/peg_grammar.rs and highlight/src/highlight_parser.rs and is licensed under the following terms:
The MIT License (Expat)
@ -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:
@ -515,3 +289,24 @@ See the License for the specific language governing permissions and
limitations under the License.
===========================================================
* iced - https://github.com/iced-rs/iced
Copyright 2019 Héctor Ramón, Iced contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -2,38 +2,45 @@
Roc is a language for making delightful software.
If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest.
The [tutorial](TUTORIAL.md) is the best place to learn about how to use the language - it assumes no prior knowledge of Roc or similar languages. (If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest.)
If you're curious about where the language's name and logo came from,
[here's an explanation](https://github.com/rtfeldman/roc/blob/trunk/name-and-logo.md).
There's also a folder of [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) - the [CLI example](https://github.com/rtfeldman/roc/tree/trunk/examples/cli) in particular is a reasonable starting point to build on.
If you have a specific question, the [FAQ](FAQ.md) might have an answer, although [Roc Zulip chat](https://roc.zulipchat.com) is overall the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects.
## State of Roc
Roc is not ready for production yet. You are likely to encounter bugs. Publishing packages or documentation is not yet supported.
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C, Swift and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
## Getting started
1. [Install Rust](https://rustup.rs/)
2. [Build from source](BUILDING_FROM_SOURCE.md)
3. In a terminal, run this from the root folder:
```
cargo run repl
```
4. Check out [these tests](https://github.com/rtfeldman/roc/blob/trunk/cli/tests/repl_eval.rs) for examples of using the REPL
- [Linux x86](getting_started/linux_x86.md)
- [MacOS Apple Silicon](getting_started/macos_apple_silicon.md)
- [MacOS x86](getting_started/macos_x86.md)
- [Windows](getting_started/windows.md)
- [Other](getting_started/other.md)
### Examples
Took a look at the [examples folder](examples), [examples/benchmarks](examples/benchmarks) contains some larger examples.
Run examples as follows:
1. Navigate to `/examples`
2. Run with:
```
cargo run hello-world/Hello.roc
cargo run examples/hello-world/main.roc
```
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
For NQueens, input 10 in the terminal and press enter.
For NQueens, input 10 in the terminal and press enter.
[examples/benchmarks](examples/benchmarks) contains larger examples.
**Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic.
## Sponsors
We are very grateful for our sponsors [NoRedInk](https://www.noredink.com/) and [rwx](https://www.rwx.com).
[<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>](https://www.noredink.com/)
&nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://www.rwx.com/build/_assets/rwx_banner_transparent_cropped-RYV7W2KL.svg" height="60" alt="rwx logo"/>](https://www.rwx.com)
## Applications and Platforms
@ -64,7 +71,7 @@ By using systems-level programming languages like C and C++, platform authors sa
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, Swift or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, Swift and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!
@ -74,11 +81,9 @@ The core Roc language and standard library include no I/O operations, which give
* A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist).
* A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction.
Each Roc platform gets its own separate package repository, with packages built on top of the API that platform exposes. This means each platform has its own ecosystem where everything is built on top of the same shared set of platform-specific primitives.
## 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."
@ -105,5 +110,9 @@ never done anything with Rust and also never worked on a compiler, but we've
been able to find beginner-friendly projects to get people up to speed gradually.)
If you're interested in getting involved, check out
[CONTRIBUTING.md](https://github.com/rtfeldman/roc/blob/trunk/CONTRIBUTING.md) which has more info
and a link to our Zulip chat!
[CONTRIBUTING.md](https://github.com/rtfeldman/roc/blob/trunk/CONTRIBUTING.md)!
## Name and Logo
If you're curious about where the language's name and logo came from,
[here's an explanation](https://github.com/rtfeldman/roc/blob/trunk/name-and-logo.md).

1978
TUTORIAL.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,325 +0,0 @@
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
use bumpalo::Bump;
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::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use crate::lang::core::def::def::canonicalize_defs;
use crate::lang::core::def::def::Def;
use crate::lang::core::def::def::{sort_can_defs, Declaration};
use crate::lang::core::expr::expr2::Expr2;
use crate::lang::core::expr::output::Output;
use crate::lang::core::pattern::Pattern2;
use crate::lang::core::types::Alias;
use crate::lang::core::val_def::ValueDef;
use crate::lang::env::Env;
use crate::lang::scope::Scope;
use crate::mem_pool::pool::NodeId;
use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
pub struct ModuleOutput {
pub aliases: MutMap<Symbol, NodeId<Alias>>,
pub rigid_variables: MutMap<Variable, Lowercase>,
pub declarations: Vec<Declaration>,
pub exposed_imports: MutMap<Symbol, Variable>,
pub lookups: Vec<(Symbol, Variable, Region)>,
pub problems: Vec<Problem>,
pub ident_ids: IdentIds,
pub references: MutSet<Symbol>,
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>(
arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>],
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: MutMap<ModuleId, IdentIds>,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
mut exposed_symbols: MutSet<Symbol>,
var_store: &mut VarStore,
) -> Result<ModuleOutput, RuntimeError> {
let mut pool = Pool::with_capacity(1 << 10);
let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, &mut pool, var_store);
let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() {
let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool);
for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) {
let (poolstr, var) = &pool[targ_id];
pool[node_id] = (poolstr.shallow_clone(), *var);
}
scope.add_alias(&mut pool, name, vars, alias.actual);
}
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let mut desugared =
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
}));
}
let mut env = Env::new(
home,
arena,
&mut pool,
var_store,
dep_idents,
module_ids,
exposed_ident_ids,
);
let mut lookups = Vec::with_capacity(num_deps);
let rigid_variables = MutMap::default();
// Exposed values are treated like defs that appear before any others, e.g.
//
// imports [ Foo.{ bar, baz } ]
//
// ...is basically the same as if we'd added these extra defs at the start of the module:
//
// bar = Foo.bar
// baz = Foo.baz
//
// Here we essentially add those "defs" to "the beginning of the module"
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
for (ident, (symbol, region)) in exposed_imports {
let first_char = ident.as_inline_str().chars().next().unwrap();
if first_char.is_lowercase() {
// this is a value definition
let expr_var = env.var_store.fresh();
match scope.import(ident, symbol, region) {
Ok(()) => {
// Add an entry to exposed_imports using the current module's name
// as the key; e.g. if this is the Foo module and we have
// exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
can_exposed_imports.insert(symbol, expr_var);
// This will be used during constraint generation,
// to add the usual Lookup constraint as if this were a normal def.
lookups.push((symbol, expr_var, region));
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
}
}
} else {
// This is a type alias
// the should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol));
}
}
let (defs, _scope, output, symbols_introduced) = canonicalize_defs(
&mut env,
Output::default(),
&scope,
&desugared,
PatternType::TopLevelDef,
);
// See if any of the new idents we defined went unused.
// If any were unused and also not exposed, report it.
for (symbol, region) in symbols_introduced {
if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) {
env.problem(Problem::UnusedDef(symbol, region));
}
}
// TODO register rigids
// for (var, lowercase) in output.introduced_variables.name_by_var.clone() {
// rigid_variables.insert(var, lowercase);
// }
let mut references = MutSet::default();
// Gather up all the symbols that were referenced across all the defs' lookups.
for symbol in output.references.lookups.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced across all the defs' calls.
for symbol in output.references.calls.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
match sort_can_defs(&mut env, defs, Output::default()) {
(Ok(mut declarations), output) => {
use Declaration::*;
for decl in declarations.iter() {
match decl {
Declare(def) => {
for symbol in def.symbols(env.pool) {
if exposed_symbols.contains(&symbol) {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_symbols.remove(&symbol);
}
}
}
DeclareRec(defs) => {
for def in defs {
for symbol in def.symbols(env.pool) {
if exposed_symbols.contains(&symbol) {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_symbols.remove(&symbol);
}
}
}
}
InvalidCycle(identifiers, _) => {
panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers);
}
Builtin(def) => {
// Builtins cannot be exposed in module declarations.
// This should never happen!
debug_assert!(def
.symbols(env.pool)
.iter()
.all(|symbol| !exposed_symbols.contains(symbol)));
}
}
}
let mut aliases = MutMap::default();
for (symbol, alias) in output.aliases {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_symbols.remove(&symbol);
aliases.insert(symbol, alias);
}
// By this point, all exposed symbols should have been removed from
// exposed_symbols and added to exposed_vars_by_symbol. If any were
// not, that means they were declared as exposed but there was
// no actual declaration with that name!
for symbol in exposed_symbols {
env.problem(Problem::ExposedButNotDefined(symbol));
// In case this exposed value is referenced by other modules,
// create a decl for it whose implementation is a runtime error.
let mut pattern_vars = SendMap::default();
pattern_vars.insert(symbol, env.var_store.fresh());
let runtime_error = RuntimeError::ExposedButNotDefined(symbol);
let value_def = {
let pattern_id = env.pool.add(Pattern2::Identifier(symbol));
let expr_id = env.pool.add(Expr2::RuntimeError());
ValueDef::NoAnnotation {
pattern_id,
expr_id,
expr_var: env.var_store.fresh(),
}
};
let def = Def::Value(value_def);
declarations.push(Declaration::Declare(def));
}
// Incorporate any remaining output.lookups entries into references.
for symbol in output.references.lookups {
references.insert(symbol);
}
// Incorporate any remaining output.calls entries into references.
for symbol in output.references.calls {
references.insert(symbol);
}
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
// TODO find captured variables
// for declaration in declarations.iter_mut() {
// match declaration {
// Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()),
// DeclareRec(defs) => {
// fix_values_captured_in_closure_defs(defs, &mut MutSet::default())
// }
// InvalidCycle(_, _) | Builtin(_) => {}
// }
// }
// TODO this loops over all symbols in the module, we can speed it up by having an
// iterator over all builtin symbols
// TODO move over the builtins
// for symbol in references.iter() {
// if symbol.is_builtin() {
// // this can fail when the symbol is for builtin types, or has no implementation yet
// if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) {
// declarations.push(Declaration::Builtin(def));
// }
// }
// }
Ok(ModuleOutput {
aliases,
rigid_variables,
declarations,
references,
exposed_imports: can_exposed_imports,
problems: vec![], // TODO env.problems,
lookups,
ident_ids: env.ident_ids,
})
}
(Err(runtime_error), _) => Err(runtime_error),
}
}

View File

@ -1,86 +0,0 @@
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use roc_module::ident::{Ident, IdentStr};
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use crate::lang::{core::expr::expr_to_expr2::loc_expr_to_expr2, env::Env, scope::Scope};
use super::def2::Def2;
pub fn defs_to_defs2<'a>(
arena: &'a Bump,
env: &mut Env<'a>,
scope: &mut Scope,
parsed_defs: &'a BumpVec<roc_region::all::Loc<roc_parse::ast::Def<'a>>>,
region: Region,
) -> Vec<Def2> {
parsed_defs
.iter()
.map(|loc| def_to_def2(arena, env, scope, &loc.value, region))
.collect()
}
pub fn def_to_def2<'a>(
arena: &'a Bump,
env: &mut Env<'a>,
scope: &mut Scope,
parsed_def: &'a roc_parse::ast::Def<'a>,
region: Region,
) -> Def2 {
use roc_parse::ast::Def::*;
match parsed_def {
SpaceBefore(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region),
SpaceAfter(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region),
Body(&loc_pattern, &loc_expr) => {
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
let expr_id = env.pool.add(expr2);
use roc_parse::ast::Pattern::*;
match loc_pattern.value {
Identifier(id_str) => {
let identifier_id = env.ident_ids.get_or_insert(&Ident(IdentStr::from(id_str)));
// TODO support with annotation
Def2::ValueDef {
identifier_id,
expr_id,
}
}
other => {
unimplemented!(
"I don't yet know how to convert the pattern {:?} into an expr2",
other
)
}
}
}
other => {
unimplemented!(
"I don't know how to make an expr2 from this def yet: {:?}",
other
)
}
}
}
pub fn str_to_def2<'a>(
arena: &'a Bump,
input: &'a str,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> Result<Vec<Def2>, SyntaxError<'a>> {
match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) {
Ok(vec_loc_def) => Ok(defs_to_defs2(
arena,
env,
scope,
arena.alloc(vec_loc_def),
region,
)),
Err(fail) => Err(fail),
}
}

View File

@ -1,39 +0,0 @@
use std::path::Path;
use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_load::file::LoadedModule;
pub fn load_module(src_file: &Path) -> LoadedModule {
let subs_by_module = MutMap::default();
let arena = Bump::new();
let loaded = roc_load::file::load_and_typecheck(
&arena,
src_file.to_path_buf(),
arena.alloc(roc_builtins::std::standard_stdlib()),
src_file.parent().unwrap_or_else(|| {
panic!(
"src_file {:?} did not have a parent directory but I need to have one.",
src_file
)
}),
subs_by_module,
8,
roc_can::builtins::builtin_defs_map,
);
match loaded {
Ok(x) => x,
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
panic!(
"Failed to load module from src_file {:?}. Report: {:?}",
src_file, report
);
}
Err(e) => panic!(
"Failed to load module from src_file {:?}: {:?}",
src_file, e
),
}
}

View File

@ -1,133 +0,0 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::{Parser, SyntaxError};
use roc_region::all::Located;
use std::ffi::OsStr;
use std::path::Path;
use std::{fs, io};
#[derive(Debug)]
pub struct File<'a> {
path: &'a Path,
module_header: Module<'a>,
content: Vec<'a, Located<Def<'a>>>,
}
#[derive(Debug)]
pub enum ReadError<'a> {
Read(std::io::Error),
ParseDefs(SyntaxError<'a>),
ParseHeader(SyntaxError<'a>),
DoesntHaveRocExtension,
}
impl<'a> File<'a> {
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError<'a>> {
if path.extension() != Some(OsStr::new("roc")) {
return Err(ReadError::DoesntHaveRocExtension);
}
let bytes = fs::read(path).map_err(ReadError::Read)?;
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new(allocation);
let parsed_module = roc_parse::module::parse_header(arena, module_parse_state);
match parsed_module {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(arena, state);
match parsed_defs {
Ok((_, defs, _)) => Ok(File {
path,
module_header: module,
content: defs,
}),
Err((_, error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))),
}
}
pub fn fmt(&self) -> String {
let arena = Bump::new();
let mut formatted_file = String::new();
let mut module_header_buf = bumpalo::collections::String::new_in(&arena);
fmt_module(&mut module_header_buf, &self.module_header);
formatted_file.push_str(module_header_buf.as_str());
for def in &self.content {
let mut def_buf = bumpalo::collections::String::new_in(&arena);
fmt_def(&mut def_buf, &def.value, 0);
formatted_file.push_str(def_buf.as_str());
}
formatted_file
}
pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> {
let formatted_file = self.fmt();
fs::write(write_path, formatted_file)
}
pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> {
self.fmt_then_write_to(
self.path
.with_file_name(new_name)
.with_extension("roc")
.as_path(),
)
}
pub fn fmt_then_write(&self) -> io::Result<()> {
self.fmt_then_write_to(self.path)
}
}
#[cfg(test)]
mod test_file {
use crate::lang::roc_file;
use bumpalo::Bump;
use std::path::Path;
#[test]
fn read_and_fmt_simple_roc_module() {
let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc");
let arena = Bump::new();
let file = roc_file::File::read(simple_module_path, &arena)
.expect("Could not read SimpleUnformatted.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4"#
)
);
}
}

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

@ -1,12 +1,12 @@
[package]
name = "bench-runner"
version = "0.1.0"
edition = "2018"
edition = "2021"
# 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;
@ -65,7 +65,7 @@ fn finish(all_regressed_benches: HashSet<String>, nr_repeat_benchmarks: usize) {
r#"
FAILED: The following benchmarks have shown a regression {:?} times: {:?}
"#,
nr_repeat_benchmarks, all_regressed_benches
);
@ -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

3
ci/package_release.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
cp target/release/roc ./roc # to be able to exclude "target" later in the tar command
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc LICENSE LEGAL_DETAILS examples/hello-world crates/compiler/builtins/bitcode/src/ crates/roc_std

3
ci/write_version.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
# version.txt is used by the CLI: roc --version
printf 'nightly pre-release, built from commit ' > version.txt && git log --pretty=format:'%h' -n 1 >> version.txt && printf ' on ' >> version.txt && date -u >> version.txt

236
cli/Cargo.lock generated
View File

@ -1,236 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ascii"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "autocfg"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "combine"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fraction"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "im-rc"
version = "13.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"sized-chunks 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-bigint"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-complex"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-iter"
version = "0.1.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-rational"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "roc"
version = "0.1.0"
dependencies = [
"combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fraction 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"im-rc 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "roc-cli"
version = "0.1.0"
dependencies = [
"roc 0.1.0",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "sized-chunks"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unreachable"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5fc969a8ce2c9c0c4b0429bb8431544f6658283c8326ba5ff8c762b75369335"
"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680"
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
"checksum fraction 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1055159ac82fb210c813303f716b6c8db57ace9d5ec2dbbc2e1d7a864c1dd74e"
"checksum im-rc 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0197597d095c0d11107975d3175173f810ee572c2501ff4de64f4f3f119806"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db"
"checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718"
"checksum num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fcb0cf31fb3ff77e6d2a6ebd6800df7fdcd106f2ad89113c9130bcd07f93dffc"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum sized-chunks 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2a2eb3fe454976eefb479f78f9b394d34d661b647c6326a3a6e66f68bb12c26"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

@ -1,93 +0,0 @@
[package]
name = "roc_cli"
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"
default-run = "roc"
[[bin]]
name = "roc"
path = "src/main.rs"
test = false
bench = false
[features]
default = ["target-x86", "llvm", "editor"]
wasm32-cli-run = []
i386-cli-run = []
# 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", "roc_build/llvm"]
editor = ["roc_editor"]
target-x86 = []
# arm and wasm give linker errors on some platforms
target-arm = []
target-webassembly = []
target-all = [
"target-x86",
"target-arm",
"target-webassembly"
]
[dependencies]
roc_collections = { path = "../compiler/collections" }
roc_can = { path = "../compiler/can" }
roc_docs = { path = "../docs" }
roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" }
roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" }
roc_builtins = { path = "../compiler/builtins" }
roc_constrain = { path = "../compiler/constrain" }
roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
clap = "= 3.0.0-beta.1"
const_format = "0.2"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
libloading = "0.6"
mimalloc = { version = "0.1.26", default-features = false }
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
tempfile = "3.1.0"
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
serial_test = "0.5"
tempfile = "3.1.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "cli_utils" }
[[bench]]
name = "time_bench"
harness = false

View File

@ -1,24 +0,0 @@
[package]
name = "cli_utils"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
edition = "2018"
description = "Shared code for cli tests and benchmarks"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
roc_cli = { path = "../../cli" }
roc_collections = { path = "../../compiler/collections" }
roc_load = { path = "../../compiler/load" }
roc_module = { path = "../../compiler/module" }
bumpalo = { version = "3.6.1", features = ["collections"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
inlinable_string = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
strip-ansi-escapes = "0.1"
tempfile = "3.1.0"
rlimit = "0.6.2"

File diff suppressed because one or more lines are too long

View File

@ -1,414 +0,0 @@
use bumpalo::Bump;
use roc_build::{
link::{link, rebuild_host, LinkType},
program,
};
#[cfg(feature = "llvm")]
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use tempfile::Builder;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
" {:9.3} ms {}\n",
duration.as_secs_f64() * 1000.0,
label,
));
}
pub enum BuildOutcome {
NoProblems,
OnlyWarnings,
Errors,
}
impl BuildOutcome {
pub fn status_code(&self) -> i32 {
match self {
Self::NoProblems => 0,
Self::OnlyWarnings => 1,
Self::Errors => 2,
}
}
}
pub struct BuiltFile {
pub binary_path: PathBuf,
pub outcome: BuildOutcome,
pub total_time: Duration,
}
#[cfg(feature = "llvm")]
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
src_dir: PathBuf,
roc_file_path: PathBuf,
opt_level: OptLevel,
emit_debug_info: bool,
emit_timings: bool,
link_type: LinkType,
surgically_link: bool,
precompiled: bool,
) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let loaded = roc_load::file::load_and_monomorphize(
arena,
roc_file_path.clone(),
stdlib,
src_dir.as_path(),
subs_by_module,
ptr_bytes,
builtin_defs_map,
)?;
use target_lexicon::Architecture;
let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
// TODO wasm host extension should be something else ideally
// .bc does not seem to work because
//
// > Non-Emscripten WebAssembly hasn't implemented __builtin_return_address
//
// and zig does not currently emit `.a` webassembly static libraries
let host_extension = if emit_wasm { "zig" } else { "o" };
let app_extension = if emit_wasm { "bc" } else { "o" };
let cwd = roc_file_path.parent().unwrap();
let mut binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
if emit_wasm {
binary_path.set_extension("wasm");
}
let mut host_input_path = PathBuf::from(cwd);
let path_to_platform = loaded.platform_path.clone();
host_input_path.push(&*path_to_platform);
host_input_path.push("host");
host_input_path.set_extension(host_extension);
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
let rebuild_thread = spawn_rebuild_thread(
opt_level,
surgically_link,
precompiled,
host_input_path.clone(),
binary_path.clone(),
target,
loaded
.exposed_to_host
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect(),
);
// TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required.
let app_o_file = Builder::new()
.prefix("roc_app")
.suffix(&format!(".{}", app_extension))
.tempfile()
.map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
})?;
let app_o_file = app_o_file.path();
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(
buf,
"Find Specializations",
module_timing.find_specializations,
);
report_timing(
buf,
"Make Specializations",
module_timing.make_specializations,
);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
// This only needs to be mutable for report_problems. This can't be done
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
program::report_problems_monomorphized(&mut loaded);
let loaded = loaded;
let code_gen_timing = match opt_level {
OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm(
arena,
loaded,
&roc_file_path,
target,
app_o_file,
opt_level,
emit_debug_info,
),
OptLevel::Development => {
program::gen_from_mono_module_dev(arena, loaded, target, app_o_file)
}
};
buf.push('\n');
buf.push_str(" ");
buf.push_str("Code Generation");
buf.push('\n');
report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen);
report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file);
let compilation_end = compilation_start.elapsed().unwrap();
let size = std::fs::metadata(&app_o_file)
.unwrap_or_else(|err| {
panic!(
"Could not open {:?} - which was supposed to have been generated. Error: {:?}",
app_o_file, err
);
})
.len();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
}
let rebuild_duration = rebuild_thread.join().unwrap();
if emit_timings && !precompiled {
println!(
"Finished rebuilding and preprocessing the host in {} ms\n",
rebuild_duration
);
}
// Step 2: link the precompiled host and compiled app
let link_start = SystemTime::now();
let outcome = if surgically_link {
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
.map_err(|_| {
todo!("gracefully handle failing to surgically link");
})?;
BuildOutcome::NoProblems
} else {
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
app_o_file.to_str().unwrap(),
];
if matches!(opt_level, OptLevel::Development) {
inputs.push(bitcode::OBJ_PATH);
}
let (mut child, _) = // TODO use lld
link(
target,
binary_path.clone(),
&inputs,
link_type
)
.map_err(|_| {
todo!("gracefully handle `ld` failing to spawn.");
})?;
let exit_status = child.wait().map_err(|_| {
todo!("gracefully handle error after `ld` spawned");
})?;
// TODO change this to report whether there were errors or warnings!
if exit_status.success() {
BuildOutcome::NoProblems
} else {
BuildOutcome::Errors
}
};
let linking_time = link_start.elapsed().unwrap();
if emit_timings {
println!("Finished linking in {} ms\n", linking_time.as_millis());
}
let total_time = compilation_start.elapsed().unwrap();
Ok(BuiltFile {
binary_path,
outcome,
total_time,
})
}
fn spawn_rebuild_thread(
opt_level: OptLevel,
surgically_link: bool,
precompiled: bool,
host_input_path: PathBuf,
binary_path: PathBuf,
target: &Triple,
exported_symbols: Vec<String>,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
let rebuild_host_start = SystemTime::now();
if !precompiled {
if surgically_link {
roc_linker::build_and_preprocess_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
exported_symbols,
)
.unwrap();
} else {
rebuild_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
None,
);
}
}
if surgically_link {
// Copy preprocessed host to executable location.
let prehost = host_input_path.with_file_name("preprocessedhost");
std::fs::copy(prehost, binary_path.as_path()).unwrap();
}
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
rebuild_host_end.as_millis()
})
}
#[allow(clippy::too_many_arguments)]
pub fn check_file(
arena: &Bump,
src_dir: PathBuf,
roc_file_path: PathBuf,
emit_timings: bool,
) -> Result<usize, LoadingProblem> {
let compilation_start = SystemTime::now();
// only used for generating errors. We don't do code generation, so hardcoding should be fine
// we need monomorphization for when exhaustiveness checking
let ptr_bytes = 8;
// Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let mut loaded = roc_load::file::load_and_typecheck(
arena,
roc_file_path,
stdlib,
src_dir.as_path(),
subs_by_module,
ptr_bytes,
builtin_defs_map,
)?;
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(
buf,
"Find Specializations",
module_timing.find_specializations,
);
report_timing(
buf,
"Make Specializations",
module_timing.make_specializations,
);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
let compilation_end = compilation_start.elapsed().unwrap();
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
}
Ok(program::report_problems_typechecked(&mut loaded))
}

View File

@ -1,524 +0,0 @@
#[macro_use]
extern crate const_format;
use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
use std::process;
use std::process::Command;
use target_lexicon::BinaryFormat;
use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
pub mod build;
pub mod repl;
pub const CMD_BUILD: &str = "build";
pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs";
pub const CMD_CHECK: &str = "check";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc")
.version(concatcp!(include_str!("../../version.txt"), "\n"))
.about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::with_name(ROC_FILE)
.about("The .roc file to build")
.required(true),
)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
.required(false),
)
.arg(
Arg::with_name(FLAG_LIB)
.long(FLAG_LIB)
.about("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::with_name(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(FLAG_LINK)
.long(FLAG_LINK)
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
)
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new(CMD_CHECK)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::with_name(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.about("The .roc file of an app to run")
.required(true),
)
)
.subcommand(
App::new(CMD_DOCS)
.about("Generate documentation for Roc modules")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(false)
.about("The directory or files to build documentation for")
)
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(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::with_name(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(FLAG_LINK)
.long(FLAG_LINK)
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.about("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.about("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple(true),
);
if cfg!(feature = "editor") {
app.subcommand(
App::new(CMD_EDIT).about("Launch the Roc editor").arg(
Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(false)
.about("(optional) The directory or files to open on launch."),
),
)
} else {
app
}
}
pub fn docs(files: Vec<PathBuf>) {
roc_docs::generate_docs_html(
files,
roc_builtins::std::standard_stdlib(),
Path::new("./generated-docs"),
)
}
#[derive(Debug, PartialEq, Eq)]
pub enum BuildConfig {
BuildOnly,
BuildAndRun { roc_file_arg_index: usize },
}
#[cfg(feature = "llvm")]
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use build::build_file;
use std::str::FromStr;
use BuildConfig::*;
let backend = match matches.value_of(FLAG_BACKEND) {
Some(name) => Backend::from_str(name).unwrap(),
None => Backend::default(),
};
let target = backend.to_triple();
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
let original_cwd = std::env::current_dir()?;
let opt_level = match (
matches.is_present(FLAG_OPTIMIZE),
matches.is_present(FLAG_DEV),
) {
(true, false) => OptLevel::Optimize,
(true, true) => panic!("development cannot be optimized!"),
(false, true) => OptLevel::Development,
(false, false) => OptLevel::Normal,
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
let link_type = if matches.is_present(FLAG_LIB) {
LinkType::Dylib
} else {
LinkType::Executable
};
let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &target) {
panic!(
"Link type, {:?}, with target, {}, not supported by roc linker",
link_type, target
);
}
let path = Path::new(filename);
// Spawn the root task
let path = path.canonicalize().unwrap_or_else(|err| {
use io::ErrorKind::*;
match err.kind() {
NotFound => {
match path.to_str() {
Some(path_str) => println!("File not found: {}", path_str),
None => println!("Malformed file path : {:?}", path),
}
process::exit(1);
}
_ => {
todo!("TODO Gracefully handle opening {:?} - {:?}", path, err);
}
}
});
let src_dir = path.parent().unwrap().canonicalize().unwrap();
let res_binary_path = build_file(
&arena,
&target,
src_dir,
path,
opt_level,
emit_debug_info,
emit_timings,
link_type,
surgically_link,
precompiled,
);
match res_binary_path {
Ok(BuiltFile {
binary_path,
outcome,
total_time,
}) => {
match config {
BuildOnly => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
println!(
"🎉 Built {} in {} ms",
generated_filename.to_str().unwrap(),
total_time.as_millis()
);
// Return a nonzero exit code if there were problems
Ok(outcome.status_code())
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = match target.architecture {
Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
let args = std::env::args()
.skip(roc_file_arg_index)
.collect::<Vec<_>>();
run_with_wasmer(generated_filename, &args);
return Ok(0);
}
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = target.architecture {
cmd.arg(binary_path);
}
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
//
// roc app.roc foo bar baz
//
// ...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);
}
}
match outcome {
BuildOutcome::Errors => Ok(outcome.status_code()),
_ => roc_run(cmd.current_dir(original_cwd)),
}
}
}
}
Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report);
Ok(1)
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
}
}
#[cfg(target_family = "unix")]
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
use std::os::unix::process::CommandExt;
// This is much faster than spawning a subprocess if we're on a UNIX system!
let err = cmd.exec();
// If exec actually returned, it was definitely an error! (Otherwise,
// this process would have been replaced by the other one, and we'd
// never actually reach this line of code.)
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");
// `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.");
}
}
}
fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
use wasmer::{Instance, Module, Store};
let store = Store::default();
let module = Module::from_file(&store, &wasm_path).unwrap();
// First, we create the `WasiEnv`
use wasmer_wasi::WasiState;
let mut wasi_env = WasiState::new("hello").args(args).finalize().unwrap();
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env.import_object(&module).unwrap();
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").unwrap();
use wasmer_wasi::WasiError;
match start.call(&[]) {
Ok(_) => {}
Err(e) => match e.downcast::<WasiError>() {
Ok(WasiError::Exit(0)) => {
// we run the `_start` function, so exit(0) is expected
}
other => panic!("Wasmer error: {:?}", other),
},
}
}
enum Backend {
Host,
X86_32,
X86_64,
Wasm32,
}
impl Default for Backend {
fn default() -> Self {
Backend::Host
}
}
impl Backend {
const fn as_str(&self) -> &'static str {
match self {
Backend::Host => "host",
Backend::X86_32 => "x86_32",
Backend::X86_64 => "x86_64",
Backend::Wasm32 => "wasm32",
}
}
/// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[
Backend::Host.as_str(),
Backend::X86_32.as_str(),
Backend::X86_64.as_str(),
Backend::Wasm32.as_str(),
];
fn to_triple(&self) -> Triple {
let mut triple = Triple::unknown();
match self {
Backend::Host => Triple::host(),
Backend::X86_32 => {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
triple.binary_format = BinaryFormat::Elf;
// TODO make this user-specified?
triple.operating_system = OperatingSystem::Linux;
triple
}
Backend::X86_64 => {
triple.architecture = Architecture::X86_64;
triple.binary_format = BinaryFormat::Elf;
triple
}
Backend::Wasm32 => {
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
triple
}
}
}
}
impl std::fmt::Display for Backend {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Backend {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"host" => Ok(Backend::Host),
"x86_32" => Ok(Backend::X86_32),
"x86_64" => Ok(Backend::X86_64),
"wasm32" => Ok(Backend::Wasm32),
_ => Err(()),
}
}
}

View File

@ -1,183 +0,0 @@
use roc_cli::build::check_file;
use roc_cli::{
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL,
DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
};
use roc_load::file::LoadingProblem;
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
use std::ffi::{OsStr, OsString};
#[cfg(feature = "llvm")]
use roc_cli::build;
#[cfg(not(feature = "llvm"))]
fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
panic!("Building without LLVM is not currently supported.");
}
fn main() -> io::Result<()> {
let matches = build_app().get_matches();
let exit_code = match matches.subcommand_name() {
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!
build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index })
}
None => {
launch_editor(None)?;
Ok(0)
}
}
}
Some(CMD_BUILD) => Ok(build(
matches.subcommand_matches(CMD_BUILD).unwrap(),
BuildConfig::BuildOnly,
)?),
Some(CMD_CHECK) => {
let arena = bumpalo::Bump::new();
let matches = matches.subcommand_matches(CMD_CHECK).unwrap();
let emit_timings = matches.is_present(FLAG_TIME);
let filename = matches.value_of(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) {
Ok(number_of_errors) => {
let exit_code = if number_of_errors != 0 { 1 } else { 0 };
Ok(exit_code)
}
Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report);
Ok(1)
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
}
}
Some(CMD_REPL) => {
repl::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
Some(CMD_EDIT) => {
match matches
.subcommand_matches(CMD_EDIT)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES)
.map(|mut values| values.next())
{
Some(Some(os_str)) => {
launch_editor(Some(Path::new(os_str)))?;
}
_ => {
launch_editor(None)?;
}
}
// Exit 0 if the editor exited normally
Ok(0)
}
Some(CMD_DOCS) => {
let maybe_values = matches
.subcommand_matches(CMD_DOCS)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES);
let mut values: Vec<OsString> = Vec::new();
match maybe_values {
None => {
let mut os_string_values: Vec<OsString> = Vec::new();
read_all_roc_files(&OsStr::new("./").to_os_string(), &mut os_string_values)?;
for os_string in os_string_values {
values.push(os_string);
}
}
Some(os_values) => {
for os_str in os_values {
values.push(os_str.to_os_string());
}
}
}
let mut roc_files = Vec::new();
// Populate roc_files
for os_str in values {
let metadata = fs::metadata(os_str.clone())?;
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
}
docs(roc_files);
Ok(0)
}
_ => unreachable!(),
}?;
std::process::exit(exit_code);
}
fn read_all_roc_files(
dir: &OsString,
mut roc_file_paths: &mut Vec<OsString>,
) -> Result<(), std::io::Error> {
let entries = fs::read_dir(dir)?;
for entry in entries {
let path = entry?.path();
if path.is_dir() {
read_all_roc_files(&path.into_os_string(), &mut roc_file_paths)?;
} else if path.extension().and_then(OsStr::to_str) == Some("roc") {
let file_path = path.into_os_string();
roc_file_paths.push(file_path);
}
}
Ok(())
}
fn roc_files_recursive<P: AsRef<Path>>(
path: P,
file_type: FileType,
roc_files: &mut Vec<PathBuf>,
) -> io::Result<()> {
if file_type.is_dir() {
for entry_res in fs::read_dir(path)? {
let entry = entry_res?;
roc_files_recursive(entry.path(), entry.file_type()?, roc_files)?;
}
} else {
roc_files.push(path.as_ref().to_path_buf());
}
Ok(())
}
#[cfg(feature = "editor")]
fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> {
roc_editor::launch(project_dir_path)
}
#[cfg(not(feature = "editor"))]
fn launch_editor(_project_dir_path: Option<&Path>) -> io::Result<()> {
panic!("Cannot launch the editor because this build of roc did not include `feature = \"editor\"`!");
}

View File

@ -1,248 +0,0 @@
use const_format::concatcp;
#[cfg(feature = "llvm")]
use gen::{gen_and_eval, ReplOutput};
use roc_parse::parser::{EExpr, SyntaxError};
use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow;
use std::io;
const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m";
const END_COL: &str = "\u{001b}[0m";
pub const WELCOME_MESSAGE: &str = concatcp!(
"\n The rockin ",
BLUE,
"roc repl",
END_COL,
"\n",
PINK,
"────────────────────────",
END_COL,
"\n\n"
);
pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n";
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
pub const CONT_PROMPT: &str = concatcp!(BLUE, "", END_COL, " ");
#[cfg(feature = "llvm")]
mod eval;
#[cfg(feature = "llvm")]
mod gen;
#[derive(Completer, Helper, Hinter)]
struct ReplHelper {
validator: InputValidator,
pending_src: String,
}
impl ReplHelper {
pub(crate) fn new() -> ReplHelper {
ReplHelper {
validator: InputValidator::new(),
pending_src: String::new(),
}
}
}
impl Highlighter for ReplHelper {
fn has_continuation_prompt(&self) -> bool {
true
}
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
info: PromptInfo<'_>,
) -> Cow<'b, str> {
if info.line_no() > 0 {
CONT_PROMPT.into()
} else {
prompt.into()
}
}
}
impl Validator for ReplHelper {
fn validate(
&self,
ctx: &mut validate::ValidationContext,
) -> rustyline::Result<validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct InputValidator {}
impl InputValidator {
pub(crate) fn new() -> InputValidator {
InputValidator {}
}
}
impl Validator for InputValidator {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
if ctx.input().is_empty() {
Ok(ValidationResult::Incomplete)
} else {
let arena = bumpalo::Bump::new();
let state = roc_parse::parser::State::new(ctx.input().trim().as_bytes());
match roc_parse::expr::parse_loc_expr(0, &arena, state) {
// Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::DefMissingFinalExpr(_, _), _))
| Err((_, EExpr::DefMissingFinalExpr2(_, _, _), _)) => {
Ok(ValidationResult::Incomplete)
}
_ => Ok(ValidationResult::Valid(None)),
}
}
}
}
#[cfg(not(feature = "llvm"))]
pub fn main() -> io::Result<()> {
panic!("The REPL currently requires being built with LLVM.");
}
#[cfg(feature = "llvm")]
pub fn main() -> io::Result<()> {
use rustyline::error::ReadlineError;
use rustyline::Editor;
// To debug rustyline:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
print!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
let mut prev_line_blank = false;
let mut editor = Editor::<ReplHelper>::new();
let repl_helper = ReplHelper::new();
editor.set_helper(Some(repl_helper));
loop {
let readline = editor.readline(PROMPT);
match readline {
Ok(line) => {
let trim_line = line.trim();
editor.add_history_entry(trim_line);
let pending_src = &mut editor
.helper_mut()
.expect("Editor helper was not set")
.pending_src;
match trim_line.to_lowercase().as_str() {
"" => {
if pending_src.is_empty() {
print!("\n{}", INSTRUCTIONS);
} else if prev_line_blank {
// After two blank lines in a row, give up and try parsing it
// even though it's going to fail. This way you don't get stuck.
match eval_and_format(pending_src.as_str()) {
Ok(output) => {
println!("{}", output);
}
Err(fail) => {
report_parse_error(fail);
}
}
pending_src.clear();
} else {
pending_src.push('\n');
prev_line_blank = true;
continue; // Skip the part where we reset prev_line_blank to false
}
}
":help" => {
println!("Use :exit or :q to exit.");
}
":exit" => {
break;
}
":q" => {
break;
}
_ => {
let result = if pending_src.is_empty() {
eval_and_format(trim_line)
} else {
pending_src.push('\n');
pending_src.push_str(trim_line);
eval_and_format(pending_src.as_str())
};
match result {
Ok(output) => {
println!("{}", output);
pending_src.clear();
}
// Err(Fail {
// reason: FailReason::Eof(_),
// ..
// }) => {}
Err(fail) => {
report_parse_error(fail);
pending_src.clear();
}
}
}
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
Err(ReadlineError::Eof) => {
// If we hit an eof, and we're allowed to keep going,
// append the str to the src we're building up and continue.
// (We only need to append it here if it was empty before;
// otherwise, we already appended it before calling eval_and_format.)
let pending_src = &mut editor
.helper_mut()
.expect("Editor helper was not set")
.pending_src;
if pending_src.is_empty() {
pending_src.push_str("");
}
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
prev_line_blank = false;
}
Ok(())
}
fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
#[cfg(feature = "llvm")]
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
use roc_mono::ir::OptLevel;
use target_lexicon::Triple;
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
}
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
})
}

View File

@ -1,109 +0,0 @@
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Store(tipe, var, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
used.insert(*var);
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,272 +0,0 @@
use crate::repl::eval;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::module::Linkage;
use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use roc_parse::parser::SyntaxError;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
use target_lexicon::Triple;
pub enum ReplOutput {
Problems(Vec<String>),
NoProblems { expr: String, expr_type: String },
}
pub fn gen_and_eval<'a>(
src: &[u8],
target: Triple,
opt_level: OptLevel,
) -> Result<ReplOutput, SyntaxError<'a>> {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
let arena = Bump::new();
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
let src_str: &str = unsafe { from_utf8_unchecked(src) };
let stdlib = roc_builtins::std::standard_stdlib();
let filename = PathBuf::from("REPL.roc");
let src_dir = Path::new("fake/test/path");
let module_src = promote_expr_to_module(src_str);
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
&arena,
filename,
&module_src,
&stdlib,
src_dir,
exposed_types,
ptr_bytes,
builtin_defs_map,
);
let mut loaded = match loaded {
Ok(v) => v,
Err(LoadingProblem::FormattedReport(report)) => {
return Ok(ReplOutput::Problems(vec![report]));
}
Err(e) => {
panic!("error while loading module: {:?}", e)
}
};
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
procedures,
entry_point,
interns,
exposed_to_host,
mut subs,
module_id: home,
sources,
..
} = loaded;
let mut lines = Vec::new();
for (home, (module_path, src)) in sources {
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
if error_count == 0 {
continue;
}
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
for problem in can_problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in type_problems {
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
}
for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
}
if !lines.is_empty() {
Ok(ReplOutput::Problems(lines))
} else {
let context = Context::create();
let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&target, &context, "",
));
// mark our zig-defined builtins as internal
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
}
debug_assert_eq!(exposed_to_host.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get_content_without_compacting(main_fn_var);
let expr_type_str = content_to_string(content, &subs, home, &interns);
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => *layout,
None => {
return Ok(ReplOutput::NoProblems {
expr: "<function>".to_string(),
expr_type: expr_type_str,
});
}
};
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context: &context,
interns,
module,
ptr_bytes,
is_gen_test: true, // so roc_panic is generated
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(&env);
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
opt_level,
procedures,
entry_point,
);
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// Verify the module
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module: {}\n\nUncomment things nearby to see more details.",
errors
);
}
let lib = module_to_dylib(env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let res_answer = unsafe {
eval::jit_to_ast(
&arena,
lib,
main_fn_name,
main_fn_layout,
content,
&env.interns,
home,
&subs,
ptr_bytes,
)
};
let mut expr = bumpalo::collections::String::new_in(&arena);
use eval::ToAstProblem::*;
match res_answer {
Ok(answer) => {
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
}
Err(FunctionLayout) => {
expr.push_str("<function>");
}
}
Ok(ReplOutput::NoProblems {
expr: expr.into_bump_str().to_string(),
expr_type: expr_type_str,
})
}
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer =
String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}

View File

@ -1,3 +0,0 @@
app
*.o
*.dSYM

View File

@ -1,4 +0,0 @@
interface Dep1 exposes [ str1 ] imports [ Dep2 ]
str1 : Str
str1 = Dep2.str2

View File

@ -1,4 +0,0 @@
interface Dep2 exposes [ str2 ] imports []
str2 : Str
str2 = "I am Dep2.str2"

View File

@ -1,7 +0,0 @@
app "multi-dep-str"
packages { base: "platform" }
imports [ Dep1 ]
provides [ main ] to base
main : Str
main = Dep1.str1

View File

@ -1,10 +0,0 @@
platform examples/multi-module
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View File

@ -1,4 +0,0 @@
interface Dep1 exposes [ value1 ] imports [ Dep2 ]
value1 : {} -> Str
value1 = \_ -> Dep2.value2 {}

View File

@ -1,7 +0,0 @@
app "multi-dep-thunk"
packages { base: "platform" }
imports [ Dep1 ]
provides [ main ] to base
main : Str
main = Dep1.value1 {}

View File

@ -1,10 +0,0 @@
platform examples/multi-dep-thunk
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View File

@ -1,81 +0,0 @@
const std = @import("std");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
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 mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed() RocStr;
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;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
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);
}
const Unit = extern struct {};
pub export fn main() i32 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
// start time
var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
return 0;
}
fn to_seconds(tms: std.os.timespec) f64 {
return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0);
}

View File

@ -1,592 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod repl_eval {
use cli_utils::helpers;
use roc_gen_llvm::run_roc::RocCallResult;
#[test]
fn check_discriminant_size() {
// tells us if the size of the discriminant has changed. Lots of other code
// relies on this size
let value: i64 = 1234;
assert_eq!(
std::mem::size_of_val(&RocCallResult::Success(value)),
roc_gen_llvm::run_roc::ROC_CALL_RESULT_DISCRIMINANT_SIZE
+ std::mem::size_of_val(&value)
)
}
const ERROR_MESSAGE_START: char = '─';
fn expect_success(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout, expected);
assert!(out.status.success());
}
fn expect_failure(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
// there may be some other stuff printed (e.g. unification errors)
// so skip till the header of the first error
match out.stdout.find(ERROR_MESSAGE_START) {
Some(index) => {
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout[index..], expected);
assert!(out.status.success());
}
None => {
assert_eq!(&out.stderr, "");
assert!(out.status.success());
panic!(
"I expected a failure, but there is no error message in stdout:\n\n{}",
&out.stdout
);
}
}
}
#[test]
fn literal_0() {
expect_success("0", "0 : Num *");
}
#[test]
fn literal_42() {
expect_success("42", "42 : Num *");
}
#[test]
fn literal_0x0() {
expect_success("0x0", "0 : Int *");
}
#[test]
fn literal_0x42() {
expect_success("0x42", "66 : Int *");
}
#[test]
fn literal_0point0() {
expect_success("0.0", "0 : Float *");
}
#[test]
fn literal_4point2() {
expect_success("4.2", "4.2 : Float *");
}
#[test]
fn num_addition() {
expect_success("1 + 2", "3 : Num *");
}
#[test]
fn int_addition() {
expect_success("0x1 + 2", "3 : I64");
}
#[test]
fn float_addition() {
expect_success("1.1 + 2", "3.1 : F64");
}
#[test]
fn num_rem() {
expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*");
}
#[test]
fn num_floor_division_success() {
expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*");
}
#[test]
fn num_floor_division_divby_zero() {
expect_success(
"Num.divFloor 4 0",
"Err DivByZero : Result (Int *) [ DivByZero ]*",
);
}
#[test]
fn num_ceil_division_success() {
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*")
}
#[test]
fn bool_in_record() {
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
expect_success(
"{ z: { y: { x: 1 == 1 } } }",
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
);
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
expect_success(
"{ x: 1 == 1, y: 1 != 1 }",
"{ x: True, y: False } : { x : Bool, y : Bool }",
);
}
#[test]
fn bool_basic_equality() {
expect_success("1 == 1", "True : Bool");
expect_success("1 != 1", "False : Bool");
}
#[test]
fn arbitrary_tag_unions() {
expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*");
expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*");
}
#[test]
fn tag_without_arguments() {
expect_success("True", "True : [ True ]*");
expect_success("False", "False : [ False ]*");
}
#[test]
fn byte_tag_union() {
expect_success(
"if 1 == 1 then Red else if 1 == 1 then Green else Blue",
"Red : [ Blue, Green, Red ]*",
);
expect_success(
"{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }",
"{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }",
);
}
#[test]
fn tag_in_record() {
expect_success(
"{ x: Foo 1 2 3, y : 4 }",
"{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }",
);
expect_success(
"{ x: Foo 1 2 3 }",
"{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }",
);
expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }");
}
#[test]
fn single_element_tag_union() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*");
}
#[test]
fn newtype_of_unit() {
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
}
#[test]
fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success(
"if 1 == 1 then True 3 else False 3.14",
"True 3 : [ False (Float *), True (Num *) ]*",
)
}
#[test]
fn literal_empty_str() {
expect_success("\"\"", "\"\" : Str");
}
#[test]
fn literal_ascii_str() {
expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str");
}
#[test]
fn literal_utf8_str() {
expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str");
}
#[test]
fn str_concat() {
expect_success(
"Str.concat \"Hello, \" \"World!\"",
"\"Hello, World!\" : Str",
);
}
#[test]
fn str_count_graphemes() {
expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat");
}
#[test]
fn literal_empty_list() {
expect_success("[]", "[] : List *");
}
#[test]
fn literal_empty_list_empty_record() {
expect_success("[ {} ]", "[ {} ] : List {}");
}
#[test]
fn literal_num_list() {
expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)");
}
#[test]
fn literal_int_list() {
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)");
}
#[test]
fn literal_float_list() {
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)");
}
#[test]
fn literal_string_list() {
expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#);
}
#[test]
fn nested_string_list() {
expect_success(
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#,
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#,
);
}
#[test]
fn nested_num_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#,
);
}
#[test]
fn nested_int_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#,
);
}
#[test]
fn nested_float_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#,
);
}
#[test]
fn num_bitwise_and() {
expect_success("Num.bitwiseAnd 20 20", "20 : Int *");
expect_success("Num.bitwiseAnd 25 10", "8 : Int *");
expect_success("Num.bitwiseAnd 200 0", "0 : Int *")
}
#[test]
fn num_bitwise_xor() {
expect_success("Num.bitwiseXor 20 20", "0 : Int *");
expect_success("Num.bitwiseXor 15 14", "1 : Int *");
expect_success("Num.bitwiseXor 7 15", "8 : Int *");
expect_success("Num.bitwiseXor 200 0", "200 : Int *")
}
#[test]
fn num_add_wrap() {
expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *");
}
#[test]
fn num_sub_wrap() {
expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : Int *");
}
#[test]
fn num_mul_wrap() {
expect_success("Num.mulWrap Num.maxInt 2", "-2 : Int *");
}
#[test]
fn num_add_checked() {
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.addChecked Num.maxInt 1",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
#[test]
fn num_sub_checked() {
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.subChecked Num.minInt 1",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
#[test]
fn num_mul_checked() {
expect_success(
"Num.mulChecked 20 2",
"Ok 40 : Result (Num *) [ Overflow ]*",
);
expect_success(
"Num.mulChecked Num.maxInt 2",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
#[test]
fn list_concat() {
expect_success(
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)",
);
}
#[test]
fn list_contains() {
expect_success("List.contains [] 0", "False : Bool");
expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool");
expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool");
}
#[test]
fn list_sum() {
expect_success("List.sum []", "0 : Num *");
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64");
}
#[test]
fn list_first() {
expect_success(
"List.first [ 12, 9, 6, 3 ]",
"Ok 12 : Result (Num *) [ ListWasEmpty ]*",
);
expect_success(
"List.first []",
"Err ListWasEmpty : Result * [ ListWasEmpty ]*",
);
}
#[test]
fn list_last() {
expect_success(
"List.last [ 12, 9, 6, 3 ]",
"Ok 3 : Result (Num *) [ ListWasEmpty ]*",
);
expect_success(
"List.last []",
"Err ListWasEmpty : Result * [ ListWasEmpty ]*",
);
}
#[test]
fn empty_record() {
expect_success("{}", "{} : {}");
}
#[test]
fn basic_1_field_i64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }");
}
#[test]
fn basic_1_field_f64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }");
}
#[test]
fn nested_1_field_i64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success(
"{ foo: { bar: { baz: 42 } } }",
"{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }",
);
}
#[test]
fn nested_1_field_f64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success(
"{ foo: { bar: { baz: 4.2 } } }",
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }",
);
}
#[test]
fn basic_2_field_i64_record() {
expect_success(
"{ foo: 0x4, bar: 0x2 }",
"{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }",
);
}
#[test]
fn basic_2_field_f64_record() {
expect_success(
"{ foo: 4.1, bar: 2.3 }",
"{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }",
);
}
#[test]
fn basic_2_field_mixed_record() {
expect_success(
"{ foo: 4.1, bar: 2 }",
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }",
);
}
#[test]
fn basic_3_field_record() {
expect_success(
"{ foo: 4.1, bar: 2, baz: 0x5 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }",
);
}
#[test]
fn list_of_1_field_records() {
// Even though these get unwrapped at runtime, the repl should still
// report them as records
expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }");
}
#[test]
fn list_of_2_field_records() {
expect_success(
"[ { foo: 4.1, bar: 2 } ]",
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }",
);
}
#[test]
fn three_element_record() {
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
expect_success(
"{ a: 1, b: 2, c: 3 }",
"{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }",
);
}
#[test]
fn four_element_record() {
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
expect_success(
"{ a: 1, b: 2, c: 3, d: 4 }",
"{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }",
);
}
// #[test]
// fn multiline_string() {
// // If a string contains newlines, format it as a multiline string in the output
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
// }
#[test]
fn list_of_3_field_records() {
expect_success(
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }",
);
}
#[test]
fn identity_lambda() {
expect_success("\\x -> x", "<function> : a -> a");
}
#[test]
fn sum_lambda() {
expect_success("\\x, y -> x + y", "<function> : Num a, Num a -> Num a");
}
#[test]
fn stdlib_function() {
expect_success("Num.abs", "<function> : Num a -> Num a");
}
#[test]
fn too_few_args() {
expect_failure(
"Num.add 2",
indoc!(
r#"
TOO FEW ARGS
The add function expects 2 arguments, but it got only 1:
4 Num.add 2
^^^^^^^
Roc does not allow functions to be partially applied. Use a closure to
make partial application explicit.
"#
),
);
}
#[test]
fn type_problem() {
expect_failure(
"1 + \"\"",
indoc!(
r#"
TYPE MISMATCH
The 2nd argument to add is not what I expect:
4 1 + ""
^^
This argument is a string of type:
Str
But add needs the 2nd argument to be:
Num a
"#
),
);
}
// #[test]
// fn parse_problem() {
// // can't find something that won't parse currently
// }
//
// #[test]
// fn mono_problem() {
// // can't produce a mono error (non-exhaustive pattern) yet
// }
}

View File

@ -1,149 +0,0 @@
use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId};
use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle};
use super::{attribute::Attributes, nodes, nodes::MarkupNode};
pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::EQUALS.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
new_comma_mn_ast(ASTNodeId::AExprId(expr_id), parent_id_opt)
}
pub fn new_comma_mn_ast(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::COMMA.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Comma,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Blank {
ast_node_id,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_blank_mn_w_nls(
ast_node_id: ASTNodeId,
parent_id_opt: Option<MarkNodeId>,
nr_of_newlines: usize,
) -> MarkupNode {
MarkupNode::Blank {
ast_node_id,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: nr_of_newlines,
}
}
pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
new_operator_mn(nodes::COLON.to_owned(), expr_id, parent_id_opt)
}
pub fn new_operator_mn(
content: String,
expr_id: ExprId,
parent_id_opt: Option<MarkNodeId>,
) -> MarkupNode {
MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::LEFT_ACCOLADE.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::RIGHT_ACCOLADE.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::LEFT_SQUARE_BR.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::RIGHT_SQUARE_BR.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
}
}
pub fn new_func_name_mn(content: String, expr_id: ExprId) -> MarkupNode {
MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::FunctionName,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
}
}
pub fn new_arg_name_mn(content: String, expr_id: ExprId) -> MarkupNode {
MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::FunctionArgName,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
}
}
pub fn new_arrow_mn(ast_node_id: ASTNodeId, newlines_at_end: usize) -> MarkupNode {
MarkupNode::Text {
content: nodes::ARROW.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end,
}
}

View File

@ -1,52 +0,0 @@
use crate::{
markup::{common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node},
slow_pool::{MarkNodeId, SlowPool},
};
use super::from_expr2::expr2_to_markup;
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::ASTNodeId,
def::def2::{Def2, DefId},
},
env::Env,
},
};
use roc_module::symbol::Interns;
pub fn def2_to_markup<'a>(
env: &mut Env<'a>,
def2: &Def2,
def2_node_id: DefId,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::ADefId(def2_node_id);
let mark_node_id = match def2 {
Def2::ValueDef {
identifier_id,
expr_id,
} => {
let expr_mn_id = expr2_to_markup(
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
0,
)?;
let tld_mn =
tld_mark_node(*identifier_id, expr_mn_id, ast_node_id, mark_node_pool, env)?;
mark_node_pool.add(tld_mn)
}
Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)),
};
Ok(mark_node_id)
}

View File

@ -1,43 +0,0 @@
use roc_ast::{
ast_error::ASTResult,
lang::{core::ast::ASTNodeId, env::Env},
};
use roc_module::symbol::IdentId;
use crate::{
markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode},
slow_pool::{MarkNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
pub fn tld_mark_node<'a>(
identifier_id: IdentId,
expr_mark_node_id: MarkNodeId,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
env: &Env<'a>,
) -> ASTResult<MarkupNode> {
let val_name = env.ident_ids.get_name_str_res(identifier_id)?;
let val_name_mn = MarkupNode::Text {
content: val_name.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Value,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
let val_name_mn_id = mark_node_pool.add(val_name_mn);
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
let full_let_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
parent_id_opt: None,
newlines_at_end: 2,
};
Ok(full_let_node)
}

View File

@ -1,51 +0,0 @@
[package]
name = "roc_build"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm" }
roc_gen_dev = { path = "../gen_dev" }
roc_reporting = { path = "../reporting" }
roc_std = { path = "../../roc_std" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
[features]
default = ["llvm", "target-webassembly", "target-aarch64"]
target-arm = []
target-aarch64 = []
target-webassembly = []
# 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"]

View File

@ -1,94 +0,0 @@
const std = @import("std");
const mem = std.mem;
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
pub fn build(b: *Builder) void {
// b.setPreferredReleaseMode(builtin.Mode.Debug
b.setPreferredReleaseMode(.ReleaseFast);
const mode = b.standardReleaseOptions();
// Options
const fallback_main_path = "./src/main.zig";
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\" and \"test\". Defaults to \"{s}\". ", .{fallback_main_path});
const main_path = b.option([]const u8, "main-path", main_path_desc) orelse fallback_main_path;
// Tests
var main_tests = b.addTest(main_path);
main_tests.setBuildMode(mode);
main_tests.linkSystemLibrary("c");
const test_step = b.step("test", "Run tests");
test_step.dependOn(&main_tests.step);
// LLVM IR
const obj_name = "builtins-host";
const llvm_obj = b.addObject(obj_name, main_path);
llvm_obj.setBuildMode(mode);
llvm_obj.linkSystemLibrary("c");
llvm_obj.strip = true;
llvm_obj.emit_llvm_ir = true;
llvm_obj.emit_bin = false;
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step);
// 32-bit x86, useful for debugging
var i386_target = CrossTarget.parse(.{}) catch unreachable;
i386_target.cpu_arch = std.Target.Cpu.Arch.i386;
i386_target.os_tag = std.Target.Os.Tag.linux;
i386_target.abi = std.Target.Abi.musl;
const obj_name_i386 = "builtins-i386";
const llvm_obj_i386 = b.addObject(obj_name_i386, main_path);
llvm_obj_i386.setBuildMode(mode);
llvm_obj_i386.strip = true;
llvm_obj_i386.emit_llvm_ir = true;
llvm_obj_i386.emit_bin = false;
llvm_obj_i386.target = i386_target;
const ir_i386 = b.step("ir-i386", "Build LLVM ir for 32-bit targets (x86)");
ir_i386.dependOn(&llvm_obj_i386.step);
// LLVM IR 32-bit (wasm)
var wasm32_target = CrossTarget.parse(.{}) catch unreachable;
// 32-bit wasm
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32;
wasm32_target.os_tag = std.Target.Os.Tag.freestanding;
wasm32_target.abi = std.Target.Abi.none;
const obj_name_wasm32 = "builtins-wasm32";
const llvm_obj_wasm32 = b.addObject(obj_name_wasm32, main_path);
llvm_obj_wasm32.setBuildMode(mode);
llvm_obj_wasm32.strip = true;
llvm_obj_wasm32.emit_llvm_ir = true;
llvm_obj_wasm32.emit_bin = false;
llvm_obj_wasm32.target = wasm32_target;
const ir_wasm32 = b.step("ir-wasm32", "Build LLVM ir for 32-bit targets (wasm)");
ir_wasm32.dependOn(&llvm_obj_wasm32.step);
// Object File
// TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden).
// Also, zig has -ffunction-sections, but I am not sure how to add it here.
// With both of those changes, unused zig functions will be cleaned up by the linker saving around 100k.
const obj = b.addObject("builtins-host", main_path);
obj.setBuildMode(mode);
obj.linkSystemLibrary("c");
obj.setOutputDir(".");
obj.strip = true;
const obj_step = b.step("object", "Build object file for linking");
obj_step.dependOn(&obj.step);
b.default_step = ir;
removeInstallSteps(b);
}
fn removeInstallSteps(b: *Builder) void {
for (b.top_level_steps.items) |top_level_step, i| {
const name = top_level_step.step.name;
if (mem.eql(u8, name, "install") or mem.eql(u8, name, "uninstall")) {
_ = b.top_level_steps.swapRemove(i);
}
}
}

View File

@ -1,781 +0,0 @@
const std = @import("std");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const mem = std.mem;
const assert = std.debug.assert;
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const INITIAL_SEED = 0xc70f6907;
const InPlace = enum(u8) {
InPlace,
Clone,
};
const Slot = enum(u8) {
Empty,
Filled,
PreviouslyFilled,
};
const MaybeIndexTag = enum { index, not_found };
const MaybeIndex = union(MaybeIndexTag) { index: usize, not_found: void };
fn nextSeed(seed: u64) u64 {
// TODO is this a valid way to get a new seed? are there better ways?
return seed + 1;
}
fn totalCapacityAtLevel(input: usize) usize {
if (input == 0) {
return 0;
}
var n = input;
var slots: usize = 8;
while (n > 1) : (n -= 1) {
slots = slots * 2 + slots;
}
return slots;
}
fn capacityOfLevel(input: usize) usize {
if (input == 0) {
return 0;
}
var n = input;
var slots: usize = 8;
while (n > 1) : (n -= 1) {
slots = slots * 2;
}
return slots;
}
// aligmnent of elements. The number (16 or 8) indicates the maximum
// alignment of the key and value. The tag furthermore indicates
// which has the biggest aligmnent. If both are the same, we put
// the key first
const Alignment = extern struct {
bits: u8,
const VALUE_BEFORE_KEY_FLAG: u8 = 0b1000_0000;
fn toU32(self: Alignment) u32 {
if (self.bits >= VALUE_BEFORE_KEY_FLAG) {
return self.bits ^ Alignment.VALUE_BEFORE_KEY_FLAG;
} else {
return self.bits;
}
}
fn keyFirst(self: Alignment) bool {
if (self.bits & Alignment.VALUE_BEFORE_KEY_FLAG > 0) {
return false;
} else {
return true;
}
}
};
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
alignment: Alignment,
) void {
return utils.decref(bytes_or_null, data_bytes, alignment.toU32());
}
pub fn allocateWithRefcount(
data_bytes: usize,
alignment: Alignment,
) [*]u8 {
return utils.allocateWithRefcount(data_bytes, alignment.toU32());
}
pub const RocDict = extern struct {
dict_bytes: ?[*]u8,
dict_entries_len: usize,
number_of_levels: usize,
pub fn empty() RocDict {
return RocDict{
.dict_entries_len = 0,
.number_of_levels = 0,
.dict_bytes = null,
};
}
pub fn allocate(
number_of_levels: usize,
number_of_entries: usize,
alignment: Alignment,
key_size: usize,
value_size: usize,
) RocDict {
const number_of_slots = totalCapacityAtLevel(number_of_levels);
const slot_size = slotSize(key_size, value_size);
const data_bytes = number_of_slots * slot_size;
return RocDict{
.dict_bytes = allocateWithRefcount(data_bytes, alignment),
.number_of_levels = number_of_levels,
.dict_entries_len = number_of_entries,
};
}
pub fn reallocate(
self: RocDict,
alignment: Alignment,
key_width: usize,
value_width: usize,
) RocDict {
const new_level = self.number_of_levels + 1;
const slot_size = slotSize(key_width, value_width);
const old_capacity = self.capacity();
const new_capacity = totalCapacityAtLevel(new_level);
const delta_capacity = new_capacity - old_capacity;
const data_bytes = new_capacity * slot_size;
const first_slot = allocateWithRefcount(data_bytes, alignment);
// transfer the memory
if (self.dict_bytes) |source_ptr| {
const dest_ptr = first_slot;
var source_offset: usize = 0;
var dest_offset: usize = 0;
if (alignment.keyFirst()) {
// copy keys
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width);
// copy values
source_offset = old_capacity * key_width;
dest_offset = new_capacity * key_width;
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width);
} else {
// copy values
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width);
// copy keys
source_offset = old_capacity * value_width;
dest_offset = new_capacity * value_width;
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width);
}
// copy slots
source_offset = old_capacity * (key_width + value_width);
dest_offset = new_capacity * (key_width + value_width);
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * @sizeOf(Slot));
}
var i: usize = 0;
const first_new_slot_value = first_slot + old_capacity * slot_size + delta_capacity * (key_width + value_width);
while (i < (new_capacity - old_capacity)) : (i += 1) {
(first_new_slot_value)[i] = @enumToInt(Slot.Empty);
}
const result = RocDict{
.dict_bytes = first_slot,
.number_of_levels = self.number_of_levels + 1,
.dict_entries_len = self.dict_entries_len,
};
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
decref(self.dict_bytes, self.capacity() * slotSize(key_width, value_width), alignment);
return result;
}
pub fn asU8ptr(self: RocDict) [*]u8 {
return @ptrCast([*]u8, self.dict_bytes);
}
pub fn len(self: RocDict) usize {
return self.dict_entries_len;
}
pub fn isEmpty(self: RocDict) bool {
return self.len() == 0;
}
pub fn isUnique(self: RocDict) bool {
// the empty dict is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.dict_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn capacity(self: RocDict) usize {
return totalCapacityAtLevel(self.number_of_levels);
}
pub fn makeUnique(self: RocDict, alignment: Alignment, key_width: usize, value_width: usize) RocDict {
if (self.isEmpty()) {
return self;
}
if (self.isUnique()) {
return self;
}
// unfortunately, we have to clone
var new_dict = RocDict.allocate(self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes);
const number_of_bytes = self.capacity() * (@sizeOf(Slot) + key_width + value_width);
@memcpy(new_bytes, old_bytes, number_of_bytes);
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.capacity() * slotSize(key_width, value_width);
decref(self.dict_bytes, data_bytes, alignment);
return new_dict;
}
fn getSlot(self: *const RocDict, index: usize, key_width: usize, value_width: usize) Slot {
const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot);
const ptr = self.dict_bytes orelse unreachable;
return @intToEnum(Slot, ptr[offset]);
}
fn setSlot(self: *RocDict, index: usize, key_width: usize, value_width: usize, slot: Slot) void {
const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot);
const ptr = self.dict_bytes orelse unreachable;
ptr[offset] = @enumToInt(slot);
}
fn setKey(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (key_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
} else {
break :blk (self.capacity() * value_width) + (index * key_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
const source = data orelse unreachable;
const dest = ptr + offset;
@memcpy(dest, source, key_width);
}
fn getKey(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (key_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
} else {
break :blk (self.capacity() * value_width) + (index * key_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
return ptr + offset;
}
fn setValue(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (value_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
} else {
break :blk (index * value_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
const source = data orelse unreachable;
const dest = ptr + offset;
@memcpy(dest, source, value_width);
}
fn getValue(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (value_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
} else {
break :blk (index * value_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
return ptr + offset;
}
fn findIndex(self: *const RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) MaybeIndex {
if (self.isEmpty()) {
return MaybeIndex.not_found;
}
var seed: u64 = INITIAL_SEED;
var current_level: usize = 1;
var current_level_size: usize = 8;
var next_level_size: usize = 2 * current_level_size;
while (true) {
if (current_level > self.number_of_levels) {
return MaybeIndex.not_found;
}
// hash the key, and modulo by the maximum size
// (so we get an in-bounds index)
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + @intCast(usize, (hash % current_level_size));
switch (self.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
return MaybeIndex.not_found;
},
Slot.Filled => {
// is this the same key, or a new key?
const current_key = self.getKey(index, alignment, key_width, value_width);
if (is_eq(key, current_key)) {
return MaybeIndex{ .index = index };
} else {
current_level += 1;
current_level_size *= 2;
next_level_size *= 2;
seed = nextSeed(seed);
continue;
}
},
}
}
}
};
// Dict.empty
pub fn dictEmpty(dict: *RocDict) callconv(.C) void {
dict.* = RocDict.empty();
}
pub fn slotSize(key_size: usize, value_size: usize) usize {
return @sizeOf(Slot) + key_size + value_size;
}
// Dict.len
pub fn dictLen(dict: RocDict) callconv(.C) usize {
return dict.dict_entries_len;
}
// commonly used type aliases
const Opaque = ?[*]u8;
const HashFn = fn (u64, ?[*]u8) callconv(.C) u64;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Inc = fn (?[*]u8) callconv(.C) void;
const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
// Dict.insert : Dict k v, k, v -> Dict k v
pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
var seed: u64 = INITIAL_SEED;
var result = input.makeUnique(alignment, key_width, value_width);
var current_level: usize = 1;
var current_level_size: usize = 8;
var next_level_size: usize = 2 * current_level_size;
while (true) {
if (current_level > result.number_of_levels) {
result = result.reallocate(alignment, key_width, value_width);
}
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + @intCast(usize, (hash % current_level_size));
assert(index < result.capacity());
switch (result.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
result.setSlot(index, key_width, value_width, Slot.Filled);
result.setKey(index, alignment, key_width, value_width, key);
result.setValue(index, alignment, key_width, value_width, value);
result.dict_entries_len += 1;
break;
},
Slot.Filled => {
// is this the same key, or a new key?
const current_key = result.getKey(index, alignment, key_width, value_width);
if (is_eq(key, current_key)) {
// we will override the old value, but first have to decrement its refcount
const current_value = result.getValue(index, alignment, key_width, value_width);
dec_value(current_value);
// we must consume the key argument!
dec_key(key);
result.setValue(index, alignment, key_width, value_width, value);
break;
} else {
seed = nextSeed(seed);
current_level += 1;
current_level_size *= 2;
next_level_size *= 2;
continue;
}
},
}
}
// write result into pointer
output.* = result;
}
// Dict.remove : Dict k v, k -> Dict k v
pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
switch (input.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// the key was not found; we're done
output.* = input;
return;
},
MaybeIndex.index => |index| {
var dict = input.makeUnique(alignment, key_width, value_width);
assert(index < dict.capacity());
dict.setSlot(index, key_width, value_width, Slot.PreviouslyFilled);
const old_key = dict.getKey(index, alignment, key_width, value_width);
const old_value = dict.getValue(index, alignment, key_width, value_width);
dec_key(old_key);
dec_value(old_value);
dict.dict_entries_len -= 1;
// if the dict is now completely empty, free its allocation
if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(dict.dict_bytes, data_bytes, alignment);
output.* = RocDict.empty();
return;
}
output.* = dict;
},
}
}
// Dict.contains : Dict k v, k -> Bool
pub fn dictContains(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) callconv(.C) bool {
switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
return false;
},
MaybeIndex.index => |_| {
return true;
},
}
}
// Dict.get : Dict k v, k -> { flag: bool, value: Opaque }
pub fn dictGet(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_value: Inc) callconv(.C) extern struct { value: Opaque, flag: bool } {
switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
return .{ .flag = false, .value = null };
},
MaybeIndex.index => |index| {
var value = dict.getValue(index, alignment, key_width, value_width);
inc_value(value);
return .{ .flag = true, .value = value };
},
}
}
// Dict.elementsRc
// increment or decrement all dict elements (but not the dict's allocation itself)
pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, modify_key: Inc, modify_value: Inc) callconv(.C) void {
const size = dict.capacity();
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
modify_key(dict.getKey(i, alignment, key_width, value_width));
modify_value(dict.getValue(i, alignment, key_width, value_width));
},
else => {},
}
}
}
pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_key: Inc, output: *RocList) callconv(.C) void {
const size = dict.capacity();
var length: usize = 0;
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
length += 1;
},
else => {},
}
}
if (length == 0) {
output.* = RocList.empty();
return;
}
const data_bytes = length * key_width;
var ptr = allocateWithRefcount(data_bytes, alignment);
i = 0;
var copied: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
inc_key(key);
const key_cast = @ptrCast([*]const u8, key);
@memcpy(ptr + (copied * key_width), key_cast, key_width);
copied += 1;
},
else => {},
}
}
output.* = RocList{ .bytes = ptr, .length = length };
}
pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_value: Inc, output: *RocList) callconv(.C) void {
const size = dict.capacity();
var length: usize = 0;
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
length += 1;
},
else => {},
}
}
if (length == 0) {
output.* = RocList.empty();
return;
}
const data_bytes = length * value_width;
var ptr = allocateWithRefcount(data_bytes, alignment);
i = 0;
var copied: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const value = dict.getValue(i, alignment, key_width, value_width);
inc_value(value);
const value_cast = @ptrCast([*]const u8, value);
@memcpy(ptr + (copied * value_width), value_cast, value_width);
copied += 1;
},
else => {},
}
}
output.* = RocList{ .bytes = ptr, .length = length };
}
fn doNothing(_: Opaque) callconv(.C) void {
return;
}
pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, inc_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
while (i < dict2.capacity()) : (i += 1) {
switch (dict2.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict2.getKey(i, alignment, key_width, value_width);
switch (output.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
const value = dict2.getValue(i, alignment, key_width, value_width);
inc_value(value);
// we need an extra RC token for the key
inc_key(key);
inc_value(value);
// we know the newly added key is not a duplicate, so the `dec`s are unreachable
const dec_key = doNothing;
const dec_value = doNothing;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// the key is already in the output dict
continue;
},
}
},
else => {},
}
}
}
pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// keep this key/value
continue;
},
}
},
else => {},
}
}
}
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// keep this key/value
continue;
},
MaybeIndex.index => |_| {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
}
},
else => {},
}
}
}
pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, output: *RocDict) callconv(.C) void {
output.* = RocDict.empty();
var ptr = @ptrCast([*]u8, list.bytes);
const dec_value = doNothing;
const value = null;
const size = list.length;
var i: usize = 0;
while (i < size) : (i += 1) {
const key = ptr + i * key_width;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
}
// NOTE: decref checks for the empty case
const data_bytes = size * key_width;
decref(list.bytes, data_bytes, alignment);
}
pub fn dictWalk(
dict: RocDict,
caller: Caller3,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
accum: Opaque,
alignment: Alignment,
key_width: usize,
value_width: usize,
accum_width: usize,
output: Opaque,
) callconv(.C) void {
const alignment_u32 = alignment.toU32();
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32);
var b1 = output orelse unreachable;
var b2 = bytes_ptr;
if (data_is_owned) {
inc_n_data(data, dict.len());
}
@memcpy(b2, accum orelse unreachable, accum_width);
var i: usize = 0;
const size = dict.capacity();
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
const value = dict.getValue(i, alignment, key_width, value_width);
caller(data, b2, key, value, b1);
std.mem.swap([*]u8, &b1, &b2);
},
else => {},
}
}
@memcpy(output orelse unreachable, b2, accum_width);
utils.dealloc(bytes_ptr, alignment_u32);
}

View File

@ -1,85 +0,0 @@
const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const math = std.math;
const RocList = @import("list.zig").RocList;
pub fn exportPow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(base: T, exp: T) callconv(.C) T {
return std.math.pow(T, base, exp);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportIsFinite(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isFinite(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportAsin(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) T {
return std.math.asin(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportAcos(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) T {
return std.math.acos(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) T {
return std.math.atan(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) i64 {
return @floatToInt(i64, (@round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(a: T, b: T) callconv(.C) T {
return math.divCeil(T, a, b) catch unreachable;
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
}
fn bytesToU16(arg: RocList, position: usize) u16 {
const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u16, [_]u8{ bytes[position], bytes[position + 1] });
}
pub fn bytesToU32C(arg: RocList, position: usize) callconv(.C) u32 {
return @call(.{ .modifier = always_inline }, bytesToU32, .{ arg, position });
}
fn bytesToU32(arg: RocList, position: usize) u32 {
const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] });
}

View File

@ -1,264 +0,0 @@
const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
pub fn WithOverflow(comptime T: type) type {
return extern struct { value: T, has_overflowed: bool };
}
// 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;
// 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;
// This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
// Signals to the host that the program has paniced
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
comptime {
const builtin = @import("builtin");
// During tetsts, use the testing allocators to satisfy these functions.
if (builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
}
}
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_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*c_void {
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
const slice = ptr[0..old_size];
return @ptrCast(?*c_void, std.testing.allocator.realloc(slice, new_size) catch unreachable);
}
fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void {
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
std.testing.allocator.destroy(ptr);
}
fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = c_ptr;
_ = tag_id;
@panic("Roc paniced");
}
pub fn alloc(size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
}
pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_realloc, .{ c_ptr, new_size, old_size, alignment }));
}
pub fn dealloc(c_ptr: [*]u8, alignment: u32) void {
return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment });
}
// 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 {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
}
// indirection because otherwise zig creats 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 {
_ = c_ptr;
_ = alignment;
// const cstr = @ptrCast([*:0]u8, c_ptr);
// const stderr = std.io.getStdErr().writer();
// stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable;
// std.c.exit(1);
}
pub const Inc = fn (?[*]u8) callconv(.C) void;
pub const IncN = fn (?[*]u8, u64) callconv(.C) void;
pub const Dec = fn (?[*]u8) callconv(.C) void;
const REFCOUNT_MAX_ISIZE: isize = 0;
pub const REFCOUNT_ONE_ISIZE: isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
pub const IntWidth = enum(u8) {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Usize,
};
pub fn decrefC(
bytes_or_null: ?[*]isize,
alignment: u32,
) callconv(.C) void {
// IMPORTANT: bytes_or_null is this case is expected to be a pointer to the refcount
// (NOT the start of the data, or the start of the allocation)
// this is of course unsafe, but we trust what we get from the llvm side
var bytes = @ptrCast([*]isize, bytes_or_null);
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment });
}
pub fn decrefCheckNullC(
bytes_or_null: ?[*]u8,
alignment: u32,
) callconv(.C) void {
if (bytes_or_null) |bytes| {
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ isizes - 1, alignment });
}
}
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
alignment: u32,
) void {
if (data_bytes == 0) {
return;
}
var bytes = bytes_or_null orelse return;
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
decref_ptr_to_refcount(isizes - 1, alignment);
}
inline fn decref_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
) void {
const refcount: isize = refcount_ptr[0];
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
} else if (refcount < 0) {
refcount_ptr[0] = refcount - 1;
}
}
pub fn allocateWithRefcount(
data_bytes: usize,
element_alignment: u32,
) [*]u8 {
const alignment = std.math.max(@sizeOf(usize), element_alignment);
const first_slot_offset = std.math.max(@sizeOf(usize), element_alignment);
const length = alignment + data_bytes;
switch (alignment) {
16 => {
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment));
var as_usize_array = @ptrCast([*]usize, new_bytes);
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + first_slot_offset;
return first_slot;
},
8 => {
var raw = alloc(length, alignment);
var new_bytes: [*]align(8) u8 = @alignCast(8, raw);
var as_isize_array = @ptrCast([*]isize, new_bytes);
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + first_slot_offset;
return first_slot;
},
4 => {
var raw = alloc(length, alignment);
var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
var as_isize_array = @ptrCast([*]isize, new_bytes);
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + first_slot_offset;
return first_slot;
},
else => {
// const stdout = std.io.getStdOut().writer();
// stdout.print("alignment: {d}", .{alignment}) catch unreachable;
// @panic("allocateWithRefcount with invalid alignment");
unreachable;
},
}
}
pub fn unsafeReallocate(
source_ptr: [*]u8,
alignment: u32,
old_length: usize,
new_length: usize,
element_width: usize,
) [*]u8 {
const align_width: usize = std.math.max(alignment, @sizeOf(usize));
const old_width = align_width + old_length * element_width;
const new_width = align_width + new_length * element_width;
// TODO handle out of memory
// NOTE realloc will dealloc the original allocation
const old_allocation = source_ptr - align_width;
const new_allocation = realloc(old_allocation, new_width, old_width, alignment);
const new_source = @ptrCast([*]u8, new_allocation) + align_width;
return new_source;
}
pub const RocResult = extern struct {
bytes: ?[*]u8,
pub fn isOk(self: RocResult) bool {
// assumptions
//
// - the tag is the first field
// - the tag is usize bytes wide
// - Ok has tag_id 1, because Err < Ok
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
return usizes[0] == 1;
}
pub fn isErr(self: RocResult) bool {
return !self.isOk();
}
};
pub const Ordering = enum(u8) {
EQ = 0,
GT = 1,
LT = 2,
};
pub const UpdateMode = enum(u8) {
Immutable = 0,
InPlace = 1,
};

View File

@ -1,176 +0,0 @@
use std::convert::AsRef;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
use std::str;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_obj_path = Path::new(&out_dir).join("builtins.o");
let dest_obj = dest_obj_path.to_str().expect("Invalid dest object path");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILTINS_O={}", dest_obj);
// When we build on Netlify, zig is not installed (but also not used,
// since all we're doing is generating docs), so we can skip the steps
// that require having zig installed.
if env::var_os("NO_ZIG_INSTALLED").is_some() {
// We still need to do the other things before this point, because
// setting the env vars is needed for other parts of the build.
return;
}
// "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
let src_obj_path = bitcode_path.join("builtins-host.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
let dest_ir_path = bitcode_path.join("builtins-wasm32.ll");
let dest_ir_wasm32 = dest_ir_path.to_str().expect("Invalid dest ir path");
let dest_ir_path = bitcode_path.join("builtins-i386.ll");
let dest_ir_i386 = dest_ir_path.to_str().expect("Invalid dest ir path");
let dest_ir_path = bitcode_path.join("builtins-host.ll");
let dest_ir_host = dest_ir_path.to_str().expect("Invalid dest ir path");
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
println!("Compiling host ir to: {}", dest_ir_host);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
println!("Compiling 32-bit i386 ir to: {}", dest_ir_i386);
run_command(
&bitcode_path,
"zig",
&["build", "ir-i386", "-Drelease=true"],
);
println!("Compiling 32-bit wasm32 ir to: {}", dest_ir_wasm32);
run_command(
&bitcode_path,
"zig",
&["build", "ir-wasm32", "-Drelease=true"],
);
println!("Moving zig object to: {}", dest_obj);
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
let dest_bc_path = bitcode_path.join("builtins-i386.bc");
let dest_bc_32bit = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 32-bit bitcode to: {}", dest_bc_32bit);
run_command(
&build_script_dir_path,
"llvm-as",
&[dest_ir_i386, "-o", dest_bc_32bit],
);
let dest_bc_path = bitcode_path.join("builtins-wasm32.bc");
let dest_bc_32bit = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 32-bit bitcode to: {}", dest_bc_32bit);
run_command(
&build_script_dir_path,
"llvm-as",
&[dest_ir_wasm32, "-o", dest_bc_32bit],
);
let dest_bc_path = bitcode_path.join("builtins-host.bc");
let dest_bc_64bit = dest_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],
);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;
println!(
"cargo:rerun-if-changed={}",
path.to_str().expect("Failed to convert path to str")
);
})
.unwrap();
}
fn run_command<S, I, P: AsRef<Path>>(path: P, command_str: &str, args: I)
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output_result = Command::new(OsStr::new(&command_str))
.current_dir(path)
.args(args)
.output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
panic!("{} failed: {}", command_str, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
if !path_buf.ends_with("zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else {
let path = path_buf.as_path();
match path.extension() {
Some(osstr) if osstr == "zig" => {
cb(path);
}
_ => {}
}
}
}
}
Ok(())
}
// fn get_zig_files(dir: &Path) -> io::Result<Vec<&Path>> {
// let mut vec = Vec::new();
// if dir.is_dir() {
// for entry in fs::read_dir(dir)? {
// let entry = entry?;
// let path_buf = entry.path();
// if path_buf.is_dir() {
// match get_zig_files(&path_buf) {
// Ok(sub_files) => vec = [vec, sub_files].concat(),
// Err(_) => (),
// };
// } else {
// let path = path_buf.as_path();
// let path_ext = path.extension().unwrap();
// if path_ext == "zig" {
// vec.push(path.clone());
// }
// }
// }
// }
// Ok(vec)
// }

View File

@ -1,49 +0,0 @@
interface Dict
exposes
[
Dict,
empty,
single,
get,
walk,
insert,
len,
remove,
contains,
keys,
values,
union,
intersection,
difference
]
imports []
size : Dict * * -> Nat
isEmpty : Dict * * -> Bool
## 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 beforeValue,
({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })
-> Dict afterKey afterValue
# 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

View File

@ -1,55 +0,0 @@
interface Result
exposes
[
Result,
map,
mapErr,
withDefault,
after
]
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 ]
## 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,55 +0,0 @@
interface Set
exposes
[
Set,
empty,
single,
len,
insert,
remove,
union,
difference,
intersection,
toList,
fromList,
walk,
contains
]
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
# 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
## 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,483 +0,0 @@
interface Str
exposes
[
Str,
isEmpty,
append,
concat,
joinWith,
split,
countGraphemes,
startsWith,
endsWith,
fromInt,
fromFloat,
fromUtf8,
Utf8Problem,
Utf8ByteProblem,
toUtf8,
startsWithCodePt
]
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 string begins with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster),
## return it along with the rest of the string after that grapheme.
##
## If the string does not begin with a full grapheme, for example because it was
## empty, return `Err`.
parseGrapheme : Str -> Result { val : Str, rest : Str } [ Expected [ Grapheme ]* Str ]*
## If the string begins with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point),
## return it along with the rest of the string after that code point.
##
## If the string does not begin with a valid code point, for example because it was
## empty, return `Err`.
parseCodePt : Str -> Result { val : U32, rest : Str } [ Expected [ CodePt ]* Str ]*
## If the first string begins with the second, return whatever comes
## after the second.
chomp : Str, Str -> Result Str [ Expected [ ExactStr Str ]* Str ]*
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return whatever comes after that code point.
chompCodePt : Str, U32 -> Result Str [ Expected [ ExactCodePt U32 ]* Str ]*
## 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. Here's an
## example where the `Err` branch matches `Integer Signed64`, which causes this to
## parse an [I64] because [I64] is defined as `I64 : Num [ Integer [ Signed64 ] ]`.
##
## >>> when Str.toNum "12345" is
## >>> Ok i64 -> "The I64 was: \(i64)"
## >>> Err (ExpectedNum (Integer Signed64)) -> "Not a valid I64!"
##
## 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 a) [ ExpectedNum a ]*
## 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. Here's an
## example where the `Err` branch matches `Float Binary64`, which causes this to
## parse an [F64] because [F64] is defined as `F64 : Num [ Fraction [ Float64 ] ]`.
##
## >>> when Str.parseNum input {} is
## >>> Ok { val: f64, rest } -> "The F64 was: \(f64)"
## >>> Err (ExpectedNum (Fraction Float64)) -> "Not a valid F64!"
##
## 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 a, rest : Str } [ ExpectedNum a ]*
## 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

@ -1,202 +0,0 @@
use std::ops::Index;
pub const OBJ_PATH: &str = env!(
"BUILTINS_O",
"Env var BUILTINS_O not found. Is there a problem with the build script?"
);
#[derive(Debug, Default)]
pub struct IntrinsicName {
pub options: [&'static str; 14],
}
impl IntrinsicName {
pub const fn default() -> Self {
Self { options: [""; 14] }
}
}
#[repr(u8)]
pub enum DecWidth {
Dec,
}
#[repr(u8)]
pub enum FloatWidth {
F32,
F64,
F128,
}
#[repr(u8)]
pub enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
}
impl Index<DecWidth> for IntrinsicName {
type Output = str;
fn index(&self, _: DecWidth) -> &Self::Output {
self.options[0]
}
}
impl Index<FloatWidth> for IntrinsicName {
type Output = str;
fn index(&self, index: FloatWidth) -> &Self::Output {
match index {
FloatWidth::F32 => self.options[1],
FloatWidth::F64 => self.options[2],
FloatWidth::F128 => self.options[3],
}
}
}
impl Index<IntWidth> for IntrinsicName {
type Output = str;
fn index(&self, index: IntWidth) -> &Self::Output {
match index {
IntWidth::U8 => self.options[4],
IntWidth::U16 => self.options[5],
IntWidth::U32 => self.options[6],
IntWidth::U64 => self.options[7],
IntWidth::U128 => self.options[8],
IntWidth::I8 => self.options[9],
IntWidth::I16 => self.options[10],
IntWidth::I32 => self.options[11],
IntWidth::I64 => self.options[12],
IntWidth::I128 => self.options[13],
}
}
}
#[macro_export]
macro_rules! float_intrinsic {
($name:literal) => {{
let mut output = IntrinsicName::default();
output.options[1] = concat!($name, ".f32");
output.options[2] = concat!($name, ".f64");
output.options[3] = concat!($name, ".f128");
output
}};
}
#[macro_export]
macro_rules! int_intrinsic {
($name:literal) => {{
let mut output = IntrinsicName::default();
output.options[4] = concat!($name, ".i8");
output.options[5] = concat!($name, ".i16");
output.options[6] = concat!($name, ".i32");
output.options[7] = concat!($name, ".i64");
output.options[8] = concat!($name, ".i128");
output.options[9] = concat!($name, ".i8");
output.options[10] = concat!($name, ".i16");
output.options[11] = concat!($name, ".i32");
output.options[12] = concat!($name, ".i64");
output.options[13] = concat!($name, ".i128");
output
}};
}
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
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_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith";
pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_STARTS_WITH_CODE_PT: &str = "roc_builtins.str.starts_with_code_point";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
pub const DICT_LEN: &str = "roc_builtins.dict.len";
pub const DICT_EMPTY: &str = "roc_builtins.dict.empty";
pub const DICT_INSERT: &str = "roc_builtins.dict.insert";
pub const DICT_REMOVE: &str = "roc_builtins.dict.remove";
pub const DICT_CONTAINS: &str = "roc_builtins.dict.contains";
pub const DICT_GET: &str = "roc_builtins.dict.get";
pub const DICT_ELEMENTS_RC: &str = "roc_builtins.dict.elementsRc";
pub const DICT_KEYS: &str = "roc_builtins.dict.keys";
pub const DICT_VALUES: &str = "roc_builtins.dict.values";
pub const DICT_UNION: &str = "roc_builtins.dict.union";
pub const DICT_DIFFERENCE: &str = "roc_builtins.dict.difference";
pub const DICT_INTERSECTION: &str = "roc_builtins.dict.intersection";
pub const DICT_WALK: &str = "roc_builtins.dict.walk";
pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP3: &str = "roc_builtins.list.map3";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
pub const LIST_WALK: &str = "roc_builtins.list.walk";
pub const LIST_WALK_UNTIL: &str = "roc_builtins.list.walkUntil";
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_DROP: &str = "roc_builtins.list.drop";
pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at";
pub const LIST_SWAP: &str = "roc_builtins.list.swap";
pub const LIST_SINGLE: &str = "roc_builtins.list.single";
pub const LIST_JOIN: &str = "roc_builtins.list.join";
pub const LIST_RANGE: &str = "roc_builtins.list.range";
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
pub const DEC_EQ: &str = "roc_builtins.dec.eq";
pub const DEC_NEQ: &str = "roc_builtins.dec.neq";
pub const DEC_NEGATE: &str = "roc_builtins.dec.negate";
pub const DEC_ADD_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow";
pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
pub const DEC_DIV: &str = "roc_builtins.dec.div";
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
[package]
name = "roc_can"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
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" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -1,686 +0,0 @@
use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type};
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
pub typ: Type,
pub introduced_variables: IntroducedVariables,
pub references: MutSet<Symbol>,
pub aliases: SendMap<Symbol, Alias>,
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct IntroducedVariables {
// NOTE on rigids
//
// Rigids must be unique within a type annoation.
// E.g. in `identity : a -> a`, there should only be one
// variable (a rigid one, with name "a").
// Hence `rigids : ImMap<Lowercase, Variable>`
//
// But then between annotations, the same name can occur multiple times,
// but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>,
pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
}
impl IntroducedVariables {
pub fn insert_named(&mut self, name: Lowercase, var: Variable) {
self.var_by_name.insert(name.clone(), var);
self.name_by_var.insert(var, name);
}
pub fn insert_wildcard(&mut self, var: Variable) {
self.wildcards.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.host_exposed_aliases.insert(symbol, var);
}
pub fn union(&mut self, other: &Self) {
self.wildcards.extend(other.wildcards.iter().cloned());
self.var_by_name.extend(other.var_by_name.clone());
self.name_by_var.extend(other.name_by_var.clone());
self.host_exposed_aliases
.extend(other.host_exposed_aliases.clone());
}
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
self.var_by_name.get(name)
}
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
self.name_by_var.get(&var)
}
}
fn malformed(env: &mut Env, region: Region, name: &str) {
use roc_problem::can::RuntimeError::*;
let problem = MalformedTypeName((*name).into(), region);
env.problem(roc_problem::can::Problem::RuntimeError(problem));
}
pub fn canonicalize_annotation(
env: &mut Env,
scope: &mut Scope,
annotation: &roc_parse::ast::TypeAnnotation,
region: Region,
var_store: &mut VarStore,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let typ = can_annotation_help(
env,
annotation,
region,
scope,
var_store,
&mut introduced_variables,
&mut aliases,
&mut references,
);
Annotation {
typ,
introduced_variables,
references,
aliases,
}
}
#[allow(clippy::too_many_arguments)]
fn can_annotation_help(
env: &mut Env,
annotation: &roc_parse::ast::TypeAnnotation,
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
) -> Type {
use roc_parse::ast::TypeAnnotation::*;
match annotation {
Function(argument_types, return_type) => {
let mut args = Vec::new();
for arg in *argument_types {
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
let ret = can_annotation_help(
env,
&return_type.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let closure = Type::Variable(var_store.fresh());
Type::Function(args, Box::new(closure), Box::new(ret))
}
Apply(module_name, ident, type_arguments) => {
let symbol = if module_name.is_empty() {
// Since module_name was empty, this is an unqualified type.
// Look it up in scope!
let ident: Ident = (*ident).into();
match scope.lookup(&ident, region) {
Ok(symbol) => symbol,
Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
return Type::Erroneous(Problem::UnrecognizedIdent(ident));
}
}
} else {
match env.qualified_lookup(module_name, ident, region) {
Ok(symbol) => symbol,
Err(problem) => {
// Either the module wasn't imported, or
// it was imported but it doesn't expose this ident.
env.problem(roc_problem::can::Problem::RuntimeError(problem));
return Type::Erroneous(Problem::UnrecognizedIdent((*ident).into()));
}
}
};
let mut args = Vec::new();
references.insert(symbol);
for arg in *type_arguments {
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
match scope.lookup_alias(symbol) {
Some(alias) => {
// use a known alias
let mut actual = alias.typ.clone();
let mut substitutions = ImMap::default();
let mut vars = Vec::new();
if alias.type_variables.len() != args.len() {
let error = Type::Erroneous(Problem::BadTypeArguments {
symbol,
region,
alias_needs: alias.type_variables.len() as u8,
type_got: args.len() as u8,
});
return error;
}
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
substitutions.insert(var, arg_ann.clone());
vars.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for typ in alias.lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// instantiate variables
actual.substitute(&substitutions);
Type::Alias {
symbol,
type_arguments: vars,
lambda_set_variables,
actual: Box::new(actual),
}
}
None => Type::Apply(symbol, args),
}
}
BoundVariable(v) => {
let name = Lowercase::from(*v);
match introduced_variables.var_by_name(&name) {
Some(var) => Type::Variable(*var),
None => {
let var = var_store.fresh();
introduced_variables.insert_named(name, var);
Type::Variable(var)
}
}
}
As(loc_inner, _spaces, loc_as) => match loc_as.value {
TypeAnnotation::Apply(module_name, ident, loc_vars) if module_name.is_empty() => {
let symbol = match scope.introduce(
ident.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => symbol,
Err((original_region, shadow)) => {
let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
original_region,
shadow,
});
return Type::Erroneous(problem);
}
};
let inner_type = can_annotation_help(
env,
&loc_inner.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let mut vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars = Vec::with_capacity(loc_vars.len());
references.insert(symbol);
for loc_var in loc_vars {
match loc_var.value {
BoundVariable(ident) => {
let var_name = Lowercase::from(ident);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, *var)));
} else {
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), var);
vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, var)));
}
}
_ => {
// If anything other than a lowercase identifier
// appears here, the whole annotation is invalid.
return Type::Erroneous(Problem::CanonicalizationProblem);
}
}
}
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh();
let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
let mut new_arg = arg.clone();
new_arg.substitute_alias(symbol, &Type::Variable(rec_var));
new_args.push(new_arg);
}
new_tags.push((tag_name.clone(), new_args));
}
Type::RecursiveTagUnion(rec_var, new_tags, ext)
} else {
inner_type
};
let mut hidden_variables = MutSet::default();
hidden_variables.extend(alias_actual.variables());
for loc_var in lowercase_vars.iter() {
hidden_variables.remove(&loc_var.value.1);
}
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
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);
Type::HostExposedAlias {
name: symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
actual_var,
}
} else {
Type::Alias {
symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
}
}
}
_ => {
// This is a syntactically invalid type alias.
Type::Erroneous(Problem::CanonicalizationProblem)
}
},
Record { fields, ext, .. } => {
let field_types = can_assigned_fields(
env,
fields,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyRec,
};
Type::Record(field_types, Box::new(ext_type))
}
TagUnion { tags, ext, .. } => {
let tag_types = can_tags(
env,
tags,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
loc_ann.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyTagUnion,
};
Type::TagUnion(tag_types, Box::new(ext_type))
}
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_annotation_help(
env,
nested,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
Wildcard => {
let var = var_store.fresh();
introduced_variables.insert_wildcard(var);
Type::Variable(var)
}
Malformed(string) => {
malformed(env, region, string);
let var = var_store.fresh();
introduced_variables.insert_wildcard(var);
Type::Variable(var)
}
}
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_assigned_fields<'a>(
env: &mut Env,
fields: &&[Located<AssignedField<'a, TypeAnnotation<'a>>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
) -> SendMap<Lowercase, RecordField<Type>> {
use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*;
// SendMap doesn't have a `with_capacity`
let mut field_types = SendMap::default();
// field names we've seen so far in this record
let mut seen = std::collections::HashMap::with_capacity(fields.len());
'outer: for loc_field in fields.iter() {
let mut field = &loc_field.value;
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
// when we find the name of this field, break out of the loop
// with that value, so we can check whether the field name is
// a duplicate
let new_name = 'inner: loop {
match field {
RequiredValue(field_name, _, annotation) => {
let field_type = can_annotation_help(
env,
&annotation.value,
annotation.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), Required(field_type));
break 'inner label;
}
OptionalValue(field_name, _, annotation) => {
let field_type = can_annotation_help(
env,
&annotation.value,
annotation.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), Optional(field_type));
break 'inner label;
}
LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b }
let field_name = Lowercase::from(loc_field_name.value);
let field_type = {
if let Some(var) = introduced_variables.var_by_name(&field_name) {
Type::Variable(*var)
} else {
let field_var = var_store.fresh();
introduced_variables.insert_named(field_name.clone(), field_var);
Type::Variable(field_var)
}
};
field_types.insert(field_name.clone(), Required(field_type));
break 'inner field_name;
}
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
// check the nested field instead
field = nested;
continue 'inner;
}
Malformed(string) => {
malformed(env, region, string);
// completely skip this element, advance to the next tag
continue 'outer;
}
}
};
// ensure that the new name is not already in this record:
// note that the right-most tag wins when there are two with the same name
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) {
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
field_name: new_name,
record_region: region,
field_region: loc_field.region,
replaced_region,
});
}
}
field_types
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_tags<'a>(
env: &mut Env,
tags: &'a [Located<Tag<'a>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
) -> Vec<(TagName, Vec<Type>)> {
let mut tag_types = Vec::with_capacity(tags.len());
// tag names we've seen so far in this tag union
let mut seen = std::collections::HashMap::with_capacity(tags.len());
'outer: for loc_tag in tags.iter() {
let mut tag = &loc_tag.value;
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
// when we find the name of this tag, break out of the loop
// with that value, so we can check whether the tag name is
// a duplicate
let new_name = 'inner: loop {
match tag {
Tag::Global { name, args } => {
let name = name.value.into();
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::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);
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
}
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => {
// check the nested tag instead
tag = nested;
continue 'inner;
}
Tag::Malformed(string) => {
malformed(env, region, string);
// completely skip this element, advance to the next tag
continue 'outer;
}
}
};
// ensure that the new name is not already in this tag union:
// note that the right-most tag wins when there are two with the same name
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) {
env.problem(roc_problem::can::Problem::DuplicateTag {
tag_name: new_name,
tag_region: loc_tag.region,
tag_union_region: region,
replaced_region,
});
}
}
tag_types
}

File diff suppressed because it is too large Load Diff

View File

@ -1,147 +0,0 @@
use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::types::{Category, PatternCategory, Type};
use roc_types::{subs::Variable, types::VariableDetail};
#[derive(Debug, Clone, PartialEq)]
pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region),
Store(Type, Variable, &'static str, u32),
Lookup(Symbol, Expected<Type>, Region),
Pattern(Region, PatternCategory, Type, PExpected<Type>),
True, // Used for things that always unify, e.g. blanks and runtime errors
SaveTheEnvironment,
Let(Box<LetConstraint>),
And(Vec<Constraint>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
pub def_types: SendMap<Symbol, Located<Type>>,
pub defs_constraint: Constraint,
pub ret_constraint: Constraint,
}
// VALIDATE
#[derive(Default, Clone)]
struct Declared {
pub rigid_vars: MutSet<Variable>,
pub flex_vars: MutSet<Variable>,
}
impl Constraint {
pub fn validate(&self) -> bool {
let mut unbound = Default::default();
validate_help(self, &Declared::default(), &mut unbound);
if !unbound.type_variables.is_empty() {
panic!("found unbound type variables {:?}", &unbound.type_variables);
}
if !unbound.lambda_set_variables.is_empty() {
panic!(
"found unbound lambda set variables {:?}",
&unbound.lambda_set_variables
);
}
if !unbound.recursion_variables.is_empty() {
panic!(
"found unbound recursion variables {:?}",
&unbound.recursion_variables
);
}
true
}
pub fn contains_save_the_environment(&self) -> bool {
match self {
Constraint::Eq(_, _, _, _) => false,
Constraint::Store(_, _, _, _) => false,
Constraint::Lookup(_, _, _) => false,
Constraint::Pattern(_, _, _, _) => false,
Constraint::True => false,
Constraint::SaveTheEnvironment => true,
Constraint::Let(boxed) => {
boxed.ret_constraint.contains_save_the_environment()
|| boxed.defs_constraint.contains_save_the_environment()
}
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()),
}
}
}
fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) {
for var in &detail.type_variables {
if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) {
accum.type_variables.insert(*var);
}
}
// lambda set variables are always flex
for var in &detail.lambda_set_variables {
if declared.rigid_vars.contains(var) {
panic!("lambda set variable {:?} is declared as rigid", var);
}
if !declared.flex_vars.contains(var) {
accum.lambda_set_variables.push(*var);
}
}
// recursion vars should be always rigid
for var in &detail.recursion_variables {
if declared.flex_vars.contains(var) {
panic!("recursion variable {:?} is declared as flex", var);
}
if !declared.rigid_vars.contains(var) {
accum.recursion_variables.insert(*var);
}
}
}
fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut VariableDetail) {
use Constraint::*;
match constraint {
True | SaveTheEnvironment | Lookup(_, _, _) => { /* nothing */ }
Store(typ, var, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
if !declared.flex_vars.contains(var) {
accum.type_variables.insert(*var);
}
}
Constraint::Eq(typ, expected, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
Constraint::Pattern(_, _, typ, expected) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
Constraint::Let(letcon) => {
let mut declared = declared.clone();
declared
.rigid_vars
.extend(letcon.rigid_vars.iter().copied());
declared.flex_vars.extend(letcon.flex_vars.iter().copied());
validate_help(&letcon.defs_constraint, &declared, accum);
validate_help(&letcon.ret_constraint, &declared, accum);
}
Constraint::And(inner) => {
for c in inner {
validate_help(c, declared, accum);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,152 +0,0 @@
use crate::procedure::References;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
/// The canonicalization environment for a particular module.
pub struct Env<'a> {
/// The module's path. Private tags and unqualified references to identifiers
/// are assumed to be relative to this path.
pub home: ModuleId,
pub dep_idents: &'a MutMap<ModuleId, IdentIds>,
pub module_ids: &'a ModuleIds,
/// Problems we've encountered along the way, which will be reported to the user at the end.
pub problems: Vec<Problem>,
/// Closures
pub closures: MutMap<Symbol, References>,
/// current tail-callable symbol
pub tailcallable_symbol: Option<Symbol>,
/// current closure name (if any)
pub closure_name_symbol: Option<Symbol>,
/// Symbols which were referenced by qualified lookups.
pub qualified_lookups: MutSet<Symbol>,
pub top_level_symbols: MutSet<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>,
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_lookups: MutSet::default(),
tailcallable_symbol: None,
closure_name_symbol: None,
top_level_symbols: MutSet::default(),
}
}
/// Returns Err if the symbol resolved, but it was not exposed by the given module
pub fn qualified_lookup(
&mut self,
module_name_str: &str,
ident: &str,
region: Region,
) -> Result<Symbol, RuntimeError> {
debug_assert!(
!module_name_str.is_empty(),
"Called env.qualified_lookup with an unqualified ident: {:?}",
ident
);
let module_name = ModuleName::from(module_name_str);
let ident = Ident::from(ident);
match self.module_ids.get_id(&module_name) {
Some(&module_id) => {
// 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) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol);
Ok(symbol)
}
None => Err(RuntimeError::LookupNotInScope(
Located {
value: ident,
region,
},
self.ident_ids
.idents()
.map(|(_, string)| string.as_ref().into())
.collect(),
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol);
Ok(symbol)
}
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
}
}
}
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.module_ids
.available_modules()
.map(|string| string.as_ref().into())
.collect(),
region,
}),
}
}
/// 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,520 +0,0 @@
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::env::Env;
use crate::expr::{Expr, Output};
use crate::operator::desugar_def;
use crate::pattern::Pattern;
use crate::scope::Scope;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias;
#[derive(Debug)]
pub struct Module {
pub module_id: ModuleId,
pub exposed_imports: MutMap<Symbol, Variable>,
pub exposed_symbols: MutSet<Symbol>,
pub references: MutSet<Symbol>,
pub aliases: MutMap<Symbol, Alias>,
pub rigid_variables: MutMap<Variable, Lowercase>,
}
#[derive(Debug)]
pub struct ModuleOutput {
pub aliases: MutMap<Symbol, Alias>,
pub rigid_variables: MutMap<Variable, Lowercase>,
pub declarations: Vec<Declaration>,
pub exposed_imports: MutMap<Symbol, Variable>,
pub lookups: Vec<(Symbol, Variable, Region)>,
pub problems: Vec<Problem>,
pub ident_ids: IdentIds,
pub references: MutSet<Symbol>,
pub scope: Scope,
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a, F>(
arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>],
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: &'a MutMap<ModuleId, IdentIds>,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: &MutSet<Symbol>,
var_store: &mut VarStore,
look_up_builtin: F,
) -> Result<ModuleOutput, RuntimeError>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, var_store);
let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() {
scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
}
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let mut desugared =
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
}));
}
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
let mut lookups = Vec::with_capacity(num_deps);
let mut rigid_variables = MutMap::default();
// Exposed values are treated like defs that appear before any others, e.g.
//
// imports [ Foo.{ bar, baz } ]
//
// ...is basically the same as if we'd added these extra defs at the start of the module:
//
// bar = Foo.bar
// baz = Foo.baz
//
// Here we essentially add those "defs" to "the beginning of the module"
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
for (ident, (symbol, region)) in exposed_imports {
let first_char = ident.as_inline_str().as_str().chars().next().unwrap();
if first_char.is_lowercase() {
// this is a value definition
let expr_var = var_store.fresh();
match scope.import(ident, symbol, region) {
Ok(()) => {
// Add an entry to exposed_imports using the current module's name
// as the key; e.g. if this is the Foo module and we have
// exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
can_exposed_imports.insert(symbol, expr_var);
// This will be used during constraint generation,
// to add the usual Lookup constraint as if this were a normal def.
lookups.push((symbol, expr_var, region));
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
}
}
} else {
// This is a type alias
// the symbol should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol));
// but now we know this symbol by a different identifier, so we still need to add it to
// the scope
match scope.import(ident, symbol, region) {
Ok(()) => {
// here we do nothing special
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
}
}
}
}
let (defs, scope, output, symbols_introduced) = canonicalize_defs(
&mut env,
Output::default(),
var_store,
&scope,
&desugared,
PatternType::TopLevelDef,
);
// See if any of the new idents we defined went unused.
// If any were unused and also not exposed, report it.
for (symbol, region) in symbols_introduced {
if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) {
env.problem(Problem::UnusedDef(symbol, region));
}
}
for (var, lowercase) in output.introduced_variables.name_by_var {
rigid_variables.insert(var, lowercase.clone());
}
for var in output.introduced_variables.wildcards {
rigid_variables.insert(var, "*".into());
}
let mut references = MutSet::default();
// Gather up all the symbols that were referenced across all the defs' lookups.
for symbol in output.references.lookups.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced across all the defs' calls.
for symbol in output.references.calls.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
// assume all exposed symbols are not actually defined in the module
// then as we walk the module and encounter the definitions, remove
// symbols from this set
let mut exposed_but_not_defined = exposed_symbols.clone();
match sort_can_defs(&mut env, defs, Output::default()) {
(Ok(mut declarations), output) => {
use crate::def::Declaration::*;
for decl in declarations.iter() {
match decl {
Declare(def) => {
for (symbol, _) in def.pattern_vars.iter() {
if exposed_but_not_defined.contains(symbol) {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_but_not_defined.remove(symbol);
}
}
}
DeclareRec(defs) => {
for def in defs {
for (symbol, _) in def.pattern_vars.iter() {
if exposed_but_not_defined.contains(symbol) {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_but_not_defined.remove(symbol);
}
}
}
}
InvalidCycle(entries) => {
env.problems.push(Problem::BadRecursion(entries.to_vec()));
}
Builtin(def) => {
// Builtins cannot be exposed in module declarations.
// This should never happen!
debug_assert!(def
.pattern_vars
.iter()
.all(|(symbol, _)| !exposed_but_not_defined.contains(symbol)));
}
}
}
let mut aliases = MutMap::default();
for (symbol, alias) in output.aliases {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_but_not_defined.remove(&symbol);
aliases.insert(symbol, alias);
}
// By this point, all exposed symbols should have been removed from
// exposed_symbols and added to exposed_vars_by_symbol. If any were
// not, that means they were declared as exposed but there was
// no actual declaration with that name!
for symbol in exposed_but_not_defined {
env.problem(Problem::ExposedButNotDefined(symbol));
// In case this exposed value is referenced by other modules,
// create a decl for it whose implementation is a runtime error.
let mut pattern_vars = SendMap::default();
pattern_vars.insert(symbol, var_store.fresh());
let runtime_error = RuntimeError::ExposedButNotDefined(symbol);
let def = Def {
loc_pattern: Located::new(0, 0, 0, 0, Pattern::Identifier(symbol)),
loc_expr: Located::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)),
expr_var: var_store.fresh(),
pattern_vars,
annotation: None,
};
declarations.push(Declaration::Declare(def));
}
// Incorporate any remaining output.lookups entries into references.
for symbol in output.references.lookups {
references.insert(symbol);
}
// Incorporate any remaining output.calls entries into references.
for symbol in output.references.calls {
references.insert(symbol);
}
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
for declaration in declarations.iter_mut() {
match declaration {
Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()),
DeclareRec(defs) => {
fix_values_captured_in_closure_defs(defs, &mut MutSet::default())
}
InvalidCycle(_) | Builtin(_) => {}
}
}
// TODO this loops over all symbols in the module, we can speed it up by having an
// iterator over all builtin symbols
for symbol in references.iter() {
if symbol.is_builtin() {
// this can fail when the symbol is for builtin types, or has no implementation yet
if let Some(def) = look_up_builtin(*symbol, var_store) {
declarations.push(Declaration::Builtin(def));
}
}
}
Ok(ModuleOutput {
scope,
aliases,
rigid_variables,
declarations,
references,
exposed_imports: can_exposed_imports,
problems: env.problems,
lookups,
ident_ids: env.ident_ids,
})
}
(Err(runtime_error), _) => Err(runtime_error),
}
}
fn fix_values_captured_in_closure_def(
def: &mut crate::def::Def,
no_capture_symbols: &mut MutSet<Symbol>,
) {
// patterns can contain default expressions, so much go over them too!
fix_values_captured_in_closure_pattern(&mut def.loc_pattern.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut def.loc_expr.value, no_capture_symbols);
}
fn fix_values_captured_in_closure_defs(
defs: &mut Vec<crate::def::Def>,
no_capture_symbols: &mut MutSet<Symbol>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
no_capture_symbols.extend(crate::pattern::symbols_from_pattern(&def.loc_pattern.value));
}
// TODO mutually recursive functions should both capture the union of both their capture sets
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols);
}
}
fn fix_values_captured_in_closure_pattern(
pattern: &mut crate::pattern::Pattern,
no_capture_symbols: &mut MutSet<Symbol>,
) {
use crate::pattern::Pattern::*;
match pattern {
AppliedTag {
arguments: loc_args,
..
} => {
for (_, loc_arg) in loc_args.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
}
}
RecordDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() {
use crate::pattern::DestructType::*;
match &mut loc_destruct.value.typ {
Required => {}
Optional(_, loc_expr) => {
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols)
}
Guard(_, loc_pattern) => fix_values_captured_in_closure_pattern(
&mut loc_pattern.value,
no_capture_symbols,
),
}
}
}
Identifier(_)
| NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
}
}
fn fix_values_captured_in_closure_expr(
expr: &mut crate::expr::Expr,
no_capture_symbols: &mut MutSet<Symbol>,
) {
use crate::expr::Expr::*;
match expr {
LetNonRec(def, loc_expr, _) => {
// LetNonRec(Box<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
LetRec(defs, loc_expr, _) => {
// LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_defs(defs, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Expect(condition, loc_expr) => {
fix_values_captured_in_closure_expr(&mut condition.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Closure {
captured_symbols,
name,
arguments,
loc_body,
..
} => {
captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s));
captured_symbols.retain(|(s, _)| s != name);
if captured_symbols.is_empty() {
no_capture_symbols.insert(*name);
}
// patterns can contain default expressions, so much go over them too!
for (_, loc_pat) in arguments.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols);
}
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
}
Num(_, _, _)
| Int(_, _, _, _)
| Float(_, _, _, _)
| Str(_)
| Var(_)
| EmptyRecord
| RuntimeError(_)
| Accessor { .. } => {}
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {
fix_values_captured_in_closure_expr(&mut elem.value, no_capture_symbols);
}
}
When {
loc_cond, branches, ..
} => {
fix_values_captured_in_closure_expr(&mut loc_cond.value, no_capture_symbols);
for branch in branches.iter_mut() {
fix_values_captured_in_closure_expr(&mut branch.value.value, no_capture_symbols);
// patterns can contain default expressions, so much go over them too!
for loc_pat in branch.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols);
}
if let Some(guard) = &mut branch.guard {
fix_values_captured_in_closure_expr(&mut guard.value, no_capture_symbols);
}
}
}
If {
branches,
final_else,
..
} => {
for (loc_cond, loc_then) in branches.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_cond.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_then.value, no_capture_symbols);
}
fix_values_captured_in_closure_expr(&mut final_else.value, no_capture_symbols);
}
Call(function, arguments, _) => {
fix_values_captured_in_closure_expr(&mut function.1.value, no_capture_symbols);
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
}
RunLowLevel { args, .. } | ForeignCall { args, .. } => {
for (_, arg) in args.iter_mut() {
fix_values_captured_in_closure_expr(arg, no_capture_symbols);
}
}
Record { fields, .. }
| Update {
updates: fields, ..
} => {
for field in fields.iter_mut() {
fix_values_captured_in_closure_expr(&mut field.loc_expr.value, no_capture_symbols);
}
}
Access { loc_expr, .. } => {
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
}
}
}

View File

@ -1,259 +0,0 @@
use crate::env::Env;
use crate::expr::Expr;
use roc_parse::ast::Base;
use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*;
use roc_problem::can::{FloatErrorKind, IntErrorKind};
use roc_region::all::Region;
use roc_types::subs::VarStore;
use std::i64;
// TODO use rust's integer parsing again
//
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
// There is a nightly API for exposing the parse error.
#[inline(always)]
pub fn num_expr_from_result(
var_store: &mut VarStore,
result: Result<(&str, i64), (&str, IntErrorKind)>,
region: Region,
env: &mut Env,
) -> Expr {
match result {
Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num),
Err((raw, error)) => {
// (Num *) compiles to Int if it doesn't
// get specialized to something else first,
// so use int's overflow bounds here.
let runtime_error = InvalidInt(error, Base::Decimal, region, raw.into());
env.problem(Problem::RuntimeError(runtime_error.clone()));
Expr::RuntimeError(runtime_error)
}
}
}
#[inline(always)]
pub fn int_expr_from_result(
var_store: &mut VarStore,
result: Result<(&str, i128), (&str, IntErrorKind)>,
region: Region,
base: Base,
env: &mut Env,
) -> Expr {
// Int stores a variable to generate better error messages
match result {
Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int),
Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into());
env.problem(Problem::RuntimeError(runtime_error.clone()));
Expr::RuntimeError(runtime_error)
}
}
}
#[inline(always)]
pub fn float_expr_from_result(
var_store: &mut VarStore,
result: Result<(&str, f64), (&str, FloatErrorKind)>,
region: Region,
env: &mut Env,
) -> Expr {
// Float stores a variable to generate better error messages
match result {
Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float),
Err((raw, error)) => {
let runtime_error = InvalidFloat(error, region, raw.into());
env.problem(Problem::RuntimeError(runtime_error.clone()));
Expr::RuntimeError(runtime_error)
}
}
}
#[inline(always)]
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> {
// Ignore underscores.
let radix = 10;
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))
}
#[inline(always)]
pub fn finish_parsing_base(
raw: &str,
base: Base,
is_negative: bool,
) -> Result<i64, (&str, IntErrorKind)> {
let radix = match base {
Base::Hex => 16,
Base::Decimal => 10,
Base::Octal => 8,
Base::Binary => 2,
};
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior
(if is_negative {
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix)
} else {
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
})
.map_err(|e| (raw, e.kind))
}
#[inline(always)]
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
// Ignore underscores.
match raw.replace("_", "").parse::<f64>() {
Ok(float) if float.is_finite() => Ok(float),
Ok(float) => {
if float.is_sign_positive() {
Err((raw, FloatErrorKind::PositiveInfinity))
} else {
Err((raw, FloatErrorKind::NegativeInfinity))
}
}
Err(_) => Err((raw, FloatErrorKind::Error)),
}
}
/// Integer parsing code taken from the rust libcore,
/// pulled in so we can give custom error messages
///
/// The Rust Project is dual-licensed under either Apache 2.0 or MIT,
/// at the user's choice. License information can be found in
/// the LEGAL_DETAILS file in the root directory of this distribution.
///
/// Thanks to the Rust project and its contributors!
trait FromStrRadixHelper: PartialOrd + Copy {
fn min_value() -> Self;
fn max_value() -> Self;
fn from_u32(u: u32) -> Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! doit {
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
#[inline]
fn min_value() -> Self { Self::min_value() }
#[inline]
fn max_value() -> Self { Self::max_value() }
#[inline]
fn from_u32(u: u32) -> Self { u as Self }
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other as Self)
}
})*)
}
// We only need the i64 implementation, but libcore defines
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
doit! { i64 }
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!(
(2..=36).contains(&radix),
"from_str_radix_int: must lie in the range `[2, 36]` - found {}",
radix
);
if src.is_empty() {
return Err(PIE { kind: Empty });
}
let is_signed_ty = T::from_u32(0) > T::min_value();
// all valid digits are ascii, so we will just iterate over the utf8 bytes
// and cast them to chars. .to_digit() will safely return None for anything
// other than a valid ascii digit for the given radix, including the first-byte
// of multi-byte sequences
let src = src.as_bytes();
let (is_positive, digits) = match src[0] {
b'+' => (true, &src[1..]),
b'-' if is_signed_ty => (false, &src[1..]),
_ => (true, src),
};
if digits.is_empty() {
return Err(PIE { kind: Empty });
}
let mut result = T::from_u32(0);
if is_positive {
// The number is positive
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
result = match result.checked_add(x) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
}
} else {
// The number is negative
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
result = match result.checked_sub(x) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
}
}
Ok(result)
}
/// An error which can be returned when parsing an integer.
///
/// This error is used as the error type for the `from_str_radix()` functions
/// on the primitive integer types, such as [`i8::from_str_radix`].
///
/// # Potential causes
///
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
/// in the string e.g., when it is obtained from the standard input.
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
///
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntError {
kind: IntErrorKind,
}
impl ParseIntError {
/// Outputs the detailed cause of parsing an integer failing.
pub fn kind(&self) -> &IntErrorKind {
&self.kind
}
}

View File

@ -1,519 +0,0 @@
use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output};
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use crate::scope::Scope;
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Identifier(Symbol),
AppliedTag {
whole_var: Variable,
ext_var: Variable,
tag_name: TagName,
arguments: Vec<(Variable, Located<Pattern>)>,
},
RecordDestructure {
whole_var: Variable,
ext_var: Variable,
destructs: Vec<Located<RecordDestruct>>,
},
IntLiteral(Variable, Box<str>, i64),
NumLiteral(Variable, Box<str>, i64),
FloatLiteral(Variable, Box<str>, f64),
StrLiteral(Box<str>),
Underscore,
// Runtime Exceptions
Shadowed(Region, Located<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
// parse error patterns
MalformedPattern(MalformedPatternProblem, Region),
}
#[derive(Clone, Debug, PartialEq)]
pub struct RecordDestruct {
pub var: Variable,
pub label: Lowercase,
pub symbol: Symbol,
pub typ: DestructType,
}
#[derive(Clone, Debug, PartialEq)]
pub enum DestructType {
Required,
Optional(Variable, Located<Expr>),
Guard(Variable, Located<Pattern>),
}
pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
let mut symbols = Vec::new();
symbols_from_pattern_help(pattern, &mut symbols);
symbols
}
pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
use Pattern::*;
match pattern {
Identifier(symbol) => {
symbols.push(*symbol);
}
AppliedTag { arguments, .. } => {
for (_, nested) in arguments {
symbols_from_pattern_help(&nested.value, symbols);
}
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
// when a record field has a pattern guard, only symbols in the guard are introduced
if let DestructType::Guard(_, subpattern) = &destruct.value.typ {
symbols_from_pattern_help(&subpattern.value, symbols);
} else {
symbols.push(destruct.value.symbol);
}
}
}
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
Shadowed(_, _) => {}
}
}
pub fn canonicalize_pattern<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
) -> (Output, Located<Pattern>) {
use roc_parse::ast::Pattern::*;
use PatternType::*;
let mut output = Output::default();
let can_pattern = match pattern {
Identifier(name) => match scope.introduce(
(*name).into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
Pattern::Identifier(symbol)
}
Err((original_region, shadow)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
}));
Pattern::Shadowed(original_region, shadow)
}
},
GlobalTag(name) => {
// Canonicalize the tag's name.
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name: TagName::Global((*name).into()),
arguments: vec![],
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
// Canonicalize the tag's name.
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name: TagName::Private(Symbol::new(env.home, ident_id)),
arguments: vec![],
}
}
Apply(tag, patterns) => {
let tag_name = match tag.value {
GlobalTag(name) => TagName::Global(name.into()),
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
TagName::Private(Symbol::new(env.home, ident_id))
}
_ => unreachable!("Other patterns cannot be applied"),
};
let mut can_patterns = Vec::with_capacity(patterns.len());
for loc_pattern in *patterns {
let (new_output, can_pattern) = canonicalize_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
output.union(new_output);
can_patterns.push((var_store.fresh(), can_pattern));
}
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name,
arguments: can_patterns,
}
}
FloatLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_float(str) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region)
}
Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float),
},
ptype => unsupported_pattern(env, ptype, region),
},
Underscore(_) => match pattern_type {
WhenBranch | FunctionArg => Pattern::Underscore,
TopLevelDef | DefExpr => bad_underscore(env, region),
},
NumLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_int(str) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int),
},
ptype => unsupported_pattern(env, ptype, region),
},
NonBase10Literal {
string,
base,
is_negative,
} => match pattern_type {
WhenBranch => match finish_parsing_base(string, *base, *is_negative) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedBase(*base);
malformed_pattern(env, problem, region)
}
Ok(int) => {
let sign_str = if *is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let i = if *is_negative { -int } else { int };
Pattern::IntLiteral(var_store.fresh(), int_str, i)
}
},
ptype => unsupported_pattern(env, ptype, region),
},
StrLiteral(literal) => match pattern_type {
WhenBranch => flatten_str_literal(literal),
ptype => unsupported_pattern(env, ptype, region),
},
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
}
RecordDestructure(patterns) => {
let ext_var = var_store.fresh();
let whole_var = var_store.fresh();
let mut destructs = Vec::with_capacity(patterns.len());
let mut opt_erroneous = None;
for loc_pattern in *patterns {
match loc_pattern.value {
Identifier(label) => {
match scope.introduce(
label.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
destructs.push(Located {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
label: Lowercase::from(label),
symbol,
typ: DestructType::Required,
},
});
}
Err((original_region, shadow)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
}));
// No matter what the other patterns
// are, we're definitely shadowed and will
// get a runtime exception as soon as we
// encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
}
};
}
RequiredField(label, loc_guard) => {
// a guard does not introduce the label into scope!
let symbol = scope.ignore(label.into(), &mut env.ident_ids);
let (new_output, can_guard) = canonicalize_pattern(
env,
var_store,
scope,
pattern_type,
&loc_guard.value,
loc_guard.region,
);
output.union(new_output);
destructs.push(Located {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
label: Lowercase::from(label),
symbol,
typ: DestructType::Guard(var_store.fresh(), can_guard),
},
});
}
OptionalField(label, loc_default) => {
// an optional DOES introduce the label into scope!
match scope.introduce(
label.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => {
let (can_default, expr_output) = canonicalize_expr(
env,
var_store,
scope,
loc_default.region,
&loc_default.value,
);
// an optional field binds the symbol!
output.references.bound_symbols.insert(symbol);
output.union(expr_output);
destructs.push(Located {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
label: Lowercase::from(label),
symbol,
typ: DestructType::Optional(var_store.fresh(), can_default),
},
});
}
Err((original_region, shadow)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
}));
// No matter what the other patterns
// are, we're definitely shadowed and will
// get a runtime exception as soon as we
// encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
}
};
}
_ => unreachable!("Any other pattern should have given a parse error"),
}
}
// If we encountered an erroneous pattern (e.g. one with shadowing),
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
opt_erroneous.unwrap_or(Pattern::RecordDestructure {
whole_var,
ext_var,
destructs,
})
}
RequiredField(_name, _loc_pattern) => {
unreachable!("should have been handled in RecordDestructure");
}
OptionalField(_name, _loc_pattern) => {
unreachable!("should have been handled in RecordDestructure");
}
Malformed(_str) => {
let problem = MalformedPatternProblem::Unknown;
malformed_pattern(env, problem, region)
}
MalformedIdent(_str, problem) => {
let problem = MalformedPatternProblem::BadIdent(*problem);
malformed_pattern(env, problem, region)
}
QualifiedIdentifier { .. } => {
let problem = MalformedPatternProblem::QualifiedIdentifier;
malformed_pattern(env, problem, region)
}
};
(
output,
Located {
region,
value: can_pattern,
},
)
}
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern.
fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region) -> Pattern {
use roc_problem::can::BadPattern;
env.problem(Problem::UnsupportedPattern(
BadPattern::Unsupported(pattern_type),
region,
));
Pattern::UnsupportedPattern(region)
}
fn bad_underscore(env: &mut Env, region: Region) -> Pattern {
use roc_problem::can::BadPattern;
env.problem(Problem::UnsupportedPattern(
BadPattern::UnderscoreInDef,
region,
));
Pattern::UnsupportedPattern(region)
}
/// When we detect a malformed pattern like `3.X` or `0b5`,
/// report it to Env and return an UnsupportedPattern runtime error pattern.
fn malformed_pattern(env: &mut Env, problem: MalformedPatternProblem, region: Region) -> Pattern {
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
problem, region,
)));
Pattern::MalformedPattern(problem, region)
}
pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)>
where
I: Iterator<Item = &'a Located<Pattern>>,
{
let mut answer = Vec::new();
for loc_pattern in loc_patterns {
add_bindings_from_patterns(&loc_pattern.region, &loc_pattern.value, &mut answer);
}
answer
}
/// helper function for idents_from_patterns
fn add_bindings_from_patterns(
region: &Region,
pattern: &Pattern,
answer: &mut Vec<(Symbol, Region)>,
) {
use Pattern::*;
match pattern {
Identifier(symbol) => {
answer.push((*symbol, *region));
}
AppliedTag {
arguments: loc_args,
..
} => {
for (_, loc_arg) in loc_args {
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
}
}
RecordDestructure { destructs, .. } => {
for Located {
region,
value: RecordDestruct { symbol, .. },
} in destructs
{
answer.push((*symbol, *region));
}
}
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
}
}
fn flatten_str_literal(literal: &StrLiteral<'_>) -> Pattern {
use ast::StrLiteral::*;
match literal {
PlainLine(str_slice) => Pattern::StrLiteral((*str_slice).into()),
Line(segments) => flatten_str_lines(&[segments]),
Block(lines) => flatten_str_lines(lines),
}
}
fn flatten_str_lines(lines: &[&[StrSegment<'_>]]) -> Pattern {
use StrSegment::*;
let mut buf = String::new();
for line in lines {
for segment in line.iter() {
match segment {
Plaintext(string) => {
buf.push_str(string);
}
Unicode(loc_digits) => {
todo!("parse unicode digits {:?}", loc_digits);
}
Interpolated(loc_expr) => {
return Pattern::UnsupportedPattern(loc_expr.region);
}
EscapedChar(escaped) => buf.push(unescape_char(escaped)),
}
}
}
Pattern::StrLiteral(buf.into())
}

View File

@ -1,77 +0,0 @@
use crate::expr::Expr;
use crate::pattern::Pattern;
use roc_collections::all::ImSet;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
#[derive(Clone, Debug, PartialEq)]
pub struct Procedure {
pub name: Option<Box<str>>,
pub is_self_tail_recursive: bool,
pub definition: Region,
pub args: Vec<Located<Pattern>>,
pub body: Located<Expr>,
pub references: References,
pub var: Variable,
pub ret_var: Variable,
}
impl Procedure {
pub fn new(
definition: Region,
args: Vec<Located<Pattern>>,
body: Located<Expr>,
references: References,
var: Variable,
ret_var: Variable,
) -> Procedure {
Procedure {
name: None,
is_self_tail_recursive: false,
definition,
args,
body,
references,
var,
ret_var,
}
}
}
/// These are all ordered sets because they end up getting traversed in a graph search
/// to determine how defs should be ordered. We want builds to be reproducible,
/// so it's important that building the same code gives the same order every time!
#[derive(Clone, Debug, Default, PartialEq)]
pub struct References {
pub bound_symbols: ImSet<Symbol>,
pub lookups: ImSet<Symbol>,
pub referenced_aliases: ImSet<Symbol>,
pub calls: ImSet<Symbol>,
}
impl References {
pub fn new() -> References {
Self::default()
}
pub fn union(mut self, other: References) -> Self {
self.lookups = self.lookups.union(other.lookups);
self.calls = self.calls.union(other.calls);
self.bound_symbols = self.bound_symbols.union(other.bound_symbols);
self.referenced_aliases = self.referenced_aliases.union(other.referenced_aliases);
self
}
pub fn union_mut(&mut self, other: References) {
self.lookups.extend(other.lookups);
self.calls.extend(other.calls);
self.bound_symbols.extend(other.bound_symbols);
self.referenced_aliases.extend(other.referenced_aliases);
}
pub fn has_lookup(&self, symbol: Symbol) -> bool {
self.lookups.contains(&symbol)
}
}

View File

@ -1,220 +0,0 @@
use roc_collections::all::{MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
#[derive(Clone, Debug, PartialEq)]
pub struct Scope {
/// All the identifiers in scope, mapped to were they were defined and
/// the Symbol they resolve to.
idents: SendMap<Ident, (Symbol, Region)>,
/// A cache of all the symbols in scope. This makes lookups much
/// faster when checking for unused defs and unused arguments.
symbols: SendMap<Symbol, Region>,
/// The type aliases currently in scope
aliases: SendMap<Symbol, Alias>,
/// The current module being processed. This will be used to turn
/// unqualified idents into Symbols.
home: ModuleId,
}
impl Scope {
pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope {
use roc_types::solved_types::{BuiltinAlias, FreeVars};
let solved_aliases = roc_types::builtin_aliases::aliases();
let mut aliases = SendMap::default();
for (symbol, builtin_alias) in solved_aliases {
let BuiltinAlias { region, vars, typ } = builtin_alias;
let mut free_vars = FreeVars::default();
let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store);
let mut variables = Vec::new();
// make sure to sort these variables to make them line up with the type arguments
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) {
variables.push(Located::at(loc_name.region, (loc_name.value.clone(), var)));
}
let alias = Alias {
region,
typ,
lambda_set_variables: Vec::new(),
recursion_variables: MutSet::default(),
type_variables: variables,
};
aliases.insert(symbol, alias);
}
Scope {
home,
idents: Symbol::default_in_scope(),
symbols: SendMap::default(),
aliases,
}
}
pub fn idents(&self) -> impl Iterator<Item = &(Ident, (Symbol, Region))> {
self.idents.iter()
}
pub fn symbols(&self) -> impl Iterator<Item = &(Symbol, Region)> {
self.symbols.iter()
}
pub fn contains_ident(&self, ident: &Ident) -> bool {
self.idents.contains_key(ident)
}
pub fn contains_symbol(&self, symbol: Symbol) -> bool {
self.symbols.contains_key(&symbol)
}
pub fn num_idents(&self) -> usize {
self.idents.len()
}
pub fn lookup(&self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
match self.idents.get(ident) {
Some((symbol, _)) => Ok(*symbol),
None => Err(RuntimeError::LookupNotInScope(
Located {
region,
value: ident.clone(),
},
self.idents.keys().map(|v| v.as_ref().into()).collect(),
)),
}
}
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
self.aliases.get(&symbol)
}
/// Introduce a new ident to scope.
///
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
pub fn introduce(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (Region, Located<Ident>)> {
match self.idents.get(&ident) {
Some((_, original_region)) => {
let shadow = Located {
value: ident,
region,
};
Err((*original_region, shadow))
}
None => {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// 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()),
};
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Ok(symbol)
}
}
}
/// Ignore an identifier.
///
/// 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);
Symbol::new(self.home, ident_id)
}
/// Import a Symbol from another module into this module's top-level scope.
///
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
pub fn import(
&mut self,
ident: Ident,
symbol: Symbol,
region: Region,
) -> Result<(), (Symbol, Region)> {
match self.idents.get(&ident) {
Some(shadowed) => Err(*shadowed),
None => {
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Ok(())
}
}
}
pub fn add_alias(
&mut self,
name: Symbol,
region: Region,
vars: Vec<Located<(Lowercase, Variable)>>,
typ: Type,
) {
let roc_types::types::VariableDetail {
type_variables,
lambda_set_variables,
recursion_variables,
} = typ.variables_detail();
debug_assert!({
let mut hidden = type_variables;
for loc_var in vars.iter() {
hidden.remove(&loc_var.value.1);
}
if !hidden.is_empty() {
panic!(
"Found unbound type variables {:?} \n in type alias {:?} {:?} : {:?}",
hidden, name, &vars, &typ
)
}
true
});
let lambda_set_variables: Vec<_> = lambda_set_variables
.into_iter()
.map(|v| roc_types::types::LambdaSet(Type::Variable(v)))
.collect();
let alias = Alias {
region,
type_variables: vars,
lambda_set_variables,
recursion_variables,
typ,
};
self.aliases.insert(name, alias);
}
pub fn contains_alias(&mut self, name: Symbol) -> bool {
self.aliases.contains_key(&name)
}
}

View File

@ -1,110 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate roc_can;
extern crate roc_parse;
extern crate roc_region;
mod helpers;
#[cfg(test)]
mod can_inline {
use crate::helpers::{can_expr_with, test_home};
use bumpalo::Bump;
use roc_can::expr::inline_calls;
use roc_can::expr::Expr::{self, *};
use roc_can::scope::Scope;
use roc_types::subs::VarStore;
fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) {
let arena = Bump::new();
let scope = &mut Scope::new(test_home(), var_store);
let actual_out = can_expr_with(&arena, test_home(), input);
let actual = inline_calls(var_store, scope, actual_out.loc_expr.value);
assert_eq!(actual, expected);
}
#[test]
fn inline_empty_record() {
// fn inline_list_len() {
let var_store = &mut VarStore::default();
assert_inlines_to(
indoc!(
r#"
{}
"#
),
EmptyRecord,
var_store,
);
// TODO testing with hardcoded variables is very brittle.
// Should find a better way to test this!
// (One idea would be to traverse both Exprs and zero out all the Variables,
// so they always pass equality.)
// let aliases = SendMap::default();
// assert_inlines_to(
// indoc!(
// r#"
// Int.isZero 5
// "#
// ),
// LetNonRec(
// Box::new(Def {
// loc_pattern: Located {
// region: Region::zero(),
// value: Pattern::Identifier(Symbol::ARG_1),
// },
// pattern_vars: SendMap::default(),
// loc_expr: Located {
// region: Region::new(0, 0, 11, 12),
// value: Num(unsafe { Variable::unsafe_test_debug_variable(7) }, 5),
// },
// expr_var: unsafe { Variable::unsafe_test_debug_variable(8) },
// annotation: None,
// }),
// Box::new(Located {
// region: Region::zero(),
// value: Expr::Call(
// Box::new((
// unsafe { Variable::unsafe_test_debug_variable(138) },
// Located {
// region: Region::zero(),
// value: Expr::Var(Symbol::BOOL_EQ),
// },
// unsafe { Variable::unsafe_test_debug_variable(139) },
// )),
// vec![
// (
// unsafe { Variable::unsafe_test_debug_variable(140) },
// Located {
// region: Region::zero(),
// value: Var(Symbol::ARG_1),
// },
// ),
// (
// unsafe { Variable::unsafe_test_debug_variable(141) },
// Located {
// region: Region::zero(),
// value: Int(
// unsafe { Variable::unsafe_test_debug_variable(137) },
// 0,
// ),
// },
// ),
// ],
// CalledVia::Space,
// ),
// }),
// unsafe { Variable::unsafe_test_debug_variable(198) },
// aliases,
// ),
// var_store,
// )
}
}

View File

@ -1,13 +0,0 @@
[package]
name = "roc_collections"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
wyhash = "0.3"
bumpalo = { version = "3.6.1", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }

View File

@ -1,190 +0,0 @@
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
use roc_can::expected::Expected::{self, *};
use roc_collections::all::SendMap;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::subs::Variable;
use roc_types::types::Category;
use roc_types::types::Reason;
use roc_types::types::Type::{self, *};
#[inline(always)]
pub fn int_literal(
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::IntLiteral;
exists(
vec![num_var],
And(vec![
Eq(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
region,
),
Eq(num_type, expected, Category::Int, region),
]),
)
}
#[inline(always)]
pub fn float_literal(
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::FloatLiteral;
exists(
vec![num_var, precision_var],
And(vec![
Eq(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
),
Eq(num_type, expected, Category::Float, region),
]),
)
}
#[inline(always)]
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
}
#[inline(always)]
pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type {
Type::Apply(symbol, args)
}
#[inline(always)]
pub fn empty_list_type(var: Variable) -> Type {
list_type(Type::Variable(var))
}
#[inline(always)]
pub fn list_type(typ: Type) -> Type {
builtin_type(Symbol::LIST_LIST, vec![typ])
}
#[inline(always)]
pub fn str_type() -> Type {
builtin_type(Symbol::STR_STR, Vec::new())
}
#[inline(always)]
fn builtin_alias(
symbol: Symbol,
type_arguments: Vec<(Lowercase, Type)>,
actual: Box<Type>,
) -> Type {
Type::Alias {
symbol,
type_arguments,
actual,
lambda_set_variables: vec![],
}
}
#[inline(always)]
pub fn num_float(range: Type) -> Type {
builtin_alias(
Symbol::NUM_FLOAT,
vec![("range".into(), range.clone())],
Box::new(num_num(num_floatingpoint(range))),
)
}
#[inline(always)]
pub fn num_floatingpoint(range: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
vec![range.clone()],
)],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(
Symbol::NUM_FLOATINGPOINT,
vec![("range".into(), range)],
Box::new(alias_content),
)
}
#[inline(always)]
pub fn num_binary64() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content))
}
#[inline(always)]
pub fn num_int(range: Type) -> Type {
builtin_alias(
Symbol::NUM_INT,
vec![("range".into(), range.clone())],
Box::new(num_num(num_integer(range))),
)
}
#[inline(always)]
pub fn num_signed64() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content))
}
#[inline(always)]
pub fn num_integer(range: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(
TagName::Private(Symbol::NUM_AT_INTEGER),
vec![range.clone()],
)],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(
Symbol::NUM_INTEGER,
vec![("range".into(), range)],
Box::new(alias_content),
)
}
#[inline(always)]
pub fn num_num(typ: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(
Symbol::NUM_NUM,
vec![("range".into(), typ)],
Box::new(alias_content),
)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,233 +0,0 @@
use crate::expr::constrain_decls;
use roc_builtins::std::StdLib;
use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::def::Declaration;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::solved_types::{FreeVars, SolvedType};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Problem};
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
#[derive(Clone, Debug)]
pub enum ExposedModuleTypes {
Invalid,
Valid(MutMap<Symbol, SolvedType>, MutMap<Symbol, Alias>),
}
pub struct ConstrainedModule {
pub unused_imports: MutMap<ModuleId, Region>,
pub constraint: Constraint,
}
pub fn constrain_module(
aliases: &MutMap<Symbol, Alias>,
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
let mut send_aliases = SendMap::default();
for (symbol, alias) in aliases.iter() {
send_aliases.insert(*symbol, alias.clone());
}
constrain_decls(home, declarations)
}
#[derive(Debug, Clone)]
pub struct Import {
pub loc_symbol: Located<Symbol>,
pub solved_type: SolvedType,
}
pub fn constrain_imported_values(
imports: Vec<Import>,
body_con: Constraint,
var_store: &mut VarStore,
) -> (Vec<Variable>, Constraint) {
use Constraint::*;
let mut def_types = SendMap::default();
let mut rigid_vars = Vec::new();
for import in imports {
let mut free_vars = FreeVars::default();
let loc_symbol = import.loc_symbol;
// an imported symbol can be either an alias or a value
match import.solved_type {
SolvedType::Alias(symbol, _, _, _) if symbol == loc_symbol.value => {
// do nothing, in the future the alias definitions should not be in the list of imported values
}
_ => {
let typ = roc_types::solved_types::to_type(
&import.solved_type,
&mut free_vars,
var_store,
);
def_types.insert(
loc_symbol.value,
Located {
region: loc_symbol.region,
value: typ,
},
);
for (_, var) in free_vars.named_vars {
rigid_vars.push(var);
}
for var in free_vars.wildcards {
rigid_vars.push(var);
}
// Variables can lose their name during type inference. But the unnamed
// variables are still part of a signature, and thus must be treated as rigids here!
for (_, var) in free_vars.unnamed_vars {
rigid_vars.push(var);
}
}
}
}
(
rigid_vars.clone(),
Let(Box::new(LetConstraint {
rigid_vars,
flex_vars: Vec::new(),
def_types,
defs_constraint: True,
ret_constraint: body_con,
})),
)
}
/// Run pre_constrain_imports to get imported_symbols and imported_aliases.
pub fn constrain_imports(
imported_symbols: Vec<Import>,
constraint: Constraint,
var_store: &mut VarStore,
) -> Constraint {
let (_introduced_rigids, constraint) =
constrain_imported_values(imported_symbols, constraint, var_store);
// TODO determine what to do with those rigids
// for var in introduced_rigids {
// output.ftv.insert(var, format!("internal_{:?}", var).into());
// }
constraint
}
pub struct ConstrainableImports {
pub imported_symbols: Vec<Import>,
pub imported_aliases: MutMap<Symbol, Alias>,
pub unused_imports: MutMap<ModuleId, Region>,
}
/// Run this before constraining imports.
///
/// Constraining imports is split into two different functions, because this
/// part of the work needs to be done on the main thread, whereas the rest of it
/// can be done on a different thread.
pub fn pre_constrain_imports(
home: ModuleId,
references: &MutSet<Symbol>,
imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule,
stdlib: &StdLib,
) -> ConstrainableImports {
let mut imported_symbols = Vec::with_capacity(references.len());
let mut imported_aliases = MutMap::default();
let mut unused_imports = imported_modules; // We'll remove these as we encounter them.
// Translate referenced symbols into constraints. We do this on the main
// thread because we need exclusive access to the exposed_types map, in order
// to get the necessary constraint info for any aliases we imported. We also
// resolve builtin types now, so we can use a reference to stdlib instead of
// having to either clone it or recreate it from scratch on the other thread.
for &symbol in references.iter() {
let module_id = symbol.module_id();
// We used this module, so clearly it is not unused!
unused_imports.remove(&module_id);
if module_id.is_builtin() {
// For builtin modules, we create imports from the
// hardcoded builtin map.
match stdlib.types.get(&symbol) {
Some((solved_type, region)) => {
let loc_symbol = Located {
value: symbol,
region: *region,
};
imported_symbols.push(Import {
loc_symbol,
solved_type: solved_type.clone(),
});
}
None => {
let is_valid_alias = stdlib.applies.contains(&symbol)
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|| roc_types::builtin_aliases::aliases().contains_key(&symbol);
if !is_valid_alias {
panic!(
"Could not find {:?} in builtin types {:?} or builtin aliases",
symbol, stdlib.types,
);
}
}
}
} else if module_id != home {
// We already have constraints for our own symbols.
let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up!
let loc_symbol = Located {
value: symbol,
region,
};
match exposed_types.get(&module_id) {
Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => {
// If the exposed value was invalid (e.g. it didn't have
// a corresponding definition), it won't have an entry
// in solved_types
if let Some(solved_type) = solved_types.get(&symbol) {
// TODO should this be a union?
for (k, v) in new_aliases.clone() {
imported_aliases.insert(k, v);
}
imported_symbols.push(Import {
loc_symbol,
solved_type: solved_type.clone(),
});
}
}
Some(ExposedModuleTypes::Invalid) => {
// If that module was invalid, use True constraints
// for everything imported from it.
imported_symbols.push(Import {
loc_symbol,
solved_type: SolvedType::Erroneous(Problem::InvalidModule),
});
}
None => {
panic!(
"Could not find module {:?} in exposed_types {:?}",
module_id, exposed_types
);
}
}
}
}
ConstrainableImports {
imported_symbols,
imported_aliases,
unused_imports,
}
}

View File

@ -1,336 +0,0 @@
use crate::builtins;
use crate::expr::{constrain_expr, Env};
use roc_can::constraint::Constraint;
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, RecordDestruct};
use roc_collections::all::{Index, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)]
pub struct PatternState {
pub headers: SendMap<Symbol, Located<Type>>,
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
}
/// If there is a type annotation, the pattern state headers can be optimized by putting the
/// annotation in the headers. Normally
///
/// x = 4
///
/// Would add `x => <42>` to the headers (i.e., symbol points to a type variable). If the
/// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation(
pattern: &Pattern,
annotation: &Located<Type>,
) -> Option<SendMap<Symbol, Located<Type>>> {
let mut headers = SendMap::default();
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int`
// in such incorrect cases we don't put the full annotation in headers, just a variable, and let
// inference generate a proper error.
let is_structurally_valid = headers_from_annotation_help(pattern, annotation, &mut headers);
if is_structurally_valid {
Some(headers)
} else {
None
}
}
fn headers_from_annotation_help(
pattern: &Pattern,
annotation: &Located<Type>,
headers: &mut SendMap<Symbol, Located<Type>>,
) -> bool {
match pattern {
Identifier(symbol) => {
headers.insert(*symbol, annotation.clone());
true
}
Underscore
| Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
Type::Record(fields, _) => {
for loc_destruct in destructs {
let destruct = &loc_destruct.value;
// NOTE: We ignore both Guard and optionality when
// determining the type of the assigned def (which is what
// gets added to the header here).
//
// For example, no matter whether it's `{ x } = rec` or
// `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases
// the type of `x` within the binding itself is the same.
if let Some(field_type) = fields.get(&destruct.label) {
headers.insert(
destruct.symbol,
Located::at(annotation.region, field_type.clone().into_inner()),
);
} else {
return false;
}
}
true
}
Type::EmptyRec => destructs.is_empty(),
_ => false,
},
AppliedTag {
tag_name,
arguments,
..
} => match annotation.value.shallow_dealias() {
Type::TagUnion(tags, _) => {
if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) {
if !arguments.len() == arg_types.len() {
return false;
}
arguments
.iter()
.zip(arg_types.iter())
.all(|(arg_pattern, arg_type)| {
headers_from_annotation_help(
&arg_pattern.1.value,
&Located::at(annotation.region, arg_type.clone()),
headers,
)
})
} else {
false
}
}
_ => false,
},
}
}
/// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
pub fn constrain_pattern(
env: &Env,
pattern: &Pattern,
region: Region,
expected: PExpected<Type>,
state: &mut PatternState,
) {
match pattern {
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints.
}
Identifier(symbol) => {
state.headers.insert(
*symbol,
Located {
region,
value: expected.get_type(),
},
);
}
NumLiteral(var, _, _) => {
state.vars.push(*var);
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Num,
builtins::num_num(Type::Variable(*var)),
expected,
));
}
IntLiteral(precision_var, _, _) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Int,
builtins::num_int(Type::Variable(*precision_var)),
expected,
));
}
FloatLiteral(precision_var, _, _) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
builtins::num_float(Type::Variable(*precision_var)),
expected,
));
}
StrLiteral(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Str,
builtins::str_type(),
expected,
));
}
RecordDestructure {
whole_var,
ext_var,
destructs,
} => {
state.vars.push(*whole_var);
state.vars.push(*ext_var);
let ext_type = Type::Variable(*ext_var);
let mut field_types: SendMap<Lowercase, RecordField<Type>> = SendMap::default();
for Located {
value:
RecordDestruct {
var,
label,
symbol,
typ,
},
..
} in destructs
{
let pat_type = Type::Variable(*var);
let expected = PExpected::NoExpectation(pat_type.clone());
if !state.headers.contains_key(symbol) {
state
.headers
.insert(*symbol, Located::at(region, pat_type.clone()));
}
let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::PatternGuard,
Type::Variable(*guard_var),
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
));
state.vars.push(*guard_var);
constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state);
RecordField::Demanded(pat_type)
}
DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::PatternDefault,
Type::Variable(*expr_var),
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
));
state.vars.push(*expr_var);
let expr_expected = Expected::ForReason(
Reason::RecordDefaultField(label.clone()),
pat_type.clone(),
loc_expr.region,
);
let expr_con =
constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected);
state.constraints.push(expr_con);
RecordField::Optional(pat_type)
}
DestructType::Required => {
// No extra constraints necessary.
RecordField::Demanded(pat_type)
}
};
field_types.insert(label.clone(), field_type);
state.vars.push(*var);
}
let record_type = Type::Record(field_types, Box::new(ext_type));
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage(std::file!(), std::line!()),
region,
);
let record_con = Constraint::Pattern(
region,
PatternCategory::Record,
Type::Variable(*whole_var),
expected,
);
state.constraints.push(whole_con);
state.constraints.push(record_con);
}
AppliedTag {
whole_var,
ext_var,
tag_name,
arguments,
} => {
let mut argument_types = Vec::with_capacity(arguments.len());
for (index, (pattern_var, loc_pattern)) in arguments.iter().enumerate() {
state.vars.push(*pattern_var);
let pattern_type = Type::Variable(*pattern_var);
argument_types.push(pattern_type.clone());
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index: Index::zero_based(index),
},
pattern_type,
region,
);
constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state);
}
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(Type::TagUnion(
vec![(tag_name.clone(), argument_types)],
Box::new(Type::Variable(*ext_var)),
)),
Category::Storage(std::file!(), std::line!()),
region,
);
let tag_con = Constraint::Pattern(
region,
PatternCategory::Ctor(tag_name.clone()),
Type::Variable(*whole_var),
expected,
);
state.vars.push(*whole_var);
state.vars.push(*ext_var);
state.constraints.push(whole_con);
state.constraints.push(tag_con);
}
}
}

View File

@ -1,22 +0,0 @@
[package]
name = "roc_fmt"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -1,536 +0,0 @@
use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String;
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located;
/// Does an AST node need parens around it?
///
/// Usually not, but there are two cases where it may be required
///
/// 1. In a function type, function types are in parens
///
/// a -> b, c -> d
/// (a -> b), c -> d
///
/// 2. In applications, applications are in brackets
/// This is true in patterns, type annotations and expressions
///
/// Just (Just a)
/// List (List a)
/// reverse (reverse l)
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Parens {
NotNeeded,
InFunctionType,
InApply,
}
/// In an AST node, do we show newlines around it
///
/// Sometimes, we only want to show comments, at other times
/// we also want to show newlines. By default the formatter
/// takes care of inserting newlines, but sometimes the user's
/// newlines are taken into account.
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Newlines {
Yes,
No,
}
pub trait Formattable<'a> {
fn is_multiline(&self) -> bool;
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
self.format(buf, indent);
}
fn format(&self, buf: &mut String<'a>, indent: u16) {
self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
/// A Located formattable value is also formattable
impl<'a, T> Formattable<'a> for Located<T>
where
T: Formattable<'a>,
{
fn is_multiline(&self) -> bool {
self.value.is_multiline()
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
self.value
.format_with_options(buf, parens, newlines, indent)
}
fn format(&self, buf: &mut String<'a>, indent: u16) {
self.value.format(buf, indent)
}
}
macro_rules! format_sequence {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => {
let is_multiline =
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty();
if is_multiline {
let braces_indent = $indent + INDENT;
let item_indent = braces_indent + INDENT;
if ($newline == Newlines::Yes) {
newline($buf, braces_indent);
}
$buf.push($start);
for item in $items.iter() {
match item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
newline($buf, item_indent);
fmt_comments_only(
$buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format($buf, item_indent);
$buf.push(',');
fmt_comments_only(
$buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format($buf, item_indent);
$buf.push(',');
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
newline($buf, item_indent);
sub_expr.format($buf, item_indent);
$buf.push(',');
fmt_comments_only($buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
newline($buf, item_indent);
item.format($buf, item_indent);
$buf.push(',');
}
}
}
fmt_comments_only($buf, $final_comments.iter(), NewlineAt::Top, item_indent);
newline($buf, braces_indent);
$buf.push($end);
} else {
// is_multiline == false
// there is no comment to add
$buf.push($start);
let mut iter = $items.iter().peekable();
while let Some(item) = iter.next() {
$buf.push(' ');
item.format($buf, $indent);
if iter.peek().is_some() {
$buf.push(',');
}
}
if !$items.is_empty() {
$buf.push(' ');
}
$buf.push($end);
}
};
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*;
match self {
// Return whether these spaces contain any Newlines
SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
// "spaces" always contain either a newline or comment, and comments have newlines
true
}
Wildcard | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => {
(&result.value).is_multiline()
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
}
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(),
Record {
fields,
ext,
final_comments: _,
} => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
fields.iter().any(|field| field.value.is_multiline())
}
TagUnion {
tags,
ext,
final_comments: _,
} => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
tags.iter().any(|tag| tag.value.is_multiline())
}
}
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::TypeAnnotation::*;
match self {
Function(arguments, result) => {
let write_parens = parens != Parens::NotNeeded;
if write_parens {
buf.push('(')
}
let mut it = arguments.iter().peekable();
while let Some(argument) = it.next() {
(&argument.value).format_with_options(
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if it.peek().is_some() {
buf.push_str(", ");
}
}
buf.push_str(" -> ");
(&result.value).format_with_options(
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if write_parens {
buf.push(')')
}
}
Apply(_, name, arguments) => {
// NOTE apply is never multiline
let write_parens = parens == Parens::InApply && !arguments.is_empty();
if write_parens {
buf.push('(')
}
buf.push_str(name);
for argument in *arguments {
buf.push(' ');
(&argument.value).format_with_options(
buf,
Parens::InApply,
Newlines::No,
indent,
);
}
if write_parens {
buf.push(')')
}
}
BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'),
TagUnion {
tags,
ext,
final_comments,
} => {
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record {
fields,
ext,
final_comments,
} => {
format_sequence!(
buf,
indent,
'{',
'}',
fields,
final_comments,
newlines,
AssignedField
);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
As(lhs, _spaces, rhs) => {
// TODO use spaces?
lhs.value.format(buf, indent);
buf.push_str(" as ");
rhs.value.format(buf, indent);
}
SpaceBefore(ann, spaces) => {
newline(buf, indent + INDENT);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + INDENT);
ann.format_with_options(buf, parens, Newlines::No, indent)
}
SpaceAfter(ann, spaces) => {
ann.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
// seems like this SpaceAfter is not constructible
// so this branch hasn't be tested. Please add some test if
// this branch is actually reached and remove this dbg_assert.
debug_assert!(false);
}
Malformed(raw) => buf.push_str(raw),
}
}
}
/// Fields are subtly different on the type and term level:
///
/// > type: { x : Int, y : Bool }
/// > term: { x: 100, y: True }
///
/// So we need two instances, each having the specific separator
impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, " ", newlines == Newlines::Yes);
}
}
impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, "", newlines == Newlines::Yes);
}
}
fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedField<'a, T>) -> bool {
use self::AssignedField::*;
match afield {
RequiredValue(_, spaces, ann) | OptionalValue(_, spaces, ann) => {
!spaces.is_empty() || ann.value.is_multiline()
}
LabelOnly(_) => false,
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_assigned_field_help<'a, T>(
zelf: &AssignedField<'a, T>,
buf: &mut String<'a>,
parens: Parens,
indent: u16,
separator_prefix: &str,
is_multiline: bool,
) where
T: Formattable<'a>,
{
use self::AssignedField::*;
match zelf {
RequiredValue(name, spaces, ann) => {
if is_multiline {
newline(buf, indent);
}
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator_prefix);
buf.push_str(": ");
ann.value.format(buf, indent);
}
OptionalValue(name, spaces, ann) => {
if is_multiline {
newline(buf, indent);
}
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator_prefix);
buf.push('?');
ann.value.format(buf, indent);
}
LabelOnly(name) => {
if is_multiline {
newline(buf, indent);
}
buf.push_str(name.value);
}
AssignedField::SpaceBefore(sub_field, spaces) => {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
format_assigned_field_help(
sub_field,
buf,
parens,
indent,
separator_prefix,
is_multiline,
);
}
AssignedField::SpaceAfter(sub_field, spaces) => {
format_assigned_field_help(
sub_field,
buf,
parens,
indent,
separator_prefix,
is_multiline,
);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
Malformed(raw) => {
buf.push_str(raw);
}
}
}
impl<'a> Formattable<'a> for Tag<'a> {
fn is_multiline(&self) -> bool {
use self::Tag::*;
match self {
Global { args, .. } | Private { args, .. } => {
args.iter().any(|arg| (&arg.value).is_multiline())
}
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
let is_multiline = self.is_multiline();
match self {
Tag::Global { name, args } => {
buf.push_str(name.value);
if is_multiline {
let arg_indent = indent + INDENT;
for arg in *args {
newline(buf, arg_indent);
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for arg in *args {
buf.push(' ');
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
}
Tag::Private { name, args } => {
buf.push('@');
buf.push_str(name.value);
if is_multiline {
let arg_indent = indent + INDENT;
for arg in *args {
newline(buf, arg_indent);
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for arg in *args {
buf.push(' ');
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
}
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => unreachable!(),
Tag::Malformed(raw) => buf.push_str(raw),
}
}
}

View File

@ -1,155 +0,0 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, newline, INDENT};
use bumpalo::collections::String;
use roc_parse::ast::{Def, Expr, Pattern};
use roc_region::all::Located;
/// A Located formattable value is also formattable
impl<'a> Formattable<'a> for Def<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Def::*;
match self {
Alias { ann, .. } => ann.is_multiline(),
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.is_multiline() || loc_annotation.is_multiline()
}
Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(),
AnnotatedBody { .. } => true,
Expect(loc_expr) => loc_expr.is_multiline(),
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
}
NotYetImplemented(s) => todo!("{}", s),
}
}
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::Def::*;
match self {
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.format(buf, indent);
if loc_annotation.is_multiline() {
buf.push_str(" :");
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent,
);
} else {
buf.push_str(" : ");
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
}
Alias { name, vars, ann } => {
buf.push_str(name.value);
if vars.is_empty() {
buf.push(' ');
} else {
for var in *vars {
buf.push(' ');
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
}
buf.push_str(" : ");
ann.format(buf, indent)
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
}
Expect(condition) => fmt_expect(buf, condition, self.is_multiline(), indent),
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
} => {
ann_pattern.format(buf, indent);
buf.push_str(" : ");
ann_type.format(buf, indent);
if let Some(comment_str) = comment {
buf.push_str(" # ");
buf.push_str(comment_str.trim());
}
buf.push_str("\n");
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
}
SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
sub_def.format(buf, indent);
}
SpaceAfter(sub_def, spaces) => {
sub_def.format(buf, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
NotYetImplemented(s) => todo!("{}", s),
}
}
}
fn fmt_expect<'a>(
buf: &mut String<'a>,
condition: &'a Located<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
let return_indent = if is_multiline {
indent + INDENT
} else {
indent
};
buf.push_str("expect");
condition.format(buf, return_indent);
}
pub fn fmt_def<'a>(buf: &mut String<'a>, def: &Def<'a>, indent: u16) {
def.format(buf, indent);
}
pub fn fmt_body<'a>(
buf: &mut String<'a>,
pattern: &'a Pattern<'a>,
body: &'a Expr<'a>,
indent: u16,
) {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.push_str(" =");
if body.is_multiline() {
match body {
Expr::SpaceBefore(_, _) => {
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::Record { .. } | Expr::List { .. } => {
newline(buf, indent + INDENT);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
_ => {
buf.push(' ');
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
} else {
buf.push(' ');
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod annotation;
pub mod def;
pub mod expr;
pub mod module;
pub mod pattern;
pub mod spaces;

View File

@ -1,191 +0,0 @@
use crate::spaces::{fmt_spaces, INDENT};
use bumpalo::collections::{String, Vec};
use roc_parse::ast::Module;
use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader};
use roc_region::all::Located;
pub fn fmt_module<'a>(buf: &mut String<'a>, module: &'a Module<'a>) {
match module {
Module::Interface { header } => {
fmt_interface_header(buf, header);
}
Module::App { header } => {
fmt_app_header(buf, header);
}
Module::Platform { header } => {
fmt_platform_header(buf, header);
}
}
}
pub fn fmt_interface_header<'a>(buf: &mut String<'a>, header: &'a InterfaceHeader<'a>) {
let indent = INDENT;
buf.push_str("interface");
// module name
if header.after_interface_keyword.is_empty() {
buf.push(' ');
} else {
fmt_spaces(buf, header.after_interface_keyword.iter(), indent);
}
buf.push_str(header.name.value.as_str());
// exposes
if header.before_exposes.is_empty() {
buf.push(' ');
} else {
fmt_spaces(buf, header.before_exposes.iter(), indent);
}
buf.push_str("exposes");
if header.after_exposes.is_empty() {
buf.push(' ');
} else {
fmt_spaces(buf, header.after_exposes.iter(), indent);
}
fmt_exposes(buf, &header.exposes, indent);
// imports
if header.before_imports.is_empty() {
buf.push(' ');
} else {
fmt_spaces(buf, header.before_imports.iter(), indent);
}
buf.push_str("imports");
if header.after_imports.is_empty() {
buf.push(' ');
} else {
fmt_spaces(buf, header.after_imports.iter(), indent);
}
fmt_imports(buf, &header.imports, indent);
}
pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
let indent = INDENT;
buf.push_str("app");
// imports
buf.push_str("imports");
fmt_spaces(buf, header.before_imports.iter(), indent);
fmt_imports(buf, &header.imports, indent);
fmt_spaces(buf, header.after_imports.iter(), indent);
}
pub fn fmt_platform_header<'a>(_buf: &mut String<'a>, _header: &'a PlatformHeader<'a>) {
todo!("TODO fmt platform header");
}
fn fmt_imports<'a>(
buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ImportsEntry<'a>>>,
indent: u16,
) {
buf.push('[');
if !loc_entries.is_empty() {
buf.push(' ');
}
for (index, loc_entry) in loc_entries.iter().enumerate() {
if index > 0 {
buf.push_str(", ");
}
fmt_imports_entry(buf, &loc_entry.value, indent);
}
if !loc_entries.is_empty() {
buf.push(' ');
}
buf.push(']');
}
fn fmt_exposes<'a>(
buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
indent: u16,
) {
buf.push('[');
if !loc_entries.is_empty() {
buf.push(' ');
}
for (index, loc_entry) in loc_entries.iter().enumerate() {
if index > 0 {
buf.push_str(", ");
}
fmt_exposes_entry(buf, &loc_entry.value, indent);
}
if !loc_entries.is_empty() {
buf.push(' ');
}
buf.push(']');
}
fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a, &'a str>, indent: u16) {
use roc_parse::header::ExposesEntry::*;
match entry {
Exposed(ident) => buf.push_str(ident),
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_exposes_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_exposes_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}
fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>, indent: u16) {
use roc_parse::header::ImportsEntry::*;
match entry {
Module(module, loc_exposes_entries) => {
buf.push_str(module.as_str());
if !loc_exposes_entries.is_empty() {
buf.push_str(".{ ");
for (index, loc_entry) in loc_exposes_entries.iter().enumerate() {
if index > 0 {
buf.push_str(", ");
}
fmt_exposes_entry(buf, &loc_entry.value, indent);
}
buf.push_str(" }");
}
}
Package(_pkg, _name, _entries) => {
todo!("TODO Format imported package");
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_imports_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_imports_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}

View File

@ -1,126 +0,0 @@
use bumpalo::collections::String;
use roc_parse::ast::CommentOrNewline;
/// The number of spaces to indent.
pub const INDENT: u16 = 4;
pub fn newline(buf: &mut String<'_>, indent: u16) {
buf.push('\n');
add_spaces(buf, indent);
}
pub fn add_spaces(buf: &mut String<'_>, spaces: u16) {
for _ in 0..spaces {
buf.push(' ');
}
}
pub fn fmt_spaces<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
// Only ever print two newlines back to back.
// (Two newlines renders as one blank line.)
let mut consecutive_newlines = 0;
let mut iter = spaces.peekable();
let mut encountered_comment = false;
while let Some(space) = iter.next() {
match space {
Newline => {
if !encountered_comment && (consecutive_newlines < 2) {
if iter.peek() == Some(&&Newline) {
buf.push('\n');
} else {
newline(buf, indent);
}
// Don't bother incrementing it if we're already over the limit.
// There's no upside, and it might eventually overflow,
consecutive_newlines += 1;
}
}
LineComment(comment) => {
fmt_comment(buf, comment);
newline(buf, indent);
encountered_comment = true;
}
DocComment(docs) => {
fmt_docs(buf, docs);
newline(buf, indent);
encountered_comment = true;
}
}
}
}
#[derive(Eq, PartialEq, Debug)]
pub enum NewlineAt {
Top,
Bottom,
Both,
None,
}
/// Like format_spaces, but remove newlines and keep only comments.
/// The `new_line_at` argument describes how new lines should be inserted
/// at the beginning or at the end of the block
/// in the case of there is some comment in the `spaces` argument.
pub fn fmt_comments_only<'a, I>(
buf: &mut String<'a>,
spaces: I,
new_line_at: NewlineAt,
indent: u16,
) where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
use NewlineAt::*;
let mut comment_seen = false;
for space in spaces {
match space {
Newline => {}
LineComment(comment) => {
if comment_seen || new_line_at == Top || new_line_at == Both {
newline(buf, indent);
}
fmt_comment(buf, comment);
comment_seen = true;
}
DocComment(docs) => {
if comment_seen || new_line_at == Top || new_line_at == Both {
newline(buf, indent);
}
fmt_docs(buf, docs);
comment_seen = true;
}
}
}
if comment_seen && (new_line_at == Bottom || new_line_at == Both) {
newline(buf, indent);
}
}
fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str) {
buf.push('#');
if !comment.starts_with(' ') {
buf.push(' ');
}
buf.push_str(comment);
}
fn fmt_docs<'a>(buf: &mut String<'a>, docs: &'a str) {
buf.push_str("##");
if !docs.starts_with(' ') {
buf.push(' ');
}
buf.push_str(docs);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
[package]
name = "roc_gen_dev"
description = "The development backend for the Roc compiler"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
target-lexicon = "0.12.2"
libloading = "0.6"
object = { version = "0.24", features = ["write"] }
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
tempfile = "3.1.0"
itertools = "0.9"
[features]
target-aarch64 = ["roc_build/target-aarch64"]

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