mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-11 10:55:31 +03:00
Merge remote-tracking branch 'origin/trunk' into build-nix
This commit is contained in:
commit
9a3998a10b
37
.cargo/config
Normal file
37
.cargo/config
Normal 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"
|
13
.github/workflows/benchmarks.yml
vendored
13
.github/workflows/benchmarks.yml
vendored
@ -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:
|
||||
|
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
37
.github/workflows/nightly_macos_apple_silicon.yml
vendored
Normal file
37
.github/workflows/nightly_macos_apple_silicon.yml
vendored
Normal 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
|
||||
|
52
.github/workflows/nightly_macos_x86_64.yml
vendored
Normal file
52
.github/workflows/nightly_macos_x86_64.yml
vendored
Normal 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
23
.github/workflows/nix_linux_x86_64.yml
vendored
Normal 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
|
33
.github/workflows/nix_macos_apple_silicon.yml
vendored
Normal file
33
.github/workflows/nix_macos_apple_silicon.yml
vendored
Normal 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
|
6
.github/workflows/spellcheck.yml
vendored
6
.github/workflows/spellcheck.yml
vendored
@ -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
21
.github/workflows/stale.yml
vendored
Normal 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 haven’t had activity in 60 days. This PR hasn’t 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'
|
2
.github/workflows/www.yml
vendored
2
.github/workflows/www.yml
vendored
@ -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
15
.gitignore
vendored
@ -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
|
||||
|
8
.reuse/dep5
Normal file
8
.reuse/dep5
Normal 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
44
AUTHORS
@ -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>
|
||||
|
@ -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
44
CODE_OF_CONDUCT.md
Normal 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
|
@ -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
4207
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
101
Cargo.toml
101
Cargo.toml
@ -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.
|
||||
#
|
||||
|
@ -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
108
Earthfile
@ -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
405
FAQ.md
Normal 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.
|
249
LEGAL_DETAILS
249
LEGAL_DETAILS
@ -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.
|
55
README.md
55
README.md
@ -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/)
|
||||
|
||||
[<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
1978
TUTORIAL.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -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),
|
||||
}
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
@ -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
|
||||
),
|
||||
}
|
||||
}
|
@ -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"#
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
61
ci/bench-runner/Cargo.lock
generated
61
ci/bench-runner/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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")]
|
||||
|
@ -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
3
ci/package_release.sh
Executable 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
3
ci/write_version.sh
Executable 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
236
cli/Cargo.lock
generated
@ -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"
|
@ -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
|
||||
|
@ -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
414
cli/src/build.rs
414
cli/src/build.rs
@ -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))
|
||||
}
|
524
cli/src/lib.rs
524
cli/src/lib.rs
@ -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(()),
|
||||
}
|
||||
}
|
||||
}
|
183
cli/src/main.rs
183
cli/src/main.rs
@ -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\"`!");
|
||||
}
|
248
cli/src/repl.rs
248
cli/src/repl.rs
@ -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")),
|
||||
})
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1109
cli/src/repl/eval.rs
1109
cli/src/repl/eval.rs
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
3
cli/tests/fixtures/.gitignore
vendored
3
cli/tests/fixtures/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
app
|
||||
*.o
|
||||
*.dSYM
|
4
cli/tests/fixtures/multi-dep-str/Dep1.roc
vendored
4
cli/tests/fixtures/multi-dep-str/Dep1.roc
vendored
@ -1,4 +0,0 @@
|
||||
interface Dep1 exposes [ str1 ] imports [ Dep2 ]
|
||||
|
||||
str1 : Str
|
||||
str1 = Dep2.str2
|
4
cli/tests/fixtures/multi-dep-str/Dep2.roc
vendored
4
cli/tests/fixtures/multi-dep-str/Dep2.roc
vendored
@ -1,4 +0,0 @@
|
||||
interface Dep2 exposes [ str2 ] imports []
|
||||
|
||||
str2 : Str
|
||||
str2 = "I am Dep2.str2"
|
7
cli/tests/fixtures/multi-dep-str/Main.roc
vendored
7
cli/tests/fixtures/multi-dep-str/Main.roc
vendored
@ -1,7 +0,0 @@
|
||||
app "multi-dep-str"
|
||||
packages { base: "platform" }
|
||||
imports [ Dep1 ]
|
||||
provides [ main ] to base
|
||||
|
||||
main : Str
|
||||
main = Dep1.str1
|
@ -1,10 +0,0 @@
|
||||
platform examples/multi-module
|
||||
requires {}{ main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
4
cli/tests/fixtures/multi-dep-thunk/Dep1.roc
vendored
4
cli/tests/fixtures/multi-dep-thunk/Dep1.roc
vendored
@ -1,4 +0,0 @@
|
||||
interface Dep1 exposes [ value1 ] imports [ Dep2 ]
|
||||
|
||||
value1 : {} -> Str
|
||||
value1 = \_ -> Dep2.value2 {}
|
7
cli/tests/fixtures/multi-dep-thunk/Main.roc
vendored
7
cli/tests/fixtures/multi-dep-thunk/Main.roc
vendored
@ -1,7 +0,0 @@
|
||||
app "multi-dep-thunk"
|
||||
packages { base: "platform" }
|
||||
imports [ Dep1 ]
|
||||
provides [ main ] to base
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
@ -1,10 +0,0 @@
|
||||
platform examples/multi-dep-thunk
|
||||
requires {}{ main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
@ -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);
|
||||
}
|
@ -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
|
||||
// }
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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"]
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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] });
|
||||
}
|
@ -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,
|
||||
};
|
Binary file not shown.
@ -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)
|
||||
// }
|
@ -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
|
@ -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
|
@ -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
|
@ -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 ] }
|
||||
# }
|
@ -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
@ -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"
|
@ -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
@ -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
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
// )
|
||||
}
|
||||
}
|
@ -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" ] }
|
@ -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
@ -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,
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
@ -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
Loading…
Reference in New Issue
Block a user