mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Merge remote-tracking branch 'origin/trunk' into case-multi-patterns
This commit is contained in:
commit
a4e238b09c
47 changed files with 5116 additions and 1917 deletions
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
|
@ -8,6 +8,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Install LLVM
|
||||
run: sudo ./ci/install-llvm.sh 8
|
||||
|
||||
|
@ -20,6 +21,22 @@ jobs:
|
|||
|
||||
- run: rustup component add rustfmt
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: cargo fmt --check
|
||||
with:
|
||||
|
|
214
Cargo.lock
generated
214
Cargo.lock
generated
|
@ -39,6 +39,11 @@ name = "bumpalo"
|
|||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.5.3"
|
||||
|
@ -62,6 +67,105 @@ dependencies = [
|
|||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-frontend 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bforest"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-bforest 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-codegen-meta 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-codegen-shared 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-meta"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-codegen-shared 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-shared"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-entity"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-frontend"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-module"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-native"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-simplejit"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-module 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-native 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difference"
|
||||
version = "2.0.0"
|
||||
|
@ -81,6 +185,25 @@ dependencies = [
|
|||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.1.9"
|
||||
|
@ -178,6 +301,11 @@ dependencies = [
|
|||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.5"
|
||||
|
@ -300,6 +428,14 @@ dependencies = [
|
|||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
|
@ -556,6 +692,16 @@ dependencies = [
|
|||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "7.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
|
@ -585,11 +731,26 @@ name = "regex-syntax"
|
|||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "region"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-module 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cranelift-simplejit 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"im 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"im-rc 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -603,10 +764,19 @@ dependencies = [
|
|||
"pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quickcheck 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quickcheck_macros 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wyhash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[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 = "scopeguard"
|
||||
version = "1.0.0"
|
||||
|
@ -664,6 +834,29 @@ dependencies = [
|
|||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.6"
|
||||
|
@ -742,13 +935,26 @@ dependencies = [
|
|||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum bitmaps 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81e039a80914325b37fde728ef7693c212f0ac913d5599607d7b95a9484aae0b"
|
||||
"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
"checksum bytes 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10004c15deb332055f7a4a208190aed362cf9a7c2f6ab70a305fba50e1105f38"
|
||||
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum cranelift 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f3b64a6eff63e7ea2dc39ecd36fa43f9f790c8c81ad802748bf51981409701"
|
||||
"checksum cranelift-bforest 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56aa72ef104c5d634f2f9e84ef2c47e116c1d185fae13f196b97ca84b0a514f1"
|
||||
"checksum cranelift-codegen 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "460b9d20793543599308d22f5a1172c196e63a780c4e9aacb0b3f4f63d63ffe1"
|
||||
"checksum cranelift-codegen-meta 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc70e4e8ccebd53a4f925147def857c9e9f7fe0fdbef4bb645a420473e012f50"
|
||||
"checksum cranelift-codegen-shared 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3992000be4d18df0fe332b7c42c120de896e8ec54cd7b6cfa050910a8c9f6e2f"
|
||||
"checksum cranelift-entity 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "722957e05064d97a3157bf0976deed0f3e8ee4f8a4ce167a7c724ca63a4e8bd9"
|
||||
"checksum cranelift-frontend 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13051964302dc7948e8869735de42591559ea55e319b9b92da5b38f8e6a75cb7"
|
||||
"checksum cranelift-module 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1632d5670f0b02ce967a385b859b7af9593485f6e198ebf0970fc8b0f8f9841"
|
||||
"checksum cranelift-native 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21398a0bc6ba389ea86964ac4a495426dd61080f2ddd306184777a8560fe9976"
|
||||
"checksum cranelift-simplejit 0.52.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1236096fd657486d6ef35c8958a681f3f1377d42d7dc371ef9c7193e65c07521"
|
||||
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
|
||||
"checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e"
|
||||
"checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
|
||||
"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
@ -761,6 +967,7 @@ dependencies = [
|
|||
"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
|
||||
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
|
||||
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
|
||||
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
"checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7"
|
||||
"checksum im 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b047368c60cde48aa2b2e2fa4794eec965526749b60b55f8cec1bad926e7f6e9"
|
||||
"checksum im-rc 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed5135086ffe74654d797c02fd673c4046cdb7f552c98f1b1aa6851d6572f84f"
|
||||
|
@ -774,6 +981,7 @@ dependencies = [
|
|||
"checksum llvm-sys 80.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cf2969773884a5701f0c255e2a14d48d4522a66db898ec1088cb21879a228377"
|
||||
"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
|
||||
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
|
||||
|
@ -805,10 +1013,13 @@ dependencies = [
|
|||
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
|
||||
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||
"checksum rand_xoshiro 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
|
||||
"checksum raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
|
||||
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
|
||||
"checksum region 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "448e868c6e4cfddfa49b6a72c95906c04e8547465e9536575b95c70a4044f856"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
||||
"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"
|
||||
|
@ -817,6 +1028,9 @@ dependencies = [
|
|||
"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4"
|
||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
"checksum syn 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de"
|
||||
"checksum target-lexicon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4c118a7a38378f305a9e111fcb2f7f838c0be324bfb31a77ea04f7f6e684b4"
|
||||
"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e"
|
||||
"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum tokio 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1bef565a52394086ecac0a6fa3b8ace4cb3a138ee1d96bd2b93283b56824e3"
|
||||
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||
|
|
|
@ -22,9 +22,14 @@ inlinable_string = "0.1.0"
|
|||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" }
|
||||
futures = "0.3"
|
||||
lazy_static = "1.4"
|
||||
target-lexicon = "0.9" # NOTE: we must use the same version of target-lexicon as cranelift!
|
||||
cranelift = "0.52" # All cranelift crates should have the same version!
|
||||
cranelift-simplejit = "0.52" # All cranelift crates should have the same version!
|
||||
cranelift-module = "0.52" # All cranelift crates should have the same version!
|
||||
cranelift-codegen = "0.52" # All cranelift crates should have the same version!
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
pretty_assertions = "0.5.1 "
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -444,6 +444,59 @@ It's not even syntactically possible for me to expose the `@UserId` tag, because
|
|||
> Even trying to use `==` on them would be a type mismatch, because I would be comparing
|
||||
> a `[ UserId.@UserId Int ]*` with a `[ Main.@UserId Int ]*`, which are incompatible.
|
||||
|
||||
## Phantom Types
|
||||
|
||||
[Phantom types](https://medium.com/@ckoster22/advanced-types-in-elm-phantom-types-808044c5946d)
|
||||
exist in Elm but not in Roc. This is because phantom types can't be defined
|
||||
using type aliases (in fact, there is a custom error message in Elm if you
|
||||
try to do this), and Roc only has type aliases. However, in Roc, you can achieve
|
||||
the same API and runtime performance characteristics as if you had phantom types,
|
||||
only using *phantom values* instead.
|
||||
|
||||
A phantom value is one which affects types, but which is never read at runtime.
|
||||
(It is similar to Rust's [`PhantomData`](https://doc.rust-lang.org/std/marker/struct.PhantomData.html)
|
||||
but not quite the same.)
|
||||
For example, let's say I wanted to define a units library - a classic example
|
||||
of phantom types. I could do that like this:
|
||||
|
||||
```
|
||||
Quantity units : [ @Quantity Float units ]
|
||||
|
||||
km : Float -> Quantity [ Km ]
|
||||
km = \quantity -> @Number quantity Km
|
||||
|
||||
cm : Float -> Quantity [ Cm ]
|
||||
cm = \quantity -> @Number quantity Cm
|
||||
|
||||
mm : Float -> Quantity [ Mm ]
|
||||
mm = \quantity -> @Number quantity Mm
|
||||
```
|
||||
|
||||
Because `@Quantity` is a private tag, `Quantity` is opaque, meaning nobody
|
||||
outside this module can instantiate one. That in turn means that the only
|
||||
way other modules can get a `Quantity [ Km ]` value is by calling this
|
||||
`km` function. Phantom types can do this too; the usual benefit they bring
|
||||
is that `Quantity` would only have `units` in its type, not in its value,
|
||||
which in turn means you wouldn't be carrying the value around at runtime.
|
||||
|
||||
In Roc, this is also true. Because the `units` value in `@Quantity Float units`
|
||||
is never read, it gets optimized away during code generation -
|
||||
as if it had never been defined at all. However, the fact that it *was*
|
||||
defined means it was present during type-checking, which was the whole goal.
|
||||
|
||||
The above `Quantity` unit would compile to a plain `Float` representation
|
||||
at runtime, because `units` gets optimized away as phantom, and the
|
||||
remaining `[ @Quantity Float ]` gets unboxed to `Float`.
|
||||
|
||||
> Phantom values are not quite the same as unused fields, because they
|
||||
> affect types. If we had `[ @Quantity Float Str ]` and the `Str` field
|
||||
> got set, but never read, it would generate a compiler warning for
|
||||
> being unused, becuase it is dead code that has no impact on anything.
|
||||
>
|
||||
> In contrast, phantom values - those which are set but never read, *and*
|
||||
> which affect types depending on which value is set, do not generate
|
||||
> warnings because they have exactly this useful purpose.
|
||||
|
||||
## Modules and Shadowing
|
||||
|
||||
In Elm, my main module (where `main` lives) might begin like this:
|
||||
|
|
|
@ -73,26 +73,25 @@ fn can_annotation_help(
|
|||
Type::Variable(var)
|
||||
}
|
||||
}
|
||||
Record(fields) => {
|
||||
Record { fields, ext } => {
|
||||
let mut field_types = SendMap::default();
|
||||
for field in fields {
|
||||
|
||||
for field in fields.iter() {
|
||||
can_assigned_field(&field.value, var_store, rigids, &mut field_types);
|
||||
}
|
||||
|
||||
// This is a closed record, so the fragment must be {}
|
||||
let fragment_type = Type::EmptyRec;
|
||||
let ext_type = match ext {
|
||||
Some(loc_ann) => can_annotation_help(&loc_ann.value, var_store, rigids),
|
||||
None => Type::EmptyRec,
|
||||
};
|
||||
|
||||
Type::Record(field_types, Box::new(fragment_type))
|
||||
Type::Record(field_types, Box::new(ext_type))
|
||||
}
|
||||
RecordFragment(fields, fragment) => {
|
||||
let mut field_types = SendMap::default();
|
||||
for field in fields {
|
||||
can_assigned_field(&field.value, var_store, rigids, &mut field_types);
|
||||
}
|
||||
|
||||
let fragment_type = can_annotation_help(&fragment.value, var_store, rigids);
|
||||
|
||||
Type::Record(field_types, Box::new(fragment_type))
|
||||
TagUnion { tags, ext } => {
|
||||
panic!(
|
||||
"TODO canonicalize tag union annotation: {:?} {:?}",
|
||||
tags, ext
|
||||
);
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||
can_annotation_help(nested, var_store, rigids)
|
||||
|
@ -118,11 +117,6 @@ fn can_assigned_field<'a>(
|
|||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label, field_type);
|
||||
}
|
||||
OptionalField(field_name, _, annotation) => {
|
||||
let field_type = can_annotation_help(&annotation.value, var_store, rigids);
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label, field_type);
|
||||
}
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
let field_name = Lowercase::from(loc_field_name.value);
|
||||
|
|
|
@ -46,16 +46,22 @@ pub enum Expr {
|
|||
symbol_for_lookup: Symbol,
|
||||
resolved_symbol: Symbol,
|
||||
},
|
||||
// Pattern Matching
|
||||
/// When is guaranteed to be exhaustive at this point. (If it wasn't, then
|
||||
/// a _ branch was added at the end that will throw a runtime error.)
|
||||
/// Also, `If` is desugared into `When` matching on `False` and `_` at this point.
|
||||
// Branching
|
||||
When {
|
||||
cond_var: Variable,
|
||||
expr_var: Variable,
|
||||
loc_cond: Box<Located<Expr>>,
|
||||
branches: Vec<(Located<Pattern>, Located<Expr>)>,
|
||||
},
|
||||
If {
|
||||
cond_var: Variable,
|
||||
branch_var: Variable,
|
||||
loc_cond: Box<Located<Expr>>,
|
||||
loc_then: Box<Located<Expr>>,
|
||||
loc_else: Box<Located<Expr>>,
|
||||
},
|
||||
|
||||
// Let
|
||||
LetRec(Vec<Def>, Box<Located<Expr>>, Variable),
|
||||
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable),
|
||||
|
||||
|
@ -554,8 +560,40 @@ pub fn canonicalize_expr(
|
|||
Output::default(),
|
||||
)
|
||||
}
|
||||
ast::Expr::If(_)
|
||||
| ast::Expr::MalformedIdent(_)
|
||||
ast::Expr::If((cond, then_branch, else_branch)) => {
|
||||
let (loc_cond, mut output) =
|
||||
canonicalize_expr(env, var_store, scope, cond.region, &cond.value);
|
||||
let (loc_then, then_output) = canonicalize_expr(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
then_branch.region,
|
||||
&then_branch.value,
|
||||
);
|
||||
let (loc_else, else_output) = canonicalize_expr(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
else_branch.region,
|
||||
&else_branch.value,
|
||||
);
|
||||
|
||||
output.references = output.references.union(then_output.references);
|
||||
output.references = output.references.union(else_output.references);
|
||||
|
||||
(
|
||||
If {
|
||||
cond_var: var_store.fresh(),
|
||||
branch_var: var_store.fresh(),
|
||||
loc_cond: Box::new(loc_cond),
|
||||
loc_then: Box::new(loc_then),
|
||||
loc_else: Box::new(loc_else),
|
||||
},
|
||||
output,
|
||||
)
|
||||
}
|
||||
|
||||
ast::Expr::MalformedIdent(_)
|
||||
| ast::Expr::MalformedClosure
|
||||
| ast::Expr::PrecedenceConflict(_, _, _) => {
|
||||
panic!(
|
||||
|
@ -973,8 +1011,6 @@ fn canonicalize_field<'a>(
|
|||
)
|
||||
}
|
||||
|
||||
OptionalField(_, _, _) => panic!("invalid in expressions"),
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(_) => {
|
||||
panic!("Somehow a LabelOnly record field was not desugared!");
|
||||
|
|
|
@ -298,7 +298,6 @@ fn desugar_field<'a>(
|
|||
spaces,
|
||||
desugar_expr(arena, loc_expr),
|
||||
),
|
||||
OptionalField(_, _, _) => panic!("invalid in expressions"),
|
||||
LabelOnly(loc_str) => {
|
||||
// Desugar { x } into { x: x }
|
||||
let loc_expr = Located {
|
||||
|
|
|
@ -17,7 +17,7 @@ use im_rc::Vector;
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Pattern {
|
||||
Identifier(Symbol),
|
||||
AppliedTag(Variable, Symbol, Vec<Located<Pattern>>),
|
||||
AppliedTag(Variable, Symbol, Vec<(Variable, Located<Pattern>)>),
|
||||
IntLiteral(i64),
|
||||
FloatLiteral(f64),
|
||||
StrLiteral(Box<str>),
|
||||
|
@ -94,6 +94,32 @@ pub fn canonicalize_pattern<'a>(
|
|||
vec![],
|
||||
)
|
||||
}
|
||||
&Apply(tag, patterns) => {
|
||||
let mut can_patterns = Vec::with_capacity(patterns.len());
|
||||
for loc_pattern in *patterns {
|
||||
can_patterns.push((
|
||||
var_store.fresh(),
|
||||
canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
shadowable_idents,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let symbol = match tag.value {
|
||||
GlobalTag(name) => Symbol::from_global_tag(name),
|
||||
PrivateTag(name) => Symbol::from_private_tag(&env.home, name),
|
||||
_ => unreachable!("Other patterns cannot be applied"),
|
||||
};
|
||||
|
||||
Pattern::AppliedTag(var_store.fresh(), symbol, can_patterns)
|
||||
}
|
||||
|
||||
&FloatLiteral(ref string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
let float = finish_parsing_float(string)
|
||||
|
@ -344,13 +370,10 @@ pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol,
|
|||
QualifiedIdentifier(_name) => {
|
||||
panic!("TODO implement QualifiedIdentifier pattern in remove_idents.");
|
||||
}
|
||||
Apply(_, _) => {
|
||||
panic!("TODO implement Apply pattern in remove_idents.");
|
||||
// AppliedVariant(_, Some(loc_args)) => {
|
||||
// for loc_arg in loc_args {
|
||||
// remove_idents(loc_arg.value, idents);
|
||||
// }
|
||||
// }
|
||||
Apply(_, patterns) => {
|
||||
for loc_pattern in *patterns {
|
||||
remove_idents(&loc_pattern.value, idents);
|
||||
}
|
||||
}
|
||||
RecordDestructure(patterns) => {
|
||||
for loc_pattern in patterns {
|
||||
|
@ -410,16 +433,10 @@ fn add_idents_from_pattern<'a>(
|
|||
QualifiedIdentifier(_name) => {
|
||||
panic!("TODO implement QualifiedIdentifier pattern.");
|
||||
}
|
||||
Apply(_, _) => {
|
||||
panic!("TODO implement Apply pattern.");
|
||||
// &AppliedVariant(_, ref opt_loc_args) => match opt_loc_args {
|
||||
// &None => (),
|
||||
// &Some(ref loc_args) => {
|
||||
// for loc_arg in loc_args.iter() {
|
||||
// add_idents_from_pattern(loc_arg, scope, answer);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
Apply(_tag, patterns) => {
|
||||
for loc_pattern in *patterns {
|
||||
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||
}
|
||||
}
|
||||
|
||||
RecordDestructure(patterns) => {
|
||||
|
|
|
@ -48,7 +48,7 @@ fn number_literal_type(module_name: &str, type_name: &str) -> Type {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
|
||||
pub fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
|
||||
Type::Apply {
|
||||
module_name: module_name.into(),
|
||||
name: type_name.into(),
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::constrain::builtins::{
|
|||
use crate::constrain::pattern::{constrain_pattern, PatternState};
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
use crate::types::AnnotationSource::*;
|
||||
use crate::types::AnnotationSource::{self, *};
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::PReason;
|
||||
|
@ -297,6 +297,89 @@ pub fn constrain_expr(
|
|||
]),
|
||||
)
|
||||
}
|
||||
|
||||
If {
|
||||
cond_var,
|
||||
branch_var,
|
||||
loc_cond,
|
||||
loc_then,
|
||||
loc_else,
|
||||
} => {
|
||||
// TODO use Bool alias here, so we don't allocate this type every time
|
||||
let bool_type = Type::TagUnion(
|
||||
vec![("True".into(), vec![]), ("False".into(), vec![])],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
);
|
||||
let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region);
|
||||
|
||||
let cond_con = Eq(Type::Variable(*cond_var), expect_bool, loc_cond.region);
|
||||
|
||||
match expected {
|
||||
FromAnnotation(name, arity, _, tipe) => {
|
||||
let then_con = constrain_expr(
|
||||
rigids,
|
||||
loc_then.region,
|
||||
&loc_then.value,
|
||||
FromAnnotation(
|
||||
name.clone(),
|
||||
arity,
|
||||
AnnotationSource::TypedIfBranch(0),
|
||||
tipe.clone(),
|
||||
),
|
||||
);
|
||||
let else_con = constrain_expr(
|
||||
rigids,
|
||||
loc_else.region,
|
||||
&loc_else.value,
|
||||
FromAnnotation(
|
||||
name,
|
||||
arity,
|
||||
AnnotationSource::TypedIfBranch(1),
|
||||
tipe.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
let ast_con = Eq(Type::Variable(*branch_var), NoExpectation(tipe), region);
|
||||
|
||||
exists(
|
||||
vec![*cond_var, *branch_var],
|
||||
And(vec![cond_con, then_con, else_con, ast_con]),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let then_con = constrain_expr(
|
||||
rigids,
|
||||
loc_then.region,
|
||||
&loc_then.value,
|
||||
ForReason(
|
||||
Reason::IfBranch { index: 0 },
|
||||
Type::Variable(*branch_var),
|
||||
region,
|
||||
),
|
||||
);
|
||||
let else_con = constrain_expr(
|
||||
rigids,
|
||||
loc_else.region,
|
||||
&loc_else.value,
|
||||
ForReason(
|
||||
Reason::IfBranch { index: 1 },
|
||||
Type::Variable(*branch_var),
|
||||
region,
|
||||
),
|
||||
);
|
||||
|
||||
exists(
|
||||
vec![*cond_var, *branch_var],
|
||||
And(vec![
|
||||
cond_con,
|
||||
then_con,
|
||||
else_con,
|
||||
Eq(Type::Variable(*branch_var), expected, region),
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
When {
|
||||
cond_var,
|
||||
expr_var,
|
||||
|
@ -595,8 +678,6 @@ fn constrain_def_pattern(loc_pattern: &Located<Pattern>, expr_type: Type) -> Pat
|
|||
}
|
||||
|
||||
pub fn constrain_def(rigids: &Rigids, def: &Def, body_con: Constraint) -> Constraint {
|
||||
use crate::types::AnnotationSource;
|
||||
|
||||
let expr_var = def.expr_var;
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
|
||||
|
@ -681,7 +762,6 @@ pub fn rec_defs_help(
|
|||
mut rigid_info: Info,
|
||||
mut flex_info: Info,
|
||||
) -> Constraint {
|
||||
use crate::types::AnnotationSource;
|
||||
for def in defs {
|
||||
let expr_var = def.expr_var;
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::can::symbol::Symbol;
|
|||
use crate::collections::SendMap;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
use crate::types::{Constraint, PExpected, PatternCategory, RecordFieldLabel, Type};
|
||||
use crate::types::{Constraint, Expected, PExpected, PatternCategory, RecordFieldLabel, Type};
|
||||
|
||||
pub struct PatternState {
|
||||
pub headers: SendMap<Symbol, Located<Type>>,
|
||||
|
@ -85,8 +85,14 @@ pub fn constrain_pattern(
|
|||
|
||||
field_types.insert(label.clone(), pat_type.clone());
|
||||
|
||||
// TODO investigate: shouldn't guard_var be constrained somewhere?
|
||||
if let Some((_guard_var, loc_guard)) = guard {
|
||||
if let Some((guard_var, loc_guard)) = guard {
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(*guard_var),
|
||||
Expected::NoExpectation(pat_type.clone()),
|
||||
region,
|
||||
));
|
||||
state.vars.push(*guard_var);
|
||||
|
||||
constrain_pattern(&loc_guard.value, loc_guard.region, expected, state);
|
||||
}
|
||||
|
||||
|
@ -99,12 +105,23 @@ pub fn constrain_pattern(
|
|||
|
||||
state.constraints.push(record_con);
|
||||
}
|
||||
AppliedTag(ext_var, symbol, _arguments) => {
|
||||
AppliedTag(ext_var, symbol, patterns) => {
|
||||
let mut argument_types = Vec::with_capacity(patterns.len());
|
||||
for (pattern_var, loc_pattern) in patterns {
|
||||
state.vars.push(*pattern_var);
|
||||
|
||||
let pattern_type = Type::Variable(*pattern_var);
|
||||
argument_types.push(pattern_type.clone());
|
||||
|
||||
let expected = PExpected::NoExpectation(pattern_type);
|
||||
constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state);
|
||||
}
|
||||
|
||||
let tag_con = Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Ctor(symbol.clone()),
|
||||
Type::TagUnion(
|
||||
vec![(symbol.clone(), vec![])],
|
||||
vec![(symbol.clone(), argument_types)],
|
||||
Box::new(Type::Variable(*ext_var)),
|
||||
),
|
||||
expected,
|
||||
|
|
477
src/crane/build.rs
Normal file
477
src/crane/build.rs
Normal file
|
@ -0,0 +1,477 @@
|
|||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use cranelift::frontend::Switch;
|
||||
use cranelift::prelude::{
|
||||
AbiParam, ExternalName, FloatCC, FunctionBuilder, FunctionBuilderContext, IntCC, MemFlags,
|
||||
};
|
||||
use cranelift_codegen::ir::entities::{StackSlot, Value};
|
||||
use cranelift_codegen::ir::stackslot::{StackSlotData, StackSlotKind};
|
||||
use cranelift_codegen::ir::{immediates::Offset32, types, InstBuilder, Signature, Type};
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_module::{Backend, FuncId, Linkage, Module};
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
use crate::collections::ImMap;
|
||||
use crate::crane::convert::{sig_from_layout, type_from_layout, type_from_var};
|
||||
use crate::mono::expr::{Expr, Proc, Procs};
|
||||
use crate::mono::layout::{Builtin, Layout};
|
||||
use crate::subs::{Subs, Variable};
|
||||
|
||||
type Scope = ImMap<InlinableString, ScopeEntry>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ScopeEntry {
|
||||
Stack { expr_type: Type, slot: StackSlot },
|
||||
Heap { expr_type: Type, ptr: Value },
|
||||
Arg { expr_type: Type, param: Value },
|
||||
Func { sig: Signature, func_id: FuncId },
|
||||
}
|
||||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub cfg: TargetFrontendConfig,
|
||||
pub subs: Subs,
|
||||
}
|
||||
|
||||
pub fn build_expr<'a, B: Backend>(
|
||||
env: &Env<'a>,
|
||||
scope: &Scope,
|
||||
module: &mut Module<B>,
|
||||
builder: &mut FunctionBuilder,
|
||||
expr: &Expr<'a>,
|
||||
procs: &Procs<'a>,
|
||||
) -> Value {
|
||||
use crate::mono::expr::Expr::*;
|
||||
|
||||
match expr {
|
||||
Int(num) => builder.ins().iconst(types::I64, *num),
|
||||
Float(num) => builder.ins().f64const(*num),
|
||||
Bool(val) => builder.ins().bconst(types::B1, *val),
|
||||
Byte(val) => builder.ins().iconst(types::I8, *val as i64),
|
||||
Cond {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
cond_layout,
|
||||
ret_var,
|
||||
} => {
|
||||
let branch = Branch2 {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
cond_layout,
|
||||
ret_var: *ret_var,
|
||||
};
|
||||
|
||||
build_branch2(env, scope, module, builder, branch, procs)
|
||||
}
|
||||
Switch {
|
||||
cond,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_var,
|
||||
cond_var,
|
||||
} => {
|
||||
let ret_type = type_from_var(*ret_var, &env.subs, env.cfg);
|
||||
let switch_args = SwitchArgs {
|
||||
cond_var: *cond_var,
|
||||
cond_expr: cond,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_type,
|
||||
};
|
||||
|
||||
build_switch(env, scope, module, builder, switch_args, procs)
|
||||
}
|
||||
Store(ref stores, ref ret) => {
|
||||
let mut scope = im_rc::HashMap::clone(scope);
|
||||
let arena = &env.arena;
|
||||
let subs = &env.subs;
|
||||
let cfg = env.cfg;
|
||||
|
||||
for (name, var, expr) in stores.iter() {
|
||||
let val = build_expr(env, &scope, module, builder, &expr, procs);
|
||||
let content = subs.get_without_compacting(*var).content;
|
||||
let layout = Layout::from_content(arena, content, subs)
|
||||
.unwrap_or_else(|()| panic!("TODO generate a runtime error for this Store!"));
|
||||
let expr_type = type_from_layout(cfg, &layout, subs);
|
||||
|
||||
let slot = builder.create_stack_slot(StackSlotData::new(
|
||||
StackSlotKind::ExplicitSlot,
|
||||
layout.stack_size(cfg),
|
||||
));
|
||||
|
||||
builder.ins().stack_store(val, slot, Offset32::new(0));
|
||||
|
||||
// Make a new scope which includes the binding we just encountered.
|
||||
// This should be done *after* compiling the bound expr, since any
|
||||
// recursive (in the LetRec sense) bindings should already have
|
||||
// been extracted as procedures. Nothing in here should need to
|
||||
// access itself!
|
||||
scope = im_rc::HashMap::clone(&scope);
|
||||
|
||||
scope.insert(name.clone(), ScopeEntry::Stack { expr_type, slot });
|
||||
}
|
||||
|
||||
build_expr(env, &scope, module, builder, ret, procs)
|
||||
}
|
||||
CallByName(ref name, ref args) => {
|
||||
// TODO try one of these alternative strategies (preferably the latter):
|
||||
//
|
||||
// 1. use SIMD string comparison to compare these strings faster
|
||||
// 2. pre-register Bool.or using module.add_function, and see if LLVM inlines it
|
||||
// 3. intern all these strings
|
||||
if name == "Bool.or" {
|
||||
panic!("TODO create a branch for ||");
|
||||
} else if name == "Bool.and" {
|
||||
panic!("TODO create a branch for &&");
|
||||
} else {
|
||||
let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, module, builder, arg, procs));
|
||||
}
|
||||
|
||||
let fn_id = match scope.get(name) {
|
||||
Some(ScopeEntry::Func{ func_id, .. }) => *func_id,
|
||||
other => panic!(
|
||||
"CallByName could not find function named {:?} in scope; instead, found {:?} in scope {:?}",
|
||||
name, other, scope
|
||||
),
|
||||
};
|
||||
let local_func = module.declare_func_in_func(fn_id, &mut builder.func);
|
||||
let call = builder.ins().call(local_func, &arg_vals);
|
||||
let results = builder.inst_results(call);
|
||||
|
||||
debug_assert!(results.len() == 1);
|
||||
|
||||
results[0]
|
||||
}
|
||||
}
|
||||
FunctionPointer(ref name) => {
|
||||
let fn_id = match scope.get(name) {
|
||||
Some(ScopeEntry::Func{ func_id, .. }) => *func_id,
|
||||
other => panic!(
|
||||
"FunctionPointer could not find function named {:?} in scope; instead, found {:?} in scope {:?}",
|
||||
name, other, scope
|
||||
),
|
||||
};
|
||||
|
||||
let func_ref = module.declare_func_in_func(fn_id, &mut builder.func);
|
||||
|
||||
builder.ins().func_addr(env.cfg.pointer_type(), func_ref)
|
||||
}
|
||||
CallByPointer(ref sub_expr, ref args, ref fn_var) => {
|
||||
let subs = &env.subs;
|
||||
let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, module, builder, arg, procs));
|
||||
}
|
||||
|
||||
let content = subs.get_without_compacting(*fn_var).content;
|
||||
let layout = Layout::from_content(env.arena, content, &subs)
|
||||
.unwrap_or_else(|()| panic!("TODO generate a runtime error here!"));
|
||||
let sig = sig_from_layout(env.cfg, module, layout, &subs);
|
||||
let callee = build_expr(env, scope, module, builder, sub_expr, procs);
|
||||
let sig_ref = builder.import_signature(sig);
|
||||
let call = builder.ins().call_indirect(sig_ref, callee, &arg_vals);
|
||||
let results = builder.inst_results(call);
|
||||
|
||||
debug_assert!(results.len() == 1);
|
||||
|
||||
results[0]
|
||||
}
|
||||
Load(name) => match scope.get(name) {
|
||||
Some(ScopeEntry::Stack { expr_type, slot }) => {
|
||||
builder
|
||||
.ins()
|
||||
.stack_load(*expr_type, *slot, Offset32::new(0))
|
||||
}
|
||||
Some(ScopeEntry::Arg { param, .. }) => *param,
|
||||
Some(ScopeEntry::Heap { expr_type, ptr }) => {
|
||||
builder
|
||||
.ins()
|
||||
.load(*expr_type, MemFlags::new(), *ptr, Offset32::new(0))
|
||||
}
|
||||
Some(ScopeEntry::Func { .. }) => {
|
||||
panic!("TODO I don't yet know how to return fn pointers")
|
||||
}
|
||||
None => panic!("Could not find a var for {:?} in scope {:?}", name, scope),
|
||||
},
|
||||
_ => {
|
||||
panic!("I don't yet know how to crane build {:?}", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Branch2<'a> {
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
cond_rhs: &'a Expr<'a>,
|
||||
cond_layout: &'a Layout<'a>,
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_var: Variable,
|
||||
}
|
||||
|
||||
fn build_branch2<'a, B: Backend>(
|
||||
env: &Env<'a>,
|
||||
scope: &Scope,
|
||||
module: &mut Module<B>,
|
||||
builder: &mut FunctionBuilder,
|
||||
branch: Branch2<'a>,
|
||||
procs: &Procs<'a>,
|
||||
) -> Value {
|
||||
let subs = &env.subs;
|
||||
let cfg = env.cfg;
|
||||
|
||||
// Declare a variable which each branch will mutate to be the value of that branch.
|
||||
// At the end of the expression, we will evaluate to this.
|
||||
let ret_type = type_from_var(branch.ret_var, subs, cfg);
|
||||
let ret = cranelift::frontend::Variable::with_u32(0);
|
||||
|
||||
// The block we'll jump to once the switch has completed.
|
||||
let ret_block = builder.create_ebb();
|
||||
|
||||
builder.declare_var(ret, ret_type);
|
||||
|
||||
let lhs = build_expr(env, scope, module, builder, branch.cond_lhs, procs);
|
||||
let rhs = build_expr(env, scope, module, builder, branch.cond_rhs, procs);
|
||||
let pass_block = builder.create_ebb();
|
||||
let fail_block = builder.create_ebb();
|
||||
|
||||
match branch.cond_layout {
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
// For floats, first do a `fcmp` comparison to get a bool answer about equality,
|
||||
// then use `brnz` to branch if that bool equality answer was nonzero (aka true).
|
||||
let is_eq = builder.ins().fcmp(FloatCC::Equal, lhs, rhs);
|
||||
|
||||
builder.ins().brnz(is_eq, pass_block, &[]);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
// For ints, we can compare and branch in the same instruction: `icmp`
|
||||
builder
|
||||
.ins()
|
||||
.br_icmp(IntCC::Equal, lhs, rhs, pass_block, &[]);
|
||||
}
|
||||
other => panic!("I don't know how to build a conditional for {:?}", other),
|
||||
}
|
||||
|
||||
// Unconditionally jump to fail_block (if we didn't just jump to pass_block).
|
||||
builder.ins().jump(fail_block, &[]);
|
||||
|
||||
let mut build_branch = |expr, block| {
|
||||
builder.switch_to_block(block);
|
||||
|
||||
// TODO re-enable this once Switch stops making unsealed
|
||||
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||
// builder.seal_block(block);
|
||||
|
||||
// Mutate the ret variable to be the outcome of this branch.
|
||||
let value = build_expr(env, scope, module, builder, expr, procs);
|
||||
|
||||
builder.def_var(ret, value);
|
||||
|
||||
// Unconditionally jump to ret_block, making the whole expression evaluate to ret.
|
||||
builder.ins().jump(ret_block, &[]);
|
||||
};
|
||||
|
||||
build_branch(branch.pass, pass_block);
|
||||
build_branch(branch.fail, fail_block);
|
||||
|
||||
// Finally, build ret_block - which contains our terminator instruction.
|
||||
{
|
||||
builder.switch_to_block(ret_block);
|
||||
// TODO re-enable this once Switch stops making unsealed
|
||||
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||
// builder.seal_block(block);
|
||||
|
||||
// Now that ret has been mutated by the switch statement, evaluate to it.
|
||||
builder.use_var(ret)
|
||||
}
|
||||
}
|
||||
struct SwitchArgs<'a> {
|
||||
pub cond_expr: &'a Expr<'a>,
|
||||
pub cond_var: Variable,
|
||||
pub branches: &'a [(u64, Expr<'a>)],
|
||||
pub default_branch: &'a Expr<'a>,
|
||||
pub ret_type: Type,
|
||||
}
|
||||
|
||||
fn build_switch<'a, B: Backend>(
|
||||
env: &Env<'a>,
|
||||
scope: &Scope,
|
||||
module: &mut Module<B>,
|
||||
builder: &mut FunctionBuilder,
|
||||
switch_args: SwitchArgs<'a>,
|
||||
procs: &Procs<'a>,
|
||||
) -> Value {
|
||||
let mut switch = Switch::new();
|
||||
let SwitchArgs {
|
||||
branches,
|
||||
cond_expr,
|
||||
default_branch,
|
||||
ret_type,
|
||||
..
|
||||
} = switch_args;
|
||||
let mut blocks = Vec::with_capacity_in(branches.len(), env.arena);
|
||||
|
||||
// Declare a variable which each branch will mutate to be the value of that branch.
|
||||
// At the end of the expression, we will evaluate to this.
|
||||
let ret = cranelift::frontend::Variable::with_u32(0);
|
||||
|
||||
builder.declare_var(ret, ret_type);
|
||||
|
||||
// The block for the conditional's default branch.
|
||||
let default_block = builder.create_ebb();
|
||||
|
||||
// The block we'll jump to once the switch has completed.
|
||||
let ret_block = builder.create_ebb();
|
||||
|
||||
// Build the blocks for each branch, and register them in the switch.
|
||||
// Do this before emitting the switch, because it needs to be emitted at the front.
|
||||
for (int, _) in branches {
|
||||
let block = builder.create_ebb();
|
||||
|
||||
blocks.push(block);
|
||||
|
||||
switch.set_entry(*int, block);
|
||||
}
|
||||
|
||||
// Run the switch. Each branch will mutate ret and then jump to ret_ebb.
|
||||
let cond = build_expr(env, scope, module, builder, cond_expr, procs);
|
||||
|
||||
switch.emit(builder, cond, default_block);
|
||||
|
||||
let mut build_branch = |block, expr| {
|
||||
builder.switch_to_block(block);
|
||||
// TODO re-enable this once Switch stops making unsealed
|
||||
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||
// builder.seal_block(block);
|
||||
|
||||
// Mutate the ret variable to be the outcome of this branch.
|
||||
let value = build_expr(env, scope, module, builder, expr, procs);
|
||||
|
||||
builder.def_var(ret, value);
|
||||
|
||||
// Unconditionally jump to ret_block, making the whole expression evaluate to ret.
|
||||
builder.ins().jump(ret_block, &[]);
|
||||
};
|
||||
|
||||
// Build the blocks for each branch
|
||||
for ((_, expr), block) in branches.iter().zip(blocks) {
|
||||
build_branch(block, expr);
|
||||
}
|
||||
|
||||
// Build the block for the default branch
|
||||
build_branch(default_block, default_branch);
|
||||
|
||||
// Finally, build ret_block - which contains our terminator instruction.
|
||||
{
|
||||
builder.switch_to_block(ret_block);
|
||||
// TODO re-enable this once Switch stops making unsealed
|
||||
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||
// builder.seal_block(block);
|
||||
|
||||
// Now that ret has been mutated by the switch statement, evaluate to it.
|
||||
builder.use_var(ret)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare_proc<'a, B: Backend>(
|
||||
env: &Env<'a>,
|
||||
module: &mut Module<B>,
|
||||
name: InlinableString,
|
||||
proc: &Proc<'a>,
|
||||
) -> (FuncId, Signature) {
|
||||
let args = proc.args;
|
||||
let subs = &env.subs;
|
||||
let cfg = env.cfg;
|
||||
// TODO this rtype_from_var is duplicated when building this Proc
|
||||
let ret_type = type_from_var(proc.ret_var, subs, env.cfg);
|
||||
|
||||
// Create a signature for the function
|
||||
let mut sig = module.make_signature();
|
||||
|
||||
// Add return type to the signature
|
||||
sig.returns.push(AbiParam::new(ret_type));
|
||||
|
||||
// Add params to the signature
|
||||
for (layout, _name, _var) in args.iter() {
|
||||
let arg_type = type_from_layout(cfg, &layout, subs);
|
||||
|
||||
sig.params.push(AbiParam::new(arg_type));
|
||||
}
|
||||
|
||||
// Declare the function in the module
|
||||
let fn_id = module
|
||||
.declare_function(&name, Linkage::Local, &sig)
|
||||
.unwrap_or_else(|err| panic!("Error when building function {:?} - {:?}", name, err));
|
||||
|
||||
(fn_id, sig)
|
||||
}
|
||||
|
||||
// TODO trim down these arguments
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn define_proc_body<'a, B: Backend>(
|
||||
env: &Env<'a>,
|
||||
ctx: &mut Context,
|
||||
module: &mut Module<B>,
|
||||
fn_id: FuncId,
|
||||
scope: &Scope,
|
||||
sig: Signature,
|
||||
proc: Proc<'a>,
|
||||
procs: &Procs<'a>,
|
||||
) {
|
||||
let args = proc.args;
|
||||
let subs = &env.subs;
|
||||
let cfg = env.cfg;
|
||||
|
||||
// Build the body of the function
|
||||
{
|
||||
let mut scope = scope.clone();
|
||||
let arena = env.arena;
|
||||
|
||||
ctx.func.signature = sig;
|
||||
ctx.func.name = ExternalName::user(0, fn_id.as_u32());
|
||||
|
||||
let mut func_ctx = FunctionBuilderContext::new();
|
||||
let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
|
||||
|
||||
let block = builder.create_ebb();
|
||||
|
||||
builder.switch_to_block(block);
|
||||
builder.append_ebb_params_for_function_params(block);
|
||||
|
||||
// Add args to scope
|
||||
for (¶m, (_, arg_name, var)) in builder.ebb_params(block).iter().zip(args) {
|
||||
let content = subs.get_without_compacting(*var).content;
|
||||
// TODO this type_from_content is duplicated when building this Proc
|
||||
//
|
||||
let layout = Layout::from_content(arena, content, subs)
|
||||
.unwrap_or_else(|()| panic!("TODO generate a runtime error here!"));
|
||||
let expr_type = type_from_layout(cfg, &layout, subs);
|
||||
|
||||
scope.insert(arg_name.clone(), ScopeEntry::Arg { expr_type, param });
|
||||
}
|
||||
|
||||
let body = build_expr(env, &scope, module, &mut builder, &proc.body, procs);
|
||||
|
||||
builder.ins().return_(&[body]);
|
||||
// TODO re-enable this once Switch stops making unsealed
|
||||
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||
// builder.seal_block(block);
|
||||
builder.seal_all_blocks();
|
||||
|
||||
builder.finalize();
|
||||
}
|
||||
|
||||
module
|
||||
.define_function(fn_id, ctx)
|
||||
.expect("Defining Cranelift function failed");
|
||||
|
||||
module.clear_context(ctx);
|
||||
}
|
135
src/crane/convert.rs
Normal file
135
src/crane/convert.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use cranelift::prelude::AbiParam;
|
||||
use cranelift_codegen::ir::{types, Signature, Type};
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
|
||||
use crate::mono::layout::Layout;
|
||||
use crate::subs::FlatType::*;
|
||||
use crate::subs::{Content, Subs, Variable};
|
||||
use cranelift_module::{Backend, Module};
|
||||
|
||||
pub fn type_from_var(var: Variable, subs: &Subs, cfg: TargetFrontendConfig) -> Type {
|
||||
let content = subs.get_without_compacting(var).content;
|
||||
|
||||
type_from_content(&content, subs, cfg)
|
||||
}
|
||||
|
||||
pub fn type_from_content(content: &Content, subs: &Subs, cfg: TargetFrontendConfig) -> Type {
|
||||
match content {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
let name = name.as_str();
|
||||
|
||||
if module_name == crate::types::MOD_NUM && name == crate::types::TYPE_NUM {
|
||||
let arg = *args.iter().next().unwrap();
|
||||
let arg_content = subs.get_without_compacting(arg).content;
|
||||
|
||||
num_to_crane_type(arg_content)
|
||||
} else {
|
||||
panic!(
|
||||
"TODO handle type_from_content for FlatType::Apply of {}.{} with args {:?}",
|
||||
module_name, name, args
|
||||
);
|
||||
}
|
||||
}
|
||||
Func(_, _) => cfg.pointer_type(),
|
||||
other => panic!("TODO handle type_from_content for {:?}", other),
|
||||
},
|
||||
other => panic!("Cannot convert {:?} to Crane Type", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_to_crane_type(content: Content) -> Type {
|
||||
match content {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
let name = name.as_str();
|
||||
|
||||
if module_name == crate::types::MOD_FLOAT
|
||||
&& name == crate::types::TYPE_FLOATINGPOINT
|
||||
&& args.is_empty()
|
||||
{
|
||||
debug_assert!(args.is_empty());
|
||||
types::F64
|
||||
} else if module_name == crate::types::MOD_INT
|
||||
&& name == crate::types::TYPE_INTEGER
|
||||
&& args.is_empty()
|
||||
{
|
||||
debug_assert!(args.is_empty());
|
||||
types::I64
|
||||
} else {
|
||||
panic!(
|
||||
"Unrecognized numeric type: {}.{} with args {:?}",
|
||||
module_name, name, args
|
||||
)
|
||||
}
|
||||
}
|
||||
other => panic!(
|
||||
"TODO handle num_to_crane_type (branch 0) for {:?} which is NESTED inside Num.Num",
|
||||
other
|
||||
),
|
||||
},
|
||||
|
||||
other => panic!(
|
||||
"TODO handle num_to_crane_type (branch 1) for {:?} which is NESTED inside Num.Num",
|
||||
other
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>, _subs: &Subs) -> Type {
|
||||
use crate::mono::layout::Builtin::*;
|
||||
use crate::mono::layout::Layout::*;
|
||||
|
||||
match layout {
|
||||
Pointer(_) | FunctionPointer(_, _) => cfg.pointer_type(),
|
||||
Struct(_fields) => {
|
||||
panic!("TODO layout_to_crane_type for Struct");
|
||||
}
|
||||
Builtin(builtin) => match builtin {
|
||||
Int64 => types::I64,
|
||||
Float64 => types::F64,
|
||||
Str => panic!("TODO layout_to_crane_type for Builtin::Str"),
|
||||
Map(_, _) => panic!("TODO layout_to_crane_type for Builtin::Map"),
|
||||
Set(_) => panic!("TODO layout_to_crane_type for Builtin::Set"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sig_from_layout<B: Backend>(
|
||||
cfg: TargetFrontendConfig,
|
||||
module: &mut Module<B>,
|
||||
layout: Layout,
|
||||
subs: &Subs,
|
||||
) -> Signature {
|
||||
match layout {
|
||||
Layout::FunctionPointer(args, ret) => {
|
||||
let ret_type = type_from_layout(cfg, &ret, subs);
|
||||
let mut sig = module.make_signature();
|
||||
|
||||
// Add return type to the signature
|
||||
sig.returns.push(AbiParam::new(ret_type));
|
||||
|
||||
// Add params to the signature
|
||||
for layout in args.iter() {
|
||||
let arg_type = type_from_layout(cfg, &layout, subs);
|
||||
|
||||
sig.params.push(AbiParam::new(arg_type));
|
||||
}
|
||||
|
||||
sig
|
||||
}
|
||||
_ => {
|
||||
panic!("Could not make Signature from Layout {:?}", layout);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
pub mod build;
|
||||
pub mod convert;
|
||||
pub mod env;
|
112
src/fmt/expr.rs
112
src/fmt/expr.rs
|
@ -1,6 +1,8 @@
|
|||
use crate::fmt::def::fmt_def;
|
||||
use crate::fmt::pattern::fmt_pattern;
|
||||
use crate::fmt::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, INDENT};
|
||||
use crate::fmt::spaces::{
|
||||
add_spaces, fmt_comments_only, fmt_if_spaces, fmt_spaces, is_comment, newline, INDENT,
|
||||
};
|
||||
use crate::parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern};
|
||||
use crate::region::Located;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
|
@ -73,8 +75,9 @@ pub fn fmt_expr<'a>(
|
|||
}
|
||||
buf.push_str("\"\"\"");
|
||||
}
|
||||
Int(string) => buf.push_str(string),
|
||||
Float(string) => buf.push_str(string),
|
||||
Int(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => {
|
||||
buf.push_str(string)
|
||||
}
|
||||
NonBase10Int {
|
||||
base,
|
||||
string,
|
||||
|
@ -259,22 +262,6 @@ pub fn fmt_field<'a>(
|
|||
buf.push(' ');
|
||||
fmt_expr(buf, &value.value, indent, apply_needs_parens, true);
|
||||
}
|
||||
OptionalField(name, spaces, value) => {
|
||||
if is_multiline {
|
||||
newline(buf, indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
buf.push('?');
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.push(':');
|
||||
buf.push(' ');
|
||||
fmt_expr(buf, &value.value, indent, apply_needs_parens, true);
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
if is_multiline {
|
||||
newline(buf, indent);
|
||||
|
@ -419,7 +406,6 @@ pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool {
|
|||
|
||||
match field {
|
||||
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
||||
OptionalField(_, spaces, _) => !spaces.is_empty(),
|
||||
LabelOnly(_) => false,
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||
|
@ -444,29 +430,76 @@ fn fmt_if<'a>(
|
|||
indent
|
||||
};
|
||||
|
||||
if is_multiline_condition {
|
||||
buf.push_str("if");
|
||||
|
||||
if is_multiline_condition {
|
||||
match &loc_condition.value {
|
||||
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
|
||||
fmt_if_spaces(buf, spaces_above_expr.iter(), return_indent);
|
||||
newline(buf, return_indent);
|
||||
fmt_expr(buf, &loc_condition.value, return_indent, false, false);
|
||||
|
||||
match &expr_below {
|
||||
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
|
||||
fmt_expr(buf, &expr_above, return_indent, false, false);
|
||||
fmt_if_spaces(buf, spaces_below_expr.iter(), return_indent);
|
||||
newline(buf, indent);
|
||||
buf.push_str("then");
|
||||
} else {
|
||||
buf.push_str("if ");
|
||||
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||
buf.push_str(" then");
|
||||
}
|
||||
|
||||
_ => {
|
||||
fmt_expr(buf, &expr_below, return_indent, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
fmt_expr(buf, &loc_condition.value, return_indent, false, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.push(' ');
|
||||
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
buf.push_str("then");
|
||||
|
||||
if is_multiline {
|
||||
match &loc_then.value {
|
||||
Expr::SpaceBefore(expr_below, spaces_below) => {
|
||||
let any_comments_below = spaces_below.iter().any(is_comment);
|
||||
|
||||
if !any_comments_below {
|
||||
newline(buf, return_indent);
|
||||
}
|
||||
|
||||
fmt_if_spaces(buf, spaces_below.iter(), return_indent);
|
||||
|
||||
if any_comments_below {
|
||||
newline(buf, return_indent);
|
||||
}
|
||||
|
||||
match &expr_below {
|
||||
Expr::SpaceAfter(expr_above, spaces_above) => {
|
||||
fmt_expr(buf, &expr_above, return_indent, false, false);
|
||||
|
||||
fmt_if_spaces(buf, spaces_above.iter(), return_indent);
|
||||
newline(buf, indent);
|
||||
}
|
||||
|
||||
_ => {
|
||||
fmt_expr(buf, &expr_below, return_indent, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
fmt_expr(buf, &loc_condition.value, return_indent, false, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.push_str(" ");
|
||||
fmt_expr(buf, &loc_then.value, return_indent, false, false);
|
||||
}
|
||||
|
||||
fmt_expr(buf, &loc_then.value, return_indent, false, false);
|
||||
|
||||
if is_multiline {
|
||||
buf.push('\n');
|
||||
newline(buf, indent);
|
||||
buf.push_str("else");
|
||||
newline(buf, return_indent);
|
||||
} else {
|
||||
|
@ -510,7 +543,7 @@ pub fn fmt_closure<'a>(
|
|||
any_args_printed = true;
|
||||
}
|
||||
|
||||
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||
fmt_pattern(buf, &loc_pattern.value, indent, false);
|
||||
}
|
||||
|
||||
if !arguments_are_multiline {
|
||||
|
@ -543,13 +576,26 @@ pub fn fmt_closure<'a>(
|
|||
|
||||
pub fn fmt_record<'a>(
|
||||
buf: &mut String<'a>,
|
||||
_update: Option<&'a Located<Expr<'a>>>,
|
||||
update: Option<&'a Located<Expr<'a>>>,
|
||||
loc_fields: &'a Vec<'a, Located<AssignedField<'a, Expr<'a>>>>,
|
||||
indent: u16,
|
||||
apply_needs_parens: bool,
|
||||
) {
|
||||
buf.push('{');
|
||||
|
||||
match update {
|
||||
None => {}
|
||||
// We are presuming this to be a Var()
|
||||
// If it wasnt a Var() we would not have made
|
||||
// it this far. For example "{ 4 & hello = 9 }"
|
||||
// doesnt make sense.
|
||||
Some(record_var) => {
|
||||
buf.push(' ');
|
||||
fmt_expr(buf, &record_var.value, indent, false, false);
|
||||
buf.push_str(" &");
|
||||
}
|
||||
}
|
||||
|
||||
let is_multiline = loc_fields
|
||||
.iter()
|
||||
.any(|loc_field| is_multiline_field(&loc_field.value));
|
||||
|
|
|
@ -22,15 +22,17 @@ where
|
|||
{
|
||||
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 => {
|
||||
// Only ever print two newlines back to back.
|
||||
// (Two newlines renders as one blank line.)
|
||||
if consecutive_newlines < 2 {
|
||||
if !encountered_comment && (consecutive_newlines < 2) {
|
||||
if iter.peek() == Some(&&Newline) {
|
||||
buf.push('\n');
|
||||
} else {
|
||||
|
@ -45,13 +47,41 @@ where
|
|||
LineComment(comment) => {
|
||||
fmt_comment(buf, comment, indent);
|
||||
|
||||
// Reset to 1 because we just printed a \n
|
||||
consecutive_newlines = 1;
|
||||
encountered_comment = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to fmt_comments_only, but does not finish with a newline()
|
||||
pub fn fmt_if_spaces<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
|
||||
where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
use self::CommentOrNewline::*;
|
||||
|
||||
let mut iter = spaces.peekable();
|
||||
|
||||
while let Some(space) = iter.next() {
|
||||
match space {
|
||||
Newline => {}
|
||||
LineComment(comment) => {
|
||||
buf.push('#');
|
||||
buf.push_str(comment);
|
||||
}
|
||||
}
|
||||
match iter.peek() {
|
||||
None => {}
|
||||
Some(next_space) => match next_space {
|
||||
Newline => {}
|
||||
LineComment(_) => {
|
||||
newline(buf, indent);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like format_spaces, but remove newlines and keep only comments.
|
||||
pub fn fmt_comments_only<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
|
||||
where
|
||||
|
@ -75,3 +105,10 @@ fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) {
|
|||
|
||||
newline(buf, indent);
|
||||
}
|
||||
|
||||
pub fn is_comment<'a>(space: &'a CommentOrNewline<'a>) -> bool {
|
||||
match space {
|
||||
CommentOrNewline::Newline => false,
|
||||
CommentOrNewline::LineComment(_) => true,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
use crate::subs::Subs;
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Module;
|
||||
|
||||
pub struct Env<'ctx, 'env> {
|
||||
pub context: &'ctx Context,
|
||||
pub builder: &'env Builder<'ctx>,
|
||||
pub module: &'ctx Module<'ctx>,
|
||||
pub subs: Subs,
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{BasicValue, FunctionValue, IntValue, PointerValue};
|
||||
use inkwell::{FloatPredicate, IntPredicate};
|
||||
|
||||
use crate::can::expr::Expr;
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::Pattern::{self, *};
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::ImMap;
|
||||
use crate::collections::MutMap;
|
||||
use crate::gen::convert::content_to_basic_type;
|
||||
use crate::gen::env::Env;
|
||||
use crate::subs::{Content, FlatType, Subs};
|
||||
|
||||
fn extract_procs(loc_expr: Located<Expr>, module: &Module<'ctx>, name: Option<Lowercase>, procs, &mut Procs<'ctx>) -> Located<Expr> {
|
||||
let mut procs = Vec::new();
|
||||
|
||||
match expr {
|
||||
LetNonRec(def, ret_expr, var) => {
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
let pattern = match loc_pattern.value {
|
||||
Identifier(name) => {
|
||||
match &loc_expr.value {
|
||||
Closure(_, _, _, _, _) => {
|
||||
// Extract Procs, but discard the resulting Expr::Var.
|
||||
// That Var looks up the pointer, which we won't use here!
|
||||
extract_procs(loc_expr, Some(name), procs);
|
||||
|
||||
// Discard this LetNonRec by replacing it with its ret_expr.
|
||||
return ret_expr;
|
||||
}
|
||||
_ => {
|
||||
// If this isn't a Closure, proceed as normal.
|
||||
Identifier(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
pat => pat
|
||||
}
|
||||
|
||||
// At this point, it's safe to assume we aren't assigning a Closure to a def.
|
||||
// Extract Procs from the def body and the ret expression, and return the result!
|
||||
let ret_expr = extract_procs(ret_expr, None, procs);
|
||||
let loc_expr = extract_procs(def.loc_expr, None, procs);
|
||||
let loc_pattern = Located { region: def.loc_pattern.region, value: pattern };
|
||||
let def = Def { loc_pattern, loc_expr, ..def };
|
||||
|
||||
LetNonRec(def, ret_expr, var)
|
||||
}
|
||||
|
||||
Closure(var, symbol, recursive, loc_args, boxed_ret) => {
|
||||
let (loc_ret, var) = boxed_ret;
|
||||
let name = match name {
|
||||
Some(name) => name.as_str(),
|
||||
None => {
|
||||
// Give the closure a name like "_0" or "_1".
|
||||
// We know procs.len() will be unique!
|
||||
format!("_{}", procs.len()).as_str();
|
||||
}
|
||||
};
|
||||
|
||||
let fn_val = module.add_function(name, fn_type, linkage);
|
||||
|
||||
panic!("push to procs");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -23,13 +23,14 @@ pub mod uniqueness;
|
|||
pub mod string;
|
||||
|
||||
pub mod constrain;
|
||||
pub mod crane;
|
||||
pub mod ena;
|
||||
pub mod fmt;
|
||||
pub mod gen;
|
||||
pub mod infer;
|
||||
pub mod ll;
|
||||
pub mod llvm;
|
||||
pub mod load;
|
||||
pub mod module;
|
||||
pub mod mono;
|
||||
pub mod pretty_print_types;
|
||||
pub mod solve;
|
||||
pub mod subs;
|
||||
|
|
396
src/ll/expr.rs
396
src/ll/expr.rs
|
@ -1,396 +0,0 @@
|
|||
use crate::can;
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::collections::MutMap;
|
||||
use crate::gen::convert::content_to_basic_type;
|
||||
use crate::ll::layout::Layout;
|
||||
use crate::region::Located;
|
||||
use crate::subs::{Subs, Variable};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Module;
|
||||
use inkwell::types::{BasicType, BasicTypeEnum};
|
||||
use inkwell::values::FunctionValue;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
pub type Procs<'a, 'ctx> = MutMap<InlinableString, (Option<Proc<'a>>, FunctionValue<'ctx>)>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
pub args: &'a [(Layout<'a>, InlinableString, Variable)],
|
||||
pub body: Expr<'a>,
|
||||
pub closes_over: Layout<'a>,
|
||||
pub ret_var: Variable,
|
||||
}
|
||||
|
||||
struct Env<'a, 'ctx> {
|
||||
arena: &'a Bump,
|
||||
subs: &'a Subs,
|
||||
module: &'ctx Module<'ctx>,
|
||||
context: &'ctx Context,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Expr<'a> {
|
||||
// Literals
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Str(&'a str),
|
||||
|
||||
// Load/Store
|
||||
Load(InlinableString),
|
||||
Store(&'a [(InlinableString, Variable, Expr<'a>)], &'a Expr<'a>),
|
||||
|
||||
// Functions
|
||||
FunctionPointer(InlinableString),
|
||||
CallByPointer(InlinableString, &'a [Expr<'a>]),
|
||||
CallByName(InlinableString, &'a [Expr<'a>]),
|
||||
|
||||
// Exactly two conditional branches, e.g. if/else
|
||||
Cond {
|
||||
// The left-hand side of the conditional comparison and the right-hand side.
|
||||
// These are stored separately because there are different machine instructions
|
||||
// for e.g. "compare float and jump" vs. "compare integer and jump"
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
cond_rhs: &'a Expr<'a>,
|
||||
// What to do if the condition either passes or fails
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_var: Variable,
|
||||
},
|
||||
/// More than two conditional branches, e.g. a 3-way when-expression
|
||||
Branches {
|
||||
/// The left-hand side of the conditional. We compile this to LLVM once,
|
||||
/// then reuse it to test against each different compiled cond_rhs value.
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
/// ( cond_rhs, pass, fail )
|
||||
branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
|
||||
ret_var: Variable,
|
||||
},
|
||||
|
||||
Struct(&'a [(InlinableString, Expr<'a>)]),
|
||||
|
||||
RuntimeError(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Expr<'a> {
|
||||
pub fn new<'ctx>(
|
||||
arena: &'a Bump,
|
||||
subs: &'a Subs,
|
||||
module: &'ctx Module<'ctx>,
|
||||
context: &'ctx Context,
|
||||
can_expr: can::expr::Expr,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> Self {
|
||||
let env = Env {
|
||||
arena,
|
||||
subs,
|
||||
module,
|
||||
context,
|
||||
};
|
||||
|
||||
from_can(&env, can_expr, procs, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_can<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
can_expr: can::expr::Expr,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
name: Option<InlinableString>,
|
||||
) -> Expr<'a> {
|
||||
use crate::can::expr::Expr::*;
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
match can_expr {
|
||||
Int(_, val) => Expr::Int(val),
|
||||
Float(_, val) => Expr::Float(val),
|
||||
Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)),
|
||||
Var {
|
||||
resolved_symbol, ..
|
||||
} => Expr::Load(resolved_symbol.into()),
|
||||
LetNonRec(def, ret_expr, _) => {
|
||||
let arena = env.arena;
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
let mut stored = Vec::with_capacity_in(1, arena);
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code gen later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
if let Identifier(name) = &loc_pattern.value {
|
||||
if let Closure(_, _, _, _, _) = &loc_expr.value {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
from_can(env, loc_expr.value, procs, Some(name.clone().into()));
|
||||
|
||||
// Discard this LetNonRec by replacing it with its ret_expr.
|
||||
return from_can(env, ret_expr.value, procs, None);
|
||||
}
|
||||
}
|
||||
|
||||
// If it wasn't specifically an Identifier & Closure, proceed as normal.
|
||||
store_pattern(
|
||||
env,
|
||||
loc_pattern.value,
|
||||
loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
&mut stored,
|
||||
);
|
||||
|
||||
// At this point, it's safe to assume we aren't assigning a Closure to a def.
|
||||
// Extract Procs from the def body and the ret expression, and return the result!
|
||||
let ret = from_can(env, ret_expr.value, procs, None);
|
||||
|
||||
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
|
||||
}
|
||||
|
||||
Closure(_, _symbol, _, loc_args, boxed_body) => {
|
||||
let (loc_body, ret_var) = *boxed_body;
|
||||
let name = name.unwrap_or_else(||
|
||||
// Give the closure a name like "_0" or "_1".
|
||||
// We know procs.len() will be unique!
|
||||
format!("_{}", procs.len()).into());
|
||||
|
||||
add_closure(env, name, loc_body.value, ret_var, &loc_args, procs)
|
||||
}
|
||||
|
||||
Call(boxed, loc_args, _) => {
|
||||
let (_, loc_expr, _) = *boxed;
|
||||
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
||||
for (_, loc_arg) in loc_args {
|
||||
args.push(from_can(env, loc_arg.value, procs, None));
|
||||
}
|
||||
|
||||
match from_can(env, loc_expr.value, procs, None) {
|
||||
Expr::Load(proc_name) => Expr::CallByName(proc_name, args.into_bump_slice()),
|
||||
Expr::FunctionPointer(proc_name) => {
|
||||
// Call by pointer - the closure was anonymous, e.g.
|
||||
//
|
||||
// ((\a -> a) 5)
|
||||
//
|
||||
// It might even be the anonymous result of a conditional:
|
||||
//
|
||||
// ((if x > 0 then \a -> a else \_ -> 0) 5)
|
||||
Expr::CallByPointer(proc_name, args.into_bump_slice())
|
||||
}
|
||||
non_ptr => {
|
||||
panic!(
|
||||
"Tried to call by pointer, but encountered a non-pointer: {:?}",
|
||||
non_ptr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When {
|
||||
cond_var,
|
||||
expr_var,
|
||||
loc_cond,
|
||||
branches,
|
||||
} => {
|
||||
debug_assert!(!branches.is_empty());
|
||||
|
||||
if branches.len() == 2 {
|
||||
let arena = env.arena;
|
||||
let mut iter = branches.into_iter();
|
||||
let (loc_pat1, loc_then) = iter.next().unwrap();
|
||||
let (loc_pat2, loc_else) = iter.next().unwrap();
|
||||
|
||||
match (&loc_pat1.value, &loc_pat2.value) {
|
||||
(IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => {
|
||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
||||
let cond_rhs = arena.alloc(Expr::Int(*int));
|
||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||
|
||||
Expr::Cond {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var: expr_var,
|
||||
}
|
||||
}
|
||||
(FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => {
|
||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
||||
let cond_rhs = arena.alloc(Expr::Float(*float));
|
||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||
|
||||
Expr::Cond {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var: expr_var,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("TODO handle more conds");
|
||||
}
|
||||
}
|
||||
} else if branches.len() == 1 {
|
||||
// A when-expression with exactly 1 branch is essentially a LetNonRec.
|
||||
// As such, we can compile it direcly to a Store.
|
||||
let arena = env.arena;
|
||||
let mut stored = Vec::with_capacity_in(1, arena);
|
||||
let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap();
|
||||
|
||||
store_pattern(
|
||||
env,
|
||||
loc_pattern.value,
|
||||
loc_cond.value,
|
||||
cond_var,
|
||||
procs,
|
||||
&mut stored,
|
||||
);
|
||||
|
||||
let ret = from_can(env, loc_branch.value, procs, None);
|
||||
|
||||
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
|
||||
} else {
|
||||
// /// More than two conditional branches, e.g. a 3-way when-expression
|
||||
// Expr::Branches {
|
||||
// /// The left-hand side of the conditional. We compile this to LLVM once,
|
||||
// /// then reuse it to test against each different compiled cond_rhs value.
|
||||
// cond_lhs: &'a Expr<'a>,
|
||||
// /// ( cond_rhs, pass, fail )
|
||||
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
|
||||
// ret_var: Variable,
|
||||
// },
|
||||
panic!("TODO support when-expressions of more than 2 branches.");
|
||||
}
|
||||
}
|
||||
|
||||
other => panic!("TODO convert canonicalized {:?} to ll::Expr", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_closure<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
name: InlinableString,
|
||||
can_body: can::expr::Expr,
|
||||
ret_var: Variable,
|
||||
loc_args: &[(Variable, Located<Pattern>)],
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> Expr<'a> {
|
||||
let subs = &env.subs;
|
||||
let context = env.context;
|
||||
let arena = env.arena;
|
||||
let ret_content = subs.get_without_compacting(ret_var).content;
|
||||
let ret_type = content_to_basic_type(&ret_content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function return value content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
});
|
||||
|
||||
let mut arg_names = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
let mut arg_basic_types = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
||||
for (arg_var, loc_arg) in loc_args.iter() {
|
||||
let content = subs.get_without_compacting(*arg_var).content;
|
||||
|
||||
arg_basic_types.push(
|
||||
content_to_basic_type(&content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function arg content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let layout = match Layout::from_content(arena, content, subs) {
|
||||
Ok(layout) => layout,
|
||||
Err(()) => {
|
||||
return invalid_closure(env, name, ret_type, procs);
|
||||
}
|
||||
};
|
||||
|
||||
let arg_name: InlinableString = match &loc_arg.value {
|
||||
Pattern::Identifier(name) => name.as_str().into(),
|
||||
_ => {
|
||||
panic!("TODO determine arg_name for pattern {:?}", loc_arg.value);
|
||||
}
|
||||
};
|
||||
|
||||
arg_names.push(arg_name.clone());
|
||||
proc_args.push((layout, arg_name, *arg_var));
|
||||
}
|
||||
|
||||
let fn_type = ret_type.fn_type(arg_basic_types.into_bump_slice(), false);
|
||||
let fn_val = env.module.add_function(&name, fn_type, None);
|
||||
let proc = Proc {
|
||||
args: proc_args.into_bump_slice(),
|
||||
body: from_can(env, can_body, procs, None),
|
||||
closes_over: Layout::Struct(&[]),
|
||||
ret_var,
|
||||
};
|
||||
|
||||
procs.insert(name.clone(), (Some(proc), fn_val));
|
||||
|
||||
Expr::FunctionPointer(name)
|
||||
}
|
||||
|
||||
fn invalid_closure<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
name: InlinableString,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> Expr<'a> {
|
||||
let fn_type = ret_type.fn_type(&[], false);
|
||||
let fn_val = env.module.add_function(&name, fn_type, None);
|
||||
|
||||
procs.insert(name.clone(), (None, fn_val));
|
||||
|
||||
Expr::FunctionPointer(name)
|
||||
}
|
||||
|
||||
fn store_pattern<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
can_pat: Pattern,
|
||||
can_expr: can::expr::Expr,
|
||||
var: Variable,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
stored: &mut Vec<'a, (InlinableString, Variable, Expr<'a>)>,
|
||||
) {
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code gen later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
match can_pat {
|
||||
Identifier(name) => stored.push((name.into(), var, from_can(env, can_expr, procs, None))),
|
||||
Underscore => {
|
||||
// Since _ is never read, it's safe to reassign it.
|
||||
stored.push(("_".into(), var, from_can(env, can_expr, procs, None)))
|
||||
}
|
||||
_ => {
|
||||
panic!("TODO store_pattern for {:?}", can_pat);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::{Linkage, Module};
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{FunctionValue, IntValue, PointerValue};
|
||||
|
@ -5,10 +10,11 @@ use inkwell::{FloatPredicate, IntPredicate};
|
|||
use inlinable_string::InlinableString;
|
||||
|
||||
use crate::collections::ImMap;
|
||||
use crate::gen::convert::{content_to_basic_type, layout_to_basic_type};
|
||||
use crate::gen::env::Env;
|
||||
use crate::ll::expr::{Expr, Proc, Procs};
|
||||
use crate::subs::Variable;
|
||||
use crate::llvm::convert::{
|
||||
content_to_basic_type, get_fn_type, layout_to_basic_type, type_from_var,
|
||||
};
|
||||
use crate::mono::expr::{Expr, Proc, Procs};
|
||||
use crate::subs::{Subs, Variable};
|
||||
|
||||
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
|
||||
/// output in debug builds, but we don't want it to print to stdout in release builds!
|
||||
|
@ -20,14 +26,22 @@ const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
|
|||
|
||||
type Scope<'ctx> = ImMap<InlinableString, (Variable, PointerValue<'ctx>)>;
|
||||
|
||||
pub struct Env<'a, 'ctx, 'env> {
|
||||
pub arena: &'a Bump,
|
||||
pub context: &'ctx Context,
|
||||
pub builder: &'env Builder<'ctx>,
|
||||
pub module: &'ctx Module<'ctx>,
|
||||
pub subs: Subs,
|
||||
}
|
||||
|
||||
pub fn build_expr<'a, 'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
expr: &Expr<'a>,
|
||||
procs: &Procs<'a, 'ctx>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use crate::ll::expr::Expr::*;
|
||||
use crate::mono::expr::Expr::*;
|
||||
|
||||
match expr {
|
||||
Int(num) => env.context.i64_type().const_int(*num as u64, false).into(),
|
||||
|
@ -38,8 +52,9 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
pass,
|
||||
fail,
|
||||
ret_var,
|
||||
..
|
||||
} => {
|
||||
let cond = Cond2 {
|
||||
let cond = Branch2 {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
|
@ -47,11 +62,29 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
ret_var: *ret_var,
|
||||
};
|
||||
|
||||
build_cond(env, scope, parent, cond, procs)
|
||||
build_branch2(env, scope, parent, cond, procs)
|
||||
}
|
||||
Branches { .. } => {
|
||||
panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)");
|
||||
}
|
||||
Switch {
|
||||
cond,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_var,
|
||||
cond_var,
|
||||
} => {
|
||||
let ret_type = type_from_var(*ret_var, &env.subs, env.context);
|
||||
let switch_args = SwitchArgs {
|
||||
cond_var: *cond_var,
|
||||
cond_expr: cond,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_type,
|
||||
};
|
||||
|
||||
build_switch(env, scope, parent, switch_args, procs)
|
||||
}
|
||||
Store(ref stores, ref ret) => {
|
||||
let mut scope = im_rc::HashMap::clone(scope);
|
||||
let subs = &env.subs;
|
||||
|
@ -88,12 +121,14 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
//
|
||||
// 1. use SIMD string comparison to compare these strings faster
|
||||
// 2. pre-register Bool.or using module.add_function, and see if LLVM inlines it
|
||||
// 3. intern all these strings
|
||||
if name == "Bool.or" {
|
||||
panic!("TODO create a phi node for ||");
|
||||
} else if name == "Bool.and" {
|
||||
panic!("TODO create a phi node for &&");
|
||||
} else {
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity(args.len());
|
||||
let mut arg_vals: Vec<BasicValueEnum> =
|
||||
Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, parent, arg, procs));
|
||||
|
@ -106,35 +141,45 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
|
||||
let call = env.builder.build_call(fn_val, arg_vals.as_slice(), "tmp");
|
||||
|
||||
call.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name."))
|
||||
call.try_as_basic_value().left().unwrap_or_else(|| {
|
||||
panic!("LLVM error: Invalid call by name for name {:?}", name)
|
||||
})
|
||||
}
|
||||
}
|
||||
CallByPointer(ref _ptr, ref args) => {
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity(args.len());
|
||||
FunctionPointer(ref fn_name) => {
|
||||
let ptr = env
|
||||
.module
|
||||
.get_function(fn_name)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Could not get pointer to unknown function {:?}", fn_name)
|
||||
})
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
||||
BasicValueEnum::PointerValue(ptr)
|
||||
}
|
||||
CallByPointer(ref sub_expr, ref args, _var) => {
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, parent, arg, procs));
|
||||
}
|
||||
|
||||
panic!("TODO do a load(ptr) to get back the pointer, then pass *that* in here!");
|
||||
let call = match build_expr(env, scope, parent, sub_expr, procs) {
|
||||
BasicValueEnum::PointerValue(ptr) => {
|
||||
env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
|
||||
}
|
||||
non_ptr => {
|
||||
panic!(
|
||||
"Tried to call by pointer, but encountered a non-pointer: {:?}",
|
||||
non_ptr
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// let call = match build_expr(env, scope, parent, expr, procs) {
|
||||
// BasicValueEnum::PointerValue(ptr) => {
|
||||
// env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
|
||||
// }
|
||||
// non_ptr => {
|
||||
// panic!(
|
||||
// "Tried to call by pointer, but encountered a non-pointer: {:?}",
|
||||
// non_ptr
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
// call.try_as_basic_value()
|
||||
// .left()
|
||||
// .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
|
||||
call.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
|
||||
}
|
||||
|
||||
Load(name) => match scope.get(name) {
|
||||
|
@ -142,12 +187,12 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
None => panic!("Could not find a var for {:?} in scope {:?}", name, scope),
|
||||
},
|
||||
_ => {
|
||||
panic!("I don't yet know how to build {:?}", expr);
|
||||
panic!("I don't yet know how to LLVM build {:?}", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Cond2<'a> {
|
||||
struct Branch2<'a> {
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
cond_rhs: &'a Expr<'a>,
|
||||
pass: &'a Expr<'a>,
|
||||
|
@ -155,12 +200,12 @@ struct Cond2<'a> {
|
|||
ret_var: Variable,
|
||||
}
|
||||
|
||||
fn build_cond<'a, 'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
fn build_branch2<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
cond: Cond2<'a>,
|
||||
procs: &Procs<'a, 'ctx>,
|
||||
cond: Branch2<'a>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
@ -201,88 +246,127 @@ fn build_cond<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
// fn build_branches<'a, 'ctx, 'env>(
|
||||
// env: &Env<'ctx, 'env>,
|
||||
// scope: &Scope<'ctx>,
|
||||
// parent: FunctionValue<'ctx>,
|
||||
// cond_lhs: &'a Expr<'a>,
|
||||
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
|
||||
// ret_type: BasicValueEnum<'ctx>,
|
||||
// procs: &Procs<'a, 'ctx>,
|
||||
// ) -> BasicValueEnum<'ctx> {
|
||||
// let builder = env.builder;
|
||||
// let context = env.context;
|
||||
// let lhs = build_expr(env, scope, parent, cond_lhs, procs);
|
||||
// let mut branch_iter = branches.into_iter();
|
||||
// let content = subs.get_without_compacting(cond.ret_var).content;
|
||||
// let ret_type = content_to_basic_type(&content, subs, context).unwrap_or_else(|err| {
|
||||
// panic!(
|
||||
// "Error converting cond branch ret_type content {:?} to basic type: {:?}",
|
||||
// cond.pass, err
|
||||
// )
|
||||
// });
|
||||
struct SwitchArgs<'a, 'ctx> {
|
||||
pub cond_expr: &'a Expr<'a>,
|
||||
pub cond_var: Variable,
|
||||
pub branches: &'a [(u64, Expr<'a>)],
|
||||
pub default_branch: &'a Expr<'a>,
|
||||
pub ret_type: BasicTypeEnum<'ctx>,
|
||||
}
|
||||
|
||||
// for (cond_rhs, cond_pass, cond_else) in branches {
|
||||
// let rhs = build_expr(env, scope, parent, cond_rhs, procs);
|
||||
// let pass = build_expr(env, scope, parent, cond_pass, procs);
|
||||
// let fail = build_expr(env, scope, parent, cond_else, procs);
|
||||
fn build_switch<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
switch_args: SwitchArgs<'a, 'ctx>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let arena = env.arena;
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
let SwitchArgs {
|
||||
branches,
|
||||
cond_expr,
|
||||
default_branch,
|
||||
ret_type,
|
||||
..
|
||||
} = switch_args;
|
||||
|
||||
// let cond = Cond {
|
||||
// lhs,
|
||||
// rhs,
|
||||
// pass,
|
||||
// fail,
|
||||
// ret_type,
|
||||
// };
|
||||
let cont_block = context.append_basic_block(parent, "cont");
|
||||
|
||||
// build_cond(env, scope, parent, cond, procs)
|
||||
// }
|
||||
// }
|
||||
// Build the condition
|
||||
let cond = build_expr(env, scope, parent, cond_expr, procs).into_int_value();
|
||||
|
||||
// Build the cases
|
||||
let mut incoming = Vec::with_capacity_in(branches.len(), arena);
|
||||
let mut cases = Vec::with_capacity_in(branches.len(), arena);
|
||||
|
||||
for (int, _) in branches.iter() {
|
||||
let int_val = context.i64_type().const_int(*int as u64, false);
|
||||
let block = context.append_basic_block(parent, format!("branch{}", int).as_str());
|
||||
|
||||
cases.push((int_val, &*arena.alloc(block)));
|
||||
}
|
||||
|
||||
let default_block = context.append_basic_block(parent, "default");
|
||||
|
||||
builder.build_switch(cond, &default_block, &cases);
|
||||
|
||||
for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) {
|
||||
builder.position_at_end(&block);
|
||||
|
||||
let branch_val = build_expr(env, scope, parent, branch_expr, procs);
|
||||
|
||||
builder.build_unconditional_branch(&cont_block);
|
||||
|
||||
incoming.push((branch_val, block));
|
||||
}
|
||||
|
||||
// The block for the conditional's default branch.
|
||||
builder.position_at_end(&default_block);
|
||||
|
||||
let default_val = build_expr(env, scope, parent, default_branch, procs);
|
||||
|
||||
builder.build_unconditional_branch(&cont_block);
|
||||
|
||||
incoming.push((default_val, &default_block));
|
||||
|
||||
// emit merge block
|
||||
builder.position_at_end(&cont_block);
|
||||
|
||||
let phi = builder.build_phi(ret_type, "branch");
|
||||
|
||||
for (branch_val, block) in incoming {
|
||||
phi.add_incoming(&[(&Into::<BasicValueEnum>::into(branch_val), block)]);
|
||||
}
|
||||
|
||||
phi.as_basic_value()
|
||||
}
|
||||
|
||||
// TODO trim down these arguments
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_phi2<'a, 'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
comparison: IntValue<'ctx>,
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
procs: &Procs<'a, 'ctx>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
// build branch
|
||||
let then_bb = context.append_basic_block(parent, "then");
|
||||
let else_bb = context.append_basic_block(parent, "else");
|
||||
let cont_bb = context.append_basic_block(parent, "branchcont");
|
||||
// build blocks
|
||||
let then_block = context.append_basic_block(parent, "then");
|
||||
let else_block = context.append_basic_block(parent, "else");
|
||||
let cont_block = context.append_basic_block(parent, "branchcont");
|
||||
|
||||
builder.build_conditional_branch(comparison, &then_bb, &else_bb);
|
||||
builder.build_conditional_branch(comparison, &then_block, &else_block);
|
||||
|
||||
// build then block
|
||||
builder.position_at_end(&then_bb);
|
||||
builder.position_at_end(&then_block);
|
||||
let then_val = build_expr(env, scope, parent, pass, procs);
|
||||
builder.build_unconditional_branch(&cont_bb);
|
||||
builder.build_unconditional_branch(&cont_block);
|
||||
|
||||
let then_bb = builder.get_insert_block().unwrap();
|
||||
let then_block = builder.get_insert_block().unwrap();
|
||||
|
||||
// build else block
|
||||
builder.position_at_end(&else_bb);
|
||||
builder.position_at_end(&else_block);
|
||||
let else_val = build_expr(env, scope, parent, fail, procs);
|
||||
builder.build_unconditional_branch(&cont_bb);
|
||||
builder.build_unconditional_branch(&cont_block);
|
||||
|
||||
let else_bb = builder.get_insert_block().unwrap();
|
||||
let else_block = builder.get_insert_block().unwrap();
|
||||
|
||||
// emit merge block
|
||||
builder.position_at_end(&cont_bb);
|
||||
builder.position_at_end(&cont_block);
|
||||
|
||||
let phi = builder.build_phi(ret_type, "branch");
|
||||
|
||||
phi.add_incoming(&[
|
||||
(&Into::<BasicValueEnum>::into(then_val), &then_bb),
|
||||
(&Into::<BasicValueEnum>::into(else_val), &else_bb),
|
||||
(&Into::<BasicValueEnum>::into(then_val), &then_block),
|
||||
(&Into::<BasicValueEnum>::into(else_val), &else_block),
|
||||
]);
|
||||
|
||||
phi.as_basic_value()
|
||||
|
@ -301,8 +385,8 @@ fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) {
|
|||
}
|
||||
|
||||
/// Creates a new stack allocation instruction in the entry block of the function.
|
||||
pub fn create_entry_block_alloca<'ctx>(
|
||||
env: &Env<'ctx, '_>,
|
||||
pub fn create_entry_block_alloca<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx, '_>,
|
||||
parent: FunctionValue<'_>,
|
||||
basic_type: BasicTypeEnum<'ctx>,
|
||||
name: &str,
|
||||
|
@ -319,38 +403,46 @@ pub fn create_entry_block_alloca<'ctx>(
|
|||
}
|
||||
|
||||
pub fn build_proc<'a, 'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
name: InlinableString,
|
||||
proc: Proc<'a>,
|
||||
procs: &Procs<'a, 'ctx>,
|
||||
) {
|
||||
procs: &Procs<'a>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let args = proc.args;
|
||||
let mut arg_names = Vec::new();
|
||||
let mut arg_basic_types = Vec::with_capacity(args.len());
|
||||
let arena = env.arena;
|
||||
let subs = &env.subs;
|
||||
let context = &env.context;
|
||||
let ret_content = subs.get_without_compacting(proc.ret_var).content;
|
||||
// TODO this content_to_basic_type is duplicated when building this Proc
|
||||
let ret_type = content_to_basic_type(&ret_content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function return value content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
});
|
||||
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
|
||||
let mut arg_names = Vec::new_in(arena);
|
||||
|
||||
for (layout, name, _var) in args.iter() {
|
||||
let arg_type = layout_to_basic_type(&layout, &env.subs, env.context);
|
||||
let arg_type = layout_to_basic_type(&layout, subs, env.context);
|
||||
|
||||
arg_basic_types.push(arg_type);
|
||||
arg_names.push(name);
|
||||
}
|
||||
|
||||
// Retrieve the function value from the module
|
||||
let fn_val = env.module.get_function(&name).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Function {:?} should have been registered in the LLVM module, but it was not!",
|
||||
name
|
||||
)
|
||||
});
|
||||
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
|
||||
|
||||
let fn_val = env
|
||||
.module
|
||||
.add_function(&name, fn_type, Some(Linkage::Private));
|
||||
|
||||
// Add a basic block for the entry point
|
||||
let entry = env.context.append_basic_block(fn_val, "entry");
|
||||
let entry = context.append_basic_block(fn_val, "entry");
|
||||
let builder = env.builder;
|
||||
|
||||
builder.position_at_end(&entry);
|
||||
|
||||
let mut scope = scope.clone();
|
||||
let mut scope = ImMap::default();
|
||||
|
||||
// Add args to scope
|
||||
for ((arg_val, arg_type), (_, arg_name, var)) in
|
||||
|
@ -369,9 +461,11 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
|||
|
||||
builder.build_return(Some(&body));
|
||||
|
||||
if fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
|
||||
// TODO call pass_manager.run_on(&fn_val) to optimize it!
|
||||
} else {
|
||||
fn_val
|
||||
}
|
||||
|
||||
pub fn verify_fn(fn_val: FunctionValue<'_>) {
|
||||
if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
|
||||
unsafe {
|
||||
fn_val.delete();
|
||||
}
|
|
@ -3,11 +3,26 @@ use inkwell::types::BasicTypeEnum::{self, *};
|
|||
use inkwell::types::{BasicType, FunctionType};
|
||||
use inkwell::AddressSpace;
|
||||
|
||||
use crate::ll::layout::Layout;
|
||||
use crate::mono::layout::Layout;
|
||||
use crate::subs::FlatType::*;
|
||||
use crate::subs::{Content, Subs};
|
||||
use crate::subs::{Content, Subs, Variable};
|
||||
use crate::types;
|
||||
|
||||
pub fn type_from_var<'ctx>(
|
||||
var: Variable,
|
||||
subs: &Subs,
|
||||
context: &'ctx Context,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
let content = subs.get_without_compacting(var).content;
|
||||
|
||||
content_to_basic_type(&content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting Content to basic type: {:?} - {:?}",
|
||||
content, err
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn content_to_basic_type<'ctx>(
|
||||
content: &Content,
|
||||
subs: &Subs,
|
||||
|
@ -46,8 +61,9 @@ pub fn content_to_basic_type<'ctx>(
|
|||
arg_basic_types.push(content_to_basic_type(&arg_content, subs, context)?);
|
||||
}
|
||||
let fn_type = get_fn_type(&ret_type, arg_basic_types.as_slice());
|
||||
let ptr_type = fn_type.ptr_type(AddressSpace::Generic);
|
||||
|
||||
Ok(fn_type.ptr_type(AddressSpace::Global).as_basic_type_enum())
|
||||
Ok(ptr_type.as_basic_type_enum())
|
||||
}
|
||||
other => panic!("TODO handle content_to_basic_type for {:?}", other),
|
||||
},
|
||||
|
@ -99,7 +115,7 @@ fn num_to_basic_type(content: Content, context: &Context) -> Result<BasicTypeEnu
|
|||
}
|
||||
|
||||
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
|
||||
fn get_fn_type<'ctx>(
|
||||
pub fn get_fn_type<'ctx>(
|
||||
bt_enum: &BasicTypeEnum<'ctx>,
|
||||
arg_types: &[BasicTypeEnum<'ctx>],
|
||||
) -> FunctionType<'ctx> {
|
||||
|
@ -118,8 +134,8 @@ pub fn layout_to_basic_type<'ctx>(
|
|||
_subs: &Subs,
|
||||
context: &'ctx Context,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use crate::ll::layout::Builtin::*;
|
||||
use crate::ll::layout::Layout::*;
|
||||
use crate::mono::layout::Builtin::*;
|
||||
use crate::mono::layout::Layout::*;
|
||||
|
||||
match layout {
|
||||
FunctionPointer(_arg_layouts, _ret_layout) => {
|
2
src/llvm/mod.rs
Normal file
2
src/llvm/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod build;
|
||||
pub mod convert;
|
513
src/mono/expr.rs
Normal file
513
src/mono/expr.rs
Normal file
|
@ -0,0 +1,513 @@
|
|||
use crate::can;
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::collections::MutMap;
|
||||
use crate::mono::layout::{Builtin, Layout};
|
||||
use crate::region::Located;
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
pub type Procs<'a> = MutMap<InlinableString, Option<Proc<'a>>>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
pub args: &'a [(Layout<'a>, InlinableString, Variable)],
|
||||
pub body: Expr<'a>,
|
||||
pub closes_over: Layout<'a>,
|
||||
pub ret_var: Variable,
|
||||
}
|
||||
|
||||
struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub subs: &'a Subs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Expr<'a> {
|
||||
// Literals
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Str(&'a str),
|
||||
/// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
|
||||
/// so they can (at least potentially) be emitted as 1-bit machine bools.
|
||||
///
|
||||
/// So [ True, False ] compiles to this, and so do [ A, B ] and [ Foo, Bar ].
|
||||
/// However, a union like [ True, False, Other Int ] would not.
|
||||
Bool(bool),
|
||||
/// Closed tag unions containing between 3 and 256 tags (all of 0 arity)
|
||||
/// compile to bytes, e.g. [ Blue, Black, Red, Green, White ]
|
||||
Byte(u8),
|
||||
|
||||
// Load/Store
|
||||
Load(InlinableString),
|
||||
Store(&'a [(InlinableString, Variable, Expr<'a>)], &'a Expr<'a>),
|
||||
|
||||
// Functions
|
||||
FunctionPointer(InlinableString),
|
||||
CallByName(InlinableString, &'a [Expr<'a>]),
|
||||
CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Variable),
|
||||
|
||||
// Exactly two conditional branches, e.g. if/else
|
||||
Cond {
|
||||
// The left-hand side of the conditional comparison and the right-hand side.
|
||||
// These are stored separately because there are different machine instructions
|
||||
// for e.g. "compare float and jump" vs. "compare integer and jump"
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
cond_rhs: &'a Expr<'a>,
|
||||
cond_layout: Layout<'a>,
|
||||
// What to do if the condition either passes or fails
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_var: Variable,
|
||||
},
|
||||
/// More than two conditional branches, e.g. a 3-way when-expression
|
||||
Branches {
|
||||
/// The left-hand side of the conditional. We compile this to LLVM once,
|
||||
/// then reuse it to test against each different compiled cond_rhs value.
|
||||
cond: &'a Expr<'a>,
|
||||
/// ( cond_rhs, pass, fail )
|
||||
branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
|
||||
default: &'a Expr<'a>,
|
||||
ret_var: Variable,
|
||||
},
|
||||
/// Conditional branches for integers. These are more efficient.
|
||||
Switch {
|
||||
/// This *must* be an integer, because Switch potentially compiles to a jump table.
|
||||
cond: &'a Expr<'a>,
|
||||
cond_var: Variable,
|
||||
/// The u64 in the tuple will be compared directly to the condition Expr.
|
||||
/// If they are equal, this branch will be taken.
|
||||
branches: &'a [(u64, Expr<'a>)],
|
||||
/// If no other branches pass, this default branch will be taken.
|
||||
default_branch: &'a Expr<'a>,
|
||||
/// Each branch must return a value of this type.
|
||||
ret_var: Variable,
|
||||
},
|
||||
Tag {
|
||||
variant_var: Variable,
|
||||
ext_var: Variable,
|
||||
name: InlinableString,
|
||||
arguments: &'a [Expr<'a>],
|
||||
},
|
||||
|
||||
Struct(&'a [(InlinableString, Expr<'a>)]),
|
||||
|
||||
RuntimeError(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Expr<'a> {
|
||||
pub fn new(
|
||||
arena: &'a Bump,
|
||||
subs: &'a Subs,
|
||||
can_expr: can::expr::Expr,
|
||||
procs: &mut Procs<'a>,
|
||||
) -> Self {
|
||||
let env = Env { arena, subs };
|
||||
|
||||
from_can(&env, can_expr, procs, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_can<'a>(
|
||||
env: &Env<'a>,
|
||||
can_expr: can::expr::Expr,
|
||||
procs: &mut Procs<'a>,
|
||||
name: Option<InlinableString>,
|
||||
) -> Expr<'a> {
|
||||
use crate::can::expr::Expr::*;
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
match can_expr {
|
||||
Int(_, val) => Expr::Int(val),
|
||||
Float(_, val) => Expr::Float(val),
|
||||
Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)),
|
||||
Var {
|
||||
resolved_symbol, ..
|
||||
} => Expr::Load(resolved_symbol.into()),
|
||||
LetNonRec(def, ret_expr, _) => {
|
||||
let arena = env.arena;
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
let mut stored = Vec::with_capacity_in(1, arena);
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code gen later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
if let Identifier(name) = &loc_pattern.value {
|
||||
if let Closure(_, _, _, _, _) = &loc_expr.value {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
from_can(env, loc_expr.value, procs, Some(name.clone().into()));
|
||||
|
||||
// Discard this LetNonRec by replacing it with its ret_expr.
|
||||
return from_can(env, ret_expr.value, procs, None);
|
||||
}
|
||||
}
|
||||
|
||||
// If it wasn't specifically an Identifier & Closure, proceed as normal.
|
||||
store_pattern(
|
||||
env,
|
||||
loc_pattern.value,
|
||||
loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
&mut stored,
|
||||
);
|
||||
|
||||
// At this point, it's safe to assume we aren't assigning a Closure to a def.
|
||||
// Extract Procs from the def body and the ret expression, and return the result!
|
||||
let ret = from_can(env, ret_expr.value, procs, None);
|
||||
|
||||
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
|
||||
}
|
||||
|
||||
Closure(_, _symbol, _, loc_args, boxed_body) => {
|
||||
let (loc_body, ret_var) = *boxed_body;
|
||||
let name = name.unwrap_or_else(|| gen_closure_name(procs));
|
||||
|
||||
add_closure(env, name, loc_body.value, ret_var, &loc_args, procs)
|
||||
}
|
||||
|
||||
Call(boxed, loc_args, _) => {
|
||||
let (fn_var, loc_expr, _) = *boxed;
|
||||
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
||||
for (_, loc_arg) in loc_args {
|
||||
args.push(from_can(env, loc_arg.value, procs, None));
|
||||
}
|
||||
|
||||
match from_can(env, loc_expr.value, procs, None) {
|
||||
Expr::Load(proc_name) => Expr::CallByName(proc_name, args.into_bump_slice()),
|
||||
ptr => {
|
||||
// Call by pointer - the closure was anonymous, e.g.
|
||||
//
|
||||
// ((\a -> a) 5)
|
||||
//
|
||||
// It might even be the anonymous result of a conditional:
|
||||
//
|
||||
// ((if x > 0 then \a -> a else \_ -> 0) 5)
|
||||
Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), fn_var)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When {
|
||||
cond_var,
|
||||
expr_var,
|
||||
loc_cond,
|
||||
branches,
|
||||
} => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs),
|
||||
|
||||
other => panic!("TODO convert canonicalized {:?} to ll::Expr", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_closure<'a>(
|
||||
env: &Env<'a>,
|
||||
name: InlinableString,
|
||||
can_body: can::expr::Expr,
|
||||
ret_var: Variable,
|
||||
loc_args: &[(Variable, Located<Pattern>)],
|
||||
procs: &mut Procs<'a>,
|
||||
) -> Expr<'a> {
|
||||
let subs = &env.subs;
|
||||
let arena = env.arena;
|
||||
let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
||||
for (arg_var, loc_arg) in loc_args.iter() {
|
||||
let content = subs.get_without_compacting(*arg_var).content;
|
||||
|
||||
let layout = match Layout::from_content(arena, content, subs) {
|
||||
Ok(layout) => layout,
|
||||
Err(()) => {
|
||||
// Invalid closure!
|
||||
procs.insert(name.clone(), None);
|
||||
|
||||
return Expr::FunctionPointer(name);
|
||||
}
|
||||
};
|
||||
|
||||
let arg_name: InlinableString = match &loc_arg.value {
|
||||
Pattern::Identifier(name) => name.as_str().into(),
|
||||
_ => {
|
||||
panic!("TODO determine arg_name for pattern {:?}", loc_arg.value);
|
||||
}
|
||||
};
|
||||
|
||||
proc_args.push((layout, arg_name, *arg_var));
|
||||
}
|
||||
|
||||
let proc = Proc {
|
||||
args: proc_args.into_bump_slice(),
|
||||
body: from_can(env, can_body, procs, None),
|
||||
closes_over: Layout::Struct(&[]),
|
||||
ret_var,
|
||||
};
|
||||
|
||||
procs.insert(name.clone(), Some(proc));
|
||||
|
||||
Expr::FunctionPointer(name)
|
||||
}
|
||||
|
||||
fn store_pattern<'a>(
|
||||
env: &Env<'a>,
|
||||
can_pat: Pattern,
|
||||
can_expr: can::expr::Expr,
|
||||
var: Variable,
|
||||
procs: &mut Procs<'a>,
|
||||
stored: &mut Vec<'a, (InlinableString, Variable, Expr<'a>)>,
|
||||
) {
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code gen later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
match can_pat {
|
||||
Identifier(name) => stored.push((name.into(), var, from_can(env, can_expr, procs, None))),
|
||||
Underscore => {
|
||||
// Since _ is never read, it's safe to reassign it.
|
||||
stored.push(("_".into(), var, from_can(env, can_expr, procs, None)))
|
||||
}
|
||||
_ => {
|
||||
panic!("TODO store_pattern for {:?}", can_pat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_closure_name(procs: &Procs<'_>) -> InlinableString {
|
||||
// Give the closure a name like "_0" or "_1".
|
||||
// We know procs.len() will be unique!
|
||||
format!("_{}", procs.len()).into()
|
||||
}
|
||||
|
||||
fn from_can_when<'a>(
|
||||
env: &Env<'a>,
|
||||
cond_var: Variable,
|
||||
expr_var: Variable,
|
||||
loc_cond: Located<can::expr::Expr>,
|
||||
branches: std::vec::Vec<(Located<can::pattern::Pattern>, Located<can::expr::Expr>)>,
|
||||
procs: &mut Procs<'a>,
|
||||
) -> Expr<'a> {
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
match branches.len() {
|
||||
0 => {
|
||||
// A when-expression with no branches is a runtime error.
|
||||
// We can't know what to return!
|
||||
panic!("TODO compile a 0-branch when-expression to a RuntimeError");
|
||||
}
|
||||
1 => {
|
||||
// A when-expression with exactly 1 branch is essentially a LetNonRec.
|
||||
// As such, we can compile it direcly to a Store.
|
||||
let arena = env.arena;
|
||||
let mut stored = Vec::with_capacity_in(1, arena);
|
||||
let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap();
|
||||
|
||||
store_pattern(
|
||||
env,
|
||||
loc_pattern.value,
|
||||
loc_cond.value,
|
||||
cond_var,
|
||||
procs,
|
||||
&mut stored,
|
||||
);
|
||||
|
||||
let ret = from_can(env, loc_branch.value, procs, None);
|
||||
|
||||
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
|
||||
}
|
||||
2 => {
|
||||
// A when-expression with exactly 2 branches compiles to a Cond.
|
||||
let arena = env.arena;
|
||||
let mut iter = branches.into_iter();
|
||||
let (loc_pat1, loc_then) = iter.next().unwrap();
|
||||
let (loc_pat2, loc_else) = iter.next().unwrap();
|
||||
|
||||
match (&loc_pat1.value, &loc_pat2.value) {
|
||||
(IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => {
|
||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
||||
let cond_rhs = arena.alloc(Expr::Int(*int));
|
||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||
|
||||
Expr::Cond {
|
||||
cond_layout: Layout::Builtin(Builtin::Int64),
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var: expr_var,
|
||||
}
|
||||
}
|
||||
(FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => {
|
||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
||||
let cond_rhs = arena.alloc(Expr::Float(*float));
|
||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||
|
||||
Expr::Cond {
|
||||
cond_layout: Layout::Builtin(Builtin::Float64),
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var: expr_var,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("TODO handle more conds");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// This is a when-expression with 3+ branches.
|
||||
let arena = env.arena;
|
||||
let cond = from_can(env, loc_cond.value, procs, None);
|
||||
let subs = &env.subs;
|
||||
let content = subs.get_without_compacting(cond_var).content;
|
||||
|
||||
// We can Switch on integers and tags, because they both have
|
||||
// representations that work as integer values.
|
||||
//
|
||||
// TODO we can also Switch on record fields if we're pattern matching
|
||||
// on a record field that's also Switchable.
|
||||
let is_switchable = match &content {
|
||||
Content::Structure(FlatType::Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
}) if module_name.as_str() == crate::types::MOD_NUM
|
||||
&& name.as_str() == crate::types::TYPE_NUM =>
|
||||
{
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
let arg = args.iter().next().unwrap();
|
||||
|
||||
match subs.get_without_compacting(*arg).content {
|
||||
Content::Structure(FlatType::Apply {
|
||||
module_name, name, ..
|
||||
}) if module_name.as_str() == crate::types::MOD_INT => {
|
||||
// This check shouldn't be necessary; the only
|
||||
// type that fits the pattern of Num.Num Int._____
|
||||
// is an Int!
|
||||
debug_assert!(name.as_str() == crate::types::TYPE_INTEGER);
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// If the condition is an Int or Float, we can potentially use
|
||||
// a Switch for more efficiency.
|
||||
if is_switchable {
|
||||
// These are integer literals or underscore patterns,
|
||||
// so they're eligible for user in a jump table.
|
||||
let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena);
|
||||
let mut opt_default_branch = None;
|
||||
|
||||
for (loc_pat, loc_expr) in branches {
|
||||
let mono_expr = from_can(env, loc_expr.value, procs, None);
|
||||
|
||||
match &loc_pat.value {
|
||||
IntLiteral(int) => {
|
||||
// Switch only compares the condition to the
|
||||
// alternatives based on their bit patterns,
|
||||
// so casting from i64 to u64 makes no difference here.
|
||||
jumpable_branches.push((*int as u64, mono_expr));
|
||||
}
|
||||
Identifier(_symbol) => {
|
||||
// Since this is an ident, it must be
|
||||
// the last pattern in the `when`.
|
||||
// We can safely treat this like an `_`
|
||||
// except that we need to wrap this branch
|
||||
// in a `Store` so the identifier is in scope!
|
||||
|
||||
opt_default_branch = Some(arena.alloc(if true {
|
||||
// Using `if true` for this TODO panic to avoid a warning
|
||||
panic!("TODO wrap this expr in an Expr::Store: {:?}", mono_expr)
|
||||
} else {
|
||||
mono_expr
|
||||
}));
|
||||
}
|
||||
Underscore => {
|
||||
// We should always have exactly one default branch!
|
||||
debug_assert!(opt_default_branch.is_none());
|
||||
|
||||
opt_default_branch = Some(arena.alloc(mono_expr));
|
||||
}
|
||||
Shadowed(_loc_ident) => {
|
||||
panic!("TODO runtime error for shadowing in a pattern");
|
||||
}
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(_region) => {
|
||||
panic!("TODO runtime error for unsupported pattern");
|
||||
}
|
||||
AppliedTag(_, _, _)
|
||||
| StrLiteral(_)
|
||||
| RecordDestructure(_, _)
|
||||
| FloatLiteral(_) => {
|
||||
// The type checker should have converted these mismatches into RuntimeErrors already!
|
||||
if cfg!(debug_assetions) {
|
||||
panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_pat);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the default branch was never set, that means
|
||||
// our canonical Expr didn't have one. An earlier
|
||||
// step in the compilation process should have
|
||||
// ruled this out!
|
||||
debug_assert!(opt_default_branch.is_some());
|
||||
let default_branch = opt_default_branch.unwrap();
|
||||
|
||||
Expr::Switch {
|
||||
cond: arena.alloc(cond),
|
||||
branches: jumpable_branches.into_bump_slice(),
|
||||
default_branch,
|
||||
ret_var: expr_var,
|
||||
cond_var,
|
||||
}
|
||||
} else {
|
||||
// /// More than two conditional branches, e.g. a 3-way when-expression
|
||||
// Expr::Branches {
|
||||
// /// The left-hand side of the conditional. We compile this to LLVM once,
|
||||
// /// then reuse it to test against each different compiled cond_rhs value.
|
||||
// cond_lhs: &'a Expr<'a>,
|
||||
// /// ( cond_rhs, pass, fail )
|
||||
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
|
||||
// ret_var: Variable,
|
||||
// },
|
||||
panic!(
|
||||
"TODO support when-expressions of 3+ branches whose conditions aren't integers."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use crate::subs::{Content, FlatType, Subs};
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
use crate::types;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
/// Types for code gen must be monomorphic. No type variables allowed!
|
||||
|
@ -24,6 +25,11 @@ pub enum Builtin<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub fn from_var(arena: &'a Bump, var: Variable, subs: &Subs) -> Result<Self, ()> {
|
||||
let content = subs.get_without_compacting(var).content;
|
||||
|
||||
Self::from_content(arena, content, subs)
|
||||
}
|
||||
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||
/// monomorphized away already!
|
||||
|
@ -41,6 +47,50 @@ impl<'a> Layout<'a> {
|
|||
Error => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack_size(&self, cfg: TargetFrontendConfig) -> u32 {
|
||||
use Layout::*;
|
||||
|
||||
match self {
|
||||
Builtin(builtin) => builtin.stack_size(cfg),
|
||||
Struct(fields) => {
|
||||
let mut sum = 0;
|
||||
|
||||
for (_, field_layout) in *fields {
|
||||
sum += field_layout.stack_size(cfg);
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
Pointer(_) | FunctionPointer(_, _) => pointer_size(cfg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pointer_size(cfg: TargetFrontendConfig) -> u32 {
|
||||
cfg.pointer_bytes() as u32
|
||||
}
|
||||
|
||||
impl<'a> Builtin<'a> {
|
||||
const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32;
|
||||
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
|
||||
|
||||
/// Number of machine words in an empty one of these
|
||||
const STR_WORDS: u32 = 3;
|
||||
const MAP_WORDS: u32 = 6;
|
||||
const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value
|
||||
|
||||
pub fn stack_size(&self, cfg: TargetFrontendConfig) -> u32 {
|
||||
use Builtin::*;
|
||||
|
||||
match self {
|
||||
Int64 => Builtin::I64_SIZE,
|
||||
Float64 => Builtin::F64_SIZE,
|
||||
Str => Builtin::STR_WORDS * pointer_size(cfg),
|
||||
Map(_, _) => Builtin::MAP_WORDS * pointer_size(cfg),
|
||||
Set(_) => Builtin::SET_WORDS * pointer_size(cfg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_from_flat_type<'a>(
|
||||
|
@ -102,6 +152,9 @@ fn layout_from_flat_type<'a>(
|
|||
EmptyTagUnion => {
|
||||
panic!("TODO make Layout for empty Tag Union");
|
||||
}
|
||||
Boolean(_) => {
|
||||
panic!("TODO make Layout for Boolean");
|
||||
}
|
||||
Erroneous(_) => Err(()),
|
||||
EmptyRecord => Ok(Layout::Struct(&[])),
|
||||
}
|
|
@ -217,15 +217,20 @@ pub enum TypeAnnotation<'a> {
|
|||
/// A bound type variable, e.g. `a` in `(a -> a)`
|
||||
BoundVariable(&'a str),
|
||||
|
||||
/// A plain record, e.g. `{ name: String, email: Email }`
|
||||
Record(Vec<'a, Loc<AssignedField<'a, TypeAnnotation<'a>>>>),
|
||||
Record {
|
||||
fields: &'a [Loc<AssignedField<'a, TypeAnnotation<'a>>>],
|
||||
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`.
|
||||
/// This is None if it's a closed record annotation like `{ name: Str }`.
|
||||
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
|
||||
},
|
||||
|
||||
/// A record fragment, e.g. `{ name: String, email: Email }...r`
|
||||
RecordFragment(
|
||||
Vec<'a, Loc<AssignedField<'a, TypeAnnotation<'a>>>>,
|
||||
// the fragment type variable, e.g. the `r` in `{ name: String }...r`
|
||||
&'a Loc<TypeAnnotation<'a>>,
|
||||
),
|
||||
/// A tag union, e.g. `[
|
||||
TagUnion {
|
||||
tags: &'a [Loc<Tag<'a>>],
|
||||
/// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`.
|
||||
/// This is None if it's a closed tag union like `[ Foo, Bar]`.
|
||||
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
|
||||
},
|
||||
|
||||
/// The `*` type variable, e.g. in (List *)
|
||||
Wildcard,
|
||||
|
@ -238,14 +243,31 @@ pub enum TypeAnnotation<'a> {
|
|||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Tag<'a> {
|
||||
Global {
|
||||
name: Loc<&'a str>,
|
||||
args: &'a [Loc<TypeAnnotation<'a>>],
|
||||
},
|
||||
|
||||
Private {
|
||||
name: Loc<&'a str>,
|
||||
args: &'a [Loc<TypeAnnotation<'a>>],
|
||||
},
|
||||
|
||||
// We preserve this for the formatter; canonicalization ignores it.
|
||||
SpaceBefore(&'a Tag<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a Tag<'a>, &'a [CommentOrNewline<'a>]),
|
||||
|
||||
/// A malformed tag, which will code gen to a runtime error
|
||||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AssignedField<'a, Val> {
|
||||
// Both a label and a value, e.g. `{ name: "blah" }`
|
||||
LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
|
||||
// An optional field, e.g. `{ name? : String }`. Only for types
|
||||
OptionalField(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(Loc<&'a str>),
|
||||
|
||||
|
@ -497,6 +519,15 @@ impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for Tag<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
Tag::SpaceBefore(self, spaces)
|
||||
}
|
||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
Tag::SpaceAfter(self, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for Def<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
Def::SpaceBefore(self, spaces)
|
||||
|
|
|
@ -49,7 +49,7 @@ impl<'a> Ident<'a> {
|
|||
/// Sometimes we may want to check for those later in the process, and give
|
||||
/// more contextually-aware error messages than "unexpected `if`" or the like.
|
||||
#[inline(always)]
|
||||
pub fn parse_into<'a, I>(
|
||||
pub fn parse_ident<'a, I>(
|
||||
arena: &'a Bump,
|
||||
chars: &mut I,
|
||||
state: State<'a>,
|
||||
|
@ -103,7 +103,7 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
let mut chars_parsed = 1;
|
||||
let mut chars_parsed = part_buf.len();
|
||||
let mut next_char = None;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
|
@ -310,7 +310,7 @@ where
|
|||
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> {
|
||||
move |arena: &'a Bump, state: State<'a>| {
|
||||
// Discard next_char; we don't need it.
|
||||
let ((string, _), state) = parse_into(arena, &mut state.input.chars(), state)?;
|
||||
let ((string, _), state) = parse_ident(arena, &mut state.input.chars(), state)?;
|
||||
|
||||
Ok((string, state))
|
||||
}
|
||||
|
|
|
@ -359,7 +359,6 @@ pub fn assigned_expr_field_to_pattern<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
AssignedField::OptionalField(_, _, _) => panic!("invalid in literals"),
|
||||
AssignedField::LabelOnly(name) => Pattern::Identifier(name.value),
|
||||
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
|
||||
arena.alloc(assigned_expr_field_to_pattern(arena, nested)?),
|
||||
|
@ -400,9 +399,6 @@ pub fn assigned_pattern_field_to_pattern<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
AssignedField::OptionalField(_, _, _) => {
|
||||
panic!("invalid as a pattern");
|
||||
}
|
||||
AssignedField::LabelOnly(name) => Located::at(name.region, Pattern::Identifier(name.value)),
|
||||
AssignedField::SpaceBefore(nested, spaces) => {
|
||||
let can_nested = assigned_pattern_field_to_pattern(arena, nested, backup_region)?;
|
||||
|
@ -434,7 +430,7 @@ pub fn loc_parenthetical_def<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
|
|||
space0_after(
|
||||
between!(
|
||||
char('('),
|
||||
space0_around(loc!(pattern(min_indent)), min_indent),
|
||||
space0_around(loc_pattern(min_indent), min_indent),
|
||||
char(')')
|
||||
),
|
||||
min_indent,
|
||||
|
@ -755,27 +751,36 @@ fn parse_closure_param<'a>(
|
|||
// e.g. \User.UserId userId -> ...
|
||||
between!(
|
||||
char('('),
|
||||
space0_around(loc!(pattern(min_indent)), min_indent),
|
||||
space0_around(loc_pattern(min_indent), min_indent),
|
||||
char(')')
|
||||
),
|
||||
// The least common, but still allowed, e.g. \Foo -> ...
|
||||
loc!(tag_pattern())
|
||||
loc_tag_pattern(min_indent)
|
||||
)
|
||||
.parse(arena, state)
|
||||
}
|
||||
|
||||
fn pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
||||
fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
|
||||
one_of!(
|
||||
underscore_pattern(),
|
||||
tag_pattern(),
|
||||
ident_pattern(),
|
||||
record_destructure(min_indent),
|
||||
string_pattern(),
|
||||
int_pattern()
|
||||
loc_parenthetical_pattern(min_indent),
|
||||
loc!(underscore_pattern()),
|
||||
loc_tag_pattern(min_indent),
|
||||
loc!(ident_pattern()),
|
||||
loc!(record_destructure(min_indent)),
|
||||
loc!(string_pattern()),
|
||||
loc!(number_pattern())
|
||||
)
|
||||
}
|
||||
|
||||
fn int_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||
fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
|
||||
between!(
|
||||
char('('),
|
||||
move |arena, state| loc_pattern(min_indent).parse(arena, state),
|
||||
char(')')
|
||||
)
|
||||
}
|
||||
|
||||
fn number_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||
map_with_arena!(number_literal(), |arena, expr| {
|
||||
expr_to_pattern(arena, &expr).unwrap()
|
||||
})
|
||||
|
@ -799,7 +804,7 @@ fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
|||
|
||||
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
||||
then(
|
||||
record_without_update!(loc!(pattern(min_indent)), min_indent),
|
||||
record_without_update!(loc_pattern(min_indent), min_indent),
|
||||
move |arena, state, assigned_fields| {
|
||||
let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
||||
for assigned_field in assigned_fields {
|
||||
|
@ -818,10 +823,29 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
|||
)
|
||||
}
|
||||
|
||||
fn tag_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||
one_of!(
|
||||
fn loc_tag_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
|
||||
map_with_arena!(
|
||||
and!(
|
||||
loc!(one_of!(
|
||||
map!(private_tag(), Pattern::PrivateTag),
|
||||
map!(global_tag(), Pattern::GlobalTag)
|
||||
)),
|
||||
// This can optionally be an applied pattern, e.g. (Foo bar) instead of (Foo)
|
||||
zero_or_more!(space1_before(loc_pattern(min_indent), min_indent))
|
||||
),
|
||||
|arena: &'a Bump,
|
||||
(loc_tag, loc_args): (Located<Pattern<'a>>, Vec<'a, Located<Pattern<'a>>>)| {
|
||||
if loc_args.is_empty() {
|
||||
loc_tag
|
||||
} else {
|
||||
// TODO FIME this region doesn't cover the tag's
|
||||
// arguments; need to add them to the region!
|
||||
let region = loc_tag.region;
|
||||
let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
|
||||
|
||||
Located { region, value }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -938,10 +962,11 @@ pub fn case_branches<'a>(
|
|||
Ok((branches, state))
|
||||
}
|
||||
}
|
||||
|
||||
fn alternative_patterns<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>> {
|
||||
sep_by1(
|
||||
char('|'),
|
||||
space0_around(loc!(pattern(min_indent)), min_indent),
|
||||
space0_around(loc_pattern(min_indent), min_indent),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -996,11 +1021,21 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
|
|||
then(
|
||||
// Spaces, then '-', then *not* more spaces.
|
||||
not_followed_by(
|
||||
and!(space1(min_indent), loc!(char('-'))),
|
||||
and!(
|
||||
space1(min_indent),
|
||||
either!(
|
||||
// Try to parse a number literal *before* trying to parse unary negate,
|
||||
// because otherwise (foo -1) will parse as (foo (Num.neg 1))
|
||||
loc!(number_literal()),
|
||||
loc!(char('-'))
|
||||
)
|
||||
),
|
||||
one_of!(char(' '), char('#'), char('\n')),
|
||||
),
|
||||
move |arena, state, (spaces, loc_minus_char)| {
|
||||
let region = loc_minus_char.region;
|
||||
move |arena, state, (spaces, num_or_minus_char)| {
|
||||
match num_or_minus_char {
|
||||
Either::First(loc_num_literal) => Ok((loc_num_literal, state)),
|
||||
Either::Second(Located { region, .. }) => {
|
||||
let loc_op = Located {
|
||||
region,
|
||||
value: UnaryOp::Negate,
|
||||
|
@ -1039,6 +1074,8 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
|
|||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -1406,7 +1443,17 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
|
||||
/// This is mainly for matching tags in closure params, e.g. \@Foo -> ...
|
||||
fn private_tag<'a>() -> impl Parser<'a, &'a str> {
|
||||
skip_first!(char('@'), global_tag())
|
||||
// TODO should be refactored so the name is not allocated again.
|
||||
map_with_arena!(
|
||||
skip_first!(char('@'), global_tag()),
|
||||
|arena: &'a Bump, name: &'a str| {
|
||||
use bumpalo::collections::string::String;
|
||||
let mut buf = String::with_capacity_in(1 + name.len(), arena);
|
||||
buf.push('@');
|
||||
buf.push_str(name);
|
||||
buf.into_bump_str()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// This is mainly for matching tags in closure params, e.g. \Foo -> ...
|
||||
|
|
|
@ -905,9 +905,6 @@ macro_rules! record_field {
|
|||
// You must have a field name, e.g. "email"
|
||||
let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?;
|
||||
|
||||
let (opt_field, state) =
|
||||
$crate::parse::parser::optional(char('?')).parse(arena, state)?;
|
||||
|
||||
let (spaces, state) = space0($min_indent).parse(arena, state)?;
|
||||
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
||||
// (This is true in both literals and types.)
|
||||
|
@ -917,27 +914,24 @@ macro_rules! record_field {
|
|||
))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let answer = match (opt_loc_val, opt_field) {
|
||||
(Some(loc_val), None) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
||||
(Some(loc_val), Some(_)) => OptionalField(loc_label, spaces, arena.alloc(loc_val)),
|
||||
let answer = match opt_loc_val {
|
||||
Some(loc_val) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
||||
// If no value was provided, record it as a Var.
|
||||
// Canonicalize will know what to do with a Var later.
|
||||
(None, None) => {
|
||||
None => {
|
||||
if !spaces.is_empty() {
|
||||
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
||||
} else {
|
||||
LabelOnly(loc_label)
|
||||
}
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
panic!("TODO should `{ x? }` be valid? realistically, how of often does `{ a : a }` occur in a type?");
|
||||
}
|
||||
};
|
||||
|
||||
Ok((answer, state))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! record_without_update {
|
||||
($val_parser:expr, $min_indent:expr) => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::collections::arena_join;
|
||||
use crate::parse::ast::{Attempting, TypeAnnotation};
|
||||
use crate::parse::ast::{AssignedField, Attempting, Tag, TypeAnnotation};
|
||||
use crate::parse::blankspace::{space0_around, space0_before, space1_before};
|
||||
use crate::parse::parser::{
|
||||
char, optional, string, unexpected, unexpected_eof, ParseResult, Parser, State,
|
||||
allocated, char, optional, string, unexpected, unexpected_eof, Either, ParseResult, Parser,
|
||||
State,
|
||||
};
|
||||
use crate::parse::{global_tag, private_tag};
|
||||
use crate::region::Located;
|
||||
use bumpalo::collections::string::String;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
|
@ -13,6 +15,33 @@ pub fn located<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a
|
|||
expression(min_indent)
|
||||
}
|
||||
|
||||
macro_rules! tag_union {
|
||||
($min_indent:expr) => {
|
||||
map!(
|
||||
and!(
|
||||
collection!(
|
||||
char('['),
|
||||
loc!(tag_type($min_indent)),
|
||||
char(','),
|
||||
char(']'),
|
||||
$min_indent
|
||||
),
|
||||
optional(
|
||||
// This could be an open tag union, e.g. `[ Foo, Bar ]a`
|
||||
move |arena, state| allocated(term($min_indent)).parse(arena, state)
|
||||
)
|
||||
),
|
||||
|(tags, ext): (
|
||||
Vec<'a, Located<Tag<'a>>>,
|
||||
Option<&'a Located<TypeAnnotation<'a>>>,
|
||||
)| TypeAnnotation::TagUnion {
|
||||
tags: tags.into_bump_slice(),
|
||||
ext
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
|
||||
one_of!(
|
||||
// The `*` type variable, e.g. in (List *) Wildcard,
|
||||
|
@ -21,6 +50,7 @@ pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>>
|
|||
}),
|
||||
loc_parenthetical_type(min_indent),
|
||||
loc!(record_type(min_indent)),
|
||||
loc!(tag_union!(min_indent)),
|
||||
loc!(applied_type(min_indent)),
|
||||
loc!(parse_type_variable)
|
||||
)
|
||||
|
@ -38,25 +68,56 @@ fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAn
|
|||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>> {
|
||||
map!(
|
||||
and!(
|
||||
either!(loc!(private_tag()), loc!(global_tag())),
|
||||
// Optionally parse space-separated arguments for the constructor,
|
||||
// e.g. `ok err` in `Result ok err`
|
||||
zero_or_more!(space1_before(
|
||||
move |arena, state| term(min_indent).parse(arena, state),
|
||||
min_indent,
|
||||
))
|
||||
),
|
||||
|(either_name, args): (
|
||||
Either<Located<&'a str>, Located<&'a str>>,
|
||||
Vec<'a, Located<TypeAnnotation<'a>>>
|
||||
)| match either_name {
|
||||
Either::First(name) => Tag::Private {
|
||||
name,
|
||||
args: args.into_bump_slice()
|
||||
},
|
||||
Either::Second(name) => Tag::Global {
|
||||
name,
|
||||
args: args.into_bump_slice()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
|
||||
use crate::parse::type_annotation::TypeAnnotation::*;
|
||||
|
||||
map_with_arena!(
|
||||
map!(
|
||||
and!(
|
||||
record_without_update!(
|
||||
move |arena, state| term(min_indent).parse(arena, state),
|
||||
min_indent
|
||||
),
|
||||
optional(skip_first!(
|
||||
// This could be a record fragment, e.g. `{ name: String }...r`
|
||||
string("..."),
|
||||
move |arena, state| term(min_indent).parse(arena, state)
|
||||
))
|
||||
optional(
|
||||
// This could be an open record, e.g. `{ name: Str }r`
|
||||
move |arena, state| allocated(term(min_indent)).parse(arena, state)
|
||||
)
|
||||
),
|
||||
|arena: &'a Bump, (rec, opt_bound_var)| match opt_bound_var {
|
||||
None => Record(rec),
|
||||
Some(loc_bound_var) => RecordFragment(rec, arena.alloc(loc_bound_var)),
|
||||
|(fields, ext): (
|
||||
Vec<'a, Located<AssignedField<'a, TypeAnnotation<'a>>>>,
|
||||
Option<&'a Located<TypeAnnotation<'a>>>,
|
||||
)| Record {
|
||||
fields: fields.into_bump_slice(),
|
||||
ext
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
|||
use crate::collections::{MutMap, MutSet};
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
use crate::types::{self, name_type_var};
|
||||
use crate::uniqueness::boolean_algebra::Bool;
|
||||
|
||||
static WILDCARD: &str = "*";
|
||||
static EMPTY_RECORD: &str = "{}";
|
||||
|
@ -104,6 +105,11 @@ fn find_names_needed(
|
|||
|
||||
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
Structure(Boolean(b)) => {
|
||||
for var in b.variables() {
|
||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
}
|
||||
RigidVar(name) => {
|
||||
// User-defined names are already taken.
|
||||
// We must not accidentally generate names that collide with them!
|
||||
|
@ -202,6 +208,13 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
|||
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
|
||||
Func(args, ret) => write_fn(args, ret, subs, buf, parens),
|
||||
Record(fields, ext_var) => {
|
||||
use crate::unify::gather_fields;
|
||||
use crate::unify::RecordStructure;
|
||||
|
||||
// If the `ext` has concrete fields (e.g. { foo : Int}{ bar : Bool }), merge them
|
||||
let RecordStructure { fields, ext } = gather_fields(subs, fields, ext_var);
|
||||
let ext_var = ext;
|
||||
|
||||
if fields.is_empty() {
|
||||
buf.push_str(EMPTY_RECORD)
|
||||
} else {
|
||||
|
@ -295,12 +308,52 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
|||
}
|
||||
}
|
||||
}
|
||||
Boolean(b) => {
|
||||
write_boolean(b, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Erroneous(problem) => {
|
||||
buf.push_str(&format!("<Type Mismatch: {:?}>", problem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_boolean(boolean: Bool, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
let is_atom = boolean.is_var() || boolean == Bool::Zero || boolean == Bool::One;
|
||||
let write_parens = parens == Parens::InTypeParam && !is_atom;
|
||||
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
match boolean {
|
||||
Bool::Variable(var) => write_content(subs.get(var).content, subs, buf, parens),
|
||||
Bool::Or(p, q) => {
|
||||
write_boolean(*p, subs, buf, Parens::InTypeParam);
|
||||
buf.push_str(" | ");
|
||||
write_boolean(*q, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Bool::And(p, q) => {
|
||||
write_boolean(*p, subs, buf, Parens::InTypeParam);
|
||||
buf.push_str(" & ");
|
||||
write_boolean(*q, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Bool::Not(p) => {
|
||||
buf.push_str("!");
|
||||
write_boolean(*p, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Bool::Zero => {
|
||||
buf.push_str("Attr.Shared");
|
||||
}
|
||||
Bool::One => {
|
||||
buf.push_str("Attr.Unique");
|
||||
}
|
||||
};
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_apply(
|
||||
module_name: ModuleName,
|
||||
type_name: Uppercase,
|
||||
|
|
23
src/solve.rs
23
src/solve.rs
|
@ -7,6 +7,7 @@ use crate::types::Constraint::{self, *};
|
|||
use crate::types::Problem;
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::unify::{unify, Unified};
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
|
||||
type Env = ImMap<Symbol, Variable>;
|
||||
|
||||
|
@ -401,6 +402,13 @@ fn type_to_variable(
|
|||
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
// This case is important so e.g. `Bool::Variable(v) ~ Attr.Shared`
|
||||
Boolean(boolean_algebra::Bool::Variable(var)) => *var,
|
||||
Boolean(b) => {
|
||||
let content = Content::Structure(FlatType::Boolean(b.clone()));
|
||||
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
Function(args, ret_type) => {
|
||||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
|
||||
|
@ -673,6 +681,15 @@ fn adjust_rank_content(
|
|||
rank
|
||||
}
|
||||
|
||||
Boolean(b) => {
|
||||
let mut rank = Rank::toplevel();
|
||||
for var in b.variables() {
|
||||
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
||||
}
|
||||
|
||||
rank
|
||||
}
|
||||
|
||||
Erroneous(_) => group_rank,
|
||||
}
|
||||
}
|
||||
|
@ -806,6 +823,12 @@ fn deep_copy_var_help(
|
|||
|
||||
TagUnion(new_tags, deep_copy_var_help(subs, max_rank, pools, ext_var))
|
||||
}
|
||||
|
||||
Boolean(b) => {
|
||||
let mut mapper = |var| deep_copy_var_help(subs, max_rank, pools, var);
|
||||
|
||||
Boolean(b.map_variables(&mut mapper))
|
||||
}
|
||||
};
|
||||
|
||||
subs.set(copy, make_descriptor(Structure(new_flat_type)));
|
||||
|
|
38
src/subs.rs
38
src/subs.rs
|
@ -2,7 +2,9 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
|||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
|
||||
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
||||
use crate::types;
|
||||
use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt};
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
|
@ -134,7 +136,7 @@ impl Into<Option<Variable>> for OptVariable {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Variable(usize);
|
||||
|
||||
impl Variable {
|
||||
|
@ -145,6 +147,10 @@ impl Variable {
|
|||
const NULL: Variable = Variable(0);
|
||||
|
||||
const FIRST_USER_SPACE_VAR: Variable = Variable(1);
|
||||
|
||||
pub fn unsafe_debug_variable(v: usize) -> Self {
|
||||
Variable(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<OptVariable> for Variable {
|
||||
|
@ -422,6 +428,18 @@ pub enum Content {
|
|||
Error,
|
||||
}
|
||||
|
||||
impl Content {
|
||||
#[inline(always)]
|
||||
pub fn is_number(&self) -> bool {
|
||||
match &self {
|
||||
Content::Structure(FlatType::Apply {
|
||||
module_name, name, ..
|
||||
}) => module_name.as_str() == types::MOD_NUM && name.as_str() == types::TYPE_NUM,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FlatType {
|
||||
Apply {
|
||||
|
@ -435,6 +453,7 @@ pub enum FlatType {
|
|||
Erroneous(Problem),
|
||||
EmptyRecord,
|
||||
EmptyTagUnion,
|
||||
Boolean(boolean_algebra::Bool),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
|
@ -478,6 +497,10 @@ fn occurs(subs: &mut Subs, seen: &ImSet<Variable>, var: Variable) -> bool {
|
|||
.values()
|
||||
.any(|vars| vars.iter().any(|var| occurs(subs, &new_seen, *var)))
|
||||
}
|
||||
Boolean(b) => b
|
||||
.variables()
|
||||
.iter()
|
||||
.any(|var| occurs(subs, &new_seen, *var)),
|
||||
EmptyRecord | EmptyTagUnion | Erroneous(_) => false,
|
||||
}
|
||||
}
|
||||
|
@ -556,6 +579,12 @@ fn get_var_names(
|
|||
|
||||
taken_names
|
||||
}
|
||||
FlatType::Boolean(b) => b
|
||||
.variables()
|
||||
.into_iter()
|
||||
.fold(taken_names, |answer, arg_var| {
|
||||
get_var_names(subs, arg_var, answer)
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -755,6 +784,8 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
|
|||
}
|
||||
}
|
||||
|
||||
Boolean(b) => ErrorType::Boolean(b),
|
||||
|
||||
Erroneous(_) => ErrorType::Error,
|
||||
}
|
||||
}
|
||||
|
@ -806,6 +837,11 @@ fn restore_content(subs: &mut Subs, content: &Content) {
|
|||
|
||||
subs.restore(*ext_var);
|
||||
}
|
||||
Boolean(b) => {
|
||||
for var in b.variables() {
|
||||
subs.restore(var);
|
||||
}
|
||||
}
|
||||
Erroneous(_) => (),
|
||||
},
|
||||
Alias(_, _, args, var) => {
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::operator::{ArgSide, BinOp};
|
|||
use crate::region::Located;
|
||||
use crate::region::Region;
|
||||
use crate::subs::Variable;
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
use std::fmt;
|
||||
|
||||
// The standard modules
|
||||
|
@ -39,6 +40,8 @@ pub enum Type {
|
|||
name: Uppercase,
|
||||
args: Vec<Type>,
|
||||
},
|
||||
/// Boolean type used in uniqueness inference
|
||||
Boolean(boolean_algebra::Bool),
|
||||
Variable(Variable),
|
||||
/// A type error, which will code gen to a runtime error
|
||||
Erroneous(Problem),
|
||||
|
@ -185,6 +188,7 @@ impl fmt::Debug for Type {
|
|||
}
|
||||
}
|
||||
}
|
||||
Type::Boolean(b) => write!(f, "{:?}", b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -320,6 +324,8 @@ pub enum Reason {
|
|||
IntLiteral,
|
||||
InterpolatedStringVar,
|
||||
WhenBranch { index: usize },
|
||||
IfCondition,
|
||||
IfBranch { index: usize },
|
||||
ElemInList,
|
||||
RecordUpdateValue(Lowercase),
|
||||
RecordUpdateKeys(Ident, SendMap<Lowercase, Type>),
|
||||
|
@ -389,6 +395,7 @@ pub enum ErrorType {
|
|||
Vec<(Lowercase, ErrorType)>,
|
||||
Box<ErrorType>,
|
||||
),
|
||||
Boolean(boolean_algebra::Bool),
|
||||
Error,
|
||||
}
|
||||
|
||||
|
|
21
src/unify.rs
21
src/unify.rs
|
@ -5,6 +5,7 @@ use crate::subs::Content::{self, *};
|
|||
use crate::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
|
||||
use crate::types::RecordFieldLabel;
|
||||
use crate::types::{Mismatch, Problem};
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
|
||||
type Pool = Vec<Variable>;
|
||||
|
||||
|
@ -15,9 +16,9 @@ struct Context {
|
|||
second_desc: Descriptor,
|
||||
}
|
||||
|
||||
struct RecordStructure {
|
||||
fields: ImMap<RecordFieldLabel, Variable>,
|
||||
ext: Variable,
|
||||
pub struct RecordStructure {
|
||||
pub fields: ImMap<RecordFieldLabel, Variable>,
|
||||
pub ext: Variable,
|
||||
}
|
||||
|
||||
struct TagUnionStructure {
|
||||
|
@ -404,6 +405,18 @@ fn unify_flat_type(
|
|||
unify_tag_union(subs, pool, ctx, union1, union2)
|
||||
}
|
||||
|
||||
(Boolean(b1), Boolean(b2)) => {
|
||||
if let Some(substitution) = boolean_algebra::try_unify(b1.clone(), b2.clone()) {
|
||||
for (var, replacement) in substitution {
|
||||
subs.set_content(var, Structure(FlatType::Boolean(replacement)));
|
||||
}
|
||||
|
||||
vec![]
|
||||
} else {
|
||||
mismatch()
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Apply {
|
||||
module_name: l_module_name,
|
||||
|
@ -506,7 +519,7 @@ fn unify_flex(
|
|||
}
|
||||
}
|
||||
|
||||
fn gather_fields(
|
||||
pub fn gather_fields(
|
||||
subs: &mut Subs,
|
||||
fields: ImMap<RecordFieldLabel, Variable>,
|
||||
var: Variable,
|
||||
|
|
|
@ -1,168 +1,573 @@
|
|||
use crate::collections::ImMap;
|
||||
// Based on work by Edsko de Vries for tfp 2007
|
||||
//
|
||||
// http://www.edsko.net/tcd/pub/tfp07-prototype-snapshot.tar.gz
|
||||
//
|
||||
// Thank you Edsko!
|
||||
//
|
||||
// quoting from that work:
|
||||
//
|
||||
// > Main reference for this module is "Boolean Reasoning: The Logic of
|
||||
// > Boolean Equations" by Frank Markham Brown.
|
||||
use crate::collections::{ImMap, ImSet};
|
||||
use crate::subs::Variable;
|
||||
|
||||
pub fn unify(typ: &BooleanAlgebra, _expected: &BooleanAlgebra) -> Option<Substitution> {
|
||||
// find the most general unifier.
|
||||
let mut val = typ.clone();
|
||||
let fv = val.variables();
|
||||
let (mgu, consistency_condition) = boolean_unification(&mut val, &fv);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Bool {
|
||||
Zero,
|
||||
One,
|
||||
And(Box<Bool>, Box<Bool>),
|
||||
Or(Box<Bool>, Box<Bool>),
|
||||
Not(Box<Bool>),
|
||||
Variable(Variable),
|
||||
}
|
||||
|
||||
// the consistency_condition must be a base term, and must evaluate to False
|
||||
if !consistency_condition.evaluate() {
|
||||
Some(mgu)
|
||||
use self::Bool::*;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn not(nested: Bool) -> Bool {
|
||||
Not(Box::new(nested))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn and(left: Bool, right: Bool) -> Bool {
|
||||
And(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn or(left: Bool, right: Bool) -> Bool {
|
||||
Or(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
pub fn any<I>(mut it: I) -> Bool
|
||||
where
|
||||
I: Iterator<Item = Bool>,
|
||||
{
|
||||
if let Some(first) = it.next() {
|
||||
it.fold(first, or)
|
||||
} else {
|
||||
Zero
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all(terms: Product<Bool>) -> Bool {
|
||||
let mut it = terms.into_iter();
|
||||
|
||||
if let Some(first) = it.next() {
|
||||
it.fold(first, and)
|
||||
} else {
|
||||
One
|
||||
}
|
||||
}
|
||||
|
||||
type Substitution = ImMap<Variable, Bool>;
|
||||
|
||||
type Product<A> = ImSet<A>;
|
||||
type Sum<A> = ImSet<A>;
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
impl Bool {
|
||||
pub fn variables(&self) -> ImSet<Variable> {
|
||||
let mut result = ImSet::default();
|
||||
|
||||
self.variables_help(&mut result);
|
||||
result
|
||||
}
|
||||
|
||||
fn variables_help(&self, vars: &mut ImSet<Variable>) {
|
||||
match self {
|
||||
Zero => (),
|
||||
One => (),
|
||||
And(left, right) => {
|
||||
left.variables_help(vars);
|
||||
right.variables_help(vars)
|
||||
}
|
||||
Or(left, right) => {
|
||||
left.variables_help(vars);
|
||||
right.variables_help(vars)
|
||||
}
|
||||
Not(nested) => nested.variables_help(vars),
|
||||
Variable(var) => {
|
||||
vars.insert(*var);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn map_variables<F>(&self, f: &mut F) -> Self
|
||||
where
|
||||
F: FnMut(Variable) -> Variable,
|
||||
{
|
||||
match self {
|
||||
Zero => Zero,
|
||||
One => One,
|
||||
And(left, right) => and(left.map_variables(f), right.map_variables(f)),
|
||||
Or(left, right) => or(left.map_variables(f), right.map_variables(f)),
|
||||
Not(nested) => not(nested.map_variables(f)),
|
||||
Variable(current) => Variable(f(*current)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substitute(&self, substitutions: &Substitution) -> Self {
|
||||
match self {
|
||||
Zero => Zero,
|
||||
One => One,
|
||||
And(left, right) => and(
|
||||
left.substitute(substitutions),
|
||||
right.substitute(substitutions),
|
||||
),
|
||||
Or(left, right) => or(
|
||||
left.substitute(substitutions),
|
||||
right.substitute(substitutions),
|
||||
),
|
||||
Not(nested) => not(nested.substitute(substitutions)),
|
||||
Variable(current) => match substitutions.get(current) {
|
||||
Some(new) => new.clone(),
|
||||
None => Variable(*current),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_var(&self) -> bool {
|
||||
match self {
|
||||
Variable(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn not(nested: Bool) -> Bool {
|
||||
not(nested)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn and(left: Bool, right: Bool) -> Bool {
|
||||
and(left, right)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn or(left: Bool, right: Bool) -> Bool {
|
||||
or(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simplify(term: Bool) -> Bool {
|
||||
let normalized = normalize_term(term);
|
||||
let a = term_to_sop(normalized);
|
||||
let b = normalize_sop(a);
|
||||
let after_bcf = bcf(b);
|
||||
sop_to_term(simplify_sop(after_bcf))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn sop_to_term(sop: Sop) -> Bool {
|
||||
any(sop.into_iter().map(all))
|
||||
}
|
||||
|
||||
pub fn simplify_sop(sop: Sop) -> Sop {
|
||||
// sort by length longest to shortest (proxy for how many variables there are)
|
||||
let mut sorted: Vec<ImSet<Bool>> = sop.clone().into_iter().collect();
|
||||
|
||||
sorted.sort_by(|x, y| y.len().cmp(&x.len()));
|
||||
|
||||
// filter out anything that is included in the remaining elements
|
||||
let mut active = sop;
|
||||
let mut result = ImSet::default();
|
||||
for t in sorted {
|
||||
if !(active.remove(&t).is_some() && included(all(t.clone()), sop_to_term(active.clone()))) {
|
||||
result.insert(t);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Blake canonical form
|
||||
fn bcf(sop: Sop) -> Sop {
|
||||
absorptive(syllogistic(sop))
|
||||
}
|
||||
|
||||
fn syllogistic(terms: Sop) -> Sop {
|
||||
let mut cs_prime = ImSet::default();
|
||||
|
||||
for c in cartesian_product(terms.clone())
|
||||
.iter()
|
||||
.filter_map(|(x, y)| consensus(x, y))
|
||||
{
|
||||
if !terms
|
||||
.clone()
|
||||
.into_iter()
|
||||
.any(|x| included_term(c.clone(), x))
|
||||
{
|
||||
cs_prime.insert(c);
|
||||
}
|
||||
}
|
||||
|
||||
if cs_prime.is_empty() {
|
||||
terms
|
||||
} else {
|
||||
syllogistic(terms.union(cs_prime))
|
||||
}
|
||||
}
|
||||
|
||||
/// Absorption (apply the identify p + pq = p)
|
||||
fn absorptive(sop: Sop) -> Sop {
|
||||
// TODO this is extremely inefficient!
|
||||
let mut accum: Vec<Product<Bool>> = Vec::new();
|
||||
|
||||
for product in sop {
|
||||
accum = accum
|
||||
.into_iter()
|
||||
.filter(|v| !absorbs(&product, v))
|
||||
.collect();
|
||||
accum.push(product);
|
||||
}
|
||||
|
||||
accum.into()
|
||||
}
|
||||
|
||||
/// Does p absorb q? (can we replace p + q by p?)
|
||||
/// TODO investigate: either the comment or the implementation is wrong I think?
|
||||
fn absorbs(p: &Product<Bool>, q: &Product<Bool>) -> bool {
|
||||
p.iter().all(|x| q.contains(x))
|
||||
}
|
||||
|
||||
fn consensus(p: &Product<Bool>, q: &Product<Bool>) -> Option<Product<Bool>> {
|
||||
let mut it = oppositions(p, q).into_iter();
|
||||
|
||||
// oppositions must have exactly one element
|
||||
if let Some(x) = it.next() {
|
||||
if it.next().is_none() {
|
||||
let compx = not(x.clone());
|
||||
|
||||
return Some(
|
||||
p.clone()
|
||||
.into_iter()
|
||||
.chain(q.clone().into_iter())
|
||||
.filter(|y| *y != x && *y != compx)
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn oppositions(ps: &Product<Bool>, qs: &Product<Bool>) -> Product<Bool> {
|
||||
let it1 = ps
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|p| qs.contains(¬(p.clone())));
|
||||
let it2 = qs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|q| ps.contains(¬(q.clone())));
|
||||
|
||||
it1.chain(it2).collect()
|
||||
}
|
||||
|
||||
pub fn try_unify(p: Bool, q: Bool) -> Option<Substitution> {
|
||||
let (sub, consistency) = unify(p, q);
|
||||
|
||||
let substitution = sub
|
||||
.into_iter()
|
||||
.filter(|(x, p)| *p != Variable(*x))
|
||||
.collect();
|
||||
|
||||
if consistency == Zero {
|
||||
Some(substitution)
|
||||
} else {
|
||||
// the unification has no solution
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BooleanAlgebra {
|
||||
Ground(bool),
|
||||
Disjunction(Box<BooleanAlgebra>, Box<BooleanAlgebra>),
|
||||
Conjunction(Box<BooleanAlgebra>, Box<BooleanAlgebra>),
|
||||
Negation(Box<BooleanAlgebra>),
|
||||
Variable(Variable),
|
||||
fn unify(p: Bool, q: Bool) -> (Substitution, Bool) {
|
||||
let condition = q.is_var() && !p.is_var();
|
||||
let t = if condition {
|
||||
or(and(q.clone(), not(p.clone())), and(not(q), p))
|
||||
} else {
|
||||
or(and(p.clone(), not(q.clone())), and(not(p), q))
|
||||
};
|
||||
|
||||
unify0(t.variables(), t)
|
||||
}
|
||||
|
||||
impl BooleanAlgebra {
|
||||
pub fn simplify(&mut self) {
|
||||
*self = simplify(self.clone());
|
||||
fn unify0(names: ImSet<Variable>, mut term: Bool) -> (Substitution, Bool) {
|
||||
// NOTE sort is required for stable test order that is the same as the Haskell ref. impl.
|
||||
let mut substitution: Substitution = ImMap::default();
|
||||
|
||||
let mut sorted_names: Vec<Variable> = names.into_iter().collect();
|
||||
sorted_names.sort();
|
||||
|
||||
for x in sorted_names.into_iter() {
|
||||
let mut sub_zero = ImMap::default();
|
||||
sub_zero.insert(x, Zero);
|
||||
|
||||
let mut sub_one = ImMap::default();
|
||||
sub_one.insert(x, One);
|
||||
|
||||
let subbed_zero = term.substitute(&sub_zero);
|
||||
let subbed_one = term.substitute(&sub_one);
|
||||
|
||||
term = and(subbed_zero.clone(), subbed_one.clone());
|
||||
|
||||
let replacement = simplify(or(
|
||||
subbed_zero.substitute(&substitution),
|
||||
and(Variable(x), not(subbed_one.substitute(&substitution))),
|
||||
));
|
||||
|
||||
substitution.insert(x, replacement);
|
||||
}
|
||||
|
||||
pub fn substitute(&self, var: Variable, expanded: &BooleanAlgebra) -> Self {
|
||||
use BooleanAlgebra::*;
|
||||
match self {
|
||||
Variable(v) if v == &var => expanded.clone(),
|
||||
Variable(_) | Ground(_) => self.clone(),
|
||||
(substitution, simplify(term))
|
||||
}
|
||||
|
||||
Negation(t) => Negation(Box::new(t.substitute(var, expanded))),
|
||||
// --- Simplification ---
|
||||
|
||||
Disjunction(l, r) => Disjunction(
|
||||
Box::new(l.substitute(var, expanded)),
|
||||
Box::new(r.substitute(var, expanded)),
|
||||
),
|
||||
/// Normalization of terms. Applies (in bottom-up fashion) the identities
|
||||
///
|
||||
/// x * 1 = x
|
||||
/// x * 0 = 0
|
||||
/// x + 1 = 1
|
||||
/// x + 0 = x
|
||||
/// !1 = 0
|
||||
/// !0 = 1
|
||||
pub fn normalize_term(term: Bool) -> Bool {
|
||||
match term {
|
||||
And(left, right) => {
|
||||
let p = normalize_term(*left);
|
||||
let q = normalize_term(*right);
|
||||
|
||||
Conjunction(l, r) => Conjunction(
|
||||
Box::new(l.substitute(var, expanded)),
|
||||
Box::new(r.substitute(var, expanded)),
|
||||
),
|
||||
match (p == One, p == Zero, q == One, q == Zero) {
|
||||
(true, _, _, _) => q,
|
||||
(_, true, _, _) => Zero,
|
||||
(_, _, true, _) => p,
|
||||
(_, _, _, true) => Zero,
|
||||
_ => and(p, q),
|
||||
}
|
||||
}
|
||||
Or(left, right) => {
|
||||
let p = normalize_term(*left);
|
||||
let q = normalize_term(*right);
|
||||
|
||||
pub fn evaluate(&self) -> bool {
|
||||
use BooleanAlgebra::*;
|
||||
match self {
|
||||
Variable(v) => panic!(
|
||||
"Cannot evaluate boolean expression with unbound variable {:?}",
|
||||
v
|
||||
),
|
||||
Ground(b) => *b,
|
||||
Negation(t) => !(t.evaluate()),
|
||||
Disjunction(l, r) => l.evaluate() || r.evaluate(),
|
||||
Conjunction(l, r) => l.evaluate() && r.evaluate(),
|
||||
match (p == One, p == Zero, q == One, q == Zero) {
|
||||
(true, _, _, _) => One,
|
||||
(_, true, _, _) => q,
|
||||
(_, _, true, _) => One,
|
||||
(_, _, _, true) => p,
|
||||
_ => or(p, q),
|
||||
}
|
||||
}
|
||||
Not(nested) => {
|
||||
let p = normalize_term(*nested);
|
||||
|
||||
pub fn variables(&self) -> Vec<Variable> {
|
||||
let mut vars = Vec::new();
|
||||
variables_help(self, &mut vars);
|
||||
vars
|
||||
match (p == One, p == Zero) {
|
||||
(true, _) => Zero,
|
||||
(_, true) => One,
|
||||
_ => not(p),
|
||||
}
|
||||
}
|
||||
_ => term,
|
||||
}
|
||||
}
|
||||
|
||||
fn variables_help(bconstraint: &BooleanAlgebra, variables: &mut Vec<Variable>) {
|
||||
use BooleanAlgebra::*;
|
||||
// --- Inclusion ---
|
||||
|
||||
match bconstraint {
|
||||
Variable(v) => variables.push(v.clone()),
|
||||
Ground(_) => {}
|
||||
Negation(t) => variables_help(t, variables),
|
||||
Disjunction(l, r) => {
|
||||
variables_help(l, variables);
|
||||
variables_help(r, variables);
|
||||
}
|
||||
Conjunction(l, r) => {
|
||||
variables_help(l, variables);
|
||||
variables_help(r, variables);
|
||||
pub fn included(g: Bool, h: Bool) -> bool {
|
||||
contradiction(and(g, not(h)))
|
||||
}
|
||||
|
||||
fn included_term(g: Product<Bool>, h: Product<Bool>) -> bool {
|
||||
included(all(g), all(h))
|
||||
}
|
||||
|
||||
// --- Tautology / Contradiction ---
|
||||
|
||||
fn tautology(term: Bool) -> bool {
|
||||
normalize_pos(term_to_pos(normalize_term(term))).is_empty()
|
||||
}
|
||||
|
||||
pub fn contradiction(term: Bool) -> bool {
|
||||
tautology(not(term))
|
||||
}
|
||||
|
||||
// --- Normalization of POS / SOP
|
||||
|
||||
type Pos = Product<Sum<Bool>>;
|
||||
type Sop = Sum<Product<Bool>>;
|
||||
|
||||
fn term_to_pos(term: Bool) -> Pos {
|
||||
conj_to_list(cnf(term))
|
||||
.into_iter()
|
||||
.map(disj_to_list)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn term_to_sop(term: Bool) -> Sop {
|
||||
disj_to_list(dnf(term))
|
||||
.into_iter()
|
||||
.map(conj_to_list)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn conj_to_list(term: Bool) -> Product<Bool> {
|
||||
match term {
|
||||
And(left, right) => {
|
||||
let p = conj_to_list(*left);
|
||||
let q = conj_to_list(*right);
|
||||
|
||||
p.union(q)
|
||||
}
|
||||
_ => unit(term),
|
||||
}
|
||||
}
|
||||
|
||||
fn simplify(bconstraint: BooleanAlgebra) -> BooleanAlgebra {
|
||||
use BooleanAlgebra::*;
|
||||
match bconstraint {
|
||||
Variable(_) | Ground(_) => bconstraint,
|
||||
fn disj_to_list(term: Bool) -> Sum<Bool> {
|
||||
match term {
|
||||
Or(left, right) => {
|
||||
let p = disj_to_list(*left);
|
||||
let q = disj_to_list(*right);
|
||||
|
||||
Negation(nested) => match simplify(*nested) {
|
||||
Ground(t) => Ground(!t),
|
||||
other => Negation(Box::new(other)),
|
||||
},
|
||||
|
||||
Disjunction(l, r) => match (simplify(*l), simplify(*r)) {
|
||||
(Ground(true), _) => Ground(true),
|
||||
(_, Ground(true)) => Ground(true),
|
||||
(Ground(false), rr) => rr,
|
||||
(ll, Ground(false)) => ll,
|
||||
(ll, rr) => Disjunction(Box::new(ll), Box::new(rr)),
|
||||
},
|
||||
|
||||
Conjunction(l, r) => match (simplify(*l), simplify(*r)) {
|
||||
(Ground(true), rr) => rr,
|
||||
(ll, Ground(true)) => ll,
|
||||
(Ground(false), _) => Ground(false),
|
||||
(_, Ground(false)) => Ground(false),
|
||||
(ll, rr) => Conjunction(Box::new(ll), Box::new(rr)),
|
||||
},
|
||||
p.union(q)
|
||||
}
|
||||
_ => unit(term),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Substitution {
|
||||
pairs: ImMap<Variable, BooleanAlgebra>,
|
||||
fn normalize_pos(pos: Pos) -> Pos {
|
||||
let singleton_one = unit(One);
|
||||
|
||||
pos.into_iter()
|
||||
.map(normalize_disj)
|
||||
.filter(|normalized| *normalized != singleton_one)
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Substitution {
|
||||
pub fn empty() -> Self {
|
||||
Substitution {
|
||||
pairs: ImMap::default(),
|
||||
pub fn normalize_sop(sop: Sop) -> Sop {
|
||||
let singleton_zero = unit(Zero);
|
||||
|
||||
sop.into_iter()
|
||||
.map(normalize_conj)
|
||||
.filter(|normalized| *normalized != singleton_zero)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn cartesian_product<A>(set: ImSet<A>) -> ImSet<(A, A)>
|
||||
where
|
||||
A: Eq + Clone + core::hash::Hash,
|
||||
{
|
||||
let mut result = ImSet::default();
|
||||
|
||||
for x in set.clone().into_iter() {
|
||||
for y in set.clone() {
|
||||
if x == y {
|
||||
break;
|
||||
}
|
||||
|
||||
result.insert((x.clone(), y));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, var: Variable, term: BooleanAlgebra) {
|
||||
self.pairs.insert(var, term);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn get(&self, var: Variable) -> Option<&BooleanAlgebra> {
|
||||
self.pairs.get(&var)
|
||||
fn unit<A>(a: A) -> ImSet<A>
|
||||
where
|
||||
A: Clone + Eq + core::hash::Hash,
|
||||
{
|
||||
let mut result = ImSet::default();
|
||||
result.insert(a);
|
||||
result
|
||||
}
|
||||
|
||||
fn normalize_disj(mut sum: Sum<Bool>) -> Sum<Bool> {
|
||||
let is_always_true =
|
||||
sum.clone().into_iter().any(|x| sum.contains(¬(x))) || sum.contains(&One);
|
||||
|
||||
if is_always_true {
|
||||
unit(One)
|
||||
} else {
|
||||
sum.remove(&Zero);
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
fn boolean_unification(
|
||||
term: &mut BooleanAlgebra,
|
||||
variables: &[Variable],
|
||||
) -> (Substitution, BooleanAlgebra) {
|
||||
use BooleanAlgebra::*;
|
||||
let mut substitution = Substitution::empty();
|
||||
fn normalize_conj(mut product: Product<Bool>) -> Product<Bool> {
|
||||
let is_always_false = product
|
||||
.clone()
|
||||
.into_iter()
|
||||
.any(|x| product.contains(¬(x)))
|
||||
|| product.contains(&Zero);
|
||||
|
||||
for var in variables {
|
||||
let t0 = term.clone().substitute(*var, &Ground(false));
|
||||
let t1 = term.clone().substitute(*var, &Ground(true));
|
||||
|
||||
*term = Conjunction(Box::new(t1.clone()), Box::new(t0.clone()));
|
||||
term.simplify();
|
||||
|
||||
let mut sub = Disjunction(
|
||||
Box::new(t0),
|
||||
Box::new(Conjunction(
|
||||
Box::new(Variable(*var)),
|
||||
Box::new(Negation(Box::new(t1))),
|
||||
)),
|
||||
);
|
||||
sub.simplify();
|
||||
|
||||
substitution.insert(var.clone(), sub);
|
||||
if is_always_false {
|
||||
unit(Zero)
|
||||
} else {
|
||||
product.remove(&One);
|
||||
product
|
||||
}
|
||||
}
|
||||
|
||||
/// Conjunction Normal Form
|
||||
fn cnf(term: Bool) -> Bool {
|
||||
match nnf(term) {
|
||||
And(p, q) => and(cnf(*p), cnf(*q)),
|
||||
Or(p, q) => distr_cnf(cnf(*p), cnf(*q)),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_cnf(p: Bool, q: Bool) -> Bool {
|
||||
match p {
|
||||
And(p1, p2) => and(distr_cnf(*p1, q.clone()), distr_cnf(*p2, q)),
|
||||
_ => distr_cnf_help(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_cnf_help(p: Bool, q: Bool) -> Bool {
|
||||
match q {
|
||||
And(q1, q2) => and(distr_cnf(p.clone(), *q1), distr_cnf(p, *q2)),
|
||||
_ => or(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
/// Disjunction Normal Form
|
||||
pub fn dnf(term: Bool) -> Bool {
|
||||
match nnf(term) {
|
||||
And(p, q) => distr_dnf(dnf(*p), dnf(*q)),
|
||||
Or(p, q) => or(dnf(*p), dnf(*q)),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_dnf(p: Bool, q: Bool) -> Bool {
|
||||
match p {
|
||||
Or(p1, p2) => or(distr_dnf(*p1, q.clone()), distr_dnf(*p2, q)),
|
||||
_ => distr_dnf_help(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_dnf_help(p: Bool, q: Bool) -> Bool {
|
||||
match q {
|
||||
Or(q1, q2) => or(distr_dnf(p.clone(), *q1), distr_dnf(p, *q2)),
|
||||
_ => and(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
/// Negation Normal Form
|
||||
pub fn nnf(term: Bool) -> Bool {
|
||||
match term {
|
||||
Not(n) => nnf_help(*n),
|
||||
_ => term,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nnf_help(term: Bool) -> Bool {
|
||||
match term {
|
||||
Zero => One,
|
||||
One => Zero,
|
||||
And(p, q) => or(nnf_help(*p), nnf_help(*q)),
|
||||
Or(p, q) => and(nnf_help(*p), nnf_help(*q)),
|
||||
// double negation
|
||||
Not(nested) => nnf(*nested),
|
||||
Variable(_) => not(term),
|
||||
}
|
||||
|
||||
(substitution, term.clone())
|
||||
}
|
||||
|
|
|
@ -1,103 +1,26 @@
|
|||
use crate::collections::SendMap;
|
||||
use crate::region::Region;
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::types::{self, LetConstraint, Reason};
|
||||
|
||||
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||
Constraint::Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars,
|
||||
def_types: SendMap::default(),
|
||||
defs_constraint: constraint,
|
||||
ret_constraint: Constraint::True,
|
||||
}))
|
||||
}
|
||||
use crate::subs::VarStore;
|
||||
use crate::types::Type;
|
||||
use crate::uniqueness::boolean_algebra::Bool;
|
||||
|
||||
pub fn lift(var_store: &VarStore, typ: Type) -> Type {
|
||||
let uniq_var = var_store.fresh();
|
||||
let uniq_type = Variable(uniq_var);
|
||||
let uniq_type = Bool::Variable(uniq_var);
|
||||
|
||||
attr_type(uniq_type, typ)
|
||||
}
|
||||
|
||||
pub fn int_literal(var_store: &VarStore, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = lift(var_store, number_literal_type("Int", "Integer"));
|
||||
let reason = Reason::IntLiteral;
|
||||
|
||||
num_literal(var_store, typ, reason, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_literal(var_store: &VarStore, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = lift(var_store, number_literal_type("Float", "FloatingPoint"));
|
||||
let reason = Reason::FloatLiteral;
|
||||
|
||||
num_literal(var_store, typ, reason, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num_literal(
|
||||
var_store: &VarStore,
|
||||
literal_type: Type,
|
||||
reason: Reason,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let num_var = var_store.fresh();
|
||||
let num_type = Variable(num_var);
|
||||
let expected_literal = ForReason(reason, literal_type, region);
|
||||
|
||||
And(vec![
|
||||
Eq(num_type.clone(), expected_literal, region),
|
||||
Eq(num_type, expected, region),
|
||||
])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn number_literal_type(module_name: &str, type_name: &str) -> Type {
|
||||
builtin_type(
|
||||
types::MOD_NUM,
|
||||
types::TYPE_NUM,
|
||||
vec![builtin_type(module_name, type_name, Vec::new())],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
|
||||
Type::Apply {
|
||||
module_name: module_name.into(),
|
||||
name: type_name.into(),
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_list_type(var: Variable) -> Type {
|
||||
list_type(Type::Variable(var))
|
||||
}
|
||||
|
||||
pub fn list_type(typ: Type) -> Type {
|
||||
builtin_type("List", "List", vec![typ])
|
||||
}
|
||||
|
||||
pub fn str_type() -> Type {
|
||||
builtin_type("Str", "Str", Vec::new())
|
||||
}
|
||||
|
||||
type Uniqueness = Type;
|
||||
type Uniqueness = Bool;
|
||||
|
||||
pub fn attr_type(uniq: Uniqueness, typ: Type) -> Type {
|
||||
builtin_type("Attr", "Attr", vec![uniq, typ])
|
||||
crate::constrain::builtins::builtin_type("Attr", "Attr", vec![Type::Boolean(uniq), typ])
|
||||
}
|
||||
|
||||
pub fn shared_type() -> Uniqueness {
|
||||
builtin_type("Attr", "Shared", Vec::new())
|
||||
Bool::Zero
|
||||
}
|
||||
|
||||
/// We usually just leave a type parameter unbound (written `*`) when it's unique
|
||||
#[allow(dead_code)]
|
||||
pub fn unique_type() -> Uniqueness {
|
||||
builtin_type("Attr", "Unique", Vec::new())
|
||||
Bool::One
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -89,7 +89,6 @@ pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, VarStore, Variab
|
|||
pub fn uniq_expr(
|
||||
expr_str: &str,
|
||||
) -> (
|
||||
Output,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
|
@ -108,7 +107,6 @@ pub fn uniq_expr_with(
|
|||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> (
|
||||
Output,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
|
@ -124,12 +122,13 @@ pub fn uniq_expr_with(
|
|||
|
||||
let next_var = var_store1.into();
|
||||
let subs1 = Subs::new(next_var);
|
||||
|
||||
// double check
|
||||
let var_store2 = VarStore::new(next_var);
|
||||
|
||||
let variable2 = var_store2.fresh();
|
||||
let expected2 = Expected::NoExpectation(Type::Variable(variable2));
|
||||
let (output2, constraint2) = roc::uniqueness::canonicalize_declaration(
|
||||
let constraint2 = roc::uniqueness::constrain_declaration(
|
||||
&var_store2,
|
||||
Region::zero(),
|
||||
loc_expr,
|
||||
|
@ -140,7 +139,6 @@ pub fn uniq_expr_with(
|
|||
let subs2 = Subs::new(var_store2.into());
|
||||
|
||||
(
|
||||
output2,
|
||||
output,
|
||||
problems,
|
||||
subs1,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
|
@ -8,13 +11,24 @@ mod helpers;
|
|||
|
||||
#[cfg(test)]
|
||||
mod test_boolean_algebra {
|
||||
use roc::subs;
|
||||
use roc::subs::VarStore;
|
||||
use roc::uniqueness::boolean_algebra;
|
||||
use roc::uniqueness::boolean_algebra::BooleanAlgebra::{self, *};
|
||||
use roc::uniqueness::boolean_algebra::Bool::{self, *};
|
||||
|
||||
// HELPERS
|
||||
fn simplify_eq(mut a: BooleanAlgebra, mut b: BooleanAlgebra) {
|
||||
assert_eq!(a.simplify(), b.simplify());
|
||||
fn to_var(v: usize) -> subs::Variable {
|
||||
subs::Variable::unsafe_debug_variable(v)
|
||||
}
|
||||
|
||||
fn simplify_eq(a: Bool, b: Bool) {
|
||||
assert_eq!(boolean_algebra::simplify(a), boolean_algebra::simplify(b));
|
||||
}
|
||||
|
||||
fn unify_eq(a: Bool, b: Bool, expected: std::collections::HashMap<roc::subs::Variable, Bool>) {
|
||||
let result = boolean_algebra::try_unify(a, b);
|
||||
|
||||
assert_eq!(result, Some(expected.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -22,10 +36,7 @@ mod test_boolean_algebra {
|
|||
let var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
|
||||
simplify_eq(
|
||||
Disjunction(Box::new(Ground(true)), Box::new(Variable(var))),
|
||||
Ground(true),
|
||||
);
|
||||
simplify_eq(Bool::or(One, Variable(var)), One);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -33,10 +44,7 @@ mod test_boolean_algebra {
|
|||
let var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
|
||||
simplify_eq(
|
||||
Disjunction(Box::new(Ground(false)), Box::new(Variable(var))),
|
||||
Variable(var),
|
||||
);
|
||||
simplify_eq(Bool::or(Zero, Variable(var)), Variable(var));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -44,42 +52,127 @@ mod test_boolean_algebra {
|
|||
let var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
|
||||
simplify_eq(
|
||||
Conjunction(Box::new(Ground(false)), Box::new(Variable(var))),
|
||||
Ground(false),
|
||||
simplify_eq(Bool::and(Zero, Variable(var)), Zero);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unify_example_const() {
|
||||
unify_eq(
|
||||
Variable(to_var(1)),
|
||||
Variable(to_var(4)),
|
||||
hashmap![to_var(1) => Variable(to_var(4))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(5)),
|
||||
Variable(to_var(2)),
|
||||
hashmap![to_var(2) => Variable(to_var(5))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(6)),
|
||||
Variable(to_var(0)),
|
||||
hashmap![to_var(0) => Variable(to_var(6))].into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unify_single_var() {
|
||||
let var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
|
||||
let result = boolean_algebra::unify(&Variable(var), &Ground(true));
|
||||
|
||||
if let Some(sub) = result {
|
||||
assert_eq!(Some(&Ground(false)), sub.get(var));
|
||||
} else {
|
||||
panic!("result is None");
|
||||
}
|
||||
fn unify_example_apply() {
|
||||
unify_eq(
|
||||
Variable(to_var(1)),
|
||||
Variable(to_var(6)),
|
||||
hashmap![to_var(1) => Variable(to_var(6))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(5)),
|
||||
Variable(to_var(6)),
|
||||
hashmap![to_var(5) => Variable(to_var(6))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(3)),
|
||||
Variable(to_var(7)),
|
||||
hashmap![to_var(3) => Variable(to_var(7))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(8)),
|
||||
Variable(to_var(4)),
|
||||
hashmap![to_var(4) => Variable(to_var(8))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(9)),
|
||||
Variable(to_var(2)),
|
||||
hashmap![to_var(2) => Variable(to_var(9))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(10)),
|
||||
Variable(to_var(0)),
|
||||
hashmap![to_var(0) => Variable(to_var(10))].into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unify_or() {
|
||||
let var_store = VarStore::default();
|
||||
let a = var_store.fresh();
|
||||
let b = var_store.fresh();
|
||||
|
||||
let result = boolean_algebra::unify(
|
||||
&Disjunction(Box::new(Variable(a)), Box::new(Variable(b))),
|
||||
&Ground(true),
|
||||
fn unify_example_fst() {
|
||||
unify_eq(
|
||||
Variable(to_var(1)),
|
||||
Variable(to_var(5)),
|
||||
hashmap![to_var(1) => Variable(to_var(5))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(3)),
|
||||
Variable(to_var(2)),
|
||||
hashmap![to_var(2) => Variable(to_var(3))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(5)),
|
||||
Bool::or(Bool::or(Variable(to_var(3)), Zero), Zero),
|
||||
hashmap![to_var(3) => Variable(to_var(5))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(6)),
|
||||
Variable(to_var(0)),
|
||||
hashmap![to_var(0) => Variable(to_var(6))].into(),
|
||||
);
|
||||
|
||||
if let Some(sub) = result {
|
||||
assert_eq!(Some(&Variable(b)), sub.get(a));
|
||||
assert_eq!(Some(&Ground(false)), sub.get(b));
|
||||
} else {
|
||||
panic!("result is None");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unify_example_idid() {
|
||||
unify_eq(
|
||||
Variable(to_var(3)),
|
||||
Variable(to_var(4)),
|
||||
hashmap![to_var(3) => Variable(to_var(4))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(5)),
|
||||
Variable(to_var(2)),
|
||||
hashmap![to_var(2) => Variable(to_var(5))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(5)),
|
||||
Variable(to_var(1)),
|
||||
hashmap![to_var(1) => Variable(to_var(5))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Zero,
|
||||
Variable(to_var(5)),
|
||||
hashmap![to_var(5) => Zero].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(7)),
|
||||
Variable(to_var(4)),
|
||||
hashmap![to_var(4) => Variable(to_var(7))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(8)),
|
||||
Variable(to_var(9)),
|
||||
hashmap![to_var(8) => Variable(to_var(9))].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Variable(to_var(7)),
|
||||
Zero,
|
||||
hashmap![to_var(7) => Zero].into(),
|
||||
);
|
||||
unify_eq(
|
||||
Zero,
|
||||
Variable(to_var(0)),
|
||||
hashmap![to_var(0) => Zero].into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
603
tests/test_emit.rs
Normal file
603
tests/test_emit.rs
Normal file
|
@ -0,0 +1,603 @@
|
|||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate inkwell;
|
||||
extern crate roc;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_crane {
|
||||
use crate::helpers::can_expr;
|
||||
use bumpalo::Bump;
|
||||
use cranelift::prelude::{AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext};
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
use cranelift_codegen::isa;
|
||||
use cranelift_codegen::settings;
|
||||
use cranelift_codegen::verifier::verify_function;
|
||||
use cranelift_module::{default_libcall_names, Linkage, Module};
|
||||
use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder};
|
||||
use inkwell::context::Context;
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
use inkwell::passes::PassManager;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::OptimizationLevel;
|
||||
use roc::collections::{ImMap, MutMap};
|
||||
use roc::crane::build::{declare_proc, define_proc_body, ScopeEntry};
|
||||
use roc::crane::convert::type_from_content;
|
||||
use roc::infer::infer_expr;
|
||||
use roc::llvm::build::build_proc;
|
||||
use roc::llvm::convert::content_to_basic_type;
|
||||
use roc::mono::expr::Expr;
|
||||
use roc::subs::Subs;
|
||||
use std::mem;
|
||||
use target_lexicon::HOST;
|
||||
|
||||
macro_rules! assert_crane_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
let arena = Bump::new();
|
||||
let mut module: Module<SimpleJITBackend> =
|
||||
Module::new(SimpleJITBuilder::new(default_libcall_names()));
|
||||
let mut ctx = module.make_context();
|
||||
let mut func_ctx = FunctionBuilderContext::new();
|
||||
|
||||
let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src);
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
let mut unify_problems = Vec::new();
|
||||
let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable);
|
||||
let shared_builder = settings::builder();
|
||||
let shared_flags = settings::Flags::new(shared_builder);
|
||||
let cfg = match isa::lookup(HOST) {
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"Unsupported target ISA for test runner {:?} - error: {:?}",
|
||||
HOST, err
|
||||
);
|
||||
}
|
||||
Ok(isa_builder) => {
|
||||
let isa = isa_builder.finish(shared_flags.clone());
|
||||
|
||||
isa.frontend_config()
|
||||
}
|
||||
};
|
||||
|
||||
let main_fn_name = "$Test.main";
|
||||
|
||||
// Compute main_fn_ret_type before moving subs to Env
|
||||
let main_ret_type = type_from_content(&content, &mut subs, cfg);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let mut procs = MutMap::default();
|
||||
let env = roc::crane::build::Env {
|
||||
arena: &arena,
|
||||
subs,
|
||||
cfg,
|
||||
};
|
||||
|
||||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||||
let mono_expr = Expr::new(&arena, &env.subs, expr, &mut procs);
|
||||
let mut scope = ImMap::default();
|
||||
let mut declared = Vec::with_capacity(procs.len());
|
||||
|
||||
// Declare all the Procs, then insert them into scope so their bodies
|
||||
// can look up their Funcs in scope later when calling each other by value.
|
||||
for (name, opt_proc) in procs.iter() {
|
||||
if let Some(proc) = opt_proc {
|
||||
let (func_id, sig) = declare_proc(&env, &mut module, name.clone(), proc);
|
||||
|
||||
declared.push((proc.clone(), sig.clone(), func_id));
|
||||
|
||||
scope.insert(name.clone(), ScopeEntry::Func { func_id, sig });
|
||||
}
|
||||
}
|
||||
|
||||
// Now that scope includes all the Procs, we can build their bodies.
|
||||
for (proc, sig, fn_id) in declared {
|
||||
define_proc_body(
|
||||
&env,
|
||||
&mut ctx,
|
||||
&mut module,
|
||||
fn_id,
|
||||
&scope,
|
||||
sig,
|
||||
proc,
|
||||
&procs,
|
||||
);
|
||||
|
||||
// Verify the function we just defined
|
||||
if let Err(errors) = verify_function(&ctx.func, &shared_flags) {
|
||||
// NOTE: We don't include proc here because it's already
|
||||
// been moved. If you need to know which proc failed, go back
|
||||
// and add some logging.
|
||||
panic!("Errors defining proc: {}", errors);
|
||||
}
|
||||
}
|
||||
|
||||
// Add main itself
|
||||
let mut sig = module.make_signature();
|
||||
sig.returns.push(AbiParam::new(main_ret_type));
|
||||
|
||||
let main_fn = module
|
||||
.declare_function(main_fn_name, Linkage::Local, &sig)
|
||||
.unwrap();
|
||||
|
||||
ctx.func.signature = sig;
|
||||
ctx.func.name = ExternalName::user(0, main_fn.as_u32());
|
||||
|
||||
{
|
||||
let mut builder: FunctionBuilder =
|
||||
FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
|
||||
let block = builder.create_ebb();
|
||||
|
||||
builder.switch_to_block(block);
|
||||
// TODO try deleting this line and seeing if everything still works.
|
||||
builder.append_ebb_params_for_function_params(block);
|
||||
|
||||
let main_body =
|
||||
roc::crane::build::build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs);
|
||||
|
||||
builder.ins().return_(&[main_body]);
|
||||
// TODO re-enable this once Switch stops making unsealed
|
||||
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||
// builder.seal_block(block);
|
||||
builder.seal_all_blocks();
|
||||
builder.finalize();
|
||||
}
|
||||
|
||||
module.define_function(main_fn, &mut ctx).unwrap();
|
||||
module.clear_context(&mut ctx);
|
||||
|
||||
// Perform linking
|
||||
module.finalize_definitions();
|
||||
|
||||
// Verify the main function
|
||||
if let Err(errors) = verify_function(&ctx.func, &shared_flags) {
|
||||
panic!("Errors defining {} - {}", main_fn_name, errors);
|
||||
}
|
||||
|
||||
let main_ptr = module.get_finalized_function(main_fn);
|
||||
let run_main = unsafe { mem::transmute::<_, fn() -> $ty>(main_ptr) };
|
||||
|
||||
assert_eq!(run_main(), $expected);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_llvm_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
let arena = Bump::new();
|
||||
let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src);
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
let mut unify_problems = Vec::new();
|
||||
let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable);
|
||||
|
||||
let context = Context::create();
|
||||
let module = context.create_module("app");
|
||||
let builder = context.create_builder();
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
// Enable optimizations when running cargo test --release
|
||||
if !cfg!(debug_assetions) {
|
||||
fpm.add_instruction_combining_pass();
|
||||
fpm.add_reassociate_pass();
|
||||
fpm.add_basic_alias_analysis_pass();
|
||||
fpm.add_promote_memory_to_register_pass();
|
||||
fpm.add_cfg_simplification_pass();
|
||||
fpm.add_gvn_pass();
|
||||
// TODO figure out why enabling any of these (even alone) causes LLVM to segfault
|
||||
// fpm.add_strip_dead_prototypes_pass();
|
||||
// fpm.add_dead_arg_elimination_pass();
|
||||
// fpm.add_function_inlining_pass();
|
||||
}
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let main_fn_type = content_to_basic_type(&content, &mut subs, &context)
|
||||
.expect("Unable to infer type for test expr")
|
||||
.fn_type(&[], false);
|
||||
let main_fn_name = "$Test.main";
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let mut procs = MutMap::default();
|
||||
let env = roc::llvm::build::Env {
|
||||
arena: &arena,
|
||||
subs,
|
||||
builder: &builder,
|
||||
context: &context,
|
||||
module: arena.alloc(module),
|
||||
};
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let main_body = Expr::new(&arena, &env.subs, expr, &mut procs);
|
||||
|
||||
// Add all the Procs to the module
|
||||
for (name, opt_proc) in procs.clone() {
|
||||
if let Some(proc) = opt_proc {
|
||||
// NOTE: This is here to be uncommented in case verification fails.
|
||||
// (This approach means we don't have to defensively clone name here.)
|
||||
//
|
||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||
let fn_val = build_proc(&env, name, proc, &procs);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
} else {
|
||||
// NOTE: If this fails, uncomment the above println to debug.
|
||||
panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add main to the module.
|
||||
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
|
||||
|
||||
// Add main's body
|
||||
let basic_block = context.append_basic_block(main_fn, "entry");
|
||||
|
||||
builder.position_at_end(&basic_block);
|
||||
|
||||
let ret = roc::llvm::build::build_expr(
|
||||
&env,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&mut MutMap::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
||||
if main_fn.verify(true) {
|
||||
fpm.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Function {} failed LLVM verification.", main_fn_name);
|
||||
}
|
||||
|
||||
// Uncomment this to see the module's LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
let execution_engine = env
|
||||
.module
|
||||
.create_jit_execution_engine(OptimizationLevel::None)
|
||||
.expect("Error creating JIT execution engine for test");
|
||||
|
||||
unsafe {
|
||||
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine
|
||||
.get_function(main_fn_name)
|
||||
.ok()
|
||||
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
|
||||
.expect("errored");
|
||||
|
||||
assert_eq!(main.call(), $expected);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
// Run Cranelift tests, then LLVM tests, in separate scopes.
|
||||
// These each rebuild everything from scratch, starting with
|
||||
// parsing the source, so that there's no chance their passing
|
||||
// or failing depends on leftover state from the previous one.
|
||||
{
|
||||
assert_crane_evals_to!($src, $expected, $ty);
|
||||
}
|
||||
{
|
||||
assert_llvm_evals_to!($src, $expected, $ty);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_int() {
|
||||
assert_evals_to!("123", 123, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_float() {
|
||||
assert_evals_to!("1234.0", 1234.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_first_float() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 1.23 is
|
||||
1.23 -> 12
|
||||
_ -> 34
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_second_float() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 2.34 is
|
||||
1.23 -> 63
|
||||
_ -> 48
|
||||
"#
|
||||
),
|
||||
48,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_first_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 1 is
|
||||
1 -> 12
|
||||
_ -> 34
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_second_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 2 is
|
||||
1 -> 63
|
||||
_ -> 48
|
||||
"#
|
||||
),
|
||||
48,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_when_one_branch() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 3.14 is
|
||||
_ -> 23
|
||||
"#
|
||||
),
|
||||
23,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_large_when_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
foo = \num ->
|
||||
when num is
|
||||
0 -> 200
|
||||
-3 -> 111 # TODO adding more negative numbers reproduces parsing bugs here
|
||||
3 -> 789
|
||||
1 -> 123
|
||||
2 -> 456
|
||||
_ -> 1000
|
||||
|
||||
foo -3
|
||||
"#
|
||||
),
|
||||
111,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn gen_large_when_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// foo = \num ->
|
||||
// when num is
|
||||
// 0.5 -> 200.1
|
||||
// -3.6 -> 111.2 # TODO adding more negative numbers reproduces parsing bugs here
|
||||
// 3.6 -> 789.5
|
||||
// 1.7 -> 123.3
|
||||
// 2.8 -> 456.4
|
||||
// _ -> 1000.6
|
||||
|
||||
// foo -3.6
|
||||
// "#
|
||||
// ),
|
||||
// 111.2,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn gen_basic_def() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
answer = 42
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
pi = 3.14
|
||||
|
||||
pi
|
||||
"#
|
||||
),
|
||||
3.14,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_multiple_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
answer = 42
|
||||
|
||||
pi = 3.14
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
answer = 42
|
||||
|
||||
pi = 3.14
|
||||
|
||||
pi
|
||||
"#
|
||||
),
|
||||
3.14,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_chained_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = i1
|
||||
i3 = i2
|
||||
i1 = 1337
|
||||
i2 = i1
|
||||
y = 12.4
|
||||
|
||||
i3
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_nested_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
answer =
|
||||
i3 = i2
|
||||
|
||||
nested =
|
||||
a = 1.0
|
||||
b = 5
|
||||
|
||||
i1
|
||||
|
||||
i1 = 1337
|
||||
i2 = i1
|
||||
|
||||
|
||||
nested
|
||||
|
||||
# None of this should affect anything, even though names
|
||||
# overlap with the previous nested defs
|
||||
unused =
|
||||
nested = 17
|
||||
|
||||
i1 = 84.2
|
||||
|
||||
nested
|
||||
|
||||
y = 12.4
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num Int.Integer -> Num.Num Int.Integer
|
||||
always42 = \num -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_when_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
limitedNegate = \num ->
|
||||
when num is
|
||||
1 -> -1
|
||||
-1 -> 1
|
||||
_ -> num
|
||||
|
||||
limitedNegate 1
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_unnamed_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
(\a -> a) 5
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_unnamed_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
alwaysIdentity : Num.Num Int.Integer -> (Num.Num Float.FloatingPoint -> Num.Num Float.FloatingPoint)
|
||||
alwaysIdentity = \num ->
|
||||
(\a -> a)
|
||||
|
||||
(alwaysIdentity 2) 3.14
|
||||
"#
|
||||
),
|
||||
3.14,
|
||||
f64
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,311 +0,0 @@
|
|||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate inkwell;
|
||||
extern crate roc;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_gen {
|
||||
use crate::helpers::can_expr;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::OptimizationLevel;
|
||||
use roc::collections::{ImMap, MutMap};
|
||||
use roc::gen::build::{build_expr, build_proc};
|
||||
use roc::gen::convert::content_to_basic_type;
|
||||
use roc::gen::env::Env;
|
||||
use roc::infer::infer_expr;
|
||||
use roc::ll::expr::Expr;
|
||||
use roc::subs::Subs;
|
||||
|
||||
macro_rules! assert_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
let arena = Bump::new();
|
||||
let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src);
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
let mut unify_problems = Vec::new();
|
||||
let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable);
|
||||
|
||||
let context = Context::create();
|
||||
let builder = context.create_builder();
|
||||
let module = context.create_module("app");
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let main_fn_type = content_to_basic_type(&content, &mut subs, &context)
|
||||
.expect("Unable to infer type for test expr")
|
||||
.fn_type(&[], false);
|
||||
let main_fn_name = "$Test.main";
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let mut procs = MutMap::default();
|
||||
let env = Env {
|
||||
subs,
|
||||
builder: &builder,
|
||||
context: &context,
|
||||
module: arena.alloc(module),
|
||||
};
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let main_body = Expr::new(&arena, &env.subs, &env.module, &context, expr, &mut procs);
|
||||
|
||||
// Add all the Procs to the module
|
||||
for (name, (opt_proc, _fn_val)) in procs.clone() {
|
||||
if let Some(proc) = opt_proc {
|
||||
build_proc(&env, &ImMap::default(), name, proc, &procs);
|
||||
}
|
||||
}
|
||||
|
||||
// Add main to the module.
|
||||
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
|
||||
|
||||
// Add main's body
|
||||
let basic_block = context.append_basic_block(main_fn, "entry");
|
||||
|
||||
builder.position_at_end(&basic_block);
|
||||
|
||||
let ret = build_expr(
|
||||
&env,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&mut MutMap::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
||||
if !main_fn.verify(true) {
|
||||
panic!("Function {} failed LLVM verification.", main_fn_name);
|
||||
}
|
||||
|
||||
let execution_engine = env
|
||||
.module
|
||||
.create_jit_execution_engine(OptimizationLevel::None)
|
||||
.expect("errored");
|
||||
|
||||
unsafe {
|
||||
// Uncomment this to see the module's LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine
|
||||
.get_function(main_fn_name)
|
||||
.ok()
|
||||
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
|
||||
.expect("errored");
|
||||
|
||||
assert_eq!(main.call(), $expected);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_int() {
|
||||
assert_evals_to!("123", 123, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_float() {
|
||||
assert_evals_to!("1234.0", 1234.0, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_when_take_first_branch() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 1 is
|
||||
1 -> 12
|
||||
_ -> 34
|
||||
"#
|
||||
),
|
||||
12,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_when_take_second_branch() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 2 is
|
||||
1 -> 63
|
||||
_ -> 48
|
||||
"#
|
||||
),
|
||||
48,
|
||||
i64
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn gen_when_one_branch() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when 3.14 is
|
||||
_ -> 23
|
||||
"#
|
||||
),
|
||||
23,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_def() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
answer = 42
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
pi = 3.14
|
||||
|
||||
pi
|
||||
"#
|
||||
),
|
||||
3.14,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_multiple_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
answer = 42
|
||||
|
||||
pi = 3.14
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
answer = 42
|
||||
|
||||
pi = 3.14
|
||||
|
||||
pi
|
||||
"#
|
||||
),
|
||||
3.14,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_chained_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = i1
|
||||
i3 = i2
|
||||
i1 = 1337
|
||||
i2 = i1
|
||||
y = 12.4
|
||||
|
||||
i3
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_nested_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
answer =
|
||||
i3 = i2
|
||||
|
||||
nested =
|
||||
a = 1.0
|
||||
b = 5
|
||||
|
||||
i1
|
||||
|
||||
i1 = 1337
|
||||
i2 = i1
|
||||
|
||||
|
||||
nested
|
||||
|
||||
# None of this should affect anything, even though names
|
||||
# overlap with the previous nested defs
|
||||
unused =
|
||||
nested = 17
|
||||
|
||||
i1 = 84.2
|
||||
|
||||
nested
|
||||
|
||||
y = 12.4
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num Int.Integer -> Num.Num Int.Integer
|
||||
always42 = \num -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_when_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
limitedNegate = \num ->
|
||||
when num is
|
||||
1 -> -1
|
||||
_ -> 0
|
||||
|
||||
limitedNegate 1
|
||||
"#
|
||||
),
|
||||
-1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
}
|
|
@ -121,7 +121,6 @@ mod test_format {
|
|||
indoc!(
|
||||
r#"
|
||||
# This variable is for greeting
|
||||
|
||||
a = "Hello"
|
||||
|
||||
a
|
||||
|
@ -294,6 +293,24 @@ mod test_format {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destructure_tag_closure() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
\Foo a -> Foo a
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destructure_nested_tag_closure() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
\Foo (Bar a) -> Foo (Bar a)
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// DEFS
|
||||
|
||||
#[test]
|
||||
|
@ -396,7 +413,6 @@ mod test_format {
|
|||
y = 10
|
||||
|
||||
# v-- This is the return value
|
||||
|
||||
42
|
||||
"#
|
||||
));
|
||||
|
@ -404,7 +420,8 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn space_between_comments() {
|
||||
expr_formats_same(indoc!(
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
# 9
|
||||
|
||||
|
@ -414,7 +431,17 @@ mod test_format {
|
|||
# C
|
||||
9
|
||||
"#
|
||||
));
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
# 9
|
||||
# A
|
||||
# B
|
||||
# C
|
||||
9
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -434,40 +461,38 @@ mod test_format {
|
|||
indoc!(
|
||||
r#"
|
||||
# First
|
||||
|
||||
# Second
|
||||
x
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
// expr_formats_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \x ->
|
||||
// # 1st
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// # 2nd
|
||||
// x
|
||||
//
|
||||
// f 4
|
||||
// "#
|
||||
// ),
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \x ->
|
||||
// # 1st
|
||||
//
|
||||
// # 2nd
|
||||
// x
|
||||
//
|
||||
// f 4
|
||||
// "#
|
||||
// ),
|
||||
// );
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
f = \x ->
|
||||
# 1st
|
||||
|
||||
|
||||
|
||||
|
||||
# 2nd
|
||||
x
|
||||
|
||||
f 4
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
f = \x ->
|
||||
# 1st
|
||||
# 2nd
|
||||
x
|
||||
|
||||
f 4
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn doesnt_detect_comment_in_comment() {
|
||||
|
@ -525,6 +550,68 @@ mod test_format {
|
|||
// ));
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_updating() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{ shoes & leftShoe: nothing }
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ shoes & rightShoe : nothing }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{ shoes & rightShoe: nothing }
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ shoes & rightShoe : nothing }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{ shoes & rightShoe: nothing }
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{ shoes &
|
||||
rightShoe: newRightShoe,
|
||||
leftShoe: newLeftShoe
|
||||
}
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ shoes
|
||||
& rightShoe: bareFoot
|
||||
, leftShoe: bareFoot }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{ shoes &
|
||||
rightShoe: bareFoot,
|
||||
leftShoe: bareFoot
|
||||
}
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_closure() {
|
||||
expr_formats_same(indoc!(
|
||||
|
@ -720,6 +807,36 @@ mod test_format {
|
|||
));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn multi_line_list_def() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// scores =
|
||||
// [
|
||||
// 5,
|
||||
// 10
|
||||
// ]
|
||||
//
|
||||
// scores
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn multi_line_record_def() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// pos =
|
||||
// {
|
||||
// x: 5,
|
||||
// x: 10
|
||||
// }
|
||||
//
|
||||
// pos
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn two_fields_center_newline() {
|
||||
expr_formats_to(
|
||||
|
@ -777,7 +894,6 @@ mod test_format {
|
|||
waterWillBoil pressure temperature
|
||||
then
|
||||
turnOnAc
|
||||
|
||||
else
|
||||
identity
|
||||
"#
|
||||
|
@ -805,7 +921,6 @@ mod test_format {
|
|||
willBoil home water
|
||||
then
|
||||
\_ -> leave
|
||||
|
||||
else
|
||||
identity
|
||||
"#
|
||||
|
@ -813,71 +928,114 @@ mod test_format {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn if_removes_newlines() {
|
||||
// expr_formats_to(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// if
|
||||
//
|
||||
// # You never know!
|
||||
// isPrime 8
|
||||
//
|
||||
// # Top Comment
|
||||
//
|
||||
// # Bottom Comment
|
||||
//
|
||||
//
|
||||
// then
|
||||
//
|
||||
// # A
|
||||
//
|
||||
// # B
|
||||
//
|
||||
// nothing
|
||||
//
|
||||
// # B again
|
||||
//
|
||||
// else
|
||||
//
|
||||
// # C
|
||||
// # D
|
||||
//
|
||||
// # E
|
||||
// # F
|
||||
//
|
||||
// just (div 1 8)
|
||||
// "#
|
||||
// ),
|
||||
// indoc!(
|
||||
// r#"
|
||||
// if
|
||||
// # You never know!
|
||||
// isPrime 8
|
||||
#[test]
|
||||
fn if_removes_newlines_from_else() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
isPrime 8
|
||||
then
|
||||
nothing
|
||||
else
|
||||
# C
|
||||
# D
|
||||
|
||||
// # Top Comment
|
||||
# E
|
||||
# F
|
||||
|
||||
// # Bottom Comment
|
||||
// then
|
||||
// # A
|
||||
just (div 1 8)
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
isPrime 8
|
||||
then
|
||||
nothing
|
||||
else
|
||||
# C
|
||||
# D
|
||||
# E
|
||||
# F
|
||||
just (div 1 8)
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// # B
|
||||
#[test]
|
||||
fn if_removes_newlines_from_then() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
isPrime 9
|
||||
then
|
||||
# EE
|
||||
# FF
|
||||
|
||||
// nothing
|
||||
nothing
|
||||
|
||||
// # B again
|
||||
//
|
||||
// else
|
||||
// # C
|
||||
// # D
|
||||
# GG
|
||||
|
||||
else
|
||||
just (div 1 9)
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
isPrime 9
|
||||
then
|
||||
# EE
|
||||
# FF
|
||||
nothing
|
||||
# GG
|
||||
else
|
||||
just (div 1 9)
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_removes_newlines_from_condition() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
|
||||
# Is
|
||||
|
||||
# It
|
||||
|
||||
isPrime 10
|
||||
|
||||
# Prime?
|
||||
|
||||
then
|
||||
nothing
|
||||
else
|
||||
just (div 1 10)
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
if
|
||||
# Is
|
||||
# It
|
||||
isPrime 10
|
||||
# Prime?
|
||||
then
|
||||
nothing
|
||||
else
|
||||
just (div 1 10)
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// # E
|
||||
// # F
|
||||
// just (div 1 8)
|
||||
// "#
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn multi_line_if() {
|
||||
expr_formats_to(
|
||||
|
@ -885,6 +1043,8 @@ mod test_format {
|
|||
r#"
|
||||
if lessThan four five then
|
||||
four
|
||||
|
||||
|
||||
else
|
||||
five
|
||||
"#
|
||||
|
@ -893,7 +1053,6 @@ mod test_format {
|
|||
r#"
|
||||
if lessThan four five then
|
||||
four
|
||||
|
||||
else
|
||||
five
|
||||
"#
|
||||
|
@ -921,7 +1080,6 @@ mod test_format {
|
|||
r#"
|
||||
if lessThan three four then
|
||||
three
|
||||
|
||||
else
|
||||
four
|
||||
"#
|
||||
|
@ -932,7 +1090,6 @@ mod test_format {
|
|||
r#"
|
||||
if foo bar then
|
||||
a b c
|
||||
|
||||
else
|
||||
d e f
|
||||
"#
|
||||
|
|
|
@ -607,20 +607,19 @@ mod test_infer {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// TODO FIXME this should work, but instead causes a stack overflow!
|
||||
// fn recursive_identity() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// identity = \val -> val
|
||||
#[test]
|
||||
fn recursive_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \val -> val
|
||||
|
||||
// identity identity
|
||||
// "#
|
||||
// ),
|
||||
// "a -> a",
|
||||
// );
|
||||
// }
|
||||
identity identity
|
||||
"#
|
||||
),
|
||||
"a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_function() {
|
||||
|
@ -813,20 +812,20 @@ mod test_infer {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn if_with_int_literals() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// if 1 == 1 then
|
||||
// 42
|
||||
// else
|
||||
// 24
|
||||
// "#
|
||||
// ),
|
||||
// "Int",
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn if_with_int_literals() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
if True then
|
||||
42
|
||||
else
|
||||
24
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_with_int_literals() {
|
||||
|
@ -1006,7 +1005,7 @@ mod test_infer {
|
|||
{ user & year: "foo" }
|
||||
"#
|
||||
),
|
||||
"{ year : Str }{ name : Str }",
|
||||
"{ name : Str, year : Str }",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1039,7 +1038,7 @@ mod test_infer {
|
|||
r#"\@Foo -> 42
|
||||
"#
|
||||
),
|
||||
"[ Test.Foo ]* -> Int",
|
||||
"[ Test.@Foo ]* -> Int",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1068,16 +1067,95 @@ mod test_infer {
|
|||
);
|
||||
}
|
||||
|
||||
// currently doesn't work because of a parsing issue
|
||||
// @Foo x y isn't turned into Apply(@Foo, [x,y]) currently
|
||||
// #[test]
|
||||
// fn private_tag_application() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"@Foo "happy" 2020
|
||||
// "#
|
||||
// ),
|
||||
// "[ Test.Foo Str Int ]*",
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn private_tag_application() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"@Foo "happy" 2020
|
||||
"#
|
||||
),
|
||||
"[ Test.@Foo Str Int ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_extraction() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
f = \x ->
|
||||
when x is
|
||||
{ a, b } -> a
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
"{ a : a, b : * }* -> a",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_pattern_match_with_guard() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
{ x: 4 } -> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_union_pattern_match() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\Foo x -> Foo x
|
||||
"#
|
||||
),
|
||||
"[ Foo a ]* -> [ Foo a ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_union_pattern_match_ignored_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\Foo x _ -> Foo x "y"
|
||||
"#
|
||||
),
|
||||
"[ Foo a * ]* -> [ Foo a Str ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_tag_with_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when Foo 4 is
|
||||
Foo x -> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_tag_with_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when @Foo 4 is
|
||||
@Foo x -> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ mod test_parse {
|
|||
use roc::parse::ast::CommentOrNewline::*;
|
||||
use roc::parse::ast::Expr::{self, *};
|
||||
use roc::parse::ast::Pattern::{self, *};
|
||||
use roc::parse::ast::{Attempting, Def, InterfaceHeader, Spaceable};
|
||||
use roc::parse::ast::{Attempting, Def, InterfaceHeader, Spaceable, Tag, TypeAnnotation};
|
||||
use roc::parse::module::{interface_header, module_defs};
|
||||
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
||||
use roc::region::{Located, Region};
|
||||
|
@ -641,7 +641,7 @@ mod test_parse {
|
|||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
// VARIANT
|
||||
// TAG
|
||||
|
||||
#[test]
|
||||
fn basic_global_tag() {
|
||||
|
@ -661,6 +661,38 @@ mod test_parse {
|
|||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_private_tag() {
|
||||
let arena = Bump::new();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 9, 11, Int("34")));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 5, Expr::PrivateTag("@Whee"))),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "@Whee 12 34");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_global_tag() {
|
||||
let arena = Bump::new();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 5, 7, Int("12")));
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 8, 10, Int("34")));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "Whee 12 34");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_global_tag() {
|
||||
let arena = Bump::new();
|
||||
|
@ -1245,27 +1277,17 @@ mod test_parse {
|
|||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let applied_ann = TypeAnnotation::Apply(&[], "Int", &[]);
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
9,
|
||||
roc::parse::ast::TypeAnnotation::Apply(&[], "Int", &[]),
|
||||
),
|
||||
Located::new(0, 0, 6, 9, applied_ann),
|
||||
);
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
arena.alloc(Located::new(1, 1, 6, 7, Int("4"))),
|
||||
);
|
||||
let loc_def = &*arena.alloc(Located::new(
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
7,
|
||||
Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()),
|
||||
));
|
||||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 7, spaced_def));
|
||||
|
||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||
|
@ -1288,7 +1310,7 @@ mod test_parse {
|
|||
|
||||
#[test]
|
||||
fn type_signature_function_def() {
|
||||
use roc::parse::ast::TypeAnnotation;
|
||||
use TypeAnnotation;
|
||||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
|
@ -1302,16 +1324,10 @@ mod test_parse {
|
|||
Located::new(0, 0, 11, 16, float_type)
|
||||
];
|
||||
let return_type = Located::new(0, 0, 20, 24, bool_type);
|
||||
|
||||
let fn_ann = TypeAnnotation::Function(&arguments, &return_type);
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
24,
|
||||
TypeAnnotation::Function(&arguments, &return_type),
|
||||
),
|
||||
Located::new(0, 0, 20, 24, fn_ann),
|
||||
);
|
||||
|
||||
let args = bumpalo::vec![in &arena;
|
||||
|
@ -1326,13 +1342,8 @@ mod test_parse {
|
|||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
arena.alloc(Located::new(1, 1, 6, 17, closure)),
|
||||
);
|
||||
let loc_def = &*arena.alloc(Located::new(
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
17,
|
||||
Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()),
|
||||
));
|
||||
let spaced = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 17, spaced));
|
||||
|
||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||
|
@ -1353,6 +1364,220 @@ mod test_parse {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ann_private_open_union() {
|
||||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let tag1 = Tag::Private {
|
||||
name: Located::new(0, 0, 8, 13, "@True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Private {
|
||||
name: Located::new(0, 0, 15, 23, "@Perhaps"),
|
||||
args: tag2args.into_bump_slice(),
|
||||
};
|
||||
let tags = bumpalo::vec![in &arena;
|
||||
Located::new(0, 0, 8, 13, tag1),
|
||||
Located::new(0, 0, 15, 29, tag2)
|
||||
];
|
||||
let loc_wildcard = Located::new(0, 0, 31, 32, TypeAnnotation::Wildcard);
|
||||
let applied_ann = TypeAnnotation::TagUnion {
|
||||
tags: tags.into_bump_slice(),
|
||||
ext: Some(arena.alloc(loc_wildcard)),
|
||||
};
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(0, 0, 6, 32, applied_ann),
|
||||
);
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||||
);
|
||||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||||
|
||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
assert_parses_to(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : [ @True, @Perhaps Thing ]*
|
||||
foo = True
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ann_private_closed_union() {
|
||||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let tag1 = Tag::Private {
|
||||
name: Located::new(0, 0, 8, 13, "@True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Private {
|
||||
name: Located::new(0, 0, 15, 23, "@Perhaps"),
|
||||
args: tag2args.into_bump_slice(),
|
||||
};
|
||||
let tags = bumpalo::vec![in &arena;
|
||||
Located::new(0, 0, 8, 13, tag1),
|
||||
Located::new(0, 0, 15, 29, tag2)
|
||||
];
|
||||
let applied_ann = TypeAnnotation::TagUnion {
|
||||
tags: tags.into_bump_slice(),
|
||||
ext: None,
|
||||
};
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(0, 0, 6, 31, applied_ann),
|
||||
);
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||||
);
|
||||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||||
|
||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
assert_parses_to(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : [ @True, @Perhaps Thing ]
|
||||
foo = True
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ann_global_open_union() {
|
||||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let tag1 = Tag::Global {
|
||||
name: Located::new(0, 0, 8, 12, "True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Global {
|
||||
name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||||
args: tag2args.into_bump_slice(),
|
||||
};
|
||||
let tags = bumpalo::vec![in &arena;
|
||||
Located::new(0, 0, 8, 12, tag1),
|
||||
Located::new(0, 0, 14, 27, tag2)
|
||||
];
|
||||
let loc_wildcard = Located::new(0, 0, 29, 30, TypeAnnotation::Wildcard);
|
||||
let applied_ann = TypeAnnotation::TagUnion {
|
||||
tags: tags.into_bump_slice(),
|
||||
ext: Some(arena.alloc(loc_wildcard)),
|
||||
};
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(0, 0, 6, 30, applied_ann),
|
||||
);
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||||
);
|
||||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||||
|
||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
assert_parses_to(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : [ True, Perhaps Thing ]*
|
||||
foo = True
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ann_global_closed_union() {
|
||||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let tag1 = Tag::Global {
|
||||
name: Located::new(0, 0, 8, 12, "True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Global {
|
||||
name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||||
args: tag2args.into_bump_slice(),
|
||||
};
|
||||
let tags = bumpalo::vec![in &arena;
|
||||
Located::new(0, 0, 8, 12, tag1),
|
||||
Located::new(0, 0, 14, 27, tag2)
|
||||
];
|
||||
let applied_ann = TypeAnnotation::TagUnion {
|
||||
tags: tags.into_bump_slice(),
|
||||
ext: None,
|
||||
};
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(0, 0, 6, 29, applied_ann),
|
||||
);
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||||
);
|
||||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||||
|
||||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
assert_parses_to(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : [ True, Perhaps Thing ]
|
||||
foo = True
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
// WHEN
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -17,22 +17,15 @@ mod test_infer_uniq {
|
|||
// HELPERS
|
||||
|
||||
fn infer_eq(src: &str, expected: &str) {
|
||||
let (
|
||||
_output2,
|
||||
_output1,
|
||||
_,
|
||||
mut subs1,
|
||||
variable1,
|
||||
mut subs2,
|
||||
variable2,
|
||||
constraint1,
|
||||
constraint2,
|
||||
) = uniq_expr(src);
|
||||
let (_output1, _, mut subs1, variable1, mut subs2, variable2, constraint1, constraint2) =
|
||||
uniq_expr(src);
|
||||
|
||||
let mut unify_problems = Vec::new();
|
||||
let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1);
|
||||
let content2 = infer_expr(&mut subs2, &mut unify_problems, &constraint2, variable2);
|
||||
|
||||
dbg!(unify_problems);
|
||||
|
||||
name_all_type_vars(variable1, &mut subs1);
|
||||
name_all_type_vars(variable2, &mut subs2);
|
||||
|
||||
|
@ -515,14 +508,12 @@ mod test_infer_uniq {
|
|||
indoc!(
|
||||
r#"
|
||||
identity = \a -> a
|
||||
|
||||
x = identity 5
|
||||
|
||||
identity
|
||||
"#
|
||||
),
|
||||
// TODO investigate why not shared
|
||||
// perhaps because `x` is DCE'd?
|
||||
// TODO investigate why not shared?
|
||||
"Attr.Attr * (a -> a)",
|
||||
);
|
||||
}
|
||||
|
@ -637,20 +628,19 @@ mod test_infer_uniq {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// TODO FIXME this should work, but instead causes a stack overflow!
|
||||
// fn recursive_identity() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// identity = \val -> val
|
||||
#[test]
|
||||
fn recursive_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \val -> val
|
||||
|
||||
// identity identity
|
||||
// "#
|
||||
// ),
|
||||
// "a -> a",
|
||||
// );
|
||||
// }
|
||||
identity identity
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (a -> a)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_function() {
|
||||
|
@ -843,20 +833,20 @@ mod test_infer_uniq {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn if_with_int_literals() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// if 1 == 1 then
|
||||
// 42
|
||||
// else
|
||||
// 24
|
||||
// "#
|
||||
// ),
|
||||
// "Int",
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn if_with_int_literals() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
if True then
|
||||
42
|
||||
else
|
||||
24
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_with_int_literals() {
|
||||
|
@ -872,11 +862,6 @@ mod test_infer_uniq {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessor_function() {
|
||||
infer_eq(".foo", "Attr.Attr * { foo : a }* -> a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record() {
|
||||
infer_eq("{ foo: 42 }", "Attr.Attr * { foo : (Attr.Attr * Int) }");
|
||||
|
@ -887,19 +872,6 @@ mod test_infer_uniq {
|
|||
infer_eq("{ foo: 42 }.foo", "Attr.Attr * Int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_pattern_match_infer() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
{ x: 4 }-> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record_pattern() {
|
||||
infer_eq(
|
||||
|
@ -927,7 +899,226 @@ mod test_infer_uniq {
|
|||
{ user & year: "foo" }
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * { year : (Attr.Attr * Str) }{ name : (Attr.Attr * Str) }",
|
||||
"Attr.Attr * { name : (Attr.Attr * Str), year : (Attr.Attr * Str) }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_tag() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"Foo
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * [ Foo ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_tag_pattern() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"\Foo -> 42
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr * [ Foo ]* -> Attr.Attr * Int)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_private_tag_pattern() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"\@Foo -> 42
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr * [ Test.@Foo ]* -> Attr.Attr * Int)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_tag_pattern() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"\x ->
|
||||
when x is
|
||||
True -> 1
|
||||
False -> 0
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr * [ False, True ]* -> Attr.Attr * Int)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_application() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"Foo "happy" 2020
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * [ Foo (Attr.Attr * Str) (Attr.Attr * Int) ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_tag_application() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"@Foo "happy" 2020
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * [ Test.@Foo (Attr.Attr * Str) (Attr.Attr * Int) ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_access() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\rec -> rec.left
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_accessor_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
.left
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_pattern_match() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\{ left } -> left
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_pattern_match_two() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\{ left, right } -> { left, right }
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (a | b) { left : (Attr.Attr a c), right : (Attr.Attr b d) }* -> Attr.Attr * { left : (Attr.Attr a c), right : (Attr.Attr b d) })",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_pattern_match_with_guard() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
{ x: 4 } -> x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_union_pattern_match() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\Foo x -> Foo x
|
||||
"#
|
||||
),
|
||||
// NOTE: Foo loses the relation to the uniqueness attribute `a`
|
||||
// That is fine. Whenever we try to extract from it, the relation will be enforced
|
||||
"Attr.Attr * (Attr.Attr a [ Foo (Attr.Attr a b) ]* -> Attr.Attr * [ Foo (Attr.Attr a b) ]*)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_union_pattern_match_ignored_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\Foo x _ -> Foo x "y"
|
||||
"#
|
||||
),
|
||||
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
|
||||
// i.e. the `a` could be ignored in this example, is that true in general?
|
||||
// seems like it because we don't really extract anything.
|
||||
"Attr.Attr * (Attr.Attr (b | a) [ Foo (Attr.Attr b c) (Attr.Attr a *) ]* -> Attr.Attr * [ Foo (Attr.Attr b c) (Attr.Attr * Str) ]*)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_tag_with_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when Foo 4 is
|
||||
Foo x -> x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr a Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_tag_with_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when @Foo 4 is
|
||||
@Foo x -> x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr a Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_annotation() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Int
|
||||
x = 4
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr a Int",
|
||||
);
|
||||
}
|
||||
|
||||
// TODO when type signatures are supported, ensure this works
|
||||
// #[test]
|
||||
// fn num_identity() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// numIdentity : Num a -> Num a
|
||||
// numIdentity = \x -> x
|
||||
//
|
||||
// x = numIdentity 42
|
||||
// y = numIdentity 3.14
|
||||
//
|
||||
// numIdentity
|
||||
// "#
|
||||
// ),
|
||||
// "tbd",
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue