Merge remote-tracking branch 'origin/trunk' into llvm-debug-info

This commit is contained in:
Folkert 2020-11-26 20:53:13 +01:00
commit 2dbf430892
122 changed files with 6340 additions and 1392 deletions

View file

@ -13,6 +13,9 @@ jobs:
- name: Install CI Libraries - name: Install CI Libraries
run: sudo ./ci/install-ci-libraries.sh 10 run: sudo ./ci/install-ci-libraries.sh 10
- name: Run Zig tests
run: pushd compiler/builtins/bitcode; ./run-tests.sh; popd;
- name: Enable LLD - name: Enable LLD
run: sudo ./ci/enable-lld.sh run: sudo ./ci/enable-lld.sh
@ -37,6 +40,12 @@ jobs:
path: ~/.cargo/git path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache compiled valgrind
uses: actions/cache@v1
with:
path: ~/valgrind-3.6.1/
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
name: cargo fmt --check name: cargo fmt --check
with: with:

246
Cargo.lock generated
View file

@ -142,10 +142,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f813291114c186a042350e787af10c26534601062603d888be110f59f85ef8fa" checksum = "f813291114c186a042350e787af10c26534601062603d888be110f59f85ef8fa"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"miniz_oxide", "miniz_oxide",
"object", "object 0.20.0",
"rustc-demangle", "rustc-demangle",
] ]
@ -230,6 +230,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytemuck"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.3.4"
@ -250,7 +256,7 @@ checksum = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160"
dependencies = [ dependencies = [
"mio", "mio",
"mio-extras", "mio-extras",
"nix", "nix 0.14.1",
] ]
[[package]] [[package]]
@ -277,6 +283,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.33.3" version = "2.33.3"
@ -374,6 +386,26 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "const_format"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49b6cb3f7a62b0a488baa8692286b25b69648f1dae69a598b1a9faa166d56d"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df496e1bbc93814d728a8036ff054cd95830afe9cf2275c9326688c02eff936"
dependencies = [
"proc-macro2 1.0.21",
"quote 1.0.7",
"unicode-xid 0.2.1",
]
[[package]] [[package]]
name = "copyless" name = "copyless"
version = "0.1.5" version = "0.1.5"
@ -442,13 +474,22 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"core-foundation-sys 0.7.0", "core-foundation-sys 0.7.0",
"core-graphics", "core-graphics",
"libc", "libc",
"objc", "objc",
] ]
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.3.3" version = "0.3.3"
@ -491,7 +532,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"crossbeam-channel", "crossbeam-channel",
"crossbeam-deque", "crossbeam-deque",
"crossbeam-epoch", "crossbeam-epoch",
@ -527,7 +568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [ dependencies = [
"autocfg 1.0.1", "autocfg 1.0.1",
"cfg-if", "cfg-if 0.1.10",
"crossbeam-utils", "crossbeam-utils",
"lazy_static", "lazy_static",
"maybe-uninit", "maybe-uninit",
@ -541,7 +582,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"crossbeam-utils", "crossbeam-utils",
"maybe-uninit", "maybe-uninit",
] ]
@ -553,7 +594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [ dependencies = [
"autocfg 1.0.1", "autocfg 1.0.1",
"cfg-if", "cfg-if 0.1.10",
"lazy_static", "lazy_static",
] ]
@ -616,6 +657,27 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "dirs-next"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6"
dependencies = [
"cfg-if 1.0.0",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
dependencies = [
"libc",
"redox_users",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@ -708,6 +770,18 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "flate2"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
dependencies = [
"cfg-if 1.0.0",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -876,7 +950,7 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"wasi", "wasi",
] ]
@ -1316,7 +1390,7 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9" checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -1372,7 +1446,7 @@ version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
] ]
[[package]] [[package]]
@ -1398,9 +1472,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.3.3" version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]] [[package]]
name = "memmap" name = "memmap"
@ -1451,7 +1525,7 @@ version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"fuchsia-zircon", "fuchsia-zircon",
"fuchsia-zircon-sys", "fuchsia-zircon-sys",
"iovec", "iovec",
@ -1572,7 +1646,7 @@ version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -1585,11 +1659,23 @@ checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cc", "cc",
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"void", "void",
] ]
[[package]]
name = "nix"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
dependencies = [
"bitflags",
"cc",
"cfg-if 0.1.10",
"libc",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.12" version = "0.2.12"
@ -1656,6 +1742,18 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
[[package]]
name = "object"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
dependencies = [
"crc32fast",
"flate2",
"indexmap",
"wasmparser",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.4.1" version = "1.4.1"
@ -1719,7 +1817,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"cloudabi 0.0.3", "cloudabi 0.0.3",
"libc", "libc",
"redox_syscall", "redox_syscall",
@ -1734,7 +1832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"cfg-if", "cfg-if 0.1.10",
"cloudabi 0.1.0", "cloudabi 0.1.0",
"instant", "instant",
"libc", "libc",
@ -2210,6 +2308,16 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_users"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.3.9" version = "1.3.9"
@ -2325,6 +2433,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"clap 3.0.0-beta.1", "clap 3.0.0-beta.1",
"const_format",
"im", "im",
"im-rc", "im-rc",
"indoc", "indoc",
@ -2355,6 +2464,8 @@ dependencies = [
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"roc_uniq", "roc_uniq",
"rustyline",
"rustyline-derive",
"serde", "serde",
"serde-xml-rs", "serde-xml-rs",
"serial_test", "serial_test",
@ -2399,6 +2510,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bumpalo", "bumpalo",
"bytemuck",
"env_logger 0.7.1", "env_logger 0.7.1",
"fs_extra", "fs_extra",
"futures", "futures",
@ -2498,6 +2610,45 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "roc_gen_dev"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"indoc",
"inlinable_string",
"itertools",
"libc",
"libloading",
"maplit",
"object 0.22.0",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_std",
"roc_types",
"roc_unify",
"roc_uniq",
"target-lexicon",
"tempfile",
"tokio",
]
[[package]] [[package]]
name = "roc_load" name = "roc_load"
version = "0.1.0" version = "0.1.0"
@ -2761,6 +2912,35 @@ dependencies = [
"stb_truetype", "stb_truetype",
] ]
[[package]]
name = "rustyline"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0d5e7b0219a3eadd5439498525d4765c59b7c993ef0c12244865cd2d988413"
dependencies = [
"cfg-if 0.1.10",
"dirs-next",
"libc",
"log",
"memchr",
"nix 0.18.0",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse 0.2.0",
"winapi 0.3.9",
]
[[package]]
name = "rustyline-derive"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a50e29610a5be68d4a586a5cce3bfb572ed2c2a74227e4168444b7bf4e5235"
dependencies = [
"quote 1.0.7",
"syn 1.0.40",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -2947,7 +3127,7 @@ dependencies = [
"dlib", "dlib",
"lazy_static", "lazy_static",
"memmap", "memmap",
"nix", "nix 0.14.1",
"wayland-client", "wayland-client",
"wayland-protocols", "wayland-protocols",
] ]
@ -2958,7 +3138,7 @@ version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"redox_syscall", "redox_syscall",
"winapi 0.3.9", "winapi 0.3.9",
@ -3075,7 +3255,7 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"rand 0.7.3", "rand 0.7.3",
"redox_syscall", "redox_syscall",
@ -3192,7 +3372,7 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27" checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"pin-project-lite", "pin-project-lite",
"tracing-core", "tracing-core",
] ]
@ -3284,6 +3464,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
[[package]]
name = "utf8parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"
@ -3334,7 +3520,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf" checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf"
dependencies = [ dependencies = [
"utf8parse", "utf8parse 0.1.1",
] ]
[[package]] [[package]]
@ -3360,7 +3546,7 @@ version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
@ -3385,7 +3571,7 @@ version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
@ -3420,6 +3606,12 @@ version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
[[package]]
name = "wasmparser"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6"
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.23.6" version = "0.23.6"
@ -3431,7 +3623,7 @@ dependencies = [
"downcast-rs", "downcast-rs",
"libc", "libc",
"mio", "mio",
"nix", "nix 0.14.1",
"wayland-commons", "wayland-commons",
"wayland-scanner", "wayland-scanner",
"wayland-sys", "wayland-sys",
@ -3443,7 +3635,7 @@ version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb"
dependencies = [ dependencies = [
"nix", "nix 0.14.1",
"wayland-sys", "wayland-sys",
] ]

View file

@ -18,6 +18,7 @@ members = [
"compiler/mono", "compiler/mono",
"compiler/load", "compiler/load",
"compiler/gen", "compiler/gen",
"compiler/gen_dev",
"compiler/build", "compiler/build",
"compiler/arena_pool", "compiler/arena_pool",
"vendor/ena", "vendor/ena",

View file

@ -59,7 +59,21 @@ esac
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}" add-apt-repository "${REPO_NAME}"
apt-get update apt-get update
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev valgrind apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev libc6-dbg
wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
tar -xf valgrind-3.16.1.tar.bz2
mv valgrind-3.16.1 ~
pushd ~/valgrind-3.16.1
apt-get install -y autotools-dev automake
./autogen.sh
./configure
make -j`nproc`
sudo make install
popd
# Report current valgrind version, to confirm it installed properly
valgrind --version
# install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0) # install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0)
wget -c https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz --no-check-certificate wget -c https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz --no-check-certificate

View file

@ -52,6 +52,9 @@ roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor" } roc_editor = { path = "../editor" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
const_format = "0.2.8"
rustyline = "6.3.0"
rustyline-derive = "0.3.1"
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }

View file

@ -85,6 +85,9 @@ pub fn build_file(
buf buf
); );
let cwd = app_o_file.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
program::gen_from_mono_module( program::gen_from_mono_module(
&arena, &arena,
loaded, loaded,
@ -98,23 +101,30 @@ pub fn build_file(
let compilation_end = compilation_start.elapsed().unwrap(); let compilation_end = compilation_start.elapsed().unwrap();
println!( let size = std::fs::metadata(&app_o_file).unwrap().len();
"Finished compilation and code gen in {} ms\n",
compilation_end.as_millis()
);
let cwd = app_o_file.parent().unwrap(); println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
// Step 2: link the precompiled host and compiled app // Step 2: link the precompiled host and compiled app
let host_input_path = cwd.join("platform").join("host.o"); let host_input_path = cwd.join("platform").join("host.o");
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
// TODO we should no longer need to do this once we have platforms on // TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there. // a package repository, as we can then get precompiled hosts from there.
let rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path()); rebuild_host(host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!(
"Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
// TODO try to move as much of this linking as possible to the precompiled // TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required. // host, to minimize the amount of host-application linking required.
let link_start = SystemTime::now();
let (mut child, binary_path) = // TODO use lld let (mut child, binary_path) = // TODO use lld
link( link(
target, target,
@ -130,6 +140,9 @@ pub fn build_file(
todo!("gracefully handle error after `rustc` spawned"); todo!("gracefully handle error after `rustc` spawned");
}); });
let link_end = link_start.elapsed().unwrap();
println!("Finished linking in {} ms\n", link_end.as_millis());
// Clean up the leftover .o file from the Roc, if possible. // Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.) // (If cleaning it up fails, that's fine. No need to take action.)
// TODO compile the app_o_file to a tmpdir, as an extra precaution. // TODO compile the app_o_file to a tmpdir, as an extra precaution.
@ -138,5 +151,8 @@ pub fn build_file(
// If the cmd errored out, return the Err. // If the cmd errored out, return the Err.
cmd_result?; cmd_result?;
let total_end = compilation_start.elapsed().unwrap();
println!("Finished entire process in {} ms\n", total_end.as_millis());
Ok(binary_path) Ok(binary_path)
} }

View file

@ -1,50 +1,105 @@
use gen::{gen, ReplOutput}; use const_format::concatcp;
use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{Fail, FailReason}; use roc_parse::parser::{Fail, FailReason};
use std::io::{self, Write}; use rustyline::error::ReadlineError;
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use std::io::{self};
use target_lexicon::Triple; use target_lexicon::Triple;
pub const WELCOME_MESSAGE: &str = "\n The rockin \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n"; const BLUE: &str = "\u{001b}[36m";
pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit.\n"; const PINK: &str = "\u{001b}[35m";
pub const PROMPT: &str = "\n\u{001b}[36m»\u{001b}[0m "; const END_COL: &str = "\u{001b}[0m";
pub const ELLIPSIS: &str = "\u{001b}[36m…\u{001b}[0m ";
pub const WELCOME_MESSAGE: &str = concatcp!(
"\n The rockin ",
BLUE,
"roc repl",
END_COL,
"\n",
PINK,
"────────────────────────",
END_COL,
"\n\n"
);
pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n";
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
mod eval; mod eval;
mod gen; mod gen;
pub fn main() -> io::Result<()> { #[derive(Completer, Helper, Hinter, Highlighter)]
use std::io::BufRead; struct ReplHelper {
validator: InputValidator,
pending_src: String,
}
impl ReplHelper {
pub(crate) fn new() -> ReplHelper {
ReplHelper {
validator: InputValidator::new(),
pending_src: String::new(),
}
}
}
impl Validator for ReplHelper {
fn validate(
&self,
ctx: &mut validate::ValidationContext,
) -> rustyline::Result<validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct InputValidator {}
impl InputValidator {
pub(crate) fn new() -> InputValidator {
InputValidator {}
}
}
impl Validator for InputValidator {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
if ctx.input().is_empty() {
Ok(ValidationResult::Incomplete)
} else {
Ok(ValidationResult::Valid(None))
}
}
}
pub fn main() -> io::Result<()> {
// To debug rustyline:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
print!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); print!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
// Loop
let mut pending_src = String::new();
let mut prev_line_blank = false; let mut prev_line_blank = false;
let mut editor = Editor::<ReplHelper>::new();
let repl_helper = ReplHelper::new();
editor.set_helper(Some(repl_helper));
loop { loop {
if pending_src.is_empty() { let readline = editor.readline(PROMPT);
print!("{}", PROMPT); let pending_src = &mut editor
} else { .helper_mut()
print!("{}", ELLIPSIS); .expect("Editor helper was not set")
} .pending_src;
io::stdout().flush().unwrap(); match readline {
Ok(line) => {
//TODO rl.add_history_entry(line.as_str());
let trim_line = line.trim();
let stdin = io::stdin(); match trim_line.to_lowercase().as_str() {
let line = stdin
.lock()
.lines()
.next()
.expect("there was no next line")
.expect("the line could not be read");
let line = line.trim();
match line.to_lowercase().as_str() {
":help" => {
println!("Use :exit to exit.");
}
"" => { "" => {
if pending_src.is_empty() { if pending_src.is_empty() {
print!("\n{}", INSTRUCTIONS); print!("\n{}", INSTRUCTIONS);
@ -68,15 +123,21 @@ pub fn main() -> io::Result<()> {
continue; // Skip the part where we reset prev_line_blank to false continue; // Skip the part where we reset prev_line_blank to false
} }
} }
":help" => {
println!("Use :exit or :q to exit.");
}
":exit" => { ":exit" => {
break; break;
} }
":q" => {
break;
}
_ => { _ => {
let result = if pending_src.is_empty() { let result = if pending_src.is_empty() {
eval_and_format(line) eval_and_format(trim_line)
} else { } else {
pending_src.push('\n'); pending_src.push('\n');
pending_src.push_str(line); pending_src.push_str(trim_line);
eval_and_format(pending_src.as_str()) eval_and_format(pending_src.as_str())
}; };
@ -89,21 +150,33 @@ pub fn main() -> io::Result<()> {
Err(Fail { Err(Fail {
reason: FailReason::Eof(_), reason: FailReason::Eof(_),
.. ..
}) => { }) => {}
Err(fail) => {
report_parse_error(fail);
pending_src.clear();
}
}
}
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
Err(ReadlineError::Eof) => {
// If we hit an eof, and we're allowed to keep going, // If we hit an eof, and we're allowed to keep going,
// append the str to the src we're building up and continue. // append the str to the src we're building up and continue.
// (We only need to append it here if it was empty before; // (We only need to append it here if it was empty before;
// otherwise, we already appended it before calling eval_and_format.) // otherwise, we already appended it before calling eval_and_format.)
if pending_src.is_empty() { if pending_src.is_empty() {
pending_src.push_str(line); pending_src.push_str("");
}
}
Err(fail) => {
report_parse_error(fail);
pending_src.clear();
} }
break;
} }
Err(err) => {
println!("Error: {:?}", err);
break;
} }
} }
@ -118,9 +191,9 @@ fn report_parse_error(fail: Fail) {
} }
fn eval_and_format(src: &str) -> Result<String, Fail> { fn eval_and_format(src: &str) -> Result<String, Fail> {
gen(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output { gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => { ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} \u{001b}[35m:\u{001b}[0m {}", expr, expr_type) format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
} }
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")), ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
}) })

View file

@ -2,6 +2,7 @@ use crate::repl::eval;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use roc_build::link::module_to_dylib; use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable; use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens}; use roc_fmt::annotation::{Newlines, Parens};
@ -17,7 +18,7 @@ pub enum ReplOutput {
NoProblems { expr: String, expr_type: String }, NoProblems { expr: String, expr_type: String },
} }
pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> { pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
}; };
@ -108,9 +109,18 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput
Ok(ReplOutput::Problems(lines)) Ok(ReplOutput::Problems(lines))
} else { } else {
let context = Context::create(); let context = Context::create();
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app")); let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
let builder = context.create_builder(); let builder = context.create_builder();
// mark our zig-defined builtins as internal
use inkwell::module::Linkage;
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
}
debug_assert_eq!(exposed_to_host.len(), 1); debug_assert_eq!(exposed_to_host.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap(); let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol; let main_fn_symbol = *main_fn_symbol;
@ -208,6 +218,7 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput
); );
} }
} }
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function( let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
&env, &env,
&mut layout_ids, &mut layout_ids,
@ -263,7 +274,8 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput
} }
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n"); let mut buffer =
String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n");
for line in src.lines() { for line in src.lines() {
// indent the body! // indent the body!

View file

@ -17,7 +17,13 @@ mod cli_run {
use serial_test::serial; use serial_test::serial;
use std::path::Path; use std::path::Path;
fn check_output(file: &Path, flags: &[&str], expected_ending: &str, use_valgrind: bool) { fn check_output(
file: &Path,
executable_filename: &str,
flags: &[&str],
expected_ending: &str,
use_valgrind: bool,
) {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!(compile_out.stderr); panic!(compile_out.stderr);
@ -26,14 +32,31 @@ mod cli_run {
let out = if use_valgrind { let out = if use_valgrind {
let (valgrind_out, raw_xml) = let (valgrind_out, raw_xml) =
run_with_valgrind(&[file.with_file_name("app").to_str().unwrap()]); run_with_valgrind(&[file.with_file_name(executable_filename).to_str().unwrap()]);
let memory_errors = extract_valgrind_errors(&raw_xml);
if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
});
if !memory_errors.is_empty() { if !memory_errors.is_empty() {
panic!("{:?}", memory_errors); panic!("{:?}", memory_errors);
} }
} else {
let exit_code = match valgrind_out.status.code() {
Some(code) => format!("exit code {}", code),
None => "no exit code".to_string(),
};
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
}
valgrind_out valgrind_out
} else { } else {
run_cmd(file.with_file_name("app").to_str().unwrap(), &[]) run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
&[],
)
}; };
if !&out.stdout.ends_with(expected_ending) { if !&out.stdout.ends_with(expected_ending) {
panic!( panic!(
@ -49,6 +72,7 @@ mod cli_run {
fn run_hello_world() { fn run_hello_world() {
check_output( check_output(
&example_file("hello-world", "Hello.roc"), &example_file("hello-world", "Hello.roc"),
"hello-world",
&[], &[],
"Hello, World!!!!!!!!!!!!!\n", "Hello, World!!!!!!!!!!!!!\n",
true, true,
@ -60,6 +84,7 @@ mod cli_run {
fn run_hello_world_optimized() { fn run_hello_world_optimized() {
check_output( check_output(
&example_file("hello-world", "Hello.roc"), &example_file("hello-world", "Hello.roc"),
"hello-world",
&[], &[],
"Hello, World!!!!!!!!!!!!!\n", "Hello, World!!!!!!!!!!!!!\n",
true, true,
@ -71,6 +96,7 @@ mod cli_run {
fn run_quicksort_not_optimized() { fn run_quicksort_not_optimized() {
check_output( check_output(
&example_file("quicksort", "Quicksort.roc"), &example_file("quicksort", "Quicksort.roc"),
"quicksort",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -82,6 +108,7 @@ mod cli_run {
fn run_quicksort_optimized() { fn run_quicksort_optimized() {
check_output( check_output(
&example_file("quicksort", "Quicksort.roc"), &example_file("quicksort", "Quicksort.roc"),
"quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -95,6 +122,7 @@ mod cli_run {
fn run_quicksort_valgrind() { fn run_quicksort_valgrind() {
check_output( check_output(
&example_file("quicksort", "Quicksort.roc"), &example_file("quicksort", "Quicksort.roc"),
"quicksort",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -108,6 +136,7 @@ mod cli_run {
fn run_quicksort_optimized_valgrind() { fn run_quicksort_optimized_valgrind() {
check_output( check_output(
&example_file("quicksort", "Quicksort.roc"), &example_file("quicksort", "Quicksort.roc"),
"quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -119,6 +148,7 @@ mod cli_run {
fn run_multi_module() { fn run_multi_module() {
check_output( check_output(
&example_file("multi-module", "Quicksort.roc"), &example_file("multi-module", "Quicksort.roc"),
"quicksort",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -130,6 +160,7 @@ mod cli_run {
fn run_multi_module_optimized() { fn run_multi_module_optimized() {
check_output( check_output(
&example_file("multi-module", "Quicksort.roc"), &example_file("multi-module", "Quicksort.roc"),
"quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -143,6 +174,7 @@ mod cli_run {
fn run_multi_module_valgrind() { fn run_multi_module_valgrind() {
check_output( check_output(
&example_file("multi-module", "Quicksort.roc"), &example_file("multi-module", "Quicksort.roc"),
"quicksort",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -156,6 +188,7 @@ mod cli_run {
fn run_multi_module_optimized_valgrind() { fn run_multi_module_optimized_valgrind() {
check_output( check_output(
&example_file("multi-module", "Quicksort.roc"), &example_file("multi-module", "Quicksort.roc"),
"quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -178,6 +211,7 @@ mod cli_run {
fn run_multi_dep_str_unoptimized() { fn run_multi_dep_str_unoptimized() {
check_output( check_output(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
"multi-dep-str",
&[], &[],
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
@ -189,6 +223,7 @@ mod cli_run {
fn run_multi_dep_str_optimized() { fn run_multi_dep_str_optimized() {
check_output( check_output(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
"multi-dep-str",
&["--optimize"], &["--optimize"],
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
@ -200,6 +235,7 @@ mod cli_run {
fn run_multi_dep_thunk_unoptimized() { fn run_multi_dep_thunk_unoptimized() {
check_output( check_output(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
"multi-dep-thunk",
&[], &[],
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
@ -211,6 +247,7 @@ mod cli_run {
fn run_multi_dep_thunk_optimized() { fn run_multi_dep_thunk_optimized() {
check_output( check_output(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
"multi-dep-thunk",
&["--optimize"], &["--optimize"],
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,

View file

@ -1,4 +1,3 @@
app app
host.o *.o
c_host.o *.dSYM
app.dSYM

View file

@ -0,0 +1 @@
multi-dep-str

View file

@ -1,4 +1,4 @@
app Main provides [ main ] imports [ Dep1 ] app "multi-dep-str" imports [ Dep1 ] provides [ main ] to "./platform"
main : Str main : Str
main = Dep1.str1 main = Dep1.str1

View file

@ -1,5 +1,7 @@
platform roc/quicksort platform examples/multi-module
provides [] requires { main : Str }
requires {} exposes []
packages {}
imports [] imports []
provides [ main ]
effects Effect {} effects Effect {}

View file

@ -3,7 +3,7 @@ use roc_std::RocStr;
use std::str; use std::str;
extern "C" { extern "C" {
#[link_name = "Main_main_1_exposed"] #[link_name = "roc__main_1_exposed"]
fn say_hello(output: &mut RocCallResult<RocStr>) -> (); fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
} }

View file

@ -0,0 +1 @@
multi-dep-thunk

View file

@ -1,4 +1,4 @@
app Main provides [ main ] imports [ Dep1 ] app "multi-dep-thunk" imports [ Dep1 ] provides [ main ] to "./platform"
main : Str main : Str
main = Dep1.value1 {} main = Dep1.value1 {}

View file

@ -1,5 +1,7 @@
platform roc/quicksort platform examples/multi-dep-thunk
provides [] requires { main : Str }
requires {} exposes []
packages {}
imports [] imports []
provides [ main ]
effects Effect {} effects Effect {}

View file

@ -3,7 +3,7 @@ use roc_std::RocStr;
use std::str; use std::str;
extern "C" { extern "C" {
#[link_name = "Main_main_1_exposed"] #[link_name = "roc__main_1_exposed"]
fn say_hello(output: &mut RocCallResult<RocStr>) -> (); fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
} }

View file

@ -5,7 +5,7 @@ extern crate roc_load;
extern crate roc_module; extern crate roc_module;
extern crate tempfile; extern crate tempfile;
use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE}; use roc_cli::repl::{INSTRUCTIONS, WELCOME_MESSAGE};
use serde::Deserialize; use serde::Deserialize;
use serde_xml_rs::from_str; use serde_xml_rs::from_str;
use std::env; use std::env;
@ -161,18 +161,18 @@ pub struct ValgrindErrorXWhat {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn extract_valgrind_errors(xml: &str) -> Vec<ValgrindError> { pub fn extract_valgrind_errors(xml: &str) -> Result<Vec<ValgrindError>, serde_xml_rs::Error> {
let parsed_xml: ValgrindOutput = let parsed_xml: ValgrindOutput = from_str(xml)?;
from_str(xml).unwrap_or_else(|err| let answer = parsed_xml
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nRaw valgrind output was:\n\n{}", err, xml));
parsed_xml
.fields .fields
.iter() .iter()
.filter_map(|field| match field { .filter_map(|field| match field {
ValgrindField::Error(err) => Some(err.clone()), ValgrindField::Error(err) => Some(err.clone()),
_ => None, _ => None,
}) })
.collect() .collect();
Ok(answer)
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -278,7 +278,7 @@ pub fn repl_eval(input: &str) -> Out {
// Remove the initial instructions from the output. // Remove the initial instructions from the output.
let expected_instructions = format!("{}{}{}", WELCOME_MESSAGE, INSTRUCTIONS, PROMPT); let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
let stdout = String::from_utf8(output.stdout).unwrap(); let stdout = String::from_utf8(output.stdout).unwrap();
assert!( assert!(
@ -300,7 +300,7 @@ pub fn repl_eval(input: &str) -> Out {
panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap());
} }
} else { } else {
let expected_after_answer = format!("\n{}", PROMPT); let expected_after_answer = format!("\n");
assert!( assert!(
answer.ends_with(&expected_after_answer), answer.ends_with(&expected_after_answer),

View file

@ -64,12 +64,12 @@ mod repl_eval {
#[test] #[test]
fn literal_0point0() { fn literal_0point0() {
expect_success("0.0", "0 : Float"); expect_success("0.0", "0 : F64");
} }
#[test] #[test]
fn literal_4point2() { fn literal_4point2() {
expect_success("4.2", "4.2 : Float"); expect_success("4.2", "4.2 : F64");
} }
#[test] #[test]
@ -84,7 +84,7 @@ mod repl_eval {
#[test] #[test]
fn float_addition() { fn float_addition() {
expect_success("1.1 + 2", "3.1 : Float"); expect_success("1.1 + 2", "3.1 : F64");
} }
#[test] #[test]
@ -148,7 +148,7 @@ mod repl_eval {
#[test] #[test]
fn single_element_tag_union() { fn single_element_tag_union() {
expect_success("True 1", "True 1 : [ True (Num *) ]*"); expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) Float ]*"); expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) F64 ]*");
} }
#[test] #[test]
@ -157,7 +157,7 @@ mod repl_eval {
expect_success( expect_success(
"if 1 == 1 then True 3 else False 3.14", "if 1 == 1 then True 3 else False 3.14",
"True 3 : [ False Float, True (Num *) ]*", "True 3 : [ False F64, True (Num *) ]*",
) )
} }
@ -206,7 +206,7 @@ mod repl_eval {
#[test] #[test]
fn literal_float_list() { fn literal_float_list() {
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List Float"); expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List F64");
} }
#[test] #[test]
@ -242,7 +242,7 @@ mod repl_eval {
fn nested_float_list() { fn nested_float_list() {
expect_success( expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#, r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Float))"#, r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#,
); );
} }
@ -250,7 +250,7 @@ mod repl_eval {
fn list_concat() { fn list_concat() {
expect_success( expect_success(
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]", "List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List Float", "[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List F64",
); );
} }
@ -265,7 +265,7 @@ mod repl_eval {
fn list_sum() { fn list_sum() {
expect_success("List.sum []", "0 : Num *"); expect_success("List.sum []", "0 : Num *");
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *"); expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : Float"); expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64");
} }
#[test] #[test]
@ -284,7 +284,7 @@ mod repl_eval {
fn basic_1_field_f64_record() { fn basic_1_field_f64_record() {
// Even though this gets unwrapped at runtime, the repl should still // Even though this gets unwrapped at runtime, the repl should still
// report it as a record // report it as a record
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float }"); expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : F64 }");
} }
#[test] #[test]
@ -303,7 +303,7 @@ mod repl_eval {
// report it as a record // report it as a record
expect_success( expect_success(
"{ foo: { bar: { baz: 4.2 } } }", "{ foo: { bar: { baz: 4.2 } } }",
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float } } }", "{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : F64 } } }",
); );
} }
@ -319,7 +319,7 @@ mod repl_eval {
fn basic_2_field_f64_record() { fn basic_2_field_f64_record() {
expect_success( expect_success(
"{ foo: 4.1, bar: 2.3 }", "{ foo: 4.1, bar: 2.3 }",
"{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }", "{ bar: 2.3, foo: 4.1 } : { bar : F64, foo : F64 }",
); );
} }
@ -327,7 +327,7 @@ mod repl_eval {
fn basic_2_field_mixed_record() { fn basic_2_field_mixed_record() {
expect_success( expect_success(
"{ foo: 4.1, bar: 2 }", "{ foo: 4.1, bar: 2 }",
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }", "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : F64 }",
); );
} }
@ -335,7 +335,7 @@ mod repl_eval {
fn basic_3_field_record() { fn basic_3_field_record() {
expect_success( expect_success(
"{ foo: 4.1, bar: 2, baz: 0x5 }", "{ foo: 4.1, bar: 2, baz: 0x5 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : Float }", "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : F64 }",
); );
} }
@ -350,7 +350,7 @@ mod repl_eval {
fn list_of_2_field_records() { fn list_of_2_field_records() {
expect_success( expect_success(
"[ { foo: 4.1, bar: 2 } ]", "[ { foo: 4.1, bar: 2 } ]",
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float }", "[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : F64 }",
); );
} }
@ -382,7 +382,7 @@ mod repl_eval {
fn list_of_3_field_records() { fn list_of_3_field_records() {
expect_success( expect_success(
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]", "[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : Float }", "[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : F64 }",
); );
} }

View file

@ -248,6 +248,16 @@ fn link_macos(
} }
}; };
// This path only exists on macOS Big Sur, and it causes ld errors
// on Catalina if it's specified with -L, so we replace it with a
// redundant -lSystem if the directory isn't there.
let big_sur_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib";
let big_sur_fix = if Path::new(big_sur_path).exists() {
format!("-L{}", big_sur_path)
} else {
String::from("-lSystem")
};
Ok(( Ok((
// NOTE: order of arguments to `ld` matters here! // NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments // The `-l` flags should go after the `.o` arguments
@ -263,7 +273,7 @@ fn link_macos(
.args(&[ .args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274 // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references // for discussion and further references
"-L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib", &big_sur_fix,
"-lSystem", "-lSystem",
"-lresolv", "-lresolv",
"-lpthread", "-lpthread",

View file

@ -2,6 +2,7 @@ use crate::target;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode}; use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::values::FunctionValue;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope}; use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
@ -70,6 +71,15 @@ pub fn gen_from_mono_module(
// strip Zig debug stuff // strip Zig debug stuff
// module.strip_debug_info(); // module.strip_debug_info();
// mark our zig-defined builtins as internal
use inkwell::module::Linkage;
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
}
let builder = context.create_builder(); let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module); let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level); let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
@ -221,3 +231,30 @@ pub fn gen_from_mono_module(
.write_to_file(&env.module, FileType::Object, &app_o_file) .write_to_file(&env.module, FileType::Object, &app_o_file)
.expect("Writing .o file failed"); .expect("Writing .o file failed");
} }
pub struct FunctionIterator<'ctx> {
next: Option<FunctionValue<'ctx>>,
}
impl<'ctx> FunctionIterator<'ctx> {
pub fn from_module(module: &inkwell::module::Module<'ctx>) -> Self {
Self {
next: module.get_first_function(),
}
}
}
impl<'ctx> Iterator for FunctionIterator<'ctx> {
type Item = FunctionValue<'ctx>;
fn next(&mut self) -> Option<Self::Item> {
match self.next {
Some(function) => {
self.next = function.get_next_function();
Some(function)
}
None => None,
}
}
}

View file

@ -0,0 +1,6 @@
#!/bin/bash
set -eux
# Test every zig
find src/*.zig -type f -exec zig test {} \;

View file

@ -15,6 +15,7 @@ const str = @import("str.zig");
comptime { exportStrFn(str.strSplitInPlace, "str_split_in_place"); } comptime { exportStrFn(str.strSplitInPlace, "str_split_in_place"); }
comptime { exportStrFn(str.countSegments, "count_segments"); } comptime { exportStrFn(str.countSegments, "count_segments"); }
comptime { exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); } comptime { exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); }
comptime { exportStrFn(str.startsWith, "starts_with"); }
// Export helpers - Must be run inside a comptime // Export helpers - Must be run inside a comptime
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void { fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {

View file

@ -4,30 +4,143 @@ const testing = std.testing;
const expectEqual = testing.expectEqual; const expectEqual = testing.expectEqual;
const expect = testing.expect; const expect = testing.expect;
extern fn malloc(size: usize) ?*u8;
extern fn free([*]u8) void;
const RocStr = struct { const RocStr = struct {
str_bytes_ptrs: [*]u8, str_bytes: ?[*]u8,
str_len: usize, str_len: usize,
pub fn init(bytes: [*]u8, len: usize) RocStr { pub fn empty() RocStr {
return RocStr { return RocStr {
.str_bytes_ptrs = bytes, .str_len = 0,
.str_len = len .str_bytes = null
}; };
} }
pub fn eq(self: *RocStr, other: RocStr) bool { // This takes ownership of the pointed-to bytes if they won't fit in a
if (self.str_len != other.str_len) { // small string, and returns a (pointer, len) tuple which points to them.
pub fn init(bytes: [*]const u8, length: usize) RocStr {
const rocStrSize = @sizeOf(RocStr);
if (length < rocStrSize) {
var ret_small_str = RocStr.empty();
const target_ptr = @ptrToInt(&ret_small_str);
var index : u8 = 0;
// Zero out the data, just to be safe
while (index < rocStrSize) {
var offset_ptr = @intToPtr(*u8, target_ptr + index);
offset_ptr.* = 0;
index += 1;
}
index = 0;
while (index < length) {
var offset_ptr = @intToPtr(*u8, target_ptr + index);
offset_ptr.* = bytes[index];
index += 1;
}
// set the final byte to be the length
const final_byte_ptr = @intToPtr(*u8, target_ptr + rocStrSize - 1);
final_byte_ptr.* = @truncate(u8, length) ^ 0b10000000;
return ret_small_str;
} else {
var new_bytes: [*]u8 = @ptrCast([*]u8, malloc(length));
@memcpy(new_bytes, bytes, length);
return RocStr {
.str_bytes = new_bytes,
.str_len = length
};
}
}
pub fn drop(self: RocStr) void {
if (!self.is_small_str()) {
const str_bytes: [*]u8 = self.str_bytes orelse unreachable;
free(str_bytes);
}
}
pub fn eq(self: RocStr, other: RocStr) bool {
const self_bytes_ptr: ?[*]const u8 = self.str_bytes;
const other_bytes_ptr: ?[*]const u8 = other.str_bytes;
// If they are byte-for-byte equal, they're definitely equal!
if (self_bytes_ptr == other_bytes_ptr and self.str_len == other.str_len) {
return true;
}
const self_len = self.len();
const other_len = other.len();
// If their lengths are different, they're definitely unequal.
if (self_len != other_len) {
return false; return false;
} }
var areEq: bool = true; const self_bytes_nonnull: [*]const u8 = self_bytes_ptr orelse unreachable;
const other_bytes_nonnull: [*]const u8 = other_bytes_ptr orelse unreachable;
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self);
const other_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &other);
const self_bytes: [*]const u8 = if (self_len < @sizeOf(RocStr)) self_u8_ptr else self_bytes_nonnull;
const other_bytes: [*]const u8 = if (other_len < @sizeOf(RocStr)) other_u8_ptr else other_bytes_nonnull;
var index: usize = 0; var index: usize = 0;
while (index < self.str_len and areEq) {
areEq = areEq and self.str_bytes_ptrs[index] == other.str_bytes_ptrs[index]; // TODO rewrite this into a for loop
while (index < self.str_len) {
if (self_bytes[index] != other_bytes[index]) {
return false;
}
index = index + 1; index = index + 1;
} }
return areEq; return true;
}
pub fn is_small_str(self: RocStr) bool {
return @bitCast(isize, self.str_len) < 0;
}
pub fn len(self: RocStr) usize {
const bytes: [*]const u8 = @ptrCast([*]const u8, &self);
const last_byte = bytes[@sizeOf(RocStr) - 1];
const small_len = @as(usize, last_byte ^ 0b1000_0000);
const big_len = self.str_len;
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
return if (self.is_small_str()) small_len else big_len;
}
// Given a pointer to some memory of length (self.len() + 1) bytes,
// write this RocStr's contents into it as a nul-terminated C string.
//
// This is useful so that (for example) we can write into an `alloca`
// if the C string only needs to live long enough to be passed as an
// argument to a C function - like the file path argument to `fopen`.
pub fn write_cstr(self: RocStr, dest: [*]u8) void {
const len: usize = self.len();
const small_src = @ptrCast(*u8, self);
const big_src = self.str_bytes_ptr;
// For a small string, copy the bytes directly from `self`.
// For a large string, copy from the pointed-to bytes.
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
const src: [*]u8 = if (len < @sizeOf(RocStr)) small_src else big_src;
@memcpy(dest, src, len);
// C strings must end in 0.
dest[len + 1] = 0;
} }
test "RocStr.eq: equal" { test "RocStr.eq: equal" {
@ -41,7 +154,11 @@ const RocStr = struct {
const str2_ptr: [*]u8 = &str2; const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(roc_str1.eq(roc_str2)); // TODO: fix those tests
// expect(roc_str1.eq(roc_str2));
roc_str1.drop();
roc_str2.drop();
} }
test "RocStr.eq: not equal different length" { test "RocStr.eq: not equal different length" {
@ -56,6 +173,9 @@ const RocStr = struct {
var roc_str2 = RocStr.init(str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(!roc_str1.eq(roc_str2)); expect(!roc_str1.eq(roc_str2));
roc_str1.drop();
roc_str2.drop();
} }
test "RocStr.eq: not equal same length" { test "RocStr.eq: not equal same length" {
@ -69,38 +189,37 @@ const RocStr = struct {
const str2_ptr: [*]u8 = &str2; const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(!roc_str1.eq(roc_str2)); // TODO: fix those tests
// expect(!roc_str1.eq(roc_str2));
roc_str1.drop();
roc_str2.drop();
} }
}; };
// Str.split // Str.split
pub fn strSplitInPlace( pub fn strSplitInPlace(
bytes_array: [*]u128, array: [*]RocStr,
array_len: usize, array_len: usize,
str_bytes_ptrs: [*]u8, str_bytes: [*]const u8,
str_len: usize, str_len: usize,
delimiter_bytes_ptrs: [*]u8, delimiter_bytes_ptrs: [*]const u8,
delimiter_len: usize delimiter_len: usize
) callconv(.C) void { ) callconv(.C) void {
var array = @ptrCast([*]RocStr, bytes_array);
var ret_array_index : usize = 0; var ret_array_index : usize = 0;
var sliceStart_index : usize = 0; var sliceStart_index : usize = 0;
var str_index : usize = 0; var str_index : usize = 0;
if (str_len > delimiter_len) { if (str_len > delimiter_len) {
const end_index : usize = str_len - delimiter_len; const end_index : usize = str_len - delimiter_len + 1;
while (str_index <= end_index) { while (str_index <= end_index) {
var delimiter_index : usize = 0; var delimiter_index : usize = 0;
var matches_delimiter = true; var matches_delimiter = true;
while (delimiter_index < delimiter_len) { while (delimiter_index < delimiter_len) {
var delimiterChar = delimiter_bytes_ptrs[delimiter_index]; var delimiterChar = delimiter_bytes_ptrs[delimiter_index];
var strChar = str_bytes_ptrs[str_index + delimiter_index]; var strChar = str_bytes[str_index + delimiter_index];
if (delimiterChar != strChar) { if (delimiterChar != strChar) {
matches_delimiter = false; matches_delimiter = false;
@ -111,7 +230,9 @@ pub fn strSplitInPlace(
} }
if (matches_delimiter) { if (matches_delimiter) {
array[ret_array_index] = RocStr.init(str_bytes_ptrs + sliceStart_index, str_index - sliceStart_index); const segment_len : usize = str_index - sliceStart_index;
array[ret_array_index] = RocStr.init(str_bytes + sliceStart_index, segment_len);
sliceStart_index = str_index + delimiter_len; sliceStart_index = str_index + delimiter_len;
ret_array_index += 1; ret_array_index += 1;
str_index += delimiter_len; str_index += delimiter_len;
@ -121,17 +242,17 @@ pub fn strSplitInPlace(
} }
} }
array[ret_array_index] = RocStr.init(str_bytes_ptrs + sliceStart_index, str_len - sliceStart_index); array[ret_array_index] = RocStr.init(str_bytes + sliceStart_index, str_len - sliceStart_index);
} }
test "strSplitInPlace: no delimiter" { test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ] // Str.split "abc" "!" == [ "abc" ]
var str: [3]u8 = "abc".*; var str: [3]u8 = "abc".*;
const str_ptr: [*]u8 = &str; const str_ptr: [*]const u8 = &str;
var delimiter: [1]u8 = "!".*; var delimiter: [1]u8 = "!".*;
const delimiter_ptr: [*]u8 = &delimiter; const delimiter_ptr: [*]const u8 = &delimiter;
var array: [1]RocStr = undefined; var array: [1]RocStr = undefined;
const array_ptr: [*]RocStr = &array; const array_ptr: [*]RocStr = &array;
@ -150,7 +271,60 @@ test "strSplitInPlace: no delimiter" {
}; };
expectEqual(array.len, expected.len); expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0])); // TODO: fix those tests
//expect(array[0].eq(expected[0]));
for (array) |roc_str| {
roc_str.drop();
}
for (expected) |roc_str| {
roc_str.drop();
}
}
test "strSplitInPlace: empty end" {
const str_len: usize = 50;
var str: [str_len]u8 = "1---- ---- ---- ---- ----2---- ---- ---- ---- ----".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 24;
const delimiter: [delimiter_len:0]u8 = "---- ---- ---- ---- ----".*;
const delimiter_ptr: [*]const u8 = &delimiter;
const array_len : usize = 3;
var array: [array_len]RocStr = [_]RocStr {
undefined,
undefined,
undefined,
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(
array_ptr,
array_len,
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
const first_expected_str_len: usize = 1;
var first_expected_str: [first_expected_str_len]u8 = "1".*;
const first_expected_str_ptr: [*]u8 = &first_expected_str;
var firstExpectedRocStr = RocStr.init(first_expected_str_ptr, first_expected_str_len);
const second_expected_str_len: usize = 1;
var second_expected_str: [second_expected_str_len]u8 = "2".*;
const second_expected_str_ptr: [*]u8 = &second_expected_str;
var secondExpectedRocStr = RocStr.init(second_expected_str_ptr, second_expected_str_len);
// TODO: fix those tests
// expectEqual(array.len, 3);
// expectEqual(array[0].str_len, 1);
// expect(array[0].eq(firstExpectedRocStr));
// expect(array[1].eq(secondExpectedRocStr));
// expectEqual(array[2].str_len, 0);
} }
test "strSplitInPlace: delimiter on sides" { test "strSplitInPlace: delimiter on sides" {
@ -183,13 +357,14 @@ test "strSplitInPlace: delimiter on sides" {
const expected_str_len: usize = 3; const expected_str_len: usize = 3;
var expected_str: [expected_str_len]u8 = "ghi".*; var expected_str: [expected_str_len]u8 = "ghi".*;
const expected_str_ptr: [*]u8 = &expected_str; const expected_str_ptr: [*]const u8 = &expected_str;
var expectedRocStr = RocStr.init(expected_str_ptr, expected_str_len); var expectedRocStr = RocStr.init(expected_str_ptr, expected_str_len);
expectEqual(array.len, 3); // TODO: fix those tests
expectEqual(array[0].str_len, 0); // expectEqual(array.len, 3);
expect(array[1].eq(expectedRocStr)); // expectEqual(array[0].str_len, 0);
expectEqual(array[2].str_len, 0); // expect(array[1].eq(expectedRocStr));
// expectEqual(array[2].str_len, 0);
} }
test "strSplitInPlace: three pieces" { test "strSplitInPlace: three pieces" {
@ -227,23 +402,24 @@ test "strSplitInPlace: three pieces" {
var expected_array = [array_len]RocStr{ var expected_array = [array_len]RocStr{
RocStr{ RocStr{
.str_bytes_ptrs = a_ptr, .str_bytes = a_ptr,
.str_len = 1, .str_len = 1,
}, },
RocStr{ RocStr{
.str_bytes_ptrs = b_ptr, .str_bytes = b_ptr,
.str_len = 1, .str_len = 1,
}, },
RocStr{ RocStr{
.str_bytes_ptrs = c_ptr, .str_bytes = c_ptr,
.str_len = 1, .str_len = 1,
} }
}; };
expectEqual(expected_array.len, array.len); // TODO: fix those tests
expect(array[0].eq(expected_array[0])); // expectEqual(expected_array.len, array.len);
expect(array[1].eq(expected_array[1])); // expect(array[0].eq(expected_array[0]));
expect(array[2].eq(expected_array[2])); // expect(array[1].eq(expected_array[1]));
// expect(array[2].eq(expected_array[2]));
} }
// This is used for `Str.split : Str, Str -> Array Str // This is used for `Str.split : Str, Str -> Array Str
@ -251,7 +427,7 @@ test "strSplitInPlace: three pieces" {
// needs to be broken into, so that we can allocate a array // needs to be broken into, so that we can allocate a array
// of that size. It always returns at least 1. // of that size. It always returns at least 1.
pub fn countSegments( pub fn countSegments(
str_bytes_ptrs: [*]u8, str_bytes: [*]u8,
str_len: usize, str_len: usize,
delimiter_bytes_ptrs: [*]u8, delimiter_bytes_ptrs: [*]u8,
delimiter_len: usize delimiter_len: usize
@ -260,7 +436,7 @@ pub fn countSegments(
if (str_len > delimiter_len) { if (str_len > delimiter_len) {
var str_index: usize = 0; var str_index: usize = 0;
const end_cond: usize = str_len - delimiter_len; const end_cond: usize = str_len - delimiter_len + 1;
while (str_index < end_cond) { while (str_index < end_cond) {
var delimiter_index: usize = 0; var delimiter_index: usize = 0;
@ -269,7 +445,7 @@ pub fn countSegments(
while (delimiter_index < delimiter_len) { while (delimiter_index < delimiter_len) {
const delimiterChar = delimiter_bytes_ptrs[delimiter_index]; const delimiterChar = delimiter_bytes_ptrs[delimiter_index];
const strChar = str_bytes_ptrs[str_index + delimiter_index]; const strChar = str_bytes[str_index + delimiter_index];
if (delimiterChar != strChar) { if (delimiterChar != strChar) {
matches_delimiter = false; matches_delimiter = false;
@ -438,3 +614,48 @@ test "countGraphemeClusters: emojis, ut8, and ascii characters" {
var count = countGraphemeClusters(bytes_ptr, bytes_len); var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 10); expectEqual(count, 10);
} }
// Str.startsWith
pub fn startsWith(
bytes_ptr: [*]u8,
bytes_len: usize,
prefix_ptr: [*]u8,
prefix_len: usize
) callconv(.C) bool {
if(prefix_len > bytes_len) {
return false;
}
// we won't exceed bytes_len due to the previous check
var i : usize = 0;
while(i < prefix_len) {
if(bytes_ptr[i] != prefix_ptr[i]) {
return false;
}
i += 1;
}
return true;
}
test "startsWith: 123456789123456789 starts with 123456789123456789" {
const str_len: usize = 18;
var str: [str_len]u8 = "123456789123456789".*;
const str_ptr: [*]u8 = &str;
expect(startsWith(str_ptr, str_len, str_ptr, str_len));
}
test "startsWith: 12345678912345678910 starts with 123456789123456789" {
const str_len: usize = 20;
var str: [str_len]u8 = "12345678912345678910".*;
const str_ptr: [*]u8 = &str;
const prefix_len: usize = 18;
var prefix: [prefix_len]u8 = "123456789123456789".*;
const prefix_ptr: [*]u8 = &str;
expect(startsWith(str_ptr, str_len, prefix_ptr, prefix_len));
}

View file

@ -26,3 +26,4 @@ pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place"; pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters"; pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";

View file

@ -411,6 +411,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![str_type()], Box::new(bool_type())), top_level_function(vec![str_type()], Box::new(bool_type())),
); );
// startsWith : Str, Str -> Bool
add_type(
Symbol::STR_STARTS_WITH,
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())),
);
// countGraphemes : Str -> Int // countGraphemes : Str -> Int
add_type( add_type(
Symbol::STR_COUNT_GRAPHEMES, Symbol::STR_COUNT_GRAPHEMES,
@ -483,9 +489,22 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// walkRight : List elem, (elem -> accum -> accum), accum -> accum // walk : List elem, (elem -> accum -> accum), accum -> accum
add_type( add_type(
Symbol::LIST_WALK_RIGHT, Symbol::LIST_WALK,
top_level_function(
vec![
list_type(flex(TVAR1)),
closure(vec![flex(TVAR1), flex(TVAR2)], TVAR3, Box::new(flex(TVAR2))),
flex(TVAR2),
],
Box::new(flex(TVAR2)),
),
);
// walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
add_type(
Symbol::LIST_WALK_BACKWARDS,
top_level_function( top_level_function(
vec![ vec![
list_type(flex(TVAR1)), list_type(flex(TVAR1)),

View file

@ -777,11 +777,38 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
// walkRight : Attr (* | u) (List (Attr u a)) // walk : Attr (* | u) (List (Attr u a))
// , Attr Shared (Attr u a -> b -> b) // , Attr Shared (Attr u a -> b -> b)
// , b // , b
// -> b // -> b
add_type(Symbol::LIST_WALK_RIGHT, { add_type(Symbol::LIST_WALK, {
let_tvars! { u, a, b, star1, closure };
unique_function(
vec![
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
container(star1, vec![u]),
SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]),
],
),
shared(SolvedType::Func(
vec![attr_type(u, a), flex(b)],
Box::new(flex(closure)),
Box::new(flex(b)),
)),
flex(b),
],
flex(b),
)
});
// walkBackwards : Attr (* | u) (List (Attr u a))
// , Attr Shared (Attr u a -> b -> b)
// , b
// -> b
add_type(Symbol::LIST_WALK_BACKWARDS, {
let_tvars! { u, a, b, star1, closure }; let_tvars! { u, a, b, star1, closure };
unique_function( unique_function(
@ -1063,6 +1090,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![str_type(star1), str_type(star2)], str_type(star3)) unique_function(vec![str_type(star1), str_type(star2)], str_type(star3))
}); });
// Str.startsWith : Attr * Str, Attr * Str -> Attr * Bool
add_type(Symbol::STR_STARTS_WITH, {
let_tvars! { star1, star2, star3 };
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3))
});
// Str.countGraphemes : Attr * Str, -> Attr * Int // Str.countGraphemes : Attr * Str, -> Attr * Int
add_type(Symbol::STR_COUNT_GRAPHEMES, { add_type(Symbol::STR_COUNT_GRAPHEMES, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 };
@ -1144,7 +1177,7 @@ fn float_type(u: VarId) -> SolvedType {
vec![ vec![
flex(u), flex(u),
SolvedType::Alias( SolvedType::Alias(
Symbol::NUM_FLOAT, Symbol::NUM_F64,
Vec::new(), Vec::new(),
Box::new(builtin_aliases::num_type(SolvedType::Apply( Box::new(builtin_aliases::num_type(SolvedType::Apply(
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,

View file

@ -380,7 +380,7 @@ fn can_annotation_help(
} }
}, },
Record { fields, ext } => { Record { fields, ext, .. } => {
let field_types = can_assigned_fields( let field_types = can_assigned_fields(
env, env,
fields, fields,
@ -408,7 +408,7 @@ fn can_annotation_help(
Type::Record(field_types, Box::new(ext_type)) Type::Record(field_types, Box::new(ext_type))
} }
TagUnion { tags, ext } => { TagUnion { tags, ext, .. } => {
let tag_types = can_tags( let tag_types = can_tags(
env, env,
tags, tags,

View file

@ -53,6 +53,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::STR_CONCAT => str_concat, Symbol::STR_CONCAT => str_concat,
Symbol::STR_SPLIT => str_split, Symbol::STR_SPLIT => str_split,
Symbol::STR_IS_EMPTY => str_is_empty, Symbol::STR_IS_EMPTY => str_is_empty,
Symbol::STR_STARTS_WITH => str_starts_with,
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes, Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
Symbol::LIST_LEN => list_len, Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get, Symbol::LIST_GET => list_get,
@ -70,7 +71,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_JOIN => list_join, Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map, Symbol::LIST_MAP => list_map,
Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_KEEP_IF => list_keep_if,
Symbol::LIST_WALK_RIGHT => list_walk_right, Symbol::LIST_WALK => list_walk,
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
Symbol::NUM_ADD => num_add, Symbol::NUM_ADD => num_add,
Symbol::NUM_ADD_CHECKED => num_add_checked, Symbol::NUM_ADD_CHECKED => num_add_checked,
Symbol::NUM_ADD_WRAP => num_add_wrap, Symbol::NUM_ADD_WRAP => num_add_wrap,
@ -967,6 +969,26 @@ fn str_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Str.startsWith : Str, Str -> Bool
fn str_starts_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let bool_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrStartsWith,
args: vec![(str_var, Var(Symbol::ARG_1)), (str_var, Var(Symbol::ARG_2))],
ret_var: bool_var,
};
defn(
symbol,
vec![(str_var, Symbol::ARG_1), (str_var, Symbol::ARG_2)],
var_store,
body,
bool_var,
)
}
/// Str.countGraphemes : Str -> Int /// Str.countGraphemes : Str -> Int
fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh(); let str_var = var_store.fresh();
@ -1292,14 +1314,43 @@ fn list_join(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum /// List.walk : List elem, (elem -> accum -> accum), accum -> accum
fn list_walk_right(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let func_var = var_store.fresh(); let func_var = var_store.fresh();
let accum_var = var_store.fresh(); let accum_var = var_store.fresh();
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListWalkRight, op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(func_var, Var(Symbol::ARG_2)),
(accum_var, Var(Symbol::ARG_3)),
],
ret_var: accum_var,
};
defn(
symbol,
vec![
(list_var, Symbol::ARG_1),
(func_var, Symbol::ARG_2),
(accum_var, Symbol::ARG_3),
],
var_store,
body,
accum_var,
)
}
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let func_var = var_store.fresh();
let accum_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListWalkBackwards,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(func_var, Var(Symbol::ARG_2)), (func_var, Var(Symbol::ARG_2)),

View file

@ -349,6 +349,10 @@ pub fn canonicalize_expr<'a>(
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None; output.tail_call = None;
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
let expr = match fn_expr.value { let expr = match fn_expr.value {
Var(symbol) => { Var(symbol) => {
output.references.calls.insert(symbol); output.references.calls.insert(symbol);
@ -400,10 +404,6 @@ pub fn canonicalize_expr<'a>(
} }
}; };
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
(expr, output) (expr, output)
} }
ast::Expr::Var { module_name, ident } => { ast::Expr::Var { module_name, ident } => {

View file

@ -361,7 +361,7 @@ pub fn canonicalize_pattern<'a>(
// If we encountered an erroneous pattern (e.g. one with shadowing), // If we encountered an erroneous pattern (e.g. one with shadowing),
// use the resulting RuntimeError. Otherwise, return a successful record destructure. // use the resulting RuntimeError. Otherwise, return a successful record destructure.
opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure { opt_erroneous.unwrap_or(Pattern::RecordDestructure {
whole_var, whole_var,
ext_var, ext_var,
destructs, destructs,

View file

@ -74,7 +74,7 @@ pub fn str_type() -> Type {
#[inline(always)] #[inline(always)]
pub fn num_float() -> Type { pub fn num_float() -> Type {
Type::Alias( Type::Alias(
Symbol::NUM_FLOAT, Symbol::NUM_F64,
vec![], vec![],
Box::new(num_num(num_floatingpoint())), Box::new(num_num(num_floatingpoint())),
) )

View file

@ -37,10 +37,6 @@ pub enum Newlines {
No, No,
} }
pub fn fmt_annotation<'a>(buf: &mut String<'a>, annotation: &'a TypeAnnotation<'a>, indent: u16) {
annotation.format(buf, indent);
}
pub trait Formattable<'a> { pub trait Formattable<'a> {
fn is_multiline(&self) -> bool; fn is_multiline(&self) -> bool;
@ -84,6 +80,88 @@ where
} }
} }
macro_rules! format_sequence {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => {
let is_multiline =
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty();
if is_multiline {
let braces_indent = $indent + INDENT;
let item_indent = braces_indent + INDENT;
if ($newline == Newlines::Yes) {
newline($buf, braces_indent);
}
$buf.push($start);
for item in $items.iter() {
match item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
newline($buf, item_indent);
fmt_comments_only(
$buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format($buf, item_indent);
$buf.push(',');
fmt_comments_only(
$buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format($buf, item_indent);
$buf.push(',');
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
newline($buf, item_indent);
sub_expr.format($buf, item_indent);
$buf.push(',');
fmt_comments_only($buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
newline($buf, item_indent);
item.format($buf, item_indent);
$buf.push(',');
}
}
}
fmt_comments_only($buf, $final_comments.iter(), NewlineAt::Top, item_indent);
newline($buf, braces_indent);
$buf.push($end);
} else {
// is_multiline == false
// there is no comment to add
$buf.push($start);
let mut iter = $items.iter().peekable();
while let Some(item) = iter.next() {
$buf.push(' ');
item.format($buf, $indent);
if iter.peek().is_some() {
$buf.push(',');
}
}
if !$items.is_empty() {
$buf.push(' ');
}
$buf.push($end);
}
};
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> { impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*; use roc_parse::ast::TypeAnnotation::*;
@ -105,7 +183,11 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()), Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(), As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(),
Record { fields, ext } => { Record {
fields,
ext,
final_comments: _,
} => {
match ext { match ext {
Some(ann) if ann.value.is_multiline() => return true, Some(ann) if ann.value.is_multiline() => return true,
_ => {} _ => {}
@ -114,7 +196,11 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fields.iter().any(|field| field.value.is_multiline()) fields.iter().any(|field| field.value.is_multiline())
} }
TagUnion { tags, ext } => { TagUnion {
tags,
ext,
final_comments: _,
} => {
match ext { match ext {
Some(ann) if ann.value.is_multiline() => return true, Some(ann) if ann.value.is_multiline() => return true,
_ => {} _ => {}
@ -197,16 +283,33 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
BoundVariable(v) => buf.push_str(v), BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'), Wildcard => buf.push('*'),
TagUnion { tags, ext } => { TagUnion {
tags.format(buf, indent); tags,
ext,
final_comments,
} => {
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
if let Some(loc_ext_ann) = *ext { if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent); loc_ext_ann.value.format(buf, indent);
} }
} }
Record { fields, ext } => { Record {
fields.format(buf, indent); fields,
ext,
final_comments,
} => {
format_sequence!(
buf,
indent,
'{',
'}',
fields,
final_comments,
newlines,
AssignedField
);
if let Some(loc_ext_ann) = *ext { if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent); loc_ext_ann.value.format(buf, indent);
@ -220,8 +323,18 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
rhs.value.format(buf, indent); rhs.value.format(buf, indent);
} }
SpaceBefore(ann, _spaces) | SpaceAfter(ann, _spaces) => { SpaceBefore(ann, spaces) => {
ann.format_with_options(buf, parens, newlines, indent) newline(buf, indent + INDENT);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + INDENT);
ann.format_with_options(buf, parens, Newlines::No, indent)
}
SpaceAfter(ann, spaces) => {
ann.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
// seems like this SpaceAfter is not constructible
// so this branch hasn't be tested. Please add some test if
// this branch is actually reached and remove this dbg_assert.
debug_assert!(false);
} }
Malformed(raw) => buf.push_str(raw), Malformed(raw) => buf.push_str(raw),
@ -421,116 +534,3 @@ impl<'a> Formattable<'a> for Tag<'a> {
} }
} }
} }
macro_rules! implement_format_sequence {
($start:expr, $end:expr, $t:ident) => {
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.push($start);
let mut iter = self.iter().peekable();
let is_multiline = self.is_multiline();
let item_indent = if is_multiline {
indent + INDENT
} else {
indent
};
while let Some(item) = iter.next() {
if is_multiline {
match &item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
newline(buf, item_indent);
fmt_comments_only(
buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
newline(buf, item_indent);
sub_expr.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
newline(buf, item_indent);
item.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
} else {
buf.push(' ');
item.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
if is_multiline {
newline(buf, indent);
}
if !self.is_empty() && !is_multiline {
buf.push(' ');
}
buf.push($end);
}
};
}
impl<'a> Formattable<'a> for &'a [Located<Tag<'a>>] {
fn is_multiline(&self) -> bool {
self.iter().any(|t| t.value.is_multiline())
}
implement_format_sequence!('[', ']', Tag);
}
impl<'a> Formattable<'a> for &'a [Located<AssignedField<'a, TypeAnnotation<'a>>>] {
fn is_multiline(&self) -> bool {
self.iter().any(|f| f.value.is_multiline())
}
implement_format_sequence!('{', '}', AssignedField);
}

View file

@ -36,8 +36,23 @@ impl<'a> Formattable<'a> for Def<'a> {
match self { match self {
Annotation(loc_pattern, loc_annotation) => { Annotation(loc_pattern, loc_annotation) => {
loc_pattern.format(buf, indent); loc_pattern.format(buf, indent);
if loc_annotation.is_multiline() {
buf.push_str(" :");
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent,
);
} else {
buf.push_str(" : "); buf.push_str(" : ");
loc_annotation.format(buf, indent); loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
} }
Alias { name, vars, ann } => { Alias { name, vars, ann } => {
buf.push_str(name.value); buf.push_str(name.value);

View file

@ -833,6 +833,12 @@ pub fn fmt_record<'a>(
if is_multiline { if is_multiline {
let field_indent = indent + INDENT; let field_indent = indent + INDENT;
for field in loc_fields.iter() { for field in loc_fields.iter() {
// comma addition is handled by the `format_field_multiline` function
// since we can have stuff like:
// { x # comment
// , y
// }
// In this case, we have to move the comma before the comment.
format_field_multiline(buf, &field.value, field_indent, ""); format_field_multiline(buf, &field.value, field_indent, "");
} }
@ -894,7 +900,7 @@ fn format_field_multiline<'a, T>(
} }
buf.push_str(separator_prefix); buf.push_str(separator_prefix);
buf.push('?'); buf.push_str("? ");
ann.value.format(buf, indent); ann.value.format(buf, indent);
buf.push(','); buf.push(',');
} }
@ -904,10 +910,28 @@ fn format_field_multiline<'a, T>(
buf.push(','); buf.push(',');
} }
AssignedField::SpaceBefore(sub_field, spaces) => { AssignedField::SpaceBefore(sub_field, spaces) => {
// We have something like that:
// ```
// # comment
// field,
// ```
// we'd like to preserve this
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
format_field_multiline(buf, sub_field, indent, separator_prefix); format_field_multiline(buf, sub_field, indent, separator_prefix);
} }
AssignedField::SpaceAfter(sub_field, spaces) => { AssignedField::SpaceAfter(sub_field, spaces) => {
// We have somethig like that:
// ```
// field # comment
// , otherfield
// ```
// we'd like to transform it into:
// ```
// field,
// # comment
// otherfield
// ```
format_field_multiline(buf, sub_field, indent, separator_prefix); format_field_multiline(buf, sub_field, indent, separator_prefix);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
} }

View file

@ -1,8 +1,7 @@
use crate::spaces::{fmt_spaces, INDENT}; use crate::spaces::{fmt_spaces, INDENT};
use bumpalo::collections::{String, Vec}; use bumpalo::collections::{String, Vec};
use roc_parse::ast::{ use roc_parse::ast::Module;
AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, Module, PlatformHeader, use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader};
};
use roc_region::all::Located; use roc_region::all::Located;
pub fn fmt_module<'a>(buf: &mut String<'a>, module: &'a Module<'a>) { pub fn fmt_module<'a>(buf: &mut String<'a>, module: &'a Module<'a>) {
@ -113,7 +112,7 @@ fn fmt_imports<'a>(
fn fmt_exposes<'a>( fn fmt_exposes<'a>(
buf: &mut String<'a>, buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a>>>, loc_entries: &'a Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
indent: u16, indent: u16,
) { ) {
buf.push('['); buf.push('[');
@ -137,11 +136,11 @@ fn fmt_exposes<'a>(
buf.push(']'); buf.push(']');
} }
fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a>, indent: u16) { fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a, &'a str>, indent: u16) {
use roc_parse::ast::ExposesEntry::*; use roc_parse::header::ExposesEntry::*;
match entry { match entry {
Ident(ident) => buf.push_str(ident), Exposed(ident) => buf.push_str(ident),
SpaceBefore(sub_entry, spaces) => { SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
@ -155,7 +154,7 @@ fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a>, inde
} }
fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>, indent: u16) { fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>, indent: u16) {
use roc_parse::ast::ImportsEntry::*; use roc_parse::header::ImportsEntry::*;
match entry { match entry {
Module(module, loc_exposes_entries) => { Module(module, loc_exposes_entries) => {
@ -176,6 +175,10 @@ fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>, inde
} }
} }
Package(_name, _entries) => {
todo!("TODO Format imported package");
}
SpaceBefore(sub_entry, spaces) => { SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
fmt_imports_entry(buf, sub_entry, indent); fmt_imports_entry(buf, sub_entry, indent);

View file

@ -757,6 +757,150 @@ mod test_fmt {
); );
} }
#[test]
fn trailing_comma_in_record_annotation() {
expr_formats_to(
indoc!(
r#"
f: { y : Int,
x : Int ,
}
f"#
),
indoc!(
r#"
f :
{
y : Int,
x : Int,
}
f"#
),
);
}
#[test]
fn trailing_comma_in_record_annotation_same() {
expr_formats_same(indoc!(
r#"
f :
{
y : Int,
x : Int,
}
f"#
));
}
#[test]
fn multiline_type_definition() {
expr_formats_same(indoc!(
r#"
f :
Int
f"#
));
}
#[test]
fn multiline_empty_record_type_definition() {
expr_formats_same(indoc!(
r#"
f :
{}
f"#
));
}
#[test]
fn type_definition_comment_after_colon() {
expr_formats_to(
indoc!(
r#"
f : # comment
{}
f"#
),
indoc!(
r#"
f :
# comment
{}
f"#
),
);
}
#[test]
fn final_comment_in_empty_record_type_definition() {
expr_formats_to(
indoc!(
r#"
f :
{ # comment
}
f"#
),
indoc!(
r#"
f :
{
# comment
}
f"#
),
);
}
#[test]
fn multiline_inside_empty_record_annotation() {
expr_formats_same(indoc!(
r#"
f :
{
}
f"#
));
}
#[test]
fn final_comment_record_annotation() {
expr_formats_to(
indoc!(
r#"
f :
{
x: Int # comment 1
,
# comment 2
}
f"#
),
indoc!(
r#"
f :
{
x : Int,
# comment 1
# comment 2
}
f"#
),
);
}
#[test] #[test]
fn def_closure() { fn def_closure() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
@ -2263,6 +2407,23 @@ mod test_fmt {
)); ));
} }
// TODO This raises a parse error:
// NotYetImplemented("TODO the : in this declaration seems outdented")
// #[test]
// fn multiline_tag_union_annotation() {
// expr_formats_same(indoc!(
// r#"
// b :
// [
// True,
// False,
// ]
// b
// "#
// ));
// }
#[test] #[test]
fn tag_union() { fn tag_union() {
expr_formats_same(indoc!( expr_formats_same(indoc!(

View file

@ -1,9 +1,11 @@
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat,
list_reverse, list_set, list_single, list_sum, list_walk_right, list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
};
use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_len, str_split, str_starts_with, CHAR_LAYOUT,
}; };
use crate::llvm::build_str::{str_concat, str_count_graphemes, str_len, str_split, CHAR_LAYOUT};
use crate::llvm::compare::{build_eq, build_neq}; use crate::llvm::compare::{build_eq, build_neq};
use crate::llvm::convert::{ use crate::llvm::convert::{
basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int, basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int,
@ -384,6 +386,9 @@ pub fn construct_optimization_passes<'a>(
fpm.add_instruction_combining_pass(); fpm.add_instruction_combining_pass();
fpm.add_tail_call_elimination_pass(); fpm.add_tail_call_elimination_pass();
// remove unused global values (e.g. those defined by zig, but unused in user code)
mpm.add_global_dce_pass();
let pmb = PassManagerBuilder::create(); let pmb = PassManagerBuilder::create();
match opt_level { match opt_level {
OptLevel::Normal => { OptLevel::Normal => {
@ -448,6 +453,7 @@ fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace {
}, },
Layout::Builtin(Builtin::EmptyStr) => InPlace::InPlace, Layout::Builtin(Builtin::EmptyStr) => InPlace::InPlace,
Layout::Builtin(Builtin::Str) => InPlace::Clone, Layout::Builtin(Builtin::Str) => InPlace::Clone,
Layout::Builtin(Builtin::Int1) => InPlace::Clone,
_ => unreachable!("Layout {:?} does not have an inplace", layout), _ => unreachable!("Layout {:?} does not have an inplace", layout),
} }
} }
@ -550,11 +556,9 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
let len_type = env.ptr_int(); let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false); let len = len_type.const_int(bytes_len, false);
// NOTE we rely on CHAR_LAYOUT turning into a `i8`
let ptr = allocate_list(env, InPlace::Clone, &CHAR_LAYOUT, len); let ptr = allocate_list(env, InPlace::Clone, &CHAR_LAYOUT, len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes); let struct_type = collection(ctx, ptr_bytes);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
let mut struct_val; let mut struct_val;
@ -562,9 +566,9 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
struct_val = builder struct_val = builder
.build_insert_value( .build_insert_value(
struct_type.get_undef(), struct_type.get_undef(),
ptr_as_int, ptr,
Builtin::WRAPPER_PTR, Builtin::WRAPPER_PTR,
"insert_ptr", "insert_ptr_str_literal",
) )
.unwrap(); .unwrap();
@ -731,7 +735,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// Insert field exprs into struct_val // Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() { for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder struct_val = builder
.build_insert_value(struct_val, field_val, index as u32, "insert_field") .build_insert_value(
struct_val,
field_val,
index as u32,
"insert_record_field",
)
.unwrap(); .unwrap();
} }
@ -781,7 +790,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// Insert field exprs into struct_val // Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() { for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder struct_val = builder
.build_insert_value(struct_val, field_val, index as u32, "insert_field") .build_insert_value(
struct_val,
field_val,
index as u32,
"insert_single_tag_field",
)
.unwrap(); .unwrap();
} }
@ -844,7 +858,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// Insert field exprs into struct_val // Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() { for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder struct_val = builder
.build_insert_value(struct_val, field_val, index as u32, "insert_field") .build_insert_value(
struct_val,
field_val,
index as u32,
"insert_multi_tag_field",
)
.unwrap(); .unwrap();
} }
@ -1067,7 +1086,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
let value_type = basic_type_from_layout(env.arena, ctx, layout, env.ptr_bytes); let value_type = basic_type_from_layout(env.arena, ctx, layout, env.ptr_bytes);
let len_type = env.ptr_int(); let len_type = env.ptr_int();
let extra_bytes = layout.alignment_bytes(env.ptr_bytes); let extra_bytes = layout.alignment_bytes(env.ptr_bytes).max(env.ptr_bytes);
let ptr = { let ptr = {
// number of bytes we will allocated // number of bytes we will allocated
@ -1097,7 +1116,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
let index = match extra_bytes { let index = match extra_bytes {
n if n == env.ptr_bytes => 1, n if n == env.ptr_bytes => 1,
n if n == 2 * env.ptr_bytes => 2, n if n == 2 * env.ptr_bytes => 2,
_ => unreachable!("invalid extra_bytes"), _ => unreachable!("invalid extra_bytes, {}", extra_bytes),
}; };
let index_intvalue = int_type.const_int(index, false); let index_intvalue = int_type.const_int(index, false);
@ -1168,8 +1187,10 @@ fn list_literal<'a, 'ctx, 'env>(
} }
let ptr_bytes = env.ptr_bytes; let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
let generic_ptr = cast_basic_basic(builder, ptr.into(), u8_ptr_type.into());
let struct_type = collection(ctx, ptr_bytes); let struct_type = collection(ctx, ptr_bytes);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false)); let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
let mut struct_val; let mut struct_val;
@ -1178,9 +1199,9 @@ fn list_literal<'a, 'ctx, 'env>(
struct_val = builder struct_val = builder
.build_insert_value( .build_insert_value(
struct_type.get_undef(), struct_type.get_undef(),
ptr_as_int, generic_ptr,
Builtin::WRAPPER_PTR, Builtin::WRAPPER_PTR,
"insert_ptr", "insert_ptr_list_literal",
) )
.unwrap(); .unwrap();
@ -1701,7 +1722,8 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
) { ) {
let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap()); let c_function_name: String =
format!("roc_{}_exposed", roc_function.get_name().to_str().unwrap());
let result = expose_function_to_host_help(env, roc_function, &c_function_name); let result = expose_function_to_host_help(env, roc_function, &c_function_name);
@ -2365,6 +2387,14 @@ fn run_low_level<'a, 'ctx, 'env>(
str_concat(env, inplace, scope, parent, args[0], args[1]) str_concat(env, inplace, scope, parent, args[0], args[1])
} }
StrStartsWith => {
// Str.startsWith : Str, Str -> Bool
debug_assert_eq!(args.len(), 2);
let inplace = get_inplace_from_layout(layout);
str_starts_with(env, inplace, scope, parent, args[0], args[1])
}
StrSplit => { StrSplit => {
// Str.split : Str, Str -> List Str // Str.split : Str, Str -> List Str
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -2477,8 +2507,7 @@ fn run_low_level<'a, 'ctx, 'env>(
list_contains(env, parent, elem, elem_layout, list, list_layout) list_contains(env, parent, elem, elem_layout, list, list_layout)
} }
ListWalkRight => { ListWalk => {
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
debug_assert_eq!(args.len(), 3); debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]); let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2487,7 +2516,28 @@ fn run_low_level<'a, 'ctx, 'env>(
let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]); let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]);
list_walk_right( list_walk(
env,
parent,
list,
list_layout,
func,
func_layout,
default,
default_layout,
)
}
ListWalkBackwards => {
// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
let (default, default_layout) = load_symbol_and_layout(env, scope, &args[2]);
list_walk_backwards(
env, env,
parent, parent,
list, list,

View file

@ -2,8 +2,7 @@ use crate::llvm::build::{
allocate_with_refcount_help, build_num_binop, cast_basic_basic, Env, InPlace, allocate_with_refcount_help, build_num_binop, cast_basic_basic, Env, InPlace,
}; };
use crate::llvm::compare::build_eq; use crate::llvm::compare::build_eq;
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int}; use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
use crate::llvm::refcounting::PointerToRefcount;
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::types::{BasicTypeEnum, PointerType}; use inkwell::types::{BasicTypeEnum, PointerType};
@ -810,9 +809,9 @@ pub fn list_sum<'a, 'ctx, 'env>(
builder.build_load(accum_alloca, "load_final_acum") builder.build_load(accum_alloca, "load_final_acum")
} }
/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum /// List.walk : List elem, (elem -> accum -> accum), accum -> accum
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn list_walk_right<'a, 'ctx, 'env>( pub fn list_walk<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
@ -902,6 +901,98 @@ pub fn list_walk_right<'a, 'ctx, 'env>(
builder.build_load(accum_alloca, "load_final_acum") builder.build_load(accum_alloca, "load_final_acum")
} }
/// List.walkBackwards : List elem, (elem -> accum -> accum), accum -> accum
#[allow(clippy::too_many_arguments)]
pub fn list_walk_backwards<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>,
func: BasicValueEnum<'ctx>,
func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let builder = env.builder;
let list_wrapper = list.into_struct_value();
let len = list_len(env.builder, list_wrapper);
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
builder.build_store(accum_alloca, default);
let then_block = ctx.append_basic_block(parent, "then");
let cont_block = ctx.append_basic_block(parent, "branchcont");
let condition = builder.build_int_compare(
IntPredicate::UGT,
len,
ctx.i64_type().const_zero(),
"list_non_empty",
);
builder.build_conditional_branch(condition, then_block, cont_block);
builder.position_at_end(then_block);
match (func, func_layout) {
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, _)) => {
let elem_layout = match list_layout {
Layout::Builtin(Builtin::List(_, layout)) => layout,
_ => unreachable!("can only fold over a list"),
};
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let elem_ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
// load current accumulator
let current = builder.build_load(accum_alloca, "retrieve_accum");
let call_site_value =
builder.build_call(func_ptr, &[elem, current], "#walk_right_func");
// set the calling convention explicitly for this call
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
let new_current = call_site_value
.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
builder.build_store(accum_alloca, new_current);
};
decrementing_elem_loop(
builder,
ctx,
parent,
list_ptr,
len,
"#index",
walk_right_loop,
);
}
_ => {
unreachable!(
"Invalid function basic value enum or layout for List.keepIf : {:?}",
(func, func_layout)
);
}
}
builder.build_unconditional_branch(cont_block);
builder.position_at_end(cont_block);
builder.build_load(accum_alloca, "load_final_acum")
}
/// List.contains : List elem, elem -> Bool /// List.contains : List elem, elem -> Bool
pub fn list_contains<'a, 'ctx, 'env>( pub fn list_contains<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -1538,6 +1629,7 @@ where
let current_index = builder let current_index = builder
.build_load(index_alloca, index_name) .build_load(index_alloca, index_name)
.into_int_value(); .into_int_value();
let next_index = builder.build_int_sub(current_index, one, "nextindex"); let next_index = builder.build_int_sub(current_index, one, "nextindex");
builder.build_store(index_alloca, next_index); builder.build_store(index_alloca, next_index);
@ -1547,7 +1639,7 @@ where
// #index >= 0 // #index >= 0
let condition = builder.build_int_compare( let condition = builder.build_int_compare(
IntPredicate::UGE, IntPredicate::SGE,
next_index, next_index,
ctx.i64_type().const_zero(), ctx.i64_type().const_zero(),
"bounds_check", "bounds_check",
@ -1762,12 +1854,7 @@ pub fn load_list<'ctx>(
wrapper_struct: StructValue<'ctx>, wrapper_struct: StructValue<'ctx>,
ptr_type: PointerType<'ctx>, ptr_type: PointerType<'ctx>,
) -> (IntValue<'ctx>, PointerValue<'ctx>) { ) -> (IntValue<'ctx>, PointerValue<'ctx>) {
let ptr_as_int = builder let ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_int_value();
let ptr = builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr");
let length = builder let length = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len") .build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len")
@ -1782,12 +1869,14 @@ pub fn load_list_ptr<'ctx>(
wrapper_struct: StructValue<'ctx>, wrapper_struct: StructValue<'ctx>,
ptr_type: PointerType<'ctx>, ptr_type: PointerType<'ctx>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let ptr_as_int = builder // a `*mut u8` pointer
let generic_ptr = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr") .build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap() .unwrap()
.into_int_value(); .into_pointer_value();
builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr") // cast to the expected pointer type
cast_basic_basic(builder, generic_ptr.into(), ptr_type.into()).into_pointer_value()
} }
pub fn clone_nonempty_list<'a, 'ctx, 'env>( pub fn clone_nonempty_list<'a, 'ctx, 'env>(
@ -1812,9 +1901,6 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
// Allocate space for the new array that we'll copy into. // Allocate space for the new array that we'll copy into.
let clone_ptr = allocate_list(env, inplace, elem_layout, list_len); let clone_ptr = allocate_list(env, inplace, elem_layout, list_len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr");
// TODO check if malloc returned null; if so, runtime error for OOM! // TODO check if malloc returned null; if so, runtime error for OOM!
// Either memcpy or deep clone the array elements // Either memcpy or deep clone the array elements
@ -1831,6 +1917,9 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
} }
// Create a fresh wrapper struct for the newly populated array // Create a fresh wrapper struct for the newly populated array
let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
let generic_ptr = cast_basic_basic(builder, clone_ptr.into(), u8_ptr_type.into());
let struct_type = collection(ctx, env.ptr_bytes); let struct_type = collection(ctx, env.ptr_bytes);
let mut struct_val; let mut struct_val;
@ -1838,9 +1927,9 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
struct_val = builder struct_val = builder
.build_insert_value( .build_insert_value(
struct_type.get_undef(), struct_type.get_undef(),
ptr_as_int, generic_ptr,
Builtin::WRAPPER_PTR, Builtin::WRAPPER_PTR,
"insert_ptr", "insert_ptr_clone_nonempty_list",
) )
.unwrap(); .unwrap();
@ -1917,26 +2006,28 @@ pub fn allocate_list<'a, 'ctx, 'env>(
pub fn store_list<'a, 'ctx, 'env>( pub fn store_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
list_ptr: PointerValue<'ctx>, pointer_to_first_element: PointerValue<'ctx>,
len: IntValue<'ctx>, len: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let ctx = env.context; let ctx = env.context;
let builder = env.builder; let builder = env.builder;
let ptr_bytes = env.ptr_bytes; let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes); let struct_type = collection(ctx, ptr_bytes);
let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
let generic_ptr =
cast_basic_basic(builder, pointer_to_first_element.into(), u8_ptr_type.into());
let mut struct_val; let mut struct_val;
// Store the pointer // Store the pointer
struct_val = builder struct_val = builder
.build_insert_value( .build_insert_value(
struct_type.get_undef(), struct_type.get_undef(),
ptr_as_int, generic_ptr,
Builtin::WRAPPER_PTR, Builtin::WRAPPER_PTR,
"insert_ptr", "insert_ptr_store_list",
) )
.unwrap(); .unwrap();

View file

@ -4,7 +4,7 @@ use crate::llvm::build::{
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, build_basic_phi2, empty_list, incrementing_elem_loop, load_list_ptr, store_list, allocate_list, build_basic_phi2, empty_list, incrementing_elem_loop, load_list_ptr, store_list,
}; };
use crate::llvm::convert::{collection, ptr_int}; use crate::llvm::convert::collection;
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::types::BasicTypeEnum; use inkwell::types::BasicTypeEnum;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
@ -60,17 +60,20 @@ pub fn str_split<'a, 'ctx, 'env>(
let ret_list_ptr = let ret_list_ptr =
allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count); allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
// convert `*mut RocStr` to `*mut i128` // get the RocStr type defined by zig
let ret_list_ptr_u128s = builder.build_bitcast( let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
// convert `*mut { *mut u8, i64 }` to `*mut RocStr`
let ret_list_ptr_zig_rocstr = builder.build_bitcast(
ret_list_ptr, ret_list_ptr,
ctx.i128_type().ptr_type(AddressSpace::Generic), roc_str_type.ptr_type(AddressSpace::Generic),
"ret_u128_list", "convert_to_zig_rocstr",
); );
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ &[
ret_list_ptr_u128s, ret_list_ptr_zig_rocstr,
BasicValueEnum::IntValue(segment_count), BasicValueEnum::IntValue(segment_count),
BasicValueEnum::PointerValue(str_bytes_ptr), BasicValueEnum::PointerValue(str_bytes_ptr),
BasicValueEnum::IntValue(str_len), BasicValueEnum::IntValue(str_len),
@ -532,8 +535,6 @@ fn clone_nonempty_str<'a, 'ctx, 'env>(
} }
Smallness::Big => { Smallness::Big => {
let clone_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, len); let clone_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr");
// TODO check if malloc returned null; if so, runtime error for OOM! // TODO check if malloc returned null; if so, runtime error for OOM!
@ -551,7 +552,7 @@ fn clone_nonempty_str<'a, 'ctx, 'env>(
struct_val = builder struct_val = builder
.build_insert_value( .build_insert_value(
struct_type.get_undef(), struct_type.get_undef(),
ptr_as_int, clone_ptr,
Builtin::WRAPPER_PTR, Builtin::WRAPPER_PTR,
"insert_ptr", "insert_ptr",
) )
@ -669,6 +670,50 @@ fn str_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntVa
) )
} }
/// Str.startsWith : Str, Str -> Bool
pub fn str_starts_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
str_symbol: Symbol,
prefix_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let str_ptr = ptr_from_symbol(scope, str_symbol);
let prefix_ptr = ptr_from_symbol(scope, prefix_symbol);
let ret_type = BasicTypeEnum::IntType(ctx.bool_type());
load_str(
env,
parent,
*str_ptr,
ret_type,
|str_bytes_ptr, str_len, _str_smallness| {
load_str(
env,
parent,
*prefix_ptr,
ret_type,
|prefix_bytes_ptr, prefix_len, _prefix_smallness| {
call_bitcode_fn(
env,
&[
BasicValueEnum::PointerValue(str_bytes_ptr),
BasicValueEnum::IntValue(str_len),
BasicValueEnum::PointerValue(prefix_bytes_ptr),
BasicValueEnum::IntValue(prefix_len),
],
&bitcode::STR_STARTS_WITH,
)
},
)
},
)
}
/// Str.countGraphemes : Str -> Int /// Str.countGraphemes : Str -> Int
pub fn str_count_graphemes<'a, 'ctx, 'env>( pub fn str_count_graphemes<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

View file

@ -203,26 +203,13 @@ pub fn block_of_memory<'ctx>(
/// Two usize values. Could be a wrapper for a List or a Str. /// Two usize values. Could be a wrapper for a List or a Str.
/// ///
/// It would be nicer if we could store this as a tuple containing one usize /// This way, we always initialize it to (*mut u8, usize), and may have to cast the pointer type
/// and one pointer. However, if we do that, we run into a problem with the /// for lists.
/// empty list: it doesn't know what pointer type it should initailize to,
/// so it can only create an empty (usize, usize) struct.
///
/// This way, we always initialize it to (usize, usize), and then if there's
/// actually a pointer, we use build_int_to_ptr and build_ptr_to_int to convert
/// the field when necessary. (It's not allowed to cast the entire struct from
/// (usize, usize) to (usize, ptr) or vice versa.)
pub fn collection(ctx: &Context, ptr_bytes: u32) -> StructType<'_> { pub fn collection(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
let int_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes)); let usize_type = ptr_int(ctx, ptr_bytes);
let u8_ptr = ctx.i8_type().ptr_type(AddressSpace::Generic);
ctx.struct_type(&[int_type, int_type], false) ctx.struct_type(&[u8_ptr.into(), usize_type.into()], false)
}
/// Two usize values.
pub fn collection_int_wrapper(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
let usize_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes));
ctx.struct_type(&[usize_type, usize_type], false)
} }
pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {

View file

@ -79,19 +79,13 @@ impl<'ctx> PointerToRefcount<'ctx> {
} }
pub fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self { pub fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
let ptr_as_int = env let data_ptr = env
.builder .builder
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr") .build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap() .unwrap()
.into_int_value(); .into_pointer_value();
let ptr = env.builder.build_int_to_ptr( Self::from_ptr_to_data(env, data_ptr)
ptr_as_int,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"list_int_to_ptr",
);
Self::from_ptr_to_data(env, ptr)
} }
pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
@ -133,7 +127,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function"); let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap(); let di_location = env.builder.get_current_debug_location().unwrap();
let alignment = layout.alignment_bytes(env.ptr_bytes); let alignment = layout.alignment_bytes(env.ptr_bytes).max(env.ptr_bytes);
let fn_name = &format!("decrement_refcounted_ptr_{}", alignment); let fn_name = &format!("decrement_refcounted_ptr_{}", alignment);

View file

@ -237,11 +237,11 @@ mod gen_list {
} }
#[test] #[test]
fn list_walk_right_empty_all_inline() { fn list_walk_backwards_empty_all_inline() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
List.walkRight [0x1] (\a, b -> a + b) 0 List.walkBackwards [0x1] (\a, b -> a + b) 0
"# "#
), ),
1, 1,
@ -255,7 +255,7 @@ mod gen_list {
empty = empty =
[] []
List.walkRight empty (\a, b -> a + b) 0 List.walkBackwards empty (\a, b -> a + b) 0
"# "#
), ),
0, 0,
@ -264,22 +264,22 @@ mod gen_list {
} }
#[test] #[test]
fn list_walk_right_with_str() { fn list_walk_backwards_with_str() {
assert_evals_to!( assert_evals_to!(
r#"List.walkRight [ "x", "y", "z" ] Str.concat "<""#, r#"List.walkBackwards [ "x", "y", "z" ] Str.concat "<""#,
RocStr::from("zyx<"), RocStr::from("xyz<"),
RocStr RocStr
); );
assert_evals_to!( assert_evals_to!(
r#"List.walkRight [ "Third", "Second", "First" ] Str.concat "Fourth""#, r#"List.walkBackwards [ "Third", "Second", "First" ] Str.concat "Fourth""#,
RocStr::from("FirstSecondThirdFourth"), RocStr::from("ThirdSecondFirstFourth"),
RocStr RocStr
); );
} }
#[test] #[test]
fn list_walk_right_with_record() { fn list_walk_backwards_with_record() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -295,7 +295,7 @@ mod gen_list {
Zero -> { r & zeroes: r.zeroes + 1 } Zero -> { r & zeroes: r.zeroes + 1 }
One -> { r & ones: r.ones + 1 } One -> { r & ones: r.ones + 1 }
finalCounts = List.walkRight byte acc initialCounts finalCounts = List.walkBackwards byte acc initialCounts
finalCounts.ones * 10 + finalCounts.zeroes finalCounts.ones * 10 + finalCounts.zeroes
"# "#
@ -305,6 +305,26 @@ mod gen_list {
); );
} }
#[test]
fn list_walk_with_str() {
assert_evals_to!(
r#"List.walk [ "x", "y", "z" ] Str.concat "<""#,
RocStr::from("zyx<"),
RocStr
);
assert_evals_to!(
r#"List.walk [ "Third", "Second", "First" ] Str.concat "Fourth""#,
RocStr::from("FirstSecondThirdFourth"),
RocStr
);
}
#[test]
fn list_walk_substraction() {
assert_evals_to!(r#"List.walk [ 1, 2 ] Num.sub 1"#, 2, i64);
}
#[test] #[test]
fn list_keep_if_empty_list_of_int() { fn list_keep_if_empty_list_of_int() {
assert_evals_to!( assert_evals_to!(
@ -599,7 +619,7 @@ mod gen_list {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
empty : List Float empty : List F64
empty = empty =
[] []
@ -1186,7 +1206,7 @@ mod gen_list {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Quicksort provides [ main ] imports [] app "quicksort" provides [ main ] to "./platform"
swap : Int, Int, List a -> List a swap : Int, Int, List a -> List a

View file

@ -293,7 +293,7 @@ mod gen_primitives {
indoc!( indoc!(
r#" r#"
wrapper = \{} -> wrapper = \{} ->
alwaysFloatIdentity : Int -> (Float -> Float) alwaysFloatIdentity : Int -> (F64 -> F64)
alwaysFloatIdentity = \_ -> alwaysFloatIdentity = \_ ->
(\a -> a) (\a -> a)
@ -535,7 +535,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app LinkedListLen0 provides [ main ] imports [] app "test" provides [ main ] to "./platform"
pi = 3.1415 pi = 3.1415
@ -553,7 +553,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -580,7 +580,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app LinkedListLenTwice0 provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -607,7 +607,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -634,7 +634,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -661,7 +661,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -689,7 +689,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -717,7 +717,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -744,7 +744,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
@ -907,7 +907,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
x = 42 x = 42
@ -928,7 +928,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
foo = \{} -> foo = \{} ->
x = 41 x = 41
@ -951,7 +951,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
foo = \{} -> foo = \{} ->
x = 41 x = 41
@ -978,7 +978,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
foo = \{} -> foo = \{} ->
x = 41 x = 41
@ -1006,7 +1006,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Effect a : [ @Effect ({} -> a) ] Effect a : [ @Effect ({} -> a) ]
@ -1016,11 +1016,11 @@ mod gen_primitives {
runEffect : Effect a -> a runEffect : Effect a -> a
runEffect = \@Effect thunk -> thunk {} runEffect = \@Effect thunk -> thunk {}
foo : Effect Float foo : Effect F64
foo = foo =
succeed 3.14 succeed 3.14
main : Float main : F64
main = main =
runEffect foo runEffect foo
@ -1036,19 +1036,19 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
# succeed : a -> ({} -> a) # succeed : a -> ({} -> a)
succeed = \x -> \{} -> x succeed = \x -> \{} -> x
foo : {} -> Float foo : {} -> F64
foo = foo =
succeed 3.14 succeed 3.14
# runEffect : ({} -> a) -> a # runEffect : ({} -> a) -> a
runEffect = \thunk -> thunk {} runEffect = \thunk -> thunk {}
main : Float main : F64
main = main =
runEffect foo runEffect foo
"# "#
@ -1063,7 +1063,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Effect a : [ @Effect ({} -> a) ] Effect a : [ @Effect ({} -> a) ]
@ -1085,7 +1085,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Effect a : [ @Effect ({} -> a) ] Effect a : [ @Effect ({} -> a) ]
@ -1110,7 +1110,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -1144,7 +1144,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -1175,7 +1175,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
State a : { count : Int, x : a } State a : { count : Int, x : a }
@ -1202,7 +1202,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -1284,7 +1284,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -1323,7 +1323,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Dict k : [ Node k (Dict k) (Dict k), Empty ] Dict k : [ Node k (Dict k) (Dict k), Empty ]
@ -1353,7 +1353,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -1393,7 +1393,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -1444,7 +1444,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -1470,7 +1470,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -1498,7 +1498,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -1527,7 +1527,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -1552,7 +1552,7 @@ mod gen_primitives {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
BTree : [ Node BTree BTree, Leaf Int ] BTree : [ Node BTree BTree, Leaf Int ]

View file

@ -405,7 +405,7 @@ mod gen_records {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
f = \r -> f = \r ->
when r is when r is
@ -455,7 +455,7 @@ mod gen_records {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
f = \r -> f = \r ->
{ x ? 10, y } = r { x ? 10, y } = r
@ -492,7 +492,7 @@ mod gen_records {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
f = \r -> f = \r ->
{ x ? 10, y } = r { x ? 10, y } = r
@ -512,7 +512,7 @@ mod gen_records {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
f = \r -> f = \r ->
{ x ? 10, y } = r { x ? 10, y } = r
@ -565,7 +565,7 @@ mod gen_records {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
f = \{ x ? 10, y } -> x + y f = \{ x ? 10, y } -> x + y
@ -844,4 +844,49 @@ mod gen_records {
(bool, bool) (bool, bool)
); );
} }
#[test]
fn alignment_in_record() {
assert_evals_to!(
indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
fn blue_and_present() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
f { x: Blue, y: 7 }
"#
),
7,
i64
);
}
#[test]
fn blue_and_absent() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
f { x: Blue }
"#
),
3,
i64
);
}
} }

View file

@ -8,11 +8,42 @@ extern crate inkwell;
extern crate libc; extern crate libc;
extern crate roc_gen; extern crate roc_gen;
use core;
use roc_std::RocStr;
#[macro_use] #[macro_use]
mod helpers; mod helpers;
const ROC_STR_MEM_SIZE: usize = core::mem::size_of::<RocStr>();
#[cfg(test)] #[cfg(test)]
mod gen_str { mod gen_str {
use crate::ROC_STR_MEM_SIZE;
use std::cmp::min;
fn small_str(str: &str) -> [u8; ROC_STR_MEM_SIZE] {
let mut bytes: [u8; ROC_STR_MEM_SIZE] = Default::default();
let mut index: usize = 0;
while index < ROC_STR_MEM_SIZE {
bytes[index] = 0;
index += 1;
}
let str_bytes = str.as_bytes();
let output_len: usize = min(str_bytes.len(), ROC_STR_MEM_SIZE);
index = 0;
while index < output_len {
bytes[index] = str_bytes[index];
index += 1;
}
bytes[ROC_STR_MEM_SIZE - 1] = 0b1000_0000 ^ (output_len as u8);
bytes
}
#[test] #[test]
fn str_split_bigger_delimiter_small_str() { fn str_split_bigger_delimiter_small_str() {
assert_evals_to!( assert_evals_to!(
@ -40,40 +71,48 @@ mod gen_str {
3, 3,
i64 i64
); );
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJJJ" "JJJJ there") is
// Ok str ->
// str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
//
// _ ->
// "Not Str!"
//
// "#
// ),
// "JJJJJJJJJJJJJJJJJJJJJJJJJ",
// &'static str
// );
} }
// #[test] #[test]
// fn str_split_small_str_big_delimiter() { fn str_split_str_concat_repeated() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// Str.split "JJJ" "0123456789abcdefghi" when List.first (Str.split "JJJJJ" "JJJJ there") is
// "# Ok str ->
// ), str
// &["JJJ"], |> Str.concat str
// &'static [&'static str] |> Str.concat str
// ); |> Str.concat str
// } |> Str.concat str
_ ->
"Not Str!"
"#
),
"JJJJJJJJJJJJJJJJJJJJJJJJJ",
&'static str
);
}
#[test]
fn str_split_small_str_bigger_delimiter() {
assert_evals_to!(
indoc!(
r#"
when
List.first
(Str.split "JJJ" "0123456789abcdefghi")
is
Ok str -> str
_ -> ""
"#
),
small_str("JJJ"),
[u8; ROC_STR_MEM_SIZE]
);
}
#[test] #[test]
fn str_split_big_str_small_delimiter() { fn str_split_big_str_small_delimiter() {
@ -98,21 +137,21 @@ mod gen_str {
); );
} }
// #[test] #[test]
// fn str_split_small_str_small_delimiter() { fn str_split_small_str_small_delimiter() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// Str.split "J!J!J" "!" Str.split "J!J!J" "!"
// "# "#
// ), ),
// &["J", "J", "J"], &[small_str("J"), small_str("J"), small_str("J")],
// &'static [&'static str] &'static [[u8; ROC_STR_MEM_SIZE]]
// ); );
// } }
#[test] #[test]
fn str_split_bigger_delimiter_big_str() { fn str_split_bigger_delimiter_big_strs() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -126,20 +165,74 @@ mod gen_str {
); );
} }
// #[test] #[test]
// fn str_split_big_str() { fn str_split_empty_strs() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// Str.split Str.split "" ""
// "hello 0123456789abcdef there 0123456789abcdef " "#
// " 0123456789abcdef " ),
// "# &[small_str("")],
// ), &'static [[u8; ROC_STR_MEM_SIZE]]
// &["hello", "there"], )
// &'static [&'static str] }
// );
// } #[test]
fn str_split_minimal_example() {
assert_evals_to!(
indoc!(
r#"
Str.split "a," ","
"#
),
&[small_str("a"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
)
}
#[test]
fn str_split_small_str_big_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split
"1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
"---- ---- ---- ---- ----"
|> List.len
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
Str.split
"1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
"---- ---- ---- ---- ----"
"#
),
&[small_str("1"), small_str("2"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_split_small_str_20_char_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split
"3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
"|-- -- -- -- -- -- |"
"#
),
&[small_str("3"), small_str("4"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test] #[test]
fn str_concat_big_to_big() { fn str_concat_big_to_big() {
@ -331,11 +424,25 @@ mod gen_str {
assert_evals_to!(r#"Str.isEmpty """#, true, bool); assert_evals_to!(r#"Str.isEmpty """#, true, bool);
} }
#[test]
fn str_starts_with() {
assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
}
#[test] #[test]
fn str_count_graphemes_small_str() { fn str_count_graphemes_small_str() {
assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize); assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
} }
#[test]
fn str_count_graphemes_three_js() {
assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
}
#[test] #[test]
fn str_count_graphemes_big_str() { fn str_count_graphemes_big_str() {
assert_evals_to!( assert_evals_to!(
@ -344,4 +451,36 @@ mod gen_str {
usize usize
); );
} }
#[test]
fn str_starts_with_same_big_str() {
assert_evals_to!(
r#"Str.startsWith "123456789123456789" "123456789123456789""#,
true,
bool
);
}
#[test]
fn str_starts_with_different_big_str() {
assert_evals_to!(
r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
true,
bool
);
}
#[test]
fn str_starts_with_same_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
}
#[test]
fn str_starts_with_different_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
}
#[test]
fn str_starts_with_false_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
}
} }

View file

@ -417,7 +417,7 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Maybe a : [ Just a, Nothing ] Maybe a : [ Just a, Nothing ]
@ -630,7 +630,7 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Maybe a : [ Nothing, Just a ] Maybe a : [ Nothing, Just a ]
@ -758,4 +758,114 @@ mod gen_tags {
i64 i64
); );
} }
#[test]
fn alignment_in_single_tag_construction() {
assert_evals_to!(indoc!("Three (1 == 1) 32"), (32i64, true), (i64, bool));
assert_evals_to!(
indoc!("Three (1 == 1) (if True then Red else if True then Green else Blue) 32"),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
fn alignment_in_single_tag_pattern_match() {
assert_evals_to!(
indoc!(
r"#
x = Three (1 == 1) 32
when x is
Three bool int ->
{ bool, int }
#"
),
(32i64, true),
(i64, bool)
);
assert_evals_to!(
indoc!(
r"#
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
when x is
Three bool color int ->
{ bool, color, int }
#"
),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
fn alignment_in_multi_tag_construction() {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool Int, Empty ]
x = Three (1 == 1) 32
x
#"
),
(1, 32i64, true),
(i64, i64, bool)
);
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool [ Red, Green, Blue ] Int, Empty ]
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
x
#"
),
(1, 32i64, true, 2u8),
(i64, i64, bool, u8)
);
}
#[test]
fn alignment_in_multi_tag_pattern_match() {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool Int, Empty ]
x = Three (1 == 1) 32
when x is
Three bool int ->
{ bool, int }
Empty ->
{ bool: False, int: 0 }
#"
),
(32i64, true),
(i64, bool)
);
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool [ Red, Green, Blue ] Int, Empty ]
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
when x is
Three bool color int ->
{ bool, color, int }
Empty ->
{ bool: False, color: Red, int: 0 }
#"
),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
} }

View file

@ -1,9 +1,10 @@
use libloading::Library; use libloading::Library;
use roc_build::link::module_to_dylib; use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n"); let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
for line in src.lines() { for line in src.lines() {
// indent the body! // indent the body!
@ -154,6 +155,15 @@ pub fn helper<'a>(
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module); let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
// mark our zig-defined builtins as internal
use inkwell::module::Linkage;
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
}
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,

View file

@ -0,0 +1,44 @@
[package]
name = "roc_gen_dev"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
license = "Apache-2.0"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_uniq = { path = "../uniq" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
target-lexicon = "0.10"
libloading = "0.6"
object = { version = "0.22", features = ["write"] }
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
tempfile = "3.1.0"
itertools = "0.9"

View file

@ -0,0 +1,331 @@
use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec;
use roc_collections::all::{ImSet, MutMap, MutSet};
use roc_module::symbol::Symbol;
use roc_mono::ir::{Literal, Stmt};
use std::marker::PhantomData;
use target_lexicon::Triple;
pub mod x86_64;
pub trait CallConv<GPReg> {
fn gp_param_regs() -> &'static [GPReg];
fn gp_return_regs() -> &'static [GPReg];
fn gp_default_free_regs() -> &'static [GPReg];
// A linear scan of an array may be faster than a set technically.
// That being said, fastest would likely be a trait based on calling convention/register.
fn caller_saved_regs() -> ImSet<GPReg>;
fn callee_saved_regs() -> ImSet<GPReg>;
fn stack_pointer() -> GPReg;
fn frame_pointer() -> GPReg;
fn shadow_space_size() -> u8;
// It may be worth ignoring the red zone and keeping things simpler.
fn red_zone_size() -> u8;
}
pub trait Assembler<GPReg> {
fn add_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
fn add_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
fn cmovl_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i64);
fn mov_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
fn mov_register64bit_stackoffset32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, offset: i32);
fn mov_stackoffset32bit_register64bit<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: GPReg);
fn neg_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
fn ret<'a>(buf: &mut Vec<'a, u8>);
fn sub_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
fn pop_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
fn push_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
}
#[derive(Clone, Debug, PartialEq)]
enum SymbolStorage<GPReg> {
// These may need layout, but I am not sure.
// I think whenever a symbol would be used, we specify layout anyways.
GPRegeg(GPReg),
Stack(i32),
StackAndGPRegeg(GPReg, i32),
}
pub trait GPRegTrait: Copy + Eq + std::hash::Hash + std::fmt::Debug + 'static {}
pub struct Backend64Bit<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> {
phantom_asm: PhantomData<ASM>,
phantom_cc: PhantomData<CC>,
env: &'a Env<'a>,
buf: Vec<'a, u8>,
/// leaf_function is true if the only calls this function makes are tail calls.
/// If that is the case, we can skip emitting the frame pointer and updating the stack.
leaf_function: bool,
last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
symbols_map: MutMap<Symbol, SymbolStorage<GPReg>>,
literal_map: MutMap<Symbol, Literal<'a>>,
// This should probably be smarter than a vec.
// There are certain registers we should always use first. With pushing and poping, this could get mixed.
gp_free_regs: Vec<'a, GPReg>,
// The last major thing we need is a way to decide what reg to free when all of them are full.
// Theoretically we want a basic lru cache for the currently loaded symbols.
// For now just a vec of used registers and the symbols they contain.
gp_used_regs: Vec<'a, (GPReg, Symbol)>,
stack_size: i32,
// used callee saved regs must be tracked for pushing and popping at the beginning/end of the function.
used_callee_saved_regs: MutSet<GPReg>,
}
impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<'a>
for Backend64Bit<'a, GPReg, ASM, CC>
{
fn new(env: &'a Env, _target: &Triple) -> Result<Self, String> {
Ok(Backend64Bit {
phantom_asm: PhantomData,
phantom_cc: PhantomData,
env,
leaf_function: true,
buf: bumpalo::vec!(in env.arena),
last_seen_map: MutMap::default(),
free_map: MutMap::default(),
symbols_map: MutMap::default(),
literal_map: MutMap::default(),
gp_free_regs: bumpalo::vec![in env.arena],
gp_used_regs: bumpalo::vec![in env.arena],
stack_size: 0,
used_callee_saved_regs: MutSet::default(),
})
}
fn env(&self) -> &'a Env<'a> {
self.env
}
fn reset(&mut self) {
self.stack_size = -(CC::red_zone_size() as i32);
self.leaf_function = true;
self.last_seen_map.clear();
self.free_map.clear();
self.symbols_map.clear();
self.buf.clear();
self.used_callee_saved_regs.clear();
self.gp_free_regs.clear();
self.gp_used_regs.clear();
self.gp_free_regs
.extend_from_slice(CC::gp_default_free_regs());
}
fn set_not_leaf_function(&mut self) {
self.leaf_function = false;
// If this is not a leaf function, it can't use the shadow space.
self.stack_size = CC::shadow_space_size() as i32 - CC::red_zone_size() as i32;
}
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>> {
&mut self.literal_map
}
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>> {
&mut self.last_seen_map
}
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>) {
self.free_map = map;
}
fn free_map(&mut self) -> &mut MutMap<*const Stmt<'a>, Vec<'a, Symbol>> {
&mut self.free_map
}
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String> {
let mut out = bumpalo::vec![in self.env.arena];
if !self.leaf_function {
// I believe that this will have to move away from push and to mov to be generic across backends.
ASM::push_register64bit(&mut out, CC::frame_pointer());
ASM::mov_register64bit_register64bit(
&mut out,
CC::frame_pointer(),
CC::stack_pointer(),
);
}
// Save data in all callee saved regs.
let mut pop_order = bumpalo::vec![in self.env.arena];
for reg in &self.used_callee_saved_regs {
ASM::push_register64bit(&mut out, *reg);
pop_order.push(*reg);
}
if self.stack_size > 0 {
ASM::sub_register64bit_immediate32bit(&mut out, CC::stack_pointer(), self.stack_size);
}
// Add function body.
out.extend(&self.buf);
if self.stack_size > 0 {
ASM::add_register64bit_immediate32bit(&mut out, CC::stack_pointer(), self.stack_size);
}
// Restore data in callee saved regs.
while let Some(reg) = pop_order.pop() {
ASM::pop_register64bit(&mut out, reg);
}
if !self.leaf_function {
ASM::pop_register64bit(&mut out, CC::frame_pointer());
}
ASM::ret(&mut out);
Ok((out.into_bump_slice(), &[]))
}
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> {
let dst_reg = self.claim_gp_reg(dst)?;
let src_reg = self.load_to_reg(src)?;
ASM::mov_register64bit_register64bit(&mut self.buf, dst_reg, src_reg);
ASM::neg_register64bit(&mut self.buf, dst_reg);
ASM::cmovl_register64bit_register64bit(&mut self.buf, dst_reg, src_reg);
Ok(())
}
fn build_num_add_i64(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
) -> Result<(), String> {
let dst_reg = self.claim_gp_reg(dst)?;
let src1_reg = self.load_to_reg(src1)?;
ASM::mov_register64bit_register64bit(&mut self.buf, dst_reg, src1_reg);
let src2_reg = self.load_to_reg(src2)?;
ASM::add_register64bit_register64bit(&mut self.buf, dst_reg, src2_reg);
Ok(())
}
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> {
match lit {
Literal::Int(x) => {
let reg = self.claim_gp_reg(sym)?;
let val = *x;
ASM::mov_register64bit_immediate64bit(&mut self.buf, reg, val);
Ok(())
}
x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
}
}
fn free_symbol(&mut self, sym: &Symbol) {
self.symbols_map.remove(sym);
for i in 0..self.gp_used_regs.len() {
let (reg, saved_sym) = self.gp_used_regs[i];
if saved_sym == *sym {
self.gp_free_regs.push(reg);
self.gp_used_regs.remove(i);
break;
}
}
}
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
let val = self.symbols_map.get(sym);
match val {
Some(SymbolStorage::GPRegeg(reg)) if *reg == CC::gp_return_regs()[0] => Ok(()),
Some(SymbolStorage::GPRegeg(reg)) => {
// If it fits in a general purpose register, just copy it over to.
// Technically this can be optimized to produce shorter instructions if less than 64bits.
ASM::mov_register64bit_register64bit(&mut self.buf, CC::gp_return_regs()[0], *reg);
Ok(())
}
Some(x) => Err(format!(
"returning symbol storage, {:?}, is not yet implemented",
x
)),
None => Err(format!("Unknown return symbol: {}", sym)),
}
}
}
/// This impl block is for ir related instructions that need backend specific information.
/// For example, loading a symbol for doing a computation.
impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>>
Backend64Bit<'a, GPReg, ASM, CC>
{
fn claim_gp_reg(&mut self, sym: &Symbol) -> Result<GPReg, String> {
let reg = if !self.gp_free_regs.is_empty() {
let free_reg = self.gp_free_regs.pop().unwrap();
if CC::callee_saved_regs().contains(&free_reg) {
self.used_callee_saved_regs.insert(free_reg);
}
Ok(free_reg)
} else if !self.gp_used_regs.is_empty() {
let (reg, sym) = self.gp_used_regs.remove(0);
self.free_to_stack(&sym)?;
Ok(reg)
} else {
Err("completely out of registers".to_string())
}?;
self.gp_used_regs.push((reg, *sym));
self.symbols_map.insert(*sym, SymbolStorage::GPRegeg(reg));
Ok(reg)
}
fn load_to_reg(&mut self, sym: &Symbol) -> Result<GPReg, String> {
let val = self.symbols_map.remove(sym);
match val {
Some(SymbolStorage::GPRegeg(reg)) => {
self.symbols_map.insert(*sym, SymbolStorage::GPRegeg(reg));
Ok(reg)
}
Some(SymbolStorage::StackAndGPRegeg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::StackAndGPRegeg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::Stack(offset)) => {
let reg = self.claim_gp_reg(sym)?;
self.symbols_map
.insert(*sym, SymbolStorage::StackAndGPRegeg(reg, offset));
ASM::mov_register64bit_stackoffset32bit(&mut self.buf, reg, offset as i32);
Ok(reg)
}
None => Err(format!("Unknown symbol: {}", sym)),
}
}
fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> {
let val = self.symbols_map.remove(sym);
match val {
Some(SymbolStorage::GPRegeg(reg)) => {
let offset = self.stack_size;
self.stack_size += 8;
if let Some(size) = self.stack_size.checked_add(8) {
self.stack_size = size;
} else {
return Err(format!(
"Ran out of stack space while saving symbol: {}",
sym
));
}
ASM::mov_stackoffset32bit_register64bit(&mut self.buf, offset as i32, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Stack(offset as i32));
Ok(())
}
Some(SymbolStorage::StackAndGPRegeg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset));
Ok(())
}
Some(SymbolStorage::Stack(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset));
Ok(())
}
None => Err(format!("Unknown symbol: {}", sym)),
}
}
}

View file

@ -0,0 +1,582 @@
use crate::generic64::{Assembler, CallConv, GPRegTrait};
use bumpalo::collections::Vec;
use roc_collections::all::ImSet;
// Not sure exactly how I want to represent registers.
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub enum X86_64GPReg {
RAX = 0,
RCX = 1,
RDX = 2,
RBX = 3,
RSP = 4,
RBP = 5,
RSI = 6,
RDI = 7,
R8 = 8,
R9 = 9,
R10 = 10,
R11 = 11,
R12 = 12,
R13 = 13,
R14 = 14,
R15 = 15,
}
impl GPRegTrait for X86_64GPReg {}
const REX: u8 = 0x40;
const REX_W: u8 = REX + 0x8;
fn add_rm_extension(reg: X86_64GPReg, byte: u8) -> u8 {
if reg as u8 > 7 {
byte + 1
} else {
byte
}
}
fn add_opcode_extension(reg: X86_64GPReg, byte: u8) -> u8 {
add_rm_extension(reg, byte)
}
fn add_reg_extension(reg: X86_64GPReg, byte: u8) -> u8 {
if reg as u8 > 7 {
byte + 4
} else {
byte
}
}
pub struct X86_64Assembler {}
pub struct X86_64WindowsFastcall {}
pub struct X86_64SystemV {}
impl CallConv<X86_64GPReg> for X86_64SystemV {
fn gp_param_regs() -> &'static [X86_64GPReg] {
&[
X86_64GPReg::RDI,
X86_64GPReg::RSI,
X86_64GPReg::RDX,
X86_64GPReg::RCX,
X86_64GPReg::R8,
X86_64GPReg::R9,
]
}
fn gp_return_regs() -> &'static [X86_64GPReg] {
&[X86_64GPReg::RAX, X86_64GPReg::RDX]
}
fn gp_default_free_regs() -> &'static [X86_64GPReg] {
&[
// The regs we want to use first should be at the end of this vec.
// We will use pop to get which reg to use next
// Use callee saved regs last.
X86_64GPReg::RBX,
// Don't use frame pointer: X86_64GPReg::RBP,
X86_64GPReg::R12,
X86_64GPReg::R13,
X86_64GPReg::R14,
X86_64GPReg::R15,
// Use caller saved regs first.
X86_64GPReg::RAX,
X86_64GPReg::RCX,
X86_64GPReg::RDX,
// Don't use stack pionter: X86_64GPReg::RSP,
X86_64GPReg::RSI,
X86_64GPReg::RDI,
X86_64GPReg::R8,
X86_64GPReg::R9,
X86_64GPReg::R10,
X86_64GPReg::R11,
]
}
fn caller_saved_regs() -> ImSet<X86_64GPReg> {
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
ImSet::from(vec![
X86_64GPReg::RAX,
X86_64GPReg::RCX,
X86_64GPReg::RDX,
X86_64GPReg::RSP,
X86_64GPReg::RSI,
X86_64GPReg::RDI,
X86_64GPReg::R8,
X86_64GPReg::R9,
X86_64GPReg::R10,
X86_64GPReg::R11,
])
}
fn callee_saved_regs() -> ImSet<X86_64GPReg> {
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
ImSet::from(vec![
X86_64GPReg::RBX,
X86_64GPReg::RBP,
X86_64GPReg::R12,
X86_64GPReg::R13,
X86_64GPReg::R14,
X86_64GPReg::R15,
])
}
fn stack_pointer() -> X86_64GPReg {
X86_64GPReg::RSP
}
fn frame_pointer() -> X86_64GPReg {
X86_64GPReg::RBP
}
fn shadow_space_size() -> u8 {
0
}
fn red_zone_size() -> u8 {
128
}
}
impl CallConv<X86_64GPReg> for X86_64WindowsFastcall {
fn gp_param_regs() -> &'static [X86_64GPReg] {
&[
X86_64GPReg::RCX,
X86_64GPReg::RDX,
X86_64GPReg::R8,
X86_64GPReg::R9,
]
}
fn gp_return_regs() -> &'static [X86_64GPReg] {
&[X86_64GPReg::RAX]
}
fn gp_default_free_regs() -> &'static [X86_64GPReg] {
&[
// The regs we want to use first should be at the end of this vec.
// We will use pop to get which reg to use next
// Use callee saved regs last.
X86_64GPReg::RBX,
// Don't use frame pointer: X86_64GPReg::RBP,
X86_64GPReg::RSI,
// Don't use stack pionter: X86_64GPReg::RSP,
X86_64GPReg::RDI,
X86_64GPReg::R12,
X86_64GPReg::R13,
X86_64GPReg::R14,
X86_64GPReg::R15,
// Use caller saved regs first.
X86_64GPReg::RAX,
X86_64GPReg::RCX,
X86_64GPReg::RDX,
X86_64GPReg::R8,
X86_64GPReg::R9,
X86_64GPReg::R10,
X86_64GPReg::R11,
]
}
fn caller_saved_regs() -> ImSet<X86_64GPReg> {
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
ImSet::from(vec![
X86_64GPReg::RAX,
X86_64GPReg::RCX,
X86_64GPReg::RDX,
X86_64GPReg::R8,
X86_64GPReg::R9,
X86_64GPReg::R10,
X86_64GPReg::R11,
])
}
fn callee_saved_regs() -> ImSet<X86_64GPReg> {
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
ImSet::from(vec![
X86_64GPReg::RBX,
X86_64GPReg::RBP,
X86_64GPReg::RSI,
X86_64GPReg::RSP,
X86_64GPReg::RDI,
X86_64GPReg::R12,
X86_64GPReg::R13,
X86_64GPReg::R14,
X86_64GPReg::R15,
])
}
fn stack_pointer() -> X86_64GPReg {
X86_64GPReg::RSP
}
fn frame_pointer() -> X86_64GPReg {
X86_64GPReg::RBP
}
fn shadow_space_size() -> u8 {
32
}
fn red_zone_size() -> u8 {
0
}
}
impl Assembler<X86_64GPReg> for X86_64Assembler {
// Below here are the functions for all of the assembly instructions.
// Their names are based on the instruction and operators combined.
// You should call `buf.reserve()` if you push or extend more than once.
// Unit tests are added at the bottom of the file to ensure correct asm generation.
// Please keep these in alphanumeric order.
/// `ADD r/m64, imm32` -> Add imm32 sign-extended to 64-bits from r/m64.
fn add_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
// This can be optimized if the immediate is 1 byte.
let rex = add_rm_extension(dst, REX_W);
let dst_mod = dst as u8 % 8;
buf.reserve(7);
buf.extend(&[rex, 0x81, 0xC0 + dst_mod]);
buf.extend(&imm.to_le_bytes());
}
/// `ADD r/m64,r64` -> Add r64 to r/m64.
fn add_register64bit_register64bit<'a>(
buf: &mut Vec<'a, u8>,
dst: X86_64GPReg,
src: X86_64GPReg,
) {
let rex = add_rm_extension(dst, REX_W);
let rex = add_reg_extension(src, rex);
let dst_mod = dst as u8 % 8;
let src_mod = (src as u8 % 8) << 3;
buf.extend(&[rex, 0x01, 0xC0 + dst_mod + src_mod]);
}
/// `CMOVL r64,r/m64` -> Move if less (SF≠ OF).
fn cmovl_register64bit_register64bit<'a>(
buf: &mut Vec<'a, u8>,
dst: X86_64GPReg,
src: X86_64GPReg,
) {
let rex = add_reg_extension(dst, REX_W);
let rex = add_rm_extension(src, rex);
let dst_mod = (dst as u8 % 8) << 3;
let src_mod = src as u8 % 8;
buf.extend(&[rex, 0x0F, 0x4C, 0xC0 + dst_mod + src_mod]);
}
/// `MOV r/m64, imm32` -> Move imm32 sign extended to 64-bits to r/m64.
fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
let rex = add_rm_extension(dst, REX_W);
let dst_mod = dst as u8 % 8;
buf.reserve(7);
buf.extend(&[rex, 0xC7, 0xC0 + dst_mod]);
buf.extend(&imm.to_le_bytes());
}
/// `MOV r64, imm64` -> Move imm64 to r64.
fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i64) {
if imm <= i32::MAX as i64 && imm >= i32::MIN as i64 {
Self::mov_register64bit_immediate32bit(buf, dst, imm as i32)
} else {
let rex = add_opcode_extension(dst, REX_W);
let dst_mod = dst as u8 % 8;
buf.reserve(10);
buf.extend(&[rex, 0xB8 + dst_mod]);
buf.extend(&imm.to_le_bytes());
}
}
/// `MOV r/m64,r64` -> Move r64 to r/m64.
fn mov_register64bit_register64bit<'a>(
buf: &mut Vec<'a, u8>,
dst: X86_64GPReg,
src: X86_64GPReg,
) {
let rex = add_rm_extension(dst, REX_W);
let rex = add_reg_extension(src, rex);
let dst_mod = dst as u8 % 8;
let src_mod = (src as u8 % 8) << 3;
buf.extend(&[rex, 0x89, 0xC0 + dst_mod + src_mod]);
}
/// `MOV r64,r/m64` -> Move r/m64 to r64.
fn mov_register64bit_stackoffset32bit<'a>(
buf: &mut Vec<'a, u8>,
dst: X86_64GPReg,
offset: i32,
) {
// This can be optimized based on how many bytes the offset actually is.
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
// Also, this may technically be faster genration since stack operations should be so common.
let rex = add_reg_extension(dst, REX_W);
let dst_mod = (dst as u8 % 8) << 3;
buf.reserve(8);
buf.extend(&[rex, 0x8B, 0x84 + dst_mod, 0x24]);
buf.extend(&offset.to_le_bytes());
}
/// `MOV r/m64,r64` -> Move r64 to r/m64.
fn mov_stackoffset32bit_register64bit<'a>(
buf: &mut Vec<'a, u8>,
offset: i32,
src: X86_64GPReg,
) {
// This can be optimized based on how many bytes the offset actually is.
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
// Also, this may technically be faster genration since stack operations should be so common.
let rex = add_reg_extension(src, REX_W);
let src_mod = (src as u8 % 8) << 3;
buf.reserve(8);
buf.extend(&[rex, 0x89, 0x84 + src_mod, 0x24]);
buf.extend(&offset.to_le_bytes());
}
/// `NEG r/m64` -> Two's complement negate r/m64.
fn neg_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
let rex = add_rm_extension(reg, REX_W);
let reg_mod = reg as u8 % 8;
buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]);
}
/// `RET` -> Near return to calling procedure.
fn ret<'a>(buf: &mut Vec<'a, u8>) {
buf.push(0xC3);
}
/// `SUB r/m64, imm32` -> Subtract imm32 sign-extended to 64-bits from r/m64.
fn sub_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
// This can be optimized if the immediate is 1 byte.
let rex = add_rm_extension(dst, REX_W);
let dst_mod = dst as u8 % 8;
buf.reserve(7);
buf.extend(&[rex, 0x81, 0xE8 + dst_mod]);
buf.extend(&imm.to_le_bytes());
}
/// `POP r64` -> Pop top of stack into r64; increment stack pointer. Cannot encode 32-bit operand size.
fn pop_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
let reg_mod = reg as u8 % 8;
if reg as u8 > 7 {
let rex = add_opcode_extension(reg, REX);
buf.extend(&[rex, 0x58 + reg_mod]);
} else {
buf.push(0x58 + reg_mod);
}
}
/// `PUSH r64` -> Push r64,
fn push_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
let reg_mod = reg as u8 % 8;
if reg as u8 > 7 {
let rex = add_opcode_extension(reg, REX);
buf.extend(&[rex, 0x50 + reg_mod]);
} else {
buf.push(0x50 + reg_mod);
}
}
}
// When writing tests, it is a good idea to test both a number and unnumbered register.
// This is because R8-R15 often have special instruction prefixes.
#[cfg(test)]
mod tests {
use super::*;
const TEST_I32: i32 = 0x12345678;
const TEST_I64: i64 = 0x12345678_9ABCDEF0;
#[test]
fn test_add_register64bit_immediate32bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (dst, expected) in &[
(X86_64GPReg::RAX, [0x48, 0x81, 0xC0]),
(X86_64GPReg::R15, [0x49, 0x81, 0xC7]),
] {
buf.clear();
X86_64Assembler::add_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
assert_eq!(expected, &buf[..3]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
}
}
#[test]
fn test_add_register64bit_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src), expected) in &[
((X86_64GPReg::RAX, X86_64GPReg::RAX), [0x48, 0x01, 0xC0]),
((X86_64GPReg::RAX, X86_64GPReg::R15), [0x4C, 0x01, 0xF8]),
((X86_64GPReg::R15, X86_64GPReg::RAX), [0x49, 0x01, 0xC7]),
((X86_64GPReg::R15, X86_64GPReg::R15), [0x4D, 0x01, 0xFF]),
] {
buf.clear();
X86_64Assembler::add_register64bit_register64bit(&mut buf, *dst, *src);
assert_eq!(expected, &buf[..]);
}
}
#[test]
fn test_cmovl_register64bit_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src), expected) in &[
(
(X86_64GPReg::RAX, X86_64GPReg::RAX),
[0x48, 0x0F, 0x4C, 0xC0],
),
(
(X86_64GPReg::RAX, X86_64GPReg::R15),
[0x49, 0x0F, 0x4C, 0xC7],
),
(
(X86_64GPReg::R15, X86_64GPReg::RAX),
[0x4C, 0x0F, 0x4C, 0xF8],
),
(
(X86_64GPReg::R15, X86_64GPReg::R15),
[0x4D, 0x0F, 0x4C, 0xFF],
),
] {
buf.clear();
X86_64Assembler::cmovl_register64bit_register64bit(&mut buf, *dst, *src);
assert_eq!(expected, &buf[..]);
}
}
#[test]
fn test_mov_register64bit_immediate32bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (dst, expected) in &[
(X86_64GPReg::RAX, [0x48, 0xC7, 0xC0]),
(X86_64GPReg::R15, [0x49, 0xC7, 0xC7]),
] {
buf.clear();
X86_64Assembler::mov_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
assert_eq!(expected, &buf[..3]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
}
}
#[test]
fn test_mov_register64bit_immediate64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (dst, expected) in &[
(X86_64GPReg::RAX, [0x48, 0xB8]),
(X86_64GPReg::R15, [0x49, 0xBF]),
] {
buf.clear();
X86_64Assembler::mov_register64bit_immediate64bit(&mut buf, *dst, TEST_I64);
assert_eq!(expected, &buf[..2]);
assert_eq!(TEST_I64.to_le_bytes(), &buf[2..]);
}
for (dst, expected) in &[
(X86_64GPReg::RAX, [0x48, 0xC7, 0xC0]),
(X86_64GPReg::R15, [0x49, 0xC7, 0xC7]),
] {
buf.clear();
X86_64Assembler::mov_register64bit_immediate64bit(&mut buf, *dst, TEST_I32 as i64);
assert_eq!(expected, &buf[..3]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
}
}
#[test]
fn test_mov_register64bit_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src), expected) in &[
((X86_64GPReg::RAX, X86_64GPReg::RAX), [0x48, 0x89, 0xC0]),
((X86_64GPReg::RAX, X86_64GPReg::R15), [0x4C, 0x89, 0xF8]),
((X86_64GPReg::R15, X86_64GPReg::RAX), [0x49, 0x89, 0xC7]),
((X86_64GPReg::R15, X86_64GPReg::R15), [0x4D, 0x89, 0xFF]),
] {
buf.clear();
X86_64Assembler::mov_register64bit_register64bit(&mut buf, *dst, *src);
assert_eq!(expected, &buf[..]);
}
}
#[test]
fn test_mov_register64bit_stackoffset32bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, offset), expected) in &[
((X86_64GPReg::RAX, TEST_I32), [0x48, 0x8B, 0x84, 0x24]),
((X86_64GPReg::R15, TEST_I32), [0x4C, 0x8B, 0xBC, 0x24]),
] {
buf.clear();
X86_64Assembler::mov_register64bit_stackoffset32bit(&mut buf, *dst, *offset);
assert_eq!(expected, &buf[..4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
}
}
#[test]
fn test_mov_stackoffset32bit_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((offset, src), expected) in &[
((TEST_I32, X86_64GPReg::RAX), [0x48, 0x89, 0x84, 0x24]),
((TEST_I32, X86_64GPReg::R15), [0x4C, 0x89, 0xBC, 0x24]),
] {
buf.clear();
X86_64Assembler::mov_stackoffset32bit_register64bit(&mut buf, *offset, *src);
assert_eq!(expected, &buf[..4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
}
}
#[test]
fn test_neg_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (reg, expected) in &[
(X86_64GPReg::RAX, [0x48, 0xF7, 0xD8]),
(X86_64GPReg::R15, [0x49, 0xF7, 0xDF]),
] {
buf.clear();
X86_64Assembler::neg_register64bit(&mut buf, *reg);
assert_eq!(expected, &buf[..]);
}
}
#[test]
fn test_ret() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
X86_64Assembler::ret(&mut buf);
assert_eq!(&[0xC3], &buf[..]);
}
#[test]
fn test_sub_register64bit_immediate32bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (dst, expected) in &[
(X86_64GPReg::RAX, [0x48, 0x81, 0xE8]),
(X86_64GPReg::R15, [0x49, 0x81, 0xEF]),
] {
buf.clear();
X86_64Assembler::sub_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
assert_eq!(expected, &buf[..3]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
}
}
#[test]
fn test_pop_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (dst, expected) in &[
(X86_64GPReg::RAX, vec![0x58]),
(X86_64GPReg::R15, vec![0x41, 0x5F]),
] {
buf.clear();
X86_64Assembler::pop_register64bit(&mut buf, *dst);
assert_eq!(&expected[..], &buf[..]);
}
}
#[test]
fn test_push_register64bit() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for (src, expected) in &[
(X86_64GPReg::RAX, vec![0x50]),
(X86_64GPReg::R15, vec![0x41, 0x57]),
] {
buf.clear();
X86_64Assembler::push_register64bit(&mut buf, *src);
assert_eq!(&expected[..], &buf[..]);
}
}
}

388
compiler/gen_dev/src/lib.rs Normal file
View file

@ -0,0 +1,388 @@
#![warn(clippy::all, clippy::dbg_macro)]
// I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled.
//
// It warns about a performance problem where the only quick remediation is
// to allocate more on the heap, which has lots of tradeoffs - including making it
// long-term unclear which allocations *need* to happen for compilation's sake
// (e.g. recursive structures) versus those which were only added to appease clippy.
//
// Effectively optimizing data struture memory layout isn't a quick fix,
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
use bumpalo::{collections::Vec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout};
use target_lexicon::Triple;
mod generic64;
mod object_builder;
pub use object_builder::build_module;
mod run_roc;
pub struct Env<'a> {
pub arena: &'a Bump,
pub interns: Interns,
pub exposed_to_host: MutSet<Symbol>,
pub lazy_literals: bool,
}
// INLINED_SYMBOLS is a set of all of the functions we automatically inline if seen.
const INLINED_SYMBOLS: [Symbol; 2] = [Symbol::NUM_ABS, Symbol::NUM_ADD];
// These relocations likely will need a length.
// They may even need more definition, but this should be at least good enough for how we will use elf.
#[allow(dead_code)]
enum Relocation<'a> {
LocalData { offset: u64, data: &'a [u8] },
LinkedFunction { offset: u64, name: &'a str },
LinkedData { offset: u64, name: &'a str },
}
trait Backend<'a>
where
Self: Sized,
{
/// new creates a new backend that will output to the specific Object.
fn new(env: &'a Env, target: &Triple) -> Result<Self, String>;
fn env(&self) -> &'a Env<'a>;
/// reset resets any registers or other values that may be occupied at the end of a procedure.
fn reset(&mut self);
/// finalize does any setup and cleanup that should happen around the procedure.
/// finalize does setup because things like stack size and jump locations are not know until the function is written.
/// For example, this can store the frame pionter and setup stack space.
/// finalize is run at the end of build_proc when all internal code is finalized.
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;
/// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset();
// TODO: let the backend know of all the arguments.
// let start = std::time::Instant::now();
self.scan_ast(&proc.body);
self.create_free_map();
// let duration = start.elapsed();
// println!("Time to calculate lifetimes: {:?}", duration);
// println!("{:?}", self.last_seen_map());
self.build_stmt(&proc.body)?;
self.finalize()
}
/// build_stmt builds a statement and outputs at the end of the buffer.
fn build_stmt(&mut self, stmt: &Stmt<'a>) -> Result<(), String> {
match stmt {
Stmt::Let(sym, expr, layout, following) => {
self.build_expr(sym, expr, layout)?;
self.free_symbols(stmt);
self.build_stmt(following)?;
Ok(())
}
Stmt::Ret(sym) => {
self.load_literal_symbols(&[*sym])?;
self.return_symbol(sym)?;
self.free_symbols(stmt);
Ok(())
}
x => Err(format!("the statement, {:?}, is not yet implemented", x)),
}
}
/// build_expr builds the expressions for the specified symbol.
/// The builder must keep track of the symbol because it may be refered to later.
fn build_expr(
&mut self,
sym: &Symbol,
expr: &Expr<'a>,
layout: &Layout<'a>,
) -> Result<(), String> {
match expr {
Expr::Literal(lit) => {
if self.env().lazy_literals {
self.literal_map().insert(*sym, lit.clone());
} else {
self.load_literal(sym, lit)?;
}
Ok(())
}
Expr::FunctionCall {
call_type: CallType::ByName(func_sym),
args,
..
} => {
match *func_sym {
Symbol::NUM_ABS => {
// Instead of calling the function, just inline it.
self.build_expr(sym, &Expr::RunLowLevel(LowLevel::NumAbs, args), layout)
}
Symbol::NUM_ADD => {
// Instead of calling the function, just inline it.
self.build_expr(sym, &Expr::RunLowLevel(LowLevel::NumAdd, args), layout)
}
x => Err(format!("the function, {:?}, is not yet implemented", x)),
}
}
Expr::RunLowLevel(lowlevel, args) => {
self.build_run_low_level(sym, lowlevel, args, layout)
}
x => Err(format!("the expression, {:?}, is not yet implemented", x)),
}
}
/// build_run_low_level builds the low level opertation and outputs to the specified symbol.
/// The builder must keep track of the symbol because it may be refered to later.
fn build_run_low_level(
&mut self,
sym: &Symbol,
lowlevel: &LowLevel,
args: &'a [Symbol],
layout: &Layout<'a>,
) -> Result<(), String> {
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args)?;
match lowlevel {
LowLevel::NumAbs => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => self.build_num_abs_i64(sym, &args[0]),
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
}
LowLevel::NumAdd => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => {
self.build_num_add_i64(sym, &args[0], &args[1])
}
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
}
x => Err(format!("low level, {:?}. is not yet implemented", x)),
}
}
/// build_num_abs_i64 stores the absolute value of src into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
/// build_num_add_i64 stores the absolute value of src into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_add_i64(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
) -> Result<(), String>;
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
fn load_literal_symbols(&mut self, syms: &[Symbol]) -> Result<(), String> {
if self.env().lazy_literals {
for sym in syms {
if let Some(lit) = self.literal_map().remove(sym) {
self.load_literal(sym, &lit)?;
}
}
}
Ok(())
}
/// load_literal sets a symbol to be equal to a literal.
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
/// return_symbol moves a symbol to the correct return location for the backend.
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
/// free_symbols will free all symbols for the given statement.
fn free_symbols(&mut self, stmt: &Stmt<'a>) {
if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) {
for sym in syms {
//println!("Freeing symbol: {:?}", sym);
self.free_symbol(&sym);
}
}
}
/// free_symbol frees any registers or stack space used to hold a symbol.
fn free_symbol(&mut self, sym: &Symbol);
/// set_last_seen sets the statement a symbol was last seen in.
fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) {
self.last_seen_map().insert(sym, stmt);
}
/// last_seen_map gets the map from symbol to when it is last seen in the function.
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
fn create_free_map(&mut self) {
let mut free_map = MutMap::default();
let arena = self.env().arena;
for (sym, stmt) in self.last_seen_map() {
let vals = free_map
.entry(*stmt)
.or_insert_with(|| bumpalo::vec![in arena]);
vals.push(*sym);
}
self.set_free_map(free_map);
}
/// free_map gets the map statement to the symbols that are free after they run.
fn free_map(&mut self) -> &mut MutMap<*const Stmt<'a>, Vec<'a, Symbol>>;
/// set_free_map sets the free map to the given map.
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>);
/// set_not_leaf_function lets the backend know that it is not a leaf function.
fn set_not_leaf_function(&mut self);
/// scan_ast runs through the ast and fill the last seen map.
/// It also checks if the function is a leaf function or not.
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
fn scan_ast(&mut self, stmt: &Stmt<'a>) {
match stmt {
Stmt::Let(sym, expr, _, following) => {
self.set_last_seen(*sym, stmt);
match expr {
Expr::Literal(_) => {}
Expr::FunctionPointer(sym, _) => self.set_last_seen(*sym, stmt),
Expr::FunctionCall {
call_type, args, ..
} => {
for sym in *args {
self.set_last_seen(*sym, stmt);
}
match call_type {
CallType::ByName(sym) => {
// For functions that we won't inline, we should not be a leaf function.
if !INLINED_SYMBOLS.contains(sym) {
self.set_not_leaf_function();
}
}
CallType::ByPointer(sym) => {
self.set_not_leaf_function();
self.set_last_seen(*sym, stmt);
}
}
}
Expr::RunLowLevel(_, args) => {
for sym in *args {
self.set_last_seen(*sym, stmt);
}
}
Expr::ForeignCall { arguments, .. } => {
for sym in *arguments {
self.set_last_seen(*sym, stmt);
}
self.set_not_leaf_function();
}
Expr::Tag { arguments, .. } => {
for sym in *arguments {
self.set_last_seen(*sym, stmt);
}
}
Expr::Struct(syms) => {
for sym in *syms {
self.set_last_seen(*sym, stmt);
}
}
Expr::AccessAtIndex { structure, .. } => {
self.set_last_seen(*structure, stmt);
}
Expr::Array { elems, .. } => {
for sym in *elems {
self.set_last_seen(*sym, stmt);
}
}
Expr::Reuse {
symbol,
arguments,
tag_name,
..
} => {
self.set_last_seen(*symbol, stmt);
match tag_name {
TagName::Closure(sym) => {
self.set_last_seen(*sym, stmt);
}
TagName::Private(sym) => {
self.set_last_seen(*sym, stmt);
}
TagName::Global(_) => {}
}
for sym in *arguments {
self.set_last_seen(*sym, stmt);
}
}
Expr::Reset(sym) => {
self.set_last_seen(*sym, stmt);
}
Expr::EmptyArray => {}
Expr::RuntimeErrorFunction(_) => {}
}
self.scan_ast(following);
}
Stmt::Switch {
cond_symbol,
branches,
default_branch,
..
} => {
self.set_last_seen(*cond_symbol, stmt);
for (_, branch) in *branches {
self.scan_ast(branch);
}
self.scan_ast(default_branch);
}
Stmt::Cond {
cond_symbol,
branching_symbol,
pass,
fail,
..
} => {
self.set_last_seen(*cond_symbol, stmt);
self.set_last_seen(*branching_symbol, stmt);
self.scan_ast(pass);
self.scan_ast(fail);
}
Stmt::Ret(sym) => {
self.set_last_seen(*sym, stmt);
}
Stmt::Inc(sym, following) => {
self.set_last_seen(*sym, stmt);
self.scan_ast(following);
}
Stmt::Dec(sym, following) => {
self.set_last_seen(*sym, stmt);
self.scan_ast(following);
}
Stmt::Join {
parameters,
continuation,
remainder,
..
} => {
for param in *parameters {
self.set_last_seen(param.symbol, stmt);
}
self.scan_ast(continuation);
self.scan_ast(remainder);
}
Stmt::Jump(JoinPointId(sym), symbols) => {
self.set_last_seen(*sym, stmt);
for sym in *symbols {
self.set_last_seen(*sym, stmt);
}
}
Stmt::RuntimeError(_) => {}
}
}
}

View file

@ -0,0 +1,154 @@
use crate::generic64::{x86_64, Backend64Bit};
use crate::{Backend, Env, Relocation, INLINED_SYMBOLS};
use bumpalo::collections::Vec;
use object::write;
use object::write::{Object, StandardSection, Symbol, SymbolSection};
use object::{
Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SectionKind,
SymbolFlags, SymbolKind, SymbolScope,
};
use roc_collections::all::MutMap;
use roc_module::symbol;
use roc_mono::ir::Proc;
use roc_mono::layout::Layout;
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
const VERSION: &str = env!("CARGO_PKG_VERSION");
/// build_module is the high level builder/delegator.
/// It takes the request to build a module and output the object file for the module.
pub fn build_module<'a>(
env: &'a Env,
target: &Triple,
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
) -> Result<Object, String> {
let (mut output, mut backend) = match target {
Triple {
architecture: TargetArch::X86_64,
binary_format: TargetBF::Elf,
..
} => {
let backend: Backend64Bit<
x86_64::X86_64GPReg,
x86_64::X86_64Assembler,
x86_64::X86_64SystemV,
> = Backend::new(env, target)?;
Ok((
Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little),
backend,
))
}
x => Err(format! {
"the target, {:?}, is not yet implemented",
x}),
}?;
let text = output.section_id(StandardSection::Text);
let data_section = output.section_id(StandardSection::Data);
let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString);
output.append_section_data(
comment,
format!("\0roc dev backend version {} \0", VERSION).as_bytes(),
1,
);
// Setup layout_ids for procedure calls.
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
for ((sym, layout), proc) in procedures {
// This is temporary until we support passing args to functions.
if INLINED_SYMBOLS.contains(&sym) {
continue;
}
let fn_name = layout_ids
.get(sym, &layout)
.to_symbol_string(sym, &env.interns);
let proc_symbol = Symbol {
name: fn_name.as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
// TODO: Depending on whether we are building a static or dynamic lib, this should change.
// We should use Dynamic -> anyone, Linkage -> static link, Compilation -> this module only.
scope: if env.exposed_to_host.contains(&sym) {
SymbolScope::Dynamic
} else {
SymbolScope::Linkage
},
weak: false,
section: SymbolSection::Section(text),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
procs.push((fn_name, proc_id, proc));
}
// Build procedures.
for (fn_name, proc_id, proc) in procs {
let mut local_data_index = 0;
let (proc_data, relocations) = backend.build_proc(proc)?;
let proc_offset = output.add_symbol_data(proc_id, text, proc_data, 16);
for reloc in relocations {
let elfreloc = match reloc {
Relocation::LocalData { offset, data } => {
let data_symbol = write::Symbol {
name: format!("{}.data{}", fn_name, local_data_index)
.as_bytes()
.to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Data,
scope: SymbolScope::Compilation,
weak: false,
section: write::SymbolSection::Section(data_section),
flags: SymbolFlags::None,
};
local_data_index += 1;
let data_id = output.add_symbol(data_symbol);
output.add_symbol_data(data_id, data_section, data, 4);
write::Relocation {
offset: offset + proc_offset,
size: 32,
kind: RelocationKind::Relative,
encoding: RelocationEncoding::Generic,
symbol: data_id,
addend: -4,
}
}
Relocation::LinkedData { offset, name } => {
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
write::Relocation {
offset: offset + proc_offset,
size: 32,
kind: RelocationKind::GotRelative,
encoding: RelocationEncoding::Generic,
symbol: sym_id,
addend: -4,
}
} else {
return Err(format!("failed to find symbol for {:?}", name));
}
}
Relocation::LinkedFunction { offset, name } => {
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
write::Relocation {
offset: offset + proc_offset,
size: 32,
kind: RelocationKind::PltRelative,
encoding: RelocationEncoding::Generic,
symbol: sym_id,
addend: -4,
}
} else {
return Err(format!("failed to find symbol for {:?}", name));
}
}
};
output
.add_relocation(text, elfreloc)
.map_err(|e| format!("{:?}", e))?;
}
}
Ok(output)
}

View file

@ -0,0 +1,31 @@
#[macro_export]
/// run_jit_function_raw runs an unwrapped jit function.
/// The function could throw an exception and break things, or worse, it could not throw an exception and break things.
/// This functions is generally a bad idea with an untrused backend, but is being used for now for development purposes.
macro_rules! run_jit_function_raw {
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new();
run_jit_function_raw!($lib, $main_fn_name, $ty, $transform, v)
}};
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
unsafe {
let main: libloading::Symbol<unsafe extern "C" fn() -> $ty> = $lib
.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
let result = main();
assert_eq!(
$errors,
std::vec::Vec::new(),
"Encountered errors: {:?}",
$errors
);
$transform(result)
}
}};
}

View file

@ -0,0 +1,802 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate libc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", target_arch = "x86_64"))]
mod gen_num {
//use roc_std::RocOrder;
#[test]
fn i64_values() {
assert_evals_to!("0", 0, i64);
assert_evals_to!("-0", 0, i64);
assert_evals_to!("-1", -1, i64);
assert_evals_to!("1", 1, i64);
assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64);
assert_evals_to!("0b1010", 0b1010, i64);
assert_evals_to!("0o17", 0o17, i64);
assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64);
}
#[test]
fn gen_add_i64() {
assert_evals_to!(
indoc!(
r#"
1 + 2 + 3
"#
),
6,
i64
);
}
#[test]
fn i64_force_stack() {
// This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64.
assert_evals_to!(
indoc!(
r#"
a = 0
b = 1
c = 2
d = 3
e = 4
f = 5
g = 6
h = 7
i = 8
j = 9
k = 10
l = 11
m = 12
n = 13
o = 14
p = 15
q = 16
r = 17
s = 18
t = 19
u = 20
v = 21
w = 22
x = 23
y = 24
z = 25
aa = 26
ab = 27
ac = 28
ad = 29
ae = 30
af = 31
ag = 32
# This can't be one line because it causes a stack overflow in the frontend :(
tmp = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q
tmp + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
"#
),
528,
i64
);
}
#[test]
fn i64_abs() {
assert_evals_to!("Num.abs -6", 6, i64);
assert_evals_to!("Num.abs 7", 7, i64);
assert_evals_to!("Num.abs 0", 0, i64);
assert_evals_to!("Num.abs -0", 0, i64);
assert_evals_to!("Num.abs -1", 1, i64);
assert_evals_to!("Num.abs 1", 1, i64);
assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
}
/*
#[test]
fn f64_sqrt() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!(
indoc!(
r#"
when Num.sqrt 100 is
Ok val -> val
Err _ -> -1
"#
),
10.0,
f64
);
}
#[test]
fn f64_round_old() {
assert_evals_to!("Num.round 3.6", 4, i64);
}
#[test]
fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64);
assert_evals_to!("Num.abs 5.8", 5.8, f64);
}
#[test]
fn gen_if_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
x =
if num == 1 then
-1
else if num == -1 then
1
else
num
x
limitedNegate 1
"#
),
-1,
i64
);
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
if num == 1 then
-1
else if num == -1 then
1
else
num
limitedNegate 1
"#
),
-1,
i64
);
}
#[test]
fn gen_float_eq() {
assert_evals_to!(
indoc!(
r#"
1.0 == 1.0
"#
),
true,
bool
);
}
#[test]
fn gen_add_f64() {
assert_evals_to!(
indoc!(
r#"
1.1 + 2.4 + 3
"#
),
6.5,
f64
);
}
#[test]
fn gen_wrap_add_nums() {
assert_evals_to!(
indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test]
fn gen_div_f64() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!(
indoc!(
r#"
when 48 / 2 is
Ok val -> val
Err _ -> -1
"#
),
24.0,
f64
);
}
#[test]
fn gen_int_eq() {
assert_evals_to!(
indoc!(
r#"
4 == 4
"#
),
true,
bool
);
}
#[test]
fn gen_int_neq() {
assert_evals_to!(
indoc!(
r#"
4 != 5
"#
),
true,
bool
);
}
#[test]
fn gen_wrap_int_neq() {
assert_evals_to!(
indoc!(
r#"
wrappedNotEq : a, a -> Bool
wrappedNotEq = \num1, num2 ->
num1 != num2
wrappedNotEq 2 3
"#
),
true,
bool
);
}
#[test]
fn gen_sub_f64() {
assert_evals_to!(
indoc!(
r#"
1.5 - 2.4 - 3
"#
),
-3.9,
f64
);
}
#[test]
fn gen_sub_i64() {
assert_evals_to!(
indoc!(
r#"
1 - 2 - 3
"#
),
-4,
i64
);
}
#[test]
fn gen_mul_i64() {
assert_evals_to!(
indoc!(
r#"
2 * 4 * 6
"#
),
48,
i64
);
}
#[test]
fn gen_div_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 10 is
Ok val -> val
Err _ -> -1
"#
),
100,
i64
);
}
#[test]
fn gen_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 0 is
Err DivByZero -> 99
_ -> -24
"#
),
99,
i64
);
}
#[test]
fn gen_rem_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.rem 8 3 is
Ok val -> val
Err _ -> -1
"#
),
2,
i64
);
}
#[test]
fn gen_rem_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.rem 8 0 is
Err DivByZero -> 4
Ok _ -> -23
"#
),
4,
i64
);
}
#[test]
fn gen_is_zero_i64() {
assert_evals_to!("Num.isZero 0", true, bool);
assert_evals_to!("Num.isZero 1", false, bool);
}
#[test]
fn gen_is_positive_i64() {
assert_evals_to!("Num.isPositive 0", false, bool);
assert_evals_to!("Num.isPositive 1", true, bool);
assert_evals_to!("Num.isPositive -5", false, bool);
}
#[test]
fn gen_is_negative_i64() {
assert_evals_to!("Num.isNegative 0", false, bool);
assert_evals_to!("Num.isNegative 3", false, bool);
assert_evals_to!("Num.isNegative -2", true, bool);
}
#[test]
fn gen_is_positive_f64() {
assert_evals_to!("Num.isPositive 0.0", false, bool);
assert_evals_to!("Num.isPositive 4.7", true, bool);
assert_evals_to!("Num.isPositive -8.5", false, bool);
}
#[test]
fn gen_is_negative_f64() {
assert_evals_to!("Num.isNegative 0.0", false, bool);
assert_evals_to!("Num.isNegative 9.9", false, bool);
assert_evals_to!("Num.isNegative -4.4", true, bool);
}
#[test]
fn gen_is_zero_f64() {
assert_evals_to!("Num.isZero 0", true, bool);
assert_evals_to!("Num.isZero 0_0", true, bool);
assert_evals_to!("Num.isZero 0.0", true, bool);
assert_evals_to!("Num.isZero 1", false, bool);
}
#[test]
fn gen_is_odd() {
assert_evals_to!("Num.isOdd 4", false, bool);
assert_evals_to!("Num.isOdd 5", true, bool);
}
#[test]
fn gen_is_even() {
assert_evals_to!("Num.isEven 6", true, bool);
assert_evals_to!("Num.isEven 7", false, bool);
}
#[test]
fn sin() {
assert_evals_to!("Num.sin 0", 0.0, f64);
assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
}
#[test]
fn cos() {
assert_evals_to!("Num.cos 0", 1.0, f64);
assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
}
#[test]
fn tan() {
assert_evals_to!("Num.tan 0", 0.0, f64);
assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
}
#[test]
fn lt_i64() {
assert_evals_to!("1 < 2", true, bool);
assert_evals_to!("1 < 1", false, bool);
assert_evals_to!("2 < 1", false, bool);
assert_evals_to!("0 < 0", false, bool);
}
#[test]
fn lte_i64() {
assert_evals_to!("1 <= 1", true, bool);
assert_evals_to!("2 <= 1", false, bool);
assert_evals_to!("1 <= 2", true, bool);
assert_evals_to!("0 <= 0", true, bool);
}
#[test]
fn gt_i64() {
assert_evals_to!("2 > 1", true, bool);
assert_evals_to!("2 > 2", false, bool);
assert_evals_to!("1 > 1", false, bool);
assert_evals_to!("0 > 0", false, bool);
}
#[test]
fn gte_i64() {
assert_evals_to!("1 >= 1", true, bool);
assert_evals_to!("1 >= 2", false, bool);
assert_evals_to!("2 >= 1", true, bool);
assert_evals_to!("0 >= 0", true, bool);
}
#[test]
fn lt_f64() {
assert_evals_to!("1.1 < 1.2", true, bool);
assert_evals_to!("1.1 < 1.1", false, bool);
assert_evals_to!("1.2 < 1.1", false, bool);
assert_evals_to!("0.0 < 0.0", false, bool);
}
#[test]
fn lte_f64() {
assert_evals_to!("1.1 <= 1.1", true, bool);
assert_evals_to!("1.2 <= 1.1", false, bool);
assert_evals_to!("1.1 <= 1.2", true, bool);
assert_evals_to!("0.0 <= 0.0", true, bool);
}
#[test]
fn gt_f64() {
assert_evals_to!("2.2 > 1.1", true, bool);
assert_evals_to!("2.2 > 2.2", false, bool);
assert_evals_to!("1.1 > 2.2", false, bool);
assert_evals_to!("0.0 > 0.0", false, bool);
}
#[test]
fn gte_f64() {
assert_evals_to!("1.1 >= 1.1", true, bool);
assert_evals_to!("1.1 >= 1.2", false, bool);
assert_evals_to!("1.2 >= 1.1", true, bool);
assert_evals_to!("0.0 >= 0.0", true, bool);
}
#[test]
fn gen_order_of_arithmetic_ops() {
assert_evals_to!(
indoc!(
r#"
1 + 3 * 7 - 2
"#
),
20,
i64
);
}
#[test]
fn gen_order_of_arithmetic_ops_complex_float() {
assert_evals_to!(
indoc!(
r#"
3 - 48 * 2.0
"#
),
-93.0,
f64
);
}
#[test]
fn if_guard_bind_variable_false() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 10 is
x if x == 5 -> 0
_ -> 42
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn if_guard_bind_variable_true() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 10 is
x if x == 10 -> 42
_ -> 0
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn tail_call_elimination() {
assert_evals_to!(
indoc!(
r#"
sum = \n, accum ->
when n is
0 -> accum
_ -> sum (n - 1) (n + accum)
sum 1_000_000 0
"#
),
500000500000,
i64
);
}
#[test]
fn int_negate() {
assert_evals_to!("Num.neg 123", -123, i64);
}
#[test]
fn gen_wrap_int_neg() {
assert_evals_to!(
indoc!(
r#"
wrappedNeg = \num -> -num
wrappedNeg 3
"#
),
-3,
i64
);
}
#[test]
fn gen_basic_fn() {
assert_evals_to!(
indoc!(
r#"
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
always42 = \_ -> 42
always42 5
"#
),
42,
i64
);
}
#[test]
fn int_to_float() {
assert_evals_to!("Num.toFloat 0x9", 9.0, f64);
}
#[test]
fn num_to_float() {
assert_evals_to!("Num.toFloat 9", 9.0, f64);
}
#[test]
fn float_to_float() {
assert_evals_to!("Num.toFloat 0.5", 0.5, f64);
}
#[test]
fn int_compare() {
assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
}
#[test]
fn float_compare() {
assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
}
#[test]
fn pow() {
assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
}
#[test]
fn ceiling() {
assert_evals_to!("Num.ceiling 1.1", 2, i64);
}
#[test]
fn floor() {
assert_evals_to!("Num.floor 1.9", 1, i64);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// fn int_overflow() {
// assert_evals_to!(
// indoc!(
// r#"
// 9_223_372_036_854_775_807 + 1
// "#
// ),
// 0,
// i64
// );
// }
#[test]
fn int_add_checked() {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1 2 is
Ok v -> v
_ -> -1
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 9_223_372_036_854_775_807 1 is
Err Overflow -> -1
Ok v -> v
"#
),
-1,
i64
);
}
#[test]
fn int_add_wrap() {
assert_evals_to!(
indoc!(
r#"
Num.addWrap 9_223_372_036_854_775_807 1
"#
),
std::i64::MIN,
i64
);
}
#[test]
fn float_add_checked_pass() {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1.0 0.0 is
Ok v -> v
Err Overflow -> -1.0
"#
),
1.0,
f64
);
}
#[test]
fn float_add_checked_fail() {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is
Err Overflow -> -1
Ok v -> v
"#
),
-1.0,
f64
);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
// fn float_overflow() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.7976931348623157e308 + 1.7976931348623157e308
// "#
// ),
// 0.0,
// f64
// );
// }
#[test]
fn num_max_int() {
assert_evals_to!(
indoc!(
r#"
Num.maxInt
"#
),
i64::MAX,
i64
);
}
#[test]
fn num_min_int() {
assert_evals_to!(
indoc!(
r#"
Num.minInt
"#
),
i64::MIN,
i64
);
}
*/
}

View file

@ -0,0 +1,233 @@
use libloading::Library;
use roc_build::link::{link, LinkType};
use roc_collections::all::MutMap;
use tempfile::tempdir;
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
src: &str,
stdlib: roc_builtins::std::StdLib,
_leak: bool,
lazy_literals: bool,
) -> (String, Vec<roc_problem::can::Problem>, Library) {
use std::path::{Path, PathBuf};
//let stdlib_mode = stdlib.mode;
let dir = tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
let src_dir = Path::new("fake/test/path");
let app_o_file = dir.path().join("app.o");
let module_src;
let temp;
if src.starts_with("app") {
// this is already a module
module_src = src;
} else {
// this is an expression, promote it to a module
temp = promote_expr_to_module(src);
module_src = &temp;
}
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
arena,
filename,
&module_src,
stdlib,
src_dir,
exposed_types,
);
let mut loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
procedures,
interns,
exposed_to_host,
..
} = loaded;
/*
println!("=========== Procedures ==========");
println!("{:?}", procedures);
println!("=================================\n");
println!("=========== Interns ==========");
println!("{:?}", interns);
println!("=================================\n");
println!("=========== Exposed ==========");
println!("{:?}", exposed_to_host);
println!("=================================\n");
*/
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let main_fn_name = layout_ids
.get(main_fn_symbol, &main_fn_layout)
.to_symbol_string(main_fn_symbol, &interns);
let mut lines = Vec::new();
// errors whose reporting we delay (so we can see that code gen generates runtime errors)
let mut delayed_errors = Vec::new();
for (home, (module_path, src)) in loaded.sources {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
if error_count == 0 {
continue;
}
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
use roc_problem::can::Problem::*;
for problem in can_problems.into_iter() {
// Ignore "unused" problems
match problem {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => {
delayed_errors.push(problem);
continue;
}
_ => {
let report = can_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
}
}
for problem in type_problems {
let report = type_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
}
if !lines.is_empty() {
println!("{}", lines.join("\n"));
assert_eq!(0, 1, "Mistakes were made");
}
let env = roc_gen_dev::Env {
arena,
interns,
exposed_to_host: exposed_to_host.keys().copied().collect(),
lazy_literals,
};
let target = target_lexicon::Triple::host();
let module_object =
roc_gen_dev::build_module(&env, &target, procedures).expect("failed to compile module");
let module_out = module_object
.write()
.expect("failed to build output object");
std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
let (mut child, dylib_path) = link(
&target,
app_o_file.clone(),
&[app_o_file.to_str().unwrap()],
LinkType::Dylib,
)
.expect("failed to link dynamic library");
child.wait().unwrap();
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
// std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
// std::fs::copy(&path, "/tmp/libapp.so").unwrap();
let lib = Library::new(path).expect("failed to load shared library");
(main_fn_name, delayed_errors, lib)
}
#[macro_export]
macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{
assert_evals_to!($src, $expected, $ty, (|val| val));
}};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_evals_to!($src, $expected, $ty, $transform, true);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
// Run both with and without lazy literal optimization.
{
assert_evals_to!($src, $expected, $ty, $transform, $leak, false);
}
{
assert_evals_to!($src, $expected, $ty, $transform, $leak, true);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $lazy_literals:expr) => {
use bumpalo::Bump;
use roc_gen_dev::run_jit_function_raw;
let stdlib = roc_builtins::std::standard_stdlib();
let arena = Bump::new();
let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $lazy_literals);
let transform = |success| {
let expected = $expected;
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors)
};
}

View file

@ -0,0 +1,44 @@
extern crate bumpalo;
#[macro_use]
pub mod eval;
/// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)]
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
/// Without this, some tests pass in `cargo test --release` but fail without
/// the --release flag because they run out of stack space. This increases
/// stack size for debug builds only, while leaving the stack space at the default
/// amount for release builds.
#[allow(dead_code)]
#[cfg(debug_assertions)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce() -> (),
F: Send,
F: 'static,
{
std::thread::Builder::new()
.stack_size(EXPANDED_STACK_SIZE)
.spawn(run_test)
.expect("Error while spawning expanded dev stack size thread")
.join()
.expect("Error while joining expanded dev stack size thread")
}
/// In --release builds, don't increase the stack size. Run the test normally.
/// This way, we find out if any of our tests are blowing the stack even after
/// optimizations in release builds.
#[allow(dead_code)]
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce() -> (),
F: Send,
F: 'static,
{
run_test()
}

View file

@ -137,7 +137,7 @@ fn comments_or_new_lines_to_docs<'a>(
match comment_or_new_line { match comment_or_new_line {
DocComment(doc_str) => { DocComment(doc_str) => {
docs.push_str(doc_str); docs.push_str(doc_str);
docs.push_str("\n"); docs.push('\n');
} }
Newline | LineComment(_) => {} Newline | LineComment(_) => {}
} }

View file

@ -19,9 +19,8 @@ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs, CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
}; };
use roc_mono::layout::{Layout, LayoutCache}; use roc_mono::layout::{Layout, LayoutCache};
use roc_parse::ast::{ use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation};
self, Attempting, ExposesEntry, ImportsEntry, PlatformHeader, TypeAnnotation, TypedIdent, use roc_parse::header::{ExposesEntry, ImportsEntry, PlatformHeader, TypedIdent};
};
use roc_parse::module::module_defs; use roc_parse::module::module_defs;
use roc_parse::parser::{self, Fail, Parser}; use roc_parse::parser::{self, Fail, Parser};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -40,6 +39,9 @@ use std::str::from_utf8_unchecked;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
/// Default name for the binary generated for an app, if an invalid one was specified.
const DEFAULT_APP_OUTPUT_PATH: &str = "app";
/// Filename extension for normal Roc modules /// Filename extension for normal Roc modules
const ROC_FILE_EXTENSION: &str = "roc"; const ROC_FILE_EXTENSION: &str = "roc";
@ -534,7 +536,7 @@ pub enum BuildProblem<'a> {
#[derive(Debug)] #[derive(Debug)]
struct ModuleHeader<'a> { struct ModuleHeader<'a> {
module_id: ModuleId, module_id: ModuleId,
module_name: ModuleName, module_name: AppOrInterfaceName<'a>,
module_path: PathBuf, module_path: PathBuf,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
deps_by_name: MutMap<ModuleName, ModuleId>, deps_by_name: MutMap<ModuleName, ModuleId>,
@ -581,6 +583,7 @@ pub struct MonomorphizedModule<'a> {
pub module_id: ModuleId, pub module_id: ModuleId,
pub interns: Interns, pub interns: Interns,
pub subs: Subs, pub subs: Subs,
pub output_path: Box<str>,
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>, pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>, pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>, pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
@ -599,7 +602,7 @@ pub struct VariablySizedLayouts<'a> {
#[derive(Debug)] #[derive(Debug)]
struct ParsedModule<'a> { struct ParsedModule<'a> {
module_id: ModuleId, module_id: ModuleId,
module_name: ModuleName, module_name: AppOrInterfaceName<'a>,
module_path: PathBuf, module_path: PathBuf,
src: &'a str, src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -618,7 +621,7 @@ enum Msg<'a> {
CanonicalizedAndConstrained { CanonicalizedAndConstrained {
constrained_module: ConstrainedModule, constrained_module: ConstrainedModule,
canonicalization_problems: Vec<roc_problem::can::Problem>, canonicalization_problems: Vec<roc_problem::can::Problem>,
module_docs: ModuleDocumentation, module_docs: Option<ModuleDocumentation>,
}, },
MadeEffectModule { MadeEffectModule {
constrained_module: ConstrainedModule, constrained_module: ConstrainedModule,
@ -672,6 +675,7 @@ struct State<'a> {
pub goal_phase: Phase, pub goal_phase: Phase,
pub stdlib: StdLib, pub stdlib: StdLib,
pub exposed_types: SubsByModule, pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>,
pub headers_parsed: MutSet<ModuleId>, pub headers_parsed: MutSet<ModuleId>,
@ -1243,6 +1247,7 @@ where
root_id, root_id,
goal_phase, goal_phase,
stdlib, stdlib,
output_path: None,
module_cache: ModuleCache::default(), module_cache: ModuleCache::default(),
dependencies: Dependencies::default(), dependencies: Dependencies::default(),
procedures: MutMap::default(), procedures: MutMap::default(),
@ -1427,6 +1432,22 @@ fn update<'a>(
.sources .sources
.insert(parsed.module_id, (parsed.module_path.clone(), parsed.src)); .insert(parsed.module_id, (parsed.module_path.clone(), parsed.src));
// If this was an app module, set the output path to be
// the module's declared "name".
//
// e.g. for `app "blah"` we should generate an output file named "blah"
match &parsed.module_name {
AppOrInterfaceName::App(output_str) => match output_str {
StrLiteral::PlainLine(path) => {
state.output_path = Some(path);
}
_ => {
todo!("TODO gracefully handle a malformed string literal after `app` keyword.");
}
},
AppOrInterfaceName::Interface(_) => {}
}
let module_id = parsed.module_id; let module_id = parsed.module_id;
state.module_cache.parsed.insert(parsed.module_id, parsed); state.module_cache.parsed.insert(parsed.module_id, parsed);
@ -1450,10 +1471,9 @@ fn update<'a>(
.can_problems .can_problems
.insert(module_id, canonicalization_problems); .insert(module_id, canonicalization_problems);
state if let Some(docs) = module_docs {
.module_cache state.module_cache.documentation.insert(module_id, docs);
.documentation }
.insert(module_id, module_docs);
state state
.module_cache .module_cache
@ -1751,6 +1771,7 @@ fn finish_specialization<'a>(
let State { let State {
procedures, procedures,
module_cache, module_cache,
output_path,
.. ..
} = state; } = state;
@ -1771,6 +1792,7 @@ fn finish_specialization<'a>(
can_problems, can_problems,
mono_problems, mono_problems,
type_problems, type_problems,
output_path: output_path.unwrap_or(DEFAULT_APP_OUTPUT_PATH).into(),
exposed_to_host, exposed_to_host,
module_id: state.root_id, module_id: state.root_id,
subs, subs,
@ -1967,7 +1989,10 @@ fn parse_header<'a>(
match parsed { match parsed {
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header( Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header(
header.name, Located {
region: header.name.region,
value: AppOrInterfaceName::Interface(header.name.value),
},
filename, filename,
header.exposes.into_bump_slice(), header.exposes.into_bump_slice(),
header.imports.into_bump_slice(), header.imports.into_bump_slice(),
@ -1981,7 +2006,10 @@ fn parse_header<'a>(
pkg_config_dir.pop(); pkg_config_dir.pop();
let (module_id, app_module_header_msg) = send_header( let (module_id, app_module_header_msg) = send_header(
header.name, Located {
region: header.name.region,
value: AppOrInterfaceName::App(header.name.value),
},
filename, filename,
header.provides.into_bump_slice(), header.provides.into_bump_slice(),
header.imports.into_bump_slice(), header.imports.into_bump_slice(),
@ -2083,22 +2111,36 @@ fn load_from_str<'a>(
) )
} }
#[derive(Debug)]
enum AppOrInterfaceName<'a> {
/// A filename
App(StrLiteral<'a>),
Interface(roc_parse::header::ModuleName<'a>),
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn send_header<'a>( fn send_header<'a>(
name: Located<roc_parse::header::ModuleName<'a>>, loc_name: Located<AppOrInterfaceName<'a>>,
filename: PathBuf, filename: PathBuf,
exposes: &'a [Located<ExposesEntry<'a>>], exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
imports: &'a [Located<ImportsEntry<'a>>], imports: &'a [Located<ImportsEntry<'a>>],
parse_state: parser::State<'a>, parse_state: parser::State<'a>,
module_ids: Arc<Mutex<ModuleIds>>, module_ids: Arc<Mutex<ModuleIds>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
let declared_name: ModuleName = name.value.as_str().into(); use AppOrInterfaceName::*;
// TODO check to see if declared_name is consistent with filename. let declared_name: ModuleName = match &loc_name.value {
App(_) => ModuleName::APP.into(),
Interface(module_name) => {
// TODO check to see if module_name is consistent with filename.
// If it isn't, report a problem! // If it isn't, report a problem!
module_name.as_str().into()
}
};
let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len()); let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len());
let mut imported_modules: MutSet<ModuleId> = MutSet::default(); let mut imported_modules: MutSet<ModuleId> = MutSet::default();
let mut scope_size = 0; let mut scope_size = 0;
@ -2200,15 +2242,13 @@ fn send_header<'a>(
// We always need to send these, even if deps is empty, // We always need to send these, even if deps is empty,
// because the coordinator thread needs to receive this message // because the coordinator thread needs to receive this message
// to decrement its "pending" count. // to decrement its "pending" count.
// Send the header the main thread for processing,
( (
home, home,
Msg::Header(ModuleHeader { Msg::Header(ModuleHeader {
module_id: home, module_id: home,
module_path: filename, module_path: filename,
exposed_ident_ids: ident_ids, exposed_ident_ids: ident_ids,
module_name: declared_name, module_name: loc_name.value,
imported_modules, imported_modules,
deps_by_name, deps_by_name,
exposes: exposed, exposes: exposed,
@ -2619,8 +2659,14 @@ fn canonicalize_and_constrain<'a>(
// Generate documentation information // Generate documentation information
// TODO: store timing information? // TODO: store timing information?
let module_docs = let module_docs = match module_name {
crate::docs::generate_module_docs(module_name, &exposed_ident_ids, &parsed_defs); AppOrInterfaceName::App(_) => None,
AppOrInterfaceName::Interface(name) => Some(crate::docs::generate_module_docs(
name.as_str().into(),
&exposed_ident_ids,
&parsed_defs,
)),
};
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
let canonicalized = canonicalize_module_defs( let canonicalized = canonicalize_module_defs(
@ -2737,7 +2783,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
} }
fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) { fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
use roc_parse::ast::ImportsEntry::*; use roc_parse::header::ImportsEntry::*;
match entry { match entry {
Module(module_name, exposes) => { Module(module_name, exposes) => {
@ -2750,6 +2796,10 @@ fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
(module_name.as_str().into(), exposed) (module_name.as_str().into(), exposed)
} }
Package(_package_name, _exposes) => {
todo!("TODO support exposing package-qualified module names.");
}
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => { SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => {
// Ignore spaces. // Ignore spaces.
exposed_from_import(*sub_entry) exposed_from_import(*sub_entry)
@ -2757,11 +2807,11 @@ fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
} }
} }
fn ident_from_exposed(entry: &ExposesEntry<'_>) -> Ident { fn ident_from_exposed(entry: &ExposesEntry<'_, &str>) -> Ident {
use roc_parse::ast::ExposesEntry::*; use roc_parse::header::ExposesEntry::*;
match entry { match entry {
Ident(ident) => (*ident).into(), Exposed(ident) => (*ident).into(),
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => ident_from_exposed(sub_entry), SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => ident_from_exposed(sub_entry),
} }
} }

View file

@ -8,7 +8,7 @@ interface AStar
Model position : Model position :
{ evaluated : Set position { evaluated : Set position
, openSet : Set position , openSet : Set position
, costs : Map.Map position Float , costs : Map.Map position F64
, cameFrom : Map.Map position position , cameFrom : Map.Map position position
} }
@ -22,7 +22,7 @@ initialModel = \start ->
} }
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
cheapestOpen = \costFunction, model -> cheapestOpen = \costFunction, model ->
folder = \position, resSmallestSoFar -> folder = \position, resSmallestSoFar ->
@ -80,12 +80,12 @@ updateCost = \current, neighbour, model ->
model model
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]* findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
findPath = \{ costFunction, moveFunction, start, end } -> findPath = \{ costFunction, moveFunction, start, end } ->
astar costFunction moveFunction end (initialModel start) astar costFunction moveFunction end (initialModel start)
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]* astar : (position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
astar = \costFn, moveFn, goal, model -> astar = \costFn, moveFn, goal, model ->
when cheapestOpen (\position -> costFn goal position) model is when cheapestOpen (\position -> costFn goal position) model is
Err _ -> Err _ ->

View file

@ -1,6 +1,7 @@
app Primary app "primary"
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] packages { blah: "./blah" }
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ] imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] to blah
blah2 = Dep2.two blah2 = Dep2.two
blah3 = bar blah3 = bar
@ -12,7 +13,7 @@ alwaysThree = \_ -> "foo"
identity = \a -> a identity = \a -> a
z = identity (Primary.alwaysThree {}) z = identity (alwaysThree {})
w : Dep1.Identity {} w : Dep1.Identity {}
w = Identity {} w = Identity {}

View file

@ -1,6 +1,4 @@
app Quicksort app "quicksort" provides [ swap, partition, partitionHelp, quicksort ] to "./platform"
provides [ swap, partition, partitionHelp, quicksort ]
imports []
quicksort : List (Num a), Int, Int -> List (Num a) quicksort : List (Num a), Int, Int -> List (Num a)
quicksort = \list, low, high -> quicksort = \list, low, high ->

View file

@ -1,4 +1,4 @@
app QuicksortOneDef provides [ quicksort ] imports [] app "quicksort" provides [ quicksort ] to "./platform"
quicksort = \originalList -> quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a) quicksortHelp : List (Num a), Int, Int -> List (Num a)

View file

@ -8,7 +8,7 @@ interface AStar
Model position : Model position :
{ evaluated : Set position { evaluated : Set position
, openSet : Set position , openSet : Set position
, costs : Map.Map position Float , costs : Map.Map position F64
, cameFrom : Map.Map position position , cameFrom : Map.Map position position
} }
@ -22,7 +22,7 @@ initialModel = \start ->
} }
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
cheapestOpen = \costFunction, model -> cheapestOpen = \costFunction, model ->
folder = \position, resSmallestSoFar -> folder = \position, resSmallestSoFar ->
@ -80,12 +80,12 @@ updateCost = \current, neighbour, model ->
model model
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]* findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
findPath = \{ costFunction, moveFunction, start, end } -> findPath = \{ costFunction, moveFunction, start, end } ->
astar costFunction moveFunction end (initialModel start) astar costFunction moveFunction end (initialModel start)
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]* astar : (position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
astar = \costFn, moveFn, goal, model -> astar = \costFn, moveFn, goal, model ->
when cheapestOpen (\position -> costFn goal position) model is when cheapestOpen (\position -> costFn goal position) model is
Err _ -> Err _ ->

View file

@ -23,6 +23,7 @@ mod test_load {
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_constrain::module::SubsByModule; use roc_constrain::module::SubsByModule;
use roc_load::file::LoadedModule; use roc_load::file::LoadedModule;
use roc_module::ident::ModuleName;
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs; use roc_types::subs::Subs;
@ -148,7 +149,10 @@ mod test_load {
.get_name(loaded_module.module_id) .get_name(loaded_module.module_id)
.expect("Test ModuleID not found in module_ids"); .expect("Test ModuleID not found in module_ids");
// App module names are hardcoded and not based on anything user-specified
if expected_name != ModuleName::APP {
assert_eq!(expected_name, &InlinableString::from(module_name)); assert_eq!(expected_name, &InlinableString::from(module_name));
}
loaded_module loaded_module
} }
@ -256,7 +260,10 @@ mod test_load {
"Main", "Main",
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [ RBTree ] app "test-app"
packages { blah: "./blah" }
imports [ RBTree ]
provides [ main ] to blah
empty : RBTree.Dict Int Int empty : RBTree.Dict Int Int
empty = RBTree.empty empty = RBTree.empty
@ -350,14 +357,14 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"floatTest" => "Float", "floatTest" => "F64",
"divisionFn" => "Float, Float -> Result Float [ DivByZero ]*", "divisionFn" => "F64, F64 -> Result F64 [ DivByZero ]*",
"divisionTest" => "Result Float [ DivByZero ]*", "divisionTest" => "Result F64 [ DivByZero ]*",
"intTest" => "Int", "intTest" => "Int",
"x" => "Float", "x" => "F64",
"constantNum" => "Num *", "constantNum" => "Num *",
"divDep1ByDep2" => "Result Float [ DivByZero ]*", "divDep1ByDep2" => "Result F64 [ DivByZero ]*",
"fromDep2" => "Float", "fromDep2" => "F64",
}, },
); );
} }
@ -415,12 +422,12 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"findPath" => "{ costFunction : position, position -> Float, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [ KeyNotFound ]*", "findPath" => "{ costFunction : position, position -> F64, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [ KeyNotFound ]*",
"initialModel" => "position -> Model position", "initialModel" => "position -> Model position",
"reconstructPath" => "Map position position, position -> List position", "reconstructPath" => "Map position position, position -> List position",
"updateCost" => "position, position, Model position -> Model position", "updateCost" => "position, position, Model position -> Model position",
"cheapestOpen" => "(position -> Float), Model position -> Result position [ KeyNotFound ]*", "cheapestOpen" => "(position -> F64), Model position -> Result position [ KeyNotFound ]*",
"astar" => "(position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*", "astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*",
}, },
); );
} }
@ -447,7 +454,7 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"blah2" => "Float", "blah2" => "F64",
"blah3" => "Str", "blah3" => "Str",
"str" => "Str", "str" => "Str",
"alwaysThree" => "* -> Str", "alwaysThree" => "* -> Str",
@ -469,7 +476,7 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"blah2" => "Float", "blah2" => "F64",
"blah3" => "Str", "blah3" => "Str",
"str" => "Str", "str" => "Str",
"alwaysThree" => "* -> Str", "alwaysThree" => "* -> Str",

View file

@ -22,6 +22,7 @@ mod test_uniq_load {
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_constrain::module::SubsByModule; use roc_constrain::module::SubsByModule;
use roc_load::file::LoadedModule; use roc_load::file::LoadedModule;
use roc_module::ident::ModuleName;
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs; use roc_types::subs::Subs;
@ -66,7 +67,10 @@ mod test_uniq_load {
.get_name(loaded_module.module_id) .get_name(loaded_module.module_id)
.expect("Test ModuleID not found in module_ids"); .expect("Test ModuleID not found in module_ids");
// App module names are hardcoded and not based on anything user-specified
if expected_name != ModuleName::APP {
assert_eq!(expected_name, &InlinableString::from(module_name)); assert_eq!(expected_name, &InlinableString::from(module_name));
}
loaded_module loaded_module
} }
@ -228,14 +232,14 @@ mod test_uniq_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"floatTest" => "Attr Shared Float", "floatTest" => "Attr Shared F64",
"divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*)))", "divisionFn" => "Attr Shared (Attr * F64, Attr * F64 -> Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*)))",
"divisionTest" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "divisionTest" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
"intTest" => "Attr * Int", "intTest" => "Attr * Int",
"x" => "Attr * Float", "x" => "Attr * F64",
"constantNum" => "Attr * (Num (Attr * *))", "constantNum" => "Attr * (Num (Attr * *))",
"divDep1ByDep2" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "divDep1ByDep2" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
"fromDep2" => "Attr * Float", "fromDep2" => "Attr * F64",
}, },
); );
} }
@ -249,12 +253,12 @@ mod test_uniq_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"findPath" => "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))", "findPath" => "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
"initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))", "initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))",
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))", "reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
"updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))", "updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))",
"cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr * Float), Attr (* | a | b | c) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))", "cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr * F64), Attr (* | a | b | c) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))",
"astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)", "astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)",
}, },
); );
} }
@ -314,7 +318,7 @@ mod test_uniq_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"blah2" => "Attr * Float", "blah2" => "Attr * F64",
"blah3" => "Attr * Str", "blah3" => "Attr * Str",
"str" => "Attr * Str", "str" => "Attr * Str",
"alwaysThree" => "Attr * (* -> Attr * Str)", "alwaysThree" => "Attr * (* -> Attr * Str)",

View file

@ -59,6 +59,7 @@ impl TagName {
impl ModuleName { impl ModuleName {
// NOTE: After adding one of these, go to `impl ModuleId` and // NOTE: After adding one of these, go to `impl ModuleId` and
// add a corresponding ModuleId to there! // add a corresponding ModuleId to there!
pub const APP: &'static str = ""; // app modules have no module name
pub const BOOL: &'static str = "Bool"; pub const BOOL: &'static str = "Bool";
pub const STR: &'static str = "Str"; pub const STR: &'static str = "Str";
pub const NUM: &'static str = "Num"; pub const NUM: &'static str = "Num";

View file

@ -5,6 +5,7 @@
pub enum LowLevel { pub enum LowLevel {
StrConcat, StrConcat,
StrIsEmpty, StrIsEmpty,
StrStartsWith,
StrSplit, StrSplit,
StrCountGraphemes, StrCountGraphemes,
ListLen, ListLen,
@ -21,7 +22,8 @@ pub enum LowLevel {
ListJoin, ListJoin,
ListMap, ListMap,
ListKeepIf, ListKeepIf,
ListWalkRight, ListWalk,
ListWalkBackwards,
ListSum, ListSum,
NumAdd, NumAdd,
NumAddWrap, NumAddWrap,

View file

@ -612,7 +612,7 @@ define_builtins! {
2 NUM_INT: "Int" imported // the Int.Int type alias 2 NUM_INT: "Int" imported // the Int.Int type alias
3 NUM_INTEGER: "Integer" imported // Int : Num Integer 3 NUM_INTEGER: "Integer" imported // Int : Num Integer
4 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag 4 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag
5 NUM_FLOAT: "Float" imported // the Float.Float type alias 5 NUM_F64: "F64" imported // the Num.F64 type alias
6 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint 6 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint
7 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag 7 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag
8 NUM_MAX_INT: "maxInt" 8 NUM_MAX_INT: "maxInt"
@ -672,6 +672,7 @@ define_builtins! {
4 STR_CONCAT: "concat" 4 STR_CONCAT: "concat"
5 STR_SPLIT: "split" 5 STR_SPLIT: "split"
6 STR_COUNT_GRAPHEMES: "countGraphemes" 6 STR_COUNT_GRAPHEMES: "countGraphemes"
7 STR_STARTS_WITH: "startsWith"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
@ -682,18 +683,18 @@ define_builtins! {
5 LIST_APPEND: "append" 5 LIST_APPEND: "append"
6 LIST_MAP: "map" 6 LIST_MAP: "map"
7 LIST_LEN: "len" 7 LIST_LEN: "len"
8 LIST_FOLDL: "foldl" 8 LIST_WALK_BACKWARDS: "walkBackwards"
9 LIST_WALK_RIGHT: "walkRight" 9 LIST_CONCAT: "concat"
10 LIST_CONCAT: "concat" 10 LIST_FIRST: "first"
11 LIST_FIRST: "first" 11 LIST_SINGLE: "single"
12 LIST_SINGLE: "single" 12 LIST_REPEAT: "repeat"
13 LIST_REPEAT: "repeat" 13 LIST_REVERSE: "reverse"
14 LIST_REVERSE: "reverse" 14 LIST_PREPEND: "prepend"
15 LIST_PREPEND: "prepend" 15 LIST_JOIN: "join"
16 LIST_JOIN: "join" 16 LIST_KEEP_IF: "keepIf"
17 LIST_KEEP_IF: "keepIf" 17 LIST_CONTAINS: "contains"
18 LIST_CONTAINS: "contains" 18 LIST_SUM: "sum"
19 LIST_SUM: "sum" 19 LIST_WALK: "walk"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -535,7 +535,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]), ListMap => arena.alloc_slice_copy(&[owned, irrelevant]),
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]), ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), ListWalk => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
ListSum => arena.alloc_slice_copy(&[borrowed]), ListSum => arena.alloc_slice_copy(&[borrowed]),
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt
@ -546,5 +547,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => { | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {
arena.alloc_slice_copy(&[irrelevant]) arena.alloc_slice_copy(&[irrelevant])
} }
StrStartsWith => arena.alloc_slice_copy(&[owned, borrowed]),
} }
} }

View file

@ -412,7 +412,7 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
arguments.push((Pattern::Underscore, destruct.layout.clone())); arguments.push((Pattern::Underscore, destruct.layout.clone()));
} }
DestructType::Optional(_expr) => { DestructType::Optional(_expr) => {
arguments.push((Pattern::Underscore, destruct.layout.clone())); // do nothing
} }
} }
} }
@ -540,11 +540,15 @@ fn to_relevant_branch_help<'a>(
.. ..
} => { } => {
debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into())); debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into()));
let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| { let sub_positions = destructs
.into_iter()
.filter(|destruct| !matches!(destruct.typ, DestructType::Optional(_)))
.enumerate()
.map(|(index, destruct)| {
let pattern = match destruct.typ { let pattern = match destruct.typ {
DestructType::Guard(guard) => guard.clone(), DestructType::Guard(guard) => guard.clone(),
DestructType::Required => Pattern::Underscore, DestructType::Required => Pattern::Underscore,
DestructType::Optional(_expr) => Pattern::Underscore, DestructType::Optional(_expr) => unreachable!("because of the filter"),
}; };
( (

View file

@ -907,7 +907,13 @@ where
if PRETTY_PRINT_IR_SYMBOLS { if PRETTY_PRINT_IR_SYMBOLS {
alloc.text(format!("{:?}", symbol)) alloc.text(format!("{:?}", symbol))
} else { } else {
alloc.text(format!("{}", symbol)) let text = format!("{}", symbol);
if text.starts_with('.') {
alloc.text("Test").append(text)
} else {
alloc.text(text)
}
} }
} }
@ -917,7 +923,7 @@ where
D::Doc: Clone, D::Doc: Clone,
A: Clone, A: Clone,
{ {
alloc.text(format!("{}", symbol.0)) symbol_to_doc(alloc, symbol.0)
} }
impl<'a> Expr<'a> { impl<'a> Expr<'a> {
@ -1101,7 +1107,9 @@ impl<'a> Stmt<'a> {
.chain(std::iter::once(default_doc)); .chain(std::iter::once(default_doc));
// //
alloc alloc
.text(format!("switch {}:", cond_symbol)) .text("switch ")
.append(symbol_to_doc(alloc, *cond_symbol))
.append(":")
.append(alloc.hardline()) .append(alloc.hardline())
.append( .append(
alloc.intersperse(branches_docs, alloc.hardline().append(alloc.hardline())), alloc.intersperse(branches_docs, alloc.hardline().append(alloc.hardline())),
@ -1115,7 +1123,9 @@ impl<'a> Stmt<'a> {
fail, fail,
.. ..
} => alloc } => alloc
.text(format!("if {} then", branching_symbol)) .text("if ")
.append(symbol_to_doc(alloc, *branching_symbol))
.append(" then")
.append(alloc.hardline()) .append(alloc.hardline())
.append(pass.to_doc(alloc).indent(4)) .append(pass.to_doc(alloc).indent(4))
.append(alloc.hardline()) .append(alloc.hardline())
@ -1263,7 +1273,7 @@ fn patterns_to_when<'a>(
// Even if the body was Ok, replace it with this Err. // Even if the body was Ok, replace it with this Err.
// If it was already an Err, leave it at that Err, so the first // If it was already an Err, leave it at that Err, so the first
// RuntimeError we encountered remains the first. // RuntimeError we encountered remains the first.
body = body.and_then(|_| { body = body.and({
Err(Located { Err(Located {
region: pattern.region, region: pattern.region,
value, value,
@ -2384,7 +2394,7 @@ pub fn with_hole<'a>(
Tag { Tag {
variant_var, variant_var,
name: tag_name, name: tag_name,
arguments: args, arguments: mut args,
.. ..
} => { } => {
use crate::layout::UnionVariant::*; use crate::layout::UnionVariant::*;
@ -2421,11 +2431,34 @@ pub fn with_hole<'a>(
} }
Unwrapped(field_layouts) => { Unwrapped(field_layouts) => {
let mut field_symbols_temp =
Vec::with_capacity_in(field_layouts.len(), env.arena);
for (var, arg) in args.drain(..) {
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
let layout = layout_cache
.from_var(env.arena, var, env.subs)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
let alignment = layout.alignment_bytes(8);
let symbol = possible_reuse_symbol(env, procs, &arg.value);
field_symbols_temp.push((
alignment,
symbol,
((var, arg), &*env.arena.alloc(symbol)),
));
}
field_symbols_temp.sort_by(|a, b| b.0.cmp(&a.0));
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena); let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
for (_, arg) in args.iter() { for (_, symbol, _) in field_symbols_temp.iter() {
field_symbols.push(possible_reuse_symbol(env, procs, &arg.value)); field_symbols.push(*symbol);
} }
let field_symbols = field_symbols.into_bump_slice(); let field_symbols = field_symbols.into_bump_slice();
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field // Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
@ -2438,7 +2471,7 @@ pub fn with_hole<'a>(
// even though this was originally a Tag, we treat it as a Struct from now on // even though this was originally a Tag, we treat it as a Struct from now on
let stmt = Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole); let stmt = Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole);
let iter = args.into_iter().rev().zip(field_symbols.iter().rev()); let iter = field_symbols_temp.into_iter().map(|(_, _, data)| data);
assign_to_symbols(env, procs, layout_cache, iter, stmt) assign_to_symbols(env, procs, layout_cache, iter, stmt)
} }
Wrapped(sorted_tag_layouts) => { Wrapped(sorted_tag_layouts) => {
@ -2449,12 +2482,33 @@ pub fn with_hole<'a>(
.find(|(_, (key, _))| key == &tag_name) .find(|(_, (key, _))| key == &tag_name)
.expect("tag must be in its own type"); .expect("tag must be in its own type");
let mut field_symbols_temp = Vec::with_capacity_in(args.len(), env.arena);
for (var, arg) in args.drain(..) {
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
let layout = layout_cache
.from_var(env.arena, var, env.subs)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
let alignment = layout.alignment_bytes(8);
let symbol = possible_reuse_symbol(env, procs, &arg.value);
field_symbols_temp.push((
alignment,
symbol,
((var, arg), &*env.arena.alloc(symbol)),
));
}
field_symbols_temp.sort_by(|a, b| b.0.cmp(&a.0));
let mut field_symbols: Vec<Symbol> = Vec::with_capacity_in(args.len(), arena); let mut field_symbols: Vec<Symbol> = Vec::with_capacity_in(args.len(), arena);
let tag_id_symbol = env.unique_symbol(); let tag_id_symbol = env.unique_symbol();
field_symbols.push(tag_id_symbol); field_symbols.push(tag_id_symbol);
for (_, arg) in args.iter() { for (_, symbol, _) in field_symbols_temp.iter() {
field_symbols.push(possible_reuse_symbol(env, procs, &arg.value)); field_symbols.push(*symbol);
} }
let mut layouts: Vec<&'a [Layout<'a>]> = let mut layouts: Vec<&'a [Layout<'a>]> =
@ -2475,7 +2529,11 @@ pub fn with_hole<'a>(
}; };
let mut stmt = Stmt::Let(assigned, tag, layout, hole); let mut stmt = Stmt::Let(assigned, tag, layout, hole);
let iter = args.into_iter().rev().zip(field_symbols.iter().rev()); let iter = field_symbols_temp
.drain(..)
.map(|x| x.2 .0)
.rev()
.zip(field_symbols.iter().rev());
stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt); stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt);
@ -5290,6 +5348,20 @@ pub fn from_can_pattern<'a>(
}], }],
}; };
let mut arguments = arguments.clone();
arguments.sort_by(|arg1, arg2| {
let ptr_bytes = 8;
let layout1 = layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
let layout2 = layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1)
});
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) { for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) {
mono_args.push(( mono_args.push((
@ -5333,6 +5405,20 @@ pub fn from_can_pattern<'a>(
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
// disregard the tag discriminant layout // disregard the tag discriminant layout
let mut arguments = arguments.clone();
arguments.sort_by(|arg1, arg2| {
let ptr_bytes = 8;
let layout1 = layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
let layout2 = layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1)
});
// TODO make this assert pass, it currently does not because // TODO make this assert pass, it currently does not because
// 0-sized values are dropped out // 0-sized values are dropped out
// debug_assert_eq!(arguments.len(), argument_layouts[1..].len()); // debug_assert_eq!(arguments.len(), argument_layouts[1..].len());
@ -5374,8 +5460,8 @@ pub fn from_can_pattern<'a>(
// sorted fields based on the destruct // sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
let mut destructs = destructs.clone(); let destructs_by_label = env.arena.alloc(MutMap::default());
destructs.sort_by(|a, b| a.value.label.cmp(&b.value.label)); destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x)));
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -5387,27 +5473,25 @@ pub fn from_can_pattern<'a>(
// in the source the field is not matche in the source language. // in the source the field is not matche in the source language.
// //
// Optional fields somewhat complicate the matter here // Optional fields somewhat complicate the matter here
let mut it1 = sorted_fields.into_iter();
let mut opt_sorted = it1.next();
let mut it2 = destructs.iter(); for (label, variable, res_layout) in sorted_fields.into_iter() {
let mut opt_destruct = it2.next(); match res_layout {
Ok(field_layout) => {
// the field is non-optional according to the type
loop { match destructs_by_label.remove(&label) {
match (opt_sorted, opt_destruct) { Some(destruct) => {
(Some((label, variable, Ok(field_layout))), Some(destruct)) => { // this field is destructured by the pattern
if destruct.value.label == label {
mono_destructs.push(from_can_record_destruct( mono_destructs.push(from_can_record_destruct(
env, env,
layout_cache, layout_cache,
&destruct.value, &destruct.value,
field_layout.clone(), field_layout.clone(),
)); ));
}
opt_sorted = it1.next(); None => {
opt_destruct = it2.next(); // this field is not destructured by the pattern
} else { // put in an underscore
// insert underscore pattern
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
@ -5415,15 +5499,17 @@ pub fn from_can_pattern<'a>(
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
}); });
opt_sorted = it1.next();
} }
}
// the layout of this field is part of the layout of the record
field_layouts.push(field_layout); field_layouts.push(field_layout);
} }
(Some((label, variable, Err(field_layout))), Some(destruct)) => { Err(field_layout) => {
if destruct.value.label == label { // the field is optional according to the type
opt_destruct = it2.next(); match destructs_by_label.remove(&label) {
Some(destruct) => {
// this field is destructured by the pattern
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: destruct.value.label.clone(), label: destruct.value.label.clone(),
symbol: destruct.value.symbol, symbol: destruct.value.symbol,
@ -5441,12 +5527,9 @@ pub fn from_can_pattern<'a>(
}, },
}); });
} }
opt_sorted = it1.next(); None => {
} // this field is not destructured by the pattern
// put in an underscore
(Some((label, variable, Err(field_layout))), None) => {
// the remainder of the fields (from the type) is not matched on in
// this pattern; to fill it out, we put underscores
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
@ -5454,26 +5537,14 @@ pub fn from_can_pattern<'a>(
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
}); });
}
opt_sorted = it1.next(); }
}
}
} }
(Some((label, variable, Ok(field_layout))), None) => { for (_, destruct) in destructs_by_label.drain() {
// the remainder of the fields (from the type) is not matched on in // this destruct is not in the type, but is in the pattern
// this pattern; to fill it out, we put underscores
mono_destructs.push(RecordDestruct {
label: label.clone(),
symbol: env.unique_symbol(),
variable,
layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore),
});
field_layouts.push(field_layout);
opt_sorted = it1.next();
}
(None, Some(destruct)) => {
// destruct is not in the type, but is in the pattern
// it must be an optional field, and we will use the default // it must be an optional field, and we will use the default
match &destruct.value.typ { match &destruct.value.typ {
roc_can::pattern::DestructType::Optional(field_var, loc_expr) => { roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
@ -5493,14 +5564,6 @@ pub fn from_can_pattern<'a>(
} }
_ => unreachable!("only optional destructs can be optional fields"), _ => unreachable!("only optional destructs can be optional fields"),
} }
opt_sorted = None;
opt_destruct = it2.next();
}
(None, None) => {
break;
}
}
} }
Pattern::RecordDestructure( Pattern::RecordDestructure(

View file

@ -4,6 +4,7 @@ use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
use roc_types::types::RecordField;
use std::collections::HashMap; use std::collections::HashMap;
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
@ -326,7 +327,7 @@ impl<'a> Layout<'a> {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Alias(Symbol::NUM_FLOAT, args, _) => { Alias(Symbol::NUM_F64, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
@ -726,7 +727,7 @@ fn layout_from_flat_type<'a>(
debug_assert_eq!(args.len(), 0); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Symbol::NUM_FLOAT => { Symbol::NUM_F64 => {
debug_assert_eq!(args.len(), 0); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
@ -789,59 +790,30 @@ fn layout_from_flat_type<'a>(
} }
} }
Record(fields, ext_var) => { Record(fields, ext_var) => {
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
sorted_fields.extend(fields.into_iter());
// extract any values from the ext_var // extract any values from the ext_var
let mut fields_map = MutMap::default(); let mut fields_map = MutMap::default();
fields_map.extend(fields);
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) { match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) {
Ok(()) | Err((_, Content::FlexVar(_))) => {} Ok(()) | Err((_, Content::FlexVar(_))) => {}
Err(_) => unreachable!("this would have been a type error"), Err(_) => unreachable!("this would have been a type error"),
} }
sorted_fields.extend(fields_map.into_iter()); let sorted_fields = sort_record_fields_help(env, fields_map);
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
// Determine the layouts of the fields, maintaining sort order // Determine the layouts of the fields, maintaining sort order
let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena); let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena);
for (label, field) in sorted_fields { for (_, _, res_layout) in sorted_fields {
use LayoutProblem::*; match res_layout {
let field_var = {
use roc_types::types::RecordField::*;
match field {
Optional(_) => {
// when an optional field reaches this stage, the field was truly
// optional, and not unified to be demanded or required
// therefore, there is no such field on the record, and we ignore this
// field from now on.
continue;
}
Required(var) => var,
Demanded(var) => var,
}
};
match Layout::from_var(env, field_var) {
Ok(layout) => { Ok(layout) => {
// Drop any zero-sized fields like {}. // Drop any zero-sized fields like {}.
if !layout.is_dropped_because_empty() { if !layout.is_dropped_because_empty() {
layouts.push(layout); layouts.push(layout);
} }
} }
Err(UnresolvedTypeVar(v)) => { Err(_) => {
// Invalid field! // optional field, ignore
panic!( continue;
r"I hit an unresolved type var {:?} when determining the layout of {:?} of record field: {:?} : {:?}",
field_var, v, label, field
);
}
Err(Erroneous) => {
// Invalid field!
panic!("TODO gracefully handle record with invalid field.var");
} }
} }
} }
@ -894,6 +866,15 @@ fn layout_from_flat_type<'a>(
tag_layout.push(Layout::from_var(env, var)?); tag_layout.push(Layout::from_var(env, var)?);
} }
tag_layout.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1)
});
tag_layouts.push(tag_layout.into_bump_slice()); tag_layouts.push(tag_layout.into_bump_slice());
} }
@ -924,24 +905,30 @@ pub fn sort_record_fields<'a>(
}; };
match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) { match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) {
Ok(()) | Err((_, Content::FlexVar(_))) => { Ok(()) | Err((_, Content::FlexVar(_))) => sort_record_fields_help(&mut env, fields_map),
// Sort the fields by label Err(other) => panic!("invalid content in record variable: {:?}", other),
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena); }
}
fn sort_record_fields_help<'a>(
env: &mut Env<'a, '_>,
fields_map: MutMap<Lowercase, RecordField<Variable>>,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), env.arena);
use roc_types::types::RecordField;
for (label, field) in fields_map { for (label, field) in fields_map {
let var = match field { let var = match field {
RecordField::Demanded(v) => v, RecordField::Demanded(v) => v,
RecordField::Required(v) => v, RecordField::Required(v) => v,
RecordField::Optional(v) => { RecordField::Optional(v) => {
let layout = let layout = Layout::from_var(env, v).expect("invalid layout from var");
Layout::from_var(&mut env, v).expect("invalid layout from var");
sorted_fields.push((label, v, Err(layout))); sorted_fields.push((label, v, Err(layout)));
continue; continue;
} }
}; };
let layout = Layout::from_var(&mut env, var).expect("invalid layout from var"); let layout = Layout::from_var(env, var).expect("invalid layout from var");
// Drop any zero-sized fields like {} // Drop any zero-sized fields like {}
if !layout.is_dropped_because_empty() { if !layout.is_dropped_because_empty() {
@ -949,12 +936,22 @@ pub fn sort_record_fields<'a>(
} }
} }
sorted_fields.sort_by(|(label1, _, _), (label2, _, _)| label1.cmp(label2)); sorted_fields.sort_by(
|(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 {
Ok(layout1) | Err(layout1) => match res_layout2 {
Ok(layout2) | Err(layout2) => {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1).then(label1.cmp(label2))
}
},
},
);
sorted_fields sorted_fields
}
Err(other) => panic!("invalid content in record variable: {:?}", other),
}
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
@ -1059,6 +1056,15 @@ pub fn union_sorted_tags_help<'a>(
} }
} }
layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1)
});
if layouts.is_empty() { if layouts.is_empty() {
if contains_zero_sized { if contains_zero_sized {
UnionVariant::UnitWithArguments UnionVariant::UnitWithArguments
@ -1102,6 +1108,15 @@ pub fn union_sorted_tags_help<'a>(
} }
} }
arg_layouts.sort_by(|layout1, layout2| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1)
});
answer.push((tag_name, arg_layouts.into_bump_slice())); answer.push((tag_name, arg_layouts.into_bump_slice()));
} }

View file

@ -18,7 +18,8 @@ mod test_mono {
use roc_mono::layout::Layout; use roc_mono::layout::Layout;
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n"); let mut buffer =
String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
for line in src.lines() { for line in src.lines() {
// indent the body! // indent the body!
@ -189,9 +190,9 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.0 (): procedure Test.0 ():
let Test.8 = 0i64; let Test.9 = 0i64;
let Test.9 = 3i64; let Test.8 = 3i64;
let Test.2 = Just Test.8 Test.9; let Test.2 = Just Test.9 Test.8;
let Test.5 = 0i64; let Test.5 = 0i64;
let Test.6 = Index 0 Test.2; let Test.6 = Index 0 Test.2;
let Test.7 = lowlevel Eq Test.5 Test.6; let Test.7 = lowlevel Eq Test.5 Test.6;
@ -218,10 +219,10 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.0 (): procedure Test.0 ():
let Test.10 = 1i64;
let Test.8 = 1i64; let Test.8 = 1i64;
let Test.9 = 1i64; let Test.9 = 2i64;
let Test.10 = 2i64; let Test.4 = These Test.10 Test.8 Test.9;
let Test.4 = These Test.8 Test.9 Test.10;
switch Test.4: switch Test.4:
case 2: case 2:
let Test.1 = Index 1 Test.4; let Test.1 = Index 1 Test.4;
@ -317,14 +318,14 @@ mod test_mono {
let Test.17 = 0i64; let Test.17 = 0i64;
let Test.13 = lowlevel NotEq #Attr.3 Test.17; let Test.13 = lowlevel NotEq #Attr.3 Test.17;
if Test.13 then if Test.13 then
let Test.15 = 1i64; let Test.16 = 1i64;
let Test.16 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; let Test.15 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
let Test.14 = Ok Test.15 Test.16; let Test.14 = Ok Test.16 Test.15;
ret Test.14; ret Test.14;
else else
let Test.11 = 0i64; let Test.12 = 0i64;
let Test.12 = Struct {}; let Test.11 = Struct {};
let Test.10 = Err Test.11 Test.12; let Test.10 = Err Test.12 Test.11;
ret Test.10; ret Test.10;
procedure Test.0 (): procedure Test.0 ():
@ -388,9 +389,9 @@ mod test_mono {
ret Test.5; ret Test.5;
procedure Test.0 (): procedure Test.0 ():
let Test.10 = 0i64; let Test.11 = 0i64;
let Test.11 = 41i64; let Test.10 = 41i64;
let Test.1 = Just Test.10 Test.11; let Test.1 = Just Test.11 Test.10;
let Test.7 = 0i64; let Test.7 = 0i64;
let Test.8 = Index 0 Test.1; let Test.8 = Index 0 Test.1;
let Test.9 = lowlevel Eq Test.7 Test.8; let Test.9 = lowlevel Eq Test.7 Test.8;
@ -515,11 +516,11 @@ mod test_mono {
ret Test.6; ret Test.6;
procedure Test.0 (): procedure Test.0 ():
let Test.17 = 0i64; let Test.18 = 0i64;
let Test.19 = 0i64; let Test.20 = 0i64;
let Test.20 = 41i64; let Test.19 = 41i64;
let Test.18 = Just Test.19 Test.20; let Test.17 = Just Test.20 Test.19;
let Test.2 = Just Test.17 Test.18; let Test.2 = Just Test.18 Test.17;
joinpoint Test.14: joinpoint Test.14:
let Test.8 = 1i64; let Test.8 = 1i64;
ret Test.8; ret Test.8;
@ -562,8 +563,8 @@ mod test_mono {
ret Test.6; ret Test.6;
procedure Test.0 (): procedure Test.0 ():
let Test.14 = 2i64;
let Test.15 = 3i64; let Test.15 = 3i64;
let Test.14 = 2i64;
let Test.3 = Struct {Test.14, Test.15}; let Test.3 = Struct {Test.14, Test.15};
joinpoint Test.11: joinpoint Test.11:
let Test.1 = Index 0 Test.3; let Test.1 = Index 0 Test.3;
@ -809,9 +810,9 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.1 (Test.4): procedure Test.1 (Test.4):
let Test.18 = 1i64; let Test.19 = 1i64;
let Test.19 = 2i64; let Test.18 = 2i64;
let Test.2 = Ok Test.18 Test.19; let Test.2 = Ok Test.19 Test.18;
joinpoint Test.8 Test.3: joinpoint Test.8 Test.3:
ret Test.3; ret Test.3;
in in
@ -1277,7 +1278,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
swap = \list -> swap = \list ->
when Pair (List.get list 0) (List.get list 0) is when Pair (List.get list 0) (List.get list 0) is
@ -1299,14 +1300,14 @@ mod test_mono {
let Test.38 = lowlevel ListLen #Attr.2; let Test.38 = lowlevel ListLen #Attr.2;
let Test.34 = lowlevel NumLt #Attr.3 Test.38; let Test.34 = lowlevel NumLt #Attr.3 Test.38;
if Test.34 then if Test.34 then
let Test.36 = 1i64; let Test.37 = 1i64;
let Test.37 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; let Test.36 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.35 = Ok Test.36 Test.37; let Test.35 = Ok Test.37 Test.36;
ret Test.35; ret Test.35;
else else
let Test.32 = 0i64; let Test.33 = 0i64;
let Test.33 = Struct {}; let Test.32 = Struct {};
let Test.31 = Err Test.32 Test.33; let Test.31 = Err Test.33 Test.32;
ret Test.31; ret Test.31;
procedure List.4 (#Attr.2, #Attr.3, #Attr.4): procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
@ -1320,9 +1321,9 @@ mod test_mono {
procedure Test.1 (Test.2): procedure Test.1 (Test.2):
let Test.39 = 0i64; let Test.39 = 0i64;
let Test.28 = CallByName List.3 Test.2 Test.39; let Test.29 = CallByName List.3 Test.2 Test.39;
let Test.30 = 0i64; let Test.30 = 0i64;
let Test.29 = CallByName List.3 Test.2 Test.30; let Test.28 = CallByName List.3 Test.2 Test.30;
let Test.7 = Struct {Test.28, Test.29}; let Test.7 = Struct {Test.28, Test.29};
joinpoint Test.25: joinpoint Test.25:
let Test.18 = Array []; let Test.18 = Array [];
@ -1373,7 +1374,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ] partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
@ -1409,7 +1410,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
quicksortHelp : List (Num a), Int, Int -> List (Num a) quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
@ -1618,7 +1619,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
mkPairOf = \x -> Pair x x mkPairOf = \x -> Pair x x
@ -1650,7 +1651,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
fst = \x, _ -> x fst = \x, _ -> x
@ -1687,7 +1688,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
x : List Int x : List Int
x = [1,2,3] x = [1,2,3]
@ -1763,14 +1764,14 @@ mod test_mono {
let Test.15 = lowlevel ListLen #Attr.2; let Test.15 = lowlevel ListLen #Attr.2;
let Test.11 = lowlevel NumLt #Attr.3 Test.15; let Test.11 = lowlevel NumLt #Attr.3 Test.15;
if Test.11 then if Test.11 then
let Test.13 = 1i64; let Test.14 = 1i64;
let Test.14 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; let Test.13 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.12 = Ok Test.13 Test.14; let Test.12 = Ok Test.14 Test.13;
ret Test.12; ret Test.12;
else else
let Test.9 = 0i64; let Test.10 = 0i64;
let Test.10 = Struct {}; let Test.9 = Struct {};
let Test.8 = Err Test.9 Test.10; let Test.8 = Err Test.10 Test.9;
ret Test.8; ret Test.8;
procedure Test.1 (Test.2): procedure Test.1 (Test.2):
@ -1808,14 +1809,14 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.0 (): procedure Test.0 ():
let Test.4 = 0i64; let Test.5 = 0i64;
let Test.6 = 0i64; let Test.7 = 0i64;
let Test.8 = 0i64; let Test.9 = 0i64;
let Test.10 = 1i64; let Test.10 = 1i64;
let Test.9 = Z Test.10; let Test.8 = Z Test.10;
let Test.7 = S Test.8 Test.9; let Test.6 = S Test.9 Test.8;
let Test.5 = S Test.6 Test.7; let Test.4 = S Test.7 Test.6;
let Test.2 = S Test.4 Test.5; let Test.2 = S Test.5 Test.4;
ret Test.2; ret Test.2;
"# "#
), ),
@ -1840,14 +1841,14 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.0 (): procedure Test.0 ():
let Test.8 = 0i64; let Test.9 = 0i64;
let Test.10 = 0i64; let Test.11 = 0i64;
let Test.12 = 0i64; let Test.13 = 0i64;
let Test.14 = 1i64; let Test.14 = 1i64;
let Test.13 = Z Test.14; let Test.12 = Z Test.14;
let Test.11 = S Test.12 Test.13; let Test.10 = S Test.13 Test.12;
let Test.9 = S Test.10 Test.11; let Test.8 = S Test.11 Test.10;
let Test.2 = S Test.8 Test.9; let Test.2 = S Test.9 Test.8;
let Test.5 = 1i64; let Test.5 = 1i64;
let Test.6 = Index 0 Test.2; let Test.6 = Index 0 Test.2;
dec Test.2; dec Test.2;
@ -1882,14 +1883,14 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
procedure Test.0 (): procedure Test.0 ():
let Test.14 = 0i64; let Test.15 = 0i64;
let Test.16 = 0i64; let Test.17 = 0i64;
let Test.18 = 0i64; let Test.19 = 0i64;
let Test.20 = 1i64; let Test.20 = 1i64;
let Test.19 = Z Test.20; let Test.18 = Z Test.20;
let Test.17 = S Test.18 Test.19; let Test.16 = S Test.19 Test.18;
let Test.15 = S Test.16 Test.17; let Test.14 = S Test.17 Test.16;
let Test.2 = S Test.14 Test.15; let Test.2 = S Test.15 Test.14;
let Test.11 = 0i64; let Test.11 = 0i64;
let Test.12 = Index 0 Test.2; let Test.12 = Index 0 Test.2;
let Test.13 = lowlevel Eq Test.11 Test.12; let Test.13 = lowlevel Eq Test.11 Test.12;
@ -1943,14 +1944,14 @@ mod test_mono {
ret Test.13; ret Test.13;
procedure Test.1 (Test.6): procedure Test.1 (Test.6):
let Test.18 = Index 0 Test.6; let Test.18 = Index 1 Test.6;
let Test.19 = false; let Test.19 = false;
let Test.20 = lowlevel Eq Test.19 Test.18; let Test.20 = lowlevel Eq Test.19 Test.18;
if Test.20 then if Test.20 then
let Test.8 = Index 1 Test.6; let Test.8 = Index 0 Test.6;
ret Test.8; ret Test.8;
else else
let Test.10 = Index 1 Test.6; let Test.10 = Index 0 Test.6;
ret Test.10; ret Test.10;
procedure Test.1 (Test.6): procedure Test.1 (Test.6):
@ -1971,12 +1972,12 @@ mod test_mono {
let Test.32 = false; let Test.32 = false;
let Test.26 = Struct {Test.32}; let Test.26 = Struct {Test.32};
let Test.3 = CallByName Test.1 Test.26; let Test.3 = CallByName Test.1 Test.26;
let Test.24 = true; let Test.24 = 11i64;
let Test.25 = 11i64; let Test.25 = true;
let Test.23 = Struct {Test.24, Test.25}; let Test.23 = Struct {Test.24, Test.25};
let Test.4 = CallByName Test.1 Test.23; let Test.4 = CallByName Test.1 Test.23;
let Test.21 = false; let Test.21 = 7i64;
let Test.22 = 7i64; let Test.22 = false;
let Test.15 = Struct {Test.21, Test.22}; let Test.15 = Struct {Test.21, Test.22};
let Test.2 = CallByName Test.1 Test.15; let Test.2 = CallByName Test.1 Test.15;
let Test.14 = CallByName Num.16 Test.2 Test.3; let Test.14 = CallByName Num.16 Test.2 Test.3;
@ -2010,11 +2011,11 @@ mod test_mono {
ret Test.6; ret Test.6;
procedure Test.0 (): procedure Test.0 ():
let Test.17 = 0i64; let Test.18 = 0i64;
let Test.19 = 0i64; let Test.20 = 0i64;
let Test.20 = 41i64; let Test.19 = 41i64;
let Test.18 = Just Test.19 Test.20; let Test.17 = Just Test.20 Test.19;
let Test.2 = Just Test.17 Test.18; let Test.2 = Just Test.18 Test.17;
joinpoint Test.14: joinpoint Test.14:
let Test.8 = 1i64; let Test.8 = 1i64;
ret Test.8; ret Test.8;
@ -2103,7 +2104,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
swap : Int, Int, List a -> List a swap : Int, Int, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
@ -2128,14 +2129,14 @@ mod test_mono {
let Test.40 = lowlevel ListLen #Attr.2; let Test.40 = lowlevel ListLen #Attr.2;
let Test.36 = lowlevel NumLt #Attr.3 Test.40; let Test.36 = lowlevel NumLt #Attr.3 Test.40;
if Test.36 then if Test.36 then
let Test.38 = 1i64; let Test.39 = 1i64;
let Test.39 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; let Test.38 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.37 = Ok Test.38 Test.39; let Test.37 = Ok Test.39 Test.38;
ret Test.37; ret Test.37;
else else
let Test.34 = 0i64; let Test.35 = 0i64;
let Test.35 = Struct {}; let Test.34 = Struct {};
let Test.33 = Err Test.34 Test.35; let Test.33 = Err Test.35 Test.34;
ret Test.33; ret Test.33;
procedure List.4 (#Attr.2, #Attr.3, #Attr.4): procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
@ -2148,8 +2149,8 @@ mod test_mono {
ret #Attr.2; ret #Attr.2;
procedure Test.1 (Test.2, Test.3, Test.4): procedure Test.1 (Test.2, Test.3, Test.4):
let Test.31 = CallByName List.3 Test.4 Test.2;
let Test.32 = CallByName List.3 Test.4 Test.3; let Test.32 = CallByName List.3 Test.4 Test.3;
let Test.31 = CallByName List.3 Test.4 Test.2;
let Test.12 = Struct {Test.31, Test.32}; let Test.12 = Struct {Test.31, Test.32};
joinpoint Test.28: joinpoint Test.28:
let Test.21 = Array []; let Test.21 = Array [];
@ -2260,7 +2261,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
foo = \{} -> foo = \{} ->
x = 42 x = 42
@ -2302,7 +2303,7 @@ mod test_mono {
compiles_to_ir( compiles_to_ir(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
foo = \{} -> foo = \{} ->
x = 41 x = 41

View file

@ -1,7 +1,6 @@
use crate::header::{ModuleName, PackageName}; use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent};
use crate::ident::Ident; use crate::ident::Ident;
use bumpalo::collections::String; use bumpalo::collections::String;
use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_module::operator::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
@ -13,20 +12,6 @@ pub enum Module<'a> {
Platform { header: PlatformHeader<'a> }, Platform { header: PlatformHeader<'a> },
} }
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub name: Loc<ModuleName<'a>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub after_interface_keyword: &'a [CommentOrNewline<'a>],
pub before_exposes: &'a [CommentOrNewline<'a>],
pub after_exposes: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct WhenBranch<'a> { pub struct WhenBranch<'a> {
pub patterns: &'a [Loc<Pattern<'a>>], pub patterns: &'a [Loc<Pattern<'a>>],
@ -34,94 +19,6 @@ pub struct WhenBranch<'a> {
pub guard: Option<Loc<Expr<'a>>>, pub guard: Option<Loc<Expr<'a>>>,
} }
#[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> {
pub name: Loc<ModuleName<'a>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub after_app_keyword: &'a [CommentOrNewline<'a>],
pub before_provides: &'a [CommentOrNewline<'a>],
pub after_provides: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
pub requires: Vec<'a, Loc<TypedIdent<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty.
pub after_platform_keyword: &'a [CommentOrNewline<'a>],
pub before_provides: &'a [CommentOrNewline<'a>],
pub after_provides: &'a [CommentOrNewline<'a>],
pub before_requires: &'a [CommentOrNewline<'a>],
pub after_requires: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)]
pub struct Effects<'a> {
pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
pub type_name: &'a str,
pub entries: Vec<'a, Loc<TypedIdent<'a>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> {
/// e.g.
///
/// printLine : Str -> Effect {}
Entry {
ident: Loc<&'a str>,
spaces_before_colon: &'a [CommentOrNewline<'a>],
ann: Loc<TypeAnnotation<'a>>,
},
// Spaces
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a> {
/// e.g. `Task`
Ident(&'a str),
// Spaces
SpaceBefore(&'a ExposesEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ExposesEntry<'a>, &'a [CommentOrNewline<'a>]),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }`
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a>>>),
// Spaces
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> ExposesEntry<'a> {
pub fn as_str(&'a self) -> &'a str {
use ExposesEntry::*;
match self {
Ident(string) => string,
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => sub_entry.as_str(),
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct WhenPattern<'a> { pub struct WhenPattern<'a> {
pub pattern: Loc<Pattern<'a>>, pub pattern: Loc<Pattern<'a>>,
@ -322,6 +219,7 @@ pub enum TypeAnnotation<'a> {
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`. /// 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 }`. /// This is None if it's a closed record annotation like `{ name: Str }`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>, ext: Option<&'a Loc<TypeAnnotation<'a>>>,
final_comments: &'a [CommentOrNewline<'a>],
}, },
/// A tag union, e.g. `[ /// A tag union, e.g. `[
@ -330,6 +228,7 @@ pub enum TypeAnnotation<'a> {
/// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]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]`. /// This is None if it's a closed tag union like `[ Foo, Bar]`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>, ext: Option<&'a Loc<TypeAnnotation<'a>>>,
final_comments: &'a [CommentOrNewline<'a>],
}, },
/// The `*` type variable, e.g. in (List *) /// The `*` type variable, e.g. in (List *)
@ -631,15 +530,6 @@ impl<'a> Spaceable<'a> for TypeAnnotation<'a> {
} }
} }
impl<'a> Spaceable<'a> for ExposesEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for ImportsEntry<'a> { impl<'a> Spaceable<'a> for ImportsEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ImportsEntry::SpaceBefore(self, spaces) ImportsEntry::SpaceBefore(self, spaces)

View file

@ -95,7 +95,7 @@ where
) )
} }
/// Parses the given expression with 0 or more (spaces/comments/newlines) after it. /// Parses the given expression with 0 or more (spaces/comments/newlines) before it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. /// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found.
pub fn space0_before<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space0_before<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>>
@ -119,7 +119,7 @@ where
) )
} }
/// Parses the given expression with 1 or more (spaces/comments/newlines) after it. /// Parses the given expression with 1 or more (spaces/comments/newlines) before it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. /// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found.
pub fn space1_before<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space1_before<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>>

View file

@ -544,7 +544,7 @@ fn annotation<'a>(
ascii_char(b':'), ascii_char(b':'),
// Spaces after the ':' (at a normal indentation level) and then the type. // Spaces after the ':' (at a normal indentation level) and then the type.
// The type itself must be indented more than the pattern and ':' // The type itself must be indented more than the pattern and ':'
space0_before(type_annotation::located(indented_more), indented_more) space0_before(type_annotation::located(indented_more), min_indent)
) )
) )
} }
@ -614,7 +614,7 @@ fn annotated_body<'a>(min_indent: u16) -> impl Parser<'a, AnnotationOrAnnotatedB
fn annotation_or_alias<'a>( fn annotation_or_alias<'a>(
arena: &'a Bump, arena: &'a Bump,
pattern: &Pattern<'a>, pattern: &Pattern<'a>,
region: Region, pattern_region: Region,
loc_ann: Located<TypeAnnotation<'a>>, loc_ann: Located<TypeAnnotation<'a>>,
) -> Def<'a> { ) -> Def<'a> {
use crate::ast::Pattern::*; use crate::ast::Pattern::*;
@ -625,21 +625,21 @@ fn annotation_or_alias<'a>(
GlobalTag(name) => Def::Alias { GlobalTag(name) => Def::Alias {
name: Located { name: Located {
value: name, value: name,
region, region: pattern_region,
}, },
vars: &[], vars: &[],
ann: loc_ann, ann: loc_ann,
}, },
Apply( Apply(
Located { Located {
region, region: pattern_region,
value: Pattern::GlobalTag(name), value: Pattern::GlobalTag(name),
}, },
loc_vars, loc_vars,
) => Def::Alias { ) => Def::Alias {
name: Located { name: Located {
value: name, value: name,
region: *region, region: *pattern_region,
}, },
vars: loc_vars, vars: loc_vars,
ann: loc_ann, ann: loc_ann,
@ -648,14 +648,14 @@ fn annotation_or_alias<'a>(
Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation") Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation")
} }
SpaceAfter(value, spaces_before) => Def::SpaceAfter( SpaceAfter(value, spaces_before) => Def::SpaceAfter(
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)), arena.alloc(annotation_or_alias(arena, value, pattern_region, loc_ann)),
spaces_before, spaces_before,
), ),
SpaceBefore(value, spaces_before) => Def::SpaceBefore( SpaceBefore(value, spaces_before) => Def::SpaceBefore(
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)), arena.alloc(annotation_or_alias(arena, value, pattern_region, loc_ann)),
spaces_before, spaces_before,
), ),
Nested(value) => annotation_or_alias(arena, value, region, loc_ann), Nested(value) => annotation_or_alias(arena, value, pattern_region, loc_ann),
PrivateTag(_) => { PrivateTag(_) => {
Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.") Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.")
@ -676,7 +676,7 @@ fn annotation_or_alias<'a>(
// This is a regular Annotation // This is a regular Annotation
Def::Annotation( Def::Annotation(
Located { Located {
region, region: pattern_region,
value: Pattern::Identifier(ident), value: Pattern::Identifier(ident),
}, },
loc_ann, loc_ann,
@ -686,7 +686,7 @@ fn annotation_or_alias<'a>(
// This is a record destructure Annotation // This is a record destructure Annotation
Def::Annotation( Def::Annotation(
Located { Located {
region, region: pattern_region,
value: Pattern::RecordDestructure(loc_patterns), value: Pattern::RecordDestructure(loc_patterns),
}, },
loc_ann, loc_ann,
@ -768,6 +768,8 @@ fn parse_def_expr<'a>(
region: loc_first_body.region, region: loc_first_body.region,
} }
}; };
let def_region =
Region::span_across(&loc_first_pattern.region, &loc_first_body.region);
let first_def: Def<'a> = let first_def: Def<'a> =
// TODO is there some way to eliminate this .clone() here? // TODO is there some way to eliminate this .clone() here?
@ -775,7 +777,7 @@ fn parse_def_expr<'a>(
let loc_first_def = Located { let loc_first_def = Located {
value: first_def, value: first_def,
region: loc_first_pattern.region, region: def_region,
}; };
// for formatting reasons, we must insert the first def first! // for formatting reasons, we must insert the first def first!
@ -833,7 +835,7 @@ fn parse_def_signature<'a>(
// It should be indented more than the original, and it will // It should be indented more than the original, and it will
// end when outdented again. // end when outdented again.
and_then_with_indent_level( and_then_with_indent_level(
type_annotation::located(indented_more), space0_before(type_annotation::located(indented_more), min_indent),
// The first annotation may be immediately (spaces_then_comment_or_newline()) // The first annotation may be immediately (spaces_then_comment_or_newline())
// followed by a body at the exact same indent_level // followed by a body at the exact same indent_level
// leading to an AnnotatedBody in this case // leading to an AnnotatedBody in this case
@ -866,15 +868,21 @@ fn parse_def_signature<'a>(
.map( .map(
move |(((loc_first_annotation, opt_body), (mut defs, loc_ret)), state)| { move |(((loc_first_annotation, opt_body), (mut defs, loc_ret)), state)| {
let loc_first_def: Located<Def<'a>> = match opt_body { let loc_first_def: Located<Def<'a>> = match opt_body {
None => Located { None => {
let region = Region::span_across(
&loc_first_pattern.region,
&loc_first_annotation.region,
);
Located {
value: annotation_or_alias( value: annotation_or_alias(
arena, arena,
&loc_first_pattern.value, &loc_first_pattern.value,
loc_first_pattern.region, loc_first_pattern.region,
loc_first_annotation, loc_first_annotation,
), ),
region: loc_first_pattern.region, region,
}, }
}
Some((opt_comment, (body_pattern, body_expr))) => { Some((opt_comment, (body_pattern, body_expr))) => {
let region = let region =
Region::span_across(&loc_first_pattern.region, &body_expr.region); Region::span_across(&loc_first_pattern.region, &body_expr.region);
@ -1722,17 +1730,8 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
}; };
let region = loc_ident.region; let region = loc_ident.region;
let loc_pattern = Located { region, value }; let loc_pattern = Located { region, value };
let (spaces_after_colon, state) = space0(min_indent).parse(arena, state)?;
let (parsed_expr, state) =
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
let answer = if spaces_after_colon.is_empty() { parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)
parsed_expr
} else {
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_colon)
};
Ok((answer, state))
} }
(None, None) => { (None, None) => {
// We got nothin' // We got nothin'
@ -1969,17 +1968,8 @@ fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_colon) Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_colon)
}; };
let loc_pattern = Located { region, value }; let loc_pattern = Located { region, value };
let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
let (parsed_expr, state) =
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
let answer = if spaces_after_equals.is_empty() { parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)
parsed_expr
} else {
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
};
Ok((answer, state))
} }
} }
}, },

View file

@ -1,15 +1,43 @@
use crate::ast::CommentOrNewline; use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::blankspace::space0;
use crate::ident::lowercase_ident;
use crate::module::package_name;
use crate::parser::{ascii_char, optional, Either, Parser};
use crate::string_literal;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_region::all::Loc; use roc_region::all::Loc;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct PackageName<'a> { pub struct PackageName<'a> {
pub account: &'a str, pub account: &'a str,
pub pkg: &'a str, pub pkg: &'a str,
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum Version<'a> {
Exact(&'a str),
Range {
min: &'a str,
min_comparison: VersionComparison,
max: &'a str,
max_comparison: VersionComparison,
},
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum VersionComparison {
AllowsEqual,
DisallowsEqual,
}
#[derive(Clone, PartialEq, Debug)]
pub enum PackageOrPath<'a> {
Package(PackageName<'a>, Version<'a>),
Path(StrLiteral<'a>),
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str); pub struct ModuleName<'a>(&'a str);
impl<'a> Into<&'a str> for ModuleName<'a> { impl<'a> Into<&'a str> for ModuleName<'a> {
@ -34,46 +62,224 @@ impl<'a> ModuleName<'a> {
} }
} }
// TODO is this all duplicated from parse::ast?
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> { pub struct InterfaceHeader<'a> {
pub name: Loc<ModuleName<'a>>, pub name: Loc<ModuleName<'a>>,
pub exposes: Vec<'a, Loc<Exposes<'a>>>, pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Vec<'a, (ModuleName<'a>, Vec<'a, Loc<Imports<'a>>>)>, pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
pub after_interface: &'a [CommentOrNewline<'a>], pub after_interface_keyword: &'a [CommentOrNewline<'a>],
pub before_exposes: &'a [CommentOrNewline<'a>], pub before_exposes: &'a [CommentOrNewline<'a>],
pub after_exposes: &'a [CommentOrNewline<'a>], pub after_exposes: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>], pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>], pub after_imports: &'a [CommentOrNewline<'a>],
} }
#[derive(Clone, Debug, PartialEq)]
pub enum To<'a> {
ExistingPackage(&'a str),
NewPackage(PackageOrPath<'a>),
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> { pub struct AppHeader<'a> {
pub imports: Vec<'a, (ModuleName<'a>, Loc<Imports<'a>>)>, pub name: Loc<StrLiteral<'a>>,
pub packages: Vec<'a, Loc<PackageEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub to: Loc<To<'a>>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
pub after_app_keyword: &'a [CommentOrNewline<'a>],
pub before_packages: &'a [CommentOrNewline<'a>],
pub after_packages: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
pub before_provides: &'a [CommentOrNewline<'a>],
pub after_provides: &'a [CommentOrNewline<'a>],
pub before_to: &'a [CommentOrNewline<'a>],
pub after_to: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackageOrPath<'a>>)>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub after_package_keyword: &'a [CommentOrNewline<'a>],
pub before_exposes: &'a [CommentOrNewline<'a>],
pub after_exposes: &'a [CommentOrNewline<'a>],
pub before_packages: &'a [CommentOrNewline<'a>],
pub after_packages: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>], pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>], pub after_imports: &'a [CommentOrNewline<'a>],
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Exposes<'a> { pub struct PlatformHeader<'a> {
/// e.g. `Task` pub name: Loc<PackageName<'a>>,
Ident(&'a str), pub requires: Vec<'a, Loc<TypedIdent<'a>>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
pub packages: Vec<'a, Loc<PackageEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub effects: Effects<'a>,
// Spaces // Potential comments and newlines - these will typically all be empty.
SpaceBefore(&'a Exposes<'a>, &'a [CommentOrNewline<'a>]), pub after_platform_keyword: &'a [CommentOrNewline<'a>],
SpaceAfter(&'a Exposes<'a>, &'a [CommentOrNewline<'a>]), pub before_requires: &'a [CommentOrNewline<'a>],
pub after_requires: &'a [CommentOrNewline<'a>],
pub before_exposes: &'a [CommentOrNewline<'a>],
pub after_exposes: &'a [CommentOrNewline<'a>],
pub before_packages: &'a [CommentOrNewline<'a>],
pub after_packages: &'a [CommentOrNewline<'a>],
pub before_imports: &'a [CommentOrNewline<'a>],
pub after_imports: &'a [CommentOrNewline<'a>],
pub before_provides: &'a [CommentOrNewline<'a>],
pub after_provides: &'a [CommentOrNewline<'a>],
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Imports<'a> { pub struct Effects<'a> {
/// e.g. `Task` or `Task.{ Task, after }` pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
Ident(&'a str, Vec<'a, &'a str>), pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
pub type_name: &'a str,
pub entries: Vec<'a, Loc<TypedIdent<'a>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a, T> {
/// e.g. `Task`
Exposed(T),
// Spaces // Spaces
SpaceBefore(&'a Imports<'a>, &'a [CommentOrNewline<'a>]), SpaceBefore(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a Imports<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
}
impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceAfter(self, spaces)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }`
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a, &'a str>>>),
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
Package(&'a str, Vec<'a, Loc<&'a ImportsEntry<'a>>>),
// Spaces
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> ExposesEntry<'a, &'a str> {
pub fn as_str(&'a self) -> &'a str {
use ExposesEntry::*;
match self {
Exposed(string) => string,
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => sub_entry.as_str(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> {
/// e.g.
///
/// printLine : Str -> Effect {}
Entry {
ident: Loc<&'a str>,
spaces_before_colon: &'a [CommentOrNewline<'a>],
ann: Loc<TypeAnnotation<'a>>,
},
// Spaces
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
}
#[derive(Clone, Debug, PartialEq)]
pub enum PackageEntry<'a> {
Entry {
shorthand: &'a str,
spaces_after_shorthand: &'a [CommentOrNewline<'a>],
package_or_path: Loc<PackageOrPath<'a>>,
},
// Spaces
SpaceBefore(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> Spaceable<'a> for PackageEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PackageEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PackageEntry::SpaceAfter(self, spaces)
}
}
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>> {
move |arena, state| {
// You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0`
//
// (Indirect dependencies don't have a shorthand.)
let (opt_shorthand, state) = optional(and!(
skip_second!(lowercase_ident(), ascii_char(b':')),
space0(1)
))
.parse(arena, state)?;
let (package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
shorthand,
spaces_after_shorthand,
package_or_path,
},
None => PackageEntry::Entry {
shorthand: "",
spaces_after_shorthand: &[],
package_or_path,
},
};
Ok((entry, state))
}
}
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>> {
map!(
either!(
string_literal::parse(),
and!(
package_name(),
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
)
),
|answer| {
match answer {
Either::First(str_literal) => PackageOrPath::Path(str_literal),
Either::Second((name, version)) => PackageOrPath::Package(name, version),
}
}
)
}
fn package_version<'a>() -> impl Parser<'a, Version<'a>> {
move |_, _| todo!("TODO parse package version")
} }

View file

@ -1,15 +1,17 @@
use crate::ast::{ use crate::ast::{Attempting, CommentOrNewline, Def, Module};
AppHeader, Attempting, CommentOrNewline, Def, Effects, ExposesEntry, ImportsEntry,
InterfaceHeader, Module, PlatformHeader, TypedIdent,
};
use crate::blankspace::{space0, space0_around, space0_before, space1}; use crate::blankspace::{space0, space0_around, space0_before, space1};
use crate::expr::def; use crate::expr::def;
use crate::header::{ModuleName, PackageName}; use crate::header::{
package_entry, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry,
InterfaceHeader, ModuleName, PackageEntry, PackageName, PackageOrPath, PlatformHeader, To,
TypedIdent,
};
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::{ use crate::parser::{
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected, self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected,
unexpected_eof, ParseResult, Parser, State, unexpected_eof, Either, ParseResult, Parser, State,
}; };
use crate::string_literal;
use crate::type_annotation; use crate::type_annotation;
use bumpalo::collections::{String, Vec}; use bumpalo::collections::{String, Vec};
use bumpalo::Bump; use bumpalo::Bump;
@ -44,7 +46,7 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
ascii_string("interface"), ascii_string("interface"),
and!(space1(1), loc!(module_name())) and!(space1(1), loc!(module_name()))
), ),
and!(exposes(), imports()) and!(exposes_values(), imports())
), ),
|( |(
(after_interface_keyword, name), (after_interface_keyword, name),
@ -173,66 +175,114 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
} }
#[inline(always)] #[inline(always)]
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> { pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
parser::map( map_with_arena!(
and!( and!(
skip_first!(ascii_string("app"), and!(space1(1), loc!(module_name()))), skip_first!(
and!(provides(), imports()) ascii_string("app"),
and!(space1(1), loc!(string_literal::parse()))
), ),
|( and!(
(after_app_keyword, name), optional(packages()),
and!(optional(imports()), provides_to())
)
),
|arena, ((after_app_keyword, name), (opt_pkgs, (opt_imports, provides)))| {
let (before_packages, after_packages, package_entries) = match opt_pkgs {
Some(pkgs) => {
let pkgs: Packages<'a> = pkgs; // rustc must be told the type here
( (
((before_provides, after_provides), provides), pkgs.before_packages_keyword,
((before_imports, after_imports), imports), pkgs.after_packages_keyword,
), pkgs.entries,
)| { )
}
None => (&[] as _, &[] as _, Vec::new_in(arena)),
};
// rustc must be told the type here
#[allow(clippy::type_complexity)]
let opt_imports: Option<(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>,
)> = opt_imports;
let ((before_imports, after_imports), imports) =
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena)));
let provides: ProvidesTo<'a> = provides; // rustc must be told the type here
AppHeader { AppHeader {
name, name,
provides, packages: package_entries,
imports, imports,
provides: provides.entries,
to: provides.to,
after_app_keyword, after_app_keyword,
before_provides, before_packages,
after_provides, after_packages,
before_imports, before_imports,
after_imports, after_imports,
before_provides: provides.before_provides_keyword,
after_provides: provides.after_provides_keyword,
before_to: provides.before_to_keyword,
after_to: provides.after_to_keyword,
}
} }
},
) )
} }
#[inline(always)] #[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> { pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
parser::map( parser::map(
and!( and!(
skip_first!( skip_first!(
ascii_string("platform"), ascii_string("platform"),
and!(space1(1), loc!(package_name())) and!(space1(1), loc!(package_name()))
), ),
and!(provides(), and!(requires(), and!(imports(), effects()))) and!(
and!(
and!(requires(), and!(exposes_modules(), packages())),
and!(imports(), provides_without_to())
),
effects()
)
), ),
|( |(
(after_platform_keyword, name), (after_platform_keyword, name),
( (
((before_provides, after_provides), provides), (
( (
((before_requires, after_requires), requires), ((before_requires, after_requires), requires),
(((before_imports, after_imports), imports), effects), (((before_exposes, after_exposes), exposes), packages),
), ),
(
((before_imports, after_imports), imports),
((before_provides, after_provides), provides),
),
),
effects,
), ),
)| { )| {
PlatformHeader { PlatformHeader {
name, name,
provides,
requires, requires,
exposes,
packages: packages.entries,
imports, imports,
provides,
effects, effects,
after_platform_keyword, after_platform_keyword,
before_provides,
after_provides,
before_requires, before_requires,
after_requires, after_requires,
before_exposes,
after_exposes,
before_packages: packages.before_packages_keyword,
after_packages: packages.after_packages_keyword,
before_imports, before_imports,
after_imports, after_imports,
before_provides,
after_provides,
} }
}, },
) )
@ -243,19 +293,80 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
zero_or_more!(space0_around(loc(def(0)), 0)) zero_or_more!(space0_around(loc(def(0)), 0))
} }
struct ProvidesTo<'a> {
entries: Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
to: Located<To<'a>>,
before_provides_keyword: &'a [CommentOrNewline<'a>],
after_provides_keyword: &'a [CommentOrNewline<'a>],
before_to_keyword: &'a [CommentOrNewline<'a>],
after_to_keyword: &'a [CommentOrNewline<'a>],
}
#[inline(always)] #[inline(always)]
fn provides<'a>() -> impl Parser< fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>> {
map!(
and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
and!(
collection!(
ascii_char(b'['),
loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b']'),
1
),
and!(
space1(1),
skip_first!(
ascii_string("to"),
and!(
space1(1),
loc!(either!(lowercase_ident(), package_or_path()))
)
)
)
)
),
|(
(before_provides_keyword, after_provides_keyword),
(entries, (before_to_keyword, (after_to_keyword, loc_to))),
)| {
let loc_to: Located<Either<&'a str, PackageOrPath<'a>>> = loc_to;
let to_val = match loc_to.value {
Either::First(pkg) => To::ExistingPackage(pkg),
Either::Second(pkg) => To::NewPackage(pkg),
};
let to = Located {
value: to_val,
region: loc_to.region,
};
ProvidesTo {
entries,
to,
before_provides_keyword,
after_provides_keyword,
before_to_keyword,
after_to_keyword,
}
}
)
}
#[inline(always)]
fn provides_without_to<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a>>>, Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
), ),
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)), and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
collection!( collection!(
ascii_char(b'['), ascii_char(b'['),
loc!(exposes_entry()), loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','), ascii_char(b','),
ascii_char(b']'), ascii_char(b']'),
1 1
@ -284,18 +395,18 @@ fn requires<'a>() -> impl Parser<
} }
#[inline(always)] #[inline(always)]
fn exposes<'a>() -> impl Parser< fn exposes_values<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a>>>, Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
), ),
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)), and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
collection!( collection!(
ascii_char(b'['), ascii_char(b'['),
loc!(exposes_entry()), loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','), ascii_char(b','),
ascii_char(b']'), ascii_char(b']'),
1 1
@ -303,6 +414,56 @@ fn exposes<'a>() -> impl Parser<
) )
} }
#[inline(always)]
fn exposes_modules<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
),
> {
and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
collection!(
ascii_char(b'['),
loc!(map!(module_name(), ExposesEntry::Exposed)),
ascii_char(b','),
ascii_char(b']'),
1
)
)
}
struct Packages<'a> {
entries: Vec<'a, Located<PackageEntry<'a>>>,
before_packages_keyword: &'a [CommentOrNewline<'a>],
after_packages_keyword: &'a [CommentOrNewline<'a>],
}
#[inline(always)]
fn packages<'a>() -> impl Parser<'a, Packages<'a>> {
map!(
and!(
and!(skip_second!(space1(1), ascii_string("packages")), space1(1)),
collection!(
ascii_char(b'{'),
loc!(package_entry()),
ascii_char(b','),
ascii_char(b'}'),
1
)
),
|((before_packages_keyword, after_packages_keyword), entries)| {
Packages {
entries,
before_packages_keyword,
after_packages_keyword,
}
}
)
}
#[inline(always)] #[inline(always)]
fn imports<'a>() -> impl Parser< fn imports<'a>() -> impl Parser<
'a, 'a,
@ -382,11 +543,6 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
} }
} }
#[inline(always)]
fn exposes_entry<'a>() -> impl Parser<'a, ExposesEntry<'a>> {
map!(unqualified_ident(), ExposesEntry::Ident)
}
#[inline(always)] #[inline(always)]
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> { fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
map_with_arena!( map_with_arena!(
@ -398,7 +554,7 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
ascii_char(b'.'), ascii_char(b'.'),
collection!( collection!(
ascii_char(b'{'), ascii_char(b'{'),
loc!(exposes_entry()), loc!(map!(unqualified_ident(), ExposesEntry::Exposed)),
ascii_char(b','), ascii_char(b','),
ascii_char(b'}'), ascii_char(b'}'),
1 1
@ -408,7 +564,7 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
|arena, |arena,
(module_name, opt_values): ( (module_name, opt_values): (
ModuleName<'a>, ModuleName<'a>,
Option<Vec<'a, Located<ExposesEntry<'a>>>> Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>
)| { )| {
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena)); let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena));

View file

@ -895,7 +895,7 @@ macro_rules! collection_trailing_sep {
$delimiter, $delimiter,
$crate::blankspace::space0_around($elem, $min_indent) $crate::blankspace::space0_around($elem, $min_indent)
), ),
$crate::blankspace::spaces0($min_indent) $crate::blankspace::space0($min_indent)
), ),
$closing_brace $closing_brace
) )
@ -1037,7 +1037,11 @@ macro_rules! one_or_more {
} }
} }
} }
Err((_, new_state)) => Err(unexpected_eof(0, new_state.attempting, new_state)), Err((_, new_state)) => Err($crate::parser::unexpected_eof(
0,
new_state.attempting,
new_state,
)),
} }
} }
}; };
@ -1083,9 +1087,9 @@ macro_rules! either {
let original_attempting = state.attempting; let original_attempting = state.attempting;
match $p1.parse(arena, state) { match $p1.parse(arena, state) {
Ok((output, state)) => Ok((Either::First(output), state)), Ok((output, state)) => Ok(($crate::parser::Either::First(output), state)),
Err((_, state)) => match $p2.parse(arena, state) { Err((_, state)) => match $p2.parse(arena, state) {
Ok((output, state)) => Ok((Either::Second(output), state)), Ok((output, state)) => Ok(($crate::parser::Either::Second(output), state)),
Err((fail, state)) => Err(( Err((fail, state)) => Err((
Fail { Fail {
attempting: original_attempting, attempting: original_attempting,
@ -1157,7 +1161,7 @@ macro_rules! record_field {
#[macro_export] #[macro_export]
macro_rules! record_without_update { macro_rules! record_without_update {
($val_parser:expr, $min_indent:expr) => { ($val_parser:expr, $min_indent:expr) => {
collection!( collection_trailing_sep!(
ascii_char(b'{'), ascii_char(b'{'),
loc!(record_field!($val_parser, $min_indent)), loc!(record_field!($val_parser, $min_indent)),
ascii_char(b','), ascii_char(b','),
@ -1193,14 +1197,6 @@ macro_rules! record {
// We specifically allow space characters inside here, so that // We specifically allow space characters inside here, so that
// `{ }` can be successfully parsed as an empty record, and then // `{ }` can be successfully parsed as an empty record, and then
// changed by the formatter back into `{}`. // changed by the formatter back into `{}`.
//
// We don't allow newlines or comments in the middle of empty
// roc_collections because those are normally stored in an Expr,
// and there's no Expr in which to store them in an empty collection!
//
// We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::ascii_char(b' ')), zero_or_more!($crate::parser::ascii_char(b' ')),
skip_second!( skip_second!(
and!( and!(

View file

@ -12,10 +12,10 @@ pub fn parse_expr_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)]
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> { pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module); let state = State::new(input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state); let answer = header().parse(arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(fail, _)| fail)

View file

@ -21,7 +21,7 @@ macro_rules! tag_union {
($min_indent:expr) => { ($min_indent:expr) => {
map!( map!(
and!( and!(
collection!( collection_trailing_sep!(
ascii_char(b'['), ascii_char(b'['),
loc!(tag_type($min_indent)), loc!(tag_type($min_indent)),
ascii_char(b','), ascii_char(b','),
@ -33,12 +33,13 @@ macro_rules! tag_union {
move |arena, state| allocated(term($min_indent)).parse(arena, state) move |arena, state| allocated(term($min_indent)).parse(arena, state)
) )
), ),
|(tags, ext): ( |((tags, final_comments), ext): (
Vec<'a, Located<Tag<'a>>>, (Vec<'a, Located<Tag<'a>>>, &'a [CommentOrNewline<'a>]),
Option<&'a Located<TypeAnnotation<'a>>>, Option<&'a Located<TypeAnnotation<'a>>>,
)| TypeAnnotation::TagUnion { )| TypeAnnotation::TagUnion {
tags: tags.into_bump_slice(), tags: tags.into_bump_slice(),
ext, ext,
final_comments
} }
) )
}; };
@ -153,7 +154,7 @@ fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>> {
#[inline(always)] #[inline(always)]
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> { fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
use crate::type_annotation::TypeAnnotation::*; use crate::type_annotation::TypeAnnotation::*;
type Fields<'a> = Vec<'a, Located<AssignedField<'a, TypeAnnotation<'a>>>>;
map!( map!(
and!( and!(
record_without_update!( record_without_update!(
@ -165,12 +166,15 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
move |arena, state| allocated(term(min_indent)).parse(arena, state) move |arena, state| allocated(term(min_indent)).parse(arena, state)
) )
), ),
|(fields, ext): ( |((fields, final_comments), ext): (
Vec<'a, Located<AssignedField<'a, TypeAnnotation<'a>>>>, (Fields<'a>, &'a [CommentOrNewline<'a>]),
Option<&'a Located<TypeAnnotation<'a>>>, Option<&'a Located<TypeAnnotation<'a>>>,
)| Record { )| {
Record {
fields: fields.into_bump_slice(), fields: fields.into_bump_slice(),
ext ext,
final_comments,
}
} }
) )
} }
@ -342,7 +346,7 @@ fn parse_concrete_type<'a>(
// //
// If we made it this far and don't have a next_char, then necessarily // If we made it this far and don't have a next_char, then necessarily
// we have consumed a '.' char previously. // we have consumed a '.' char previously.
return malformed(next_char.or_else(|| Some('.')), arena, state, parts); return malformed(next_char.or(Some('.')), arena, state, parts);
} }
if part_buf.is_empty() { if part_buf.is_empty() {

View file

@ -21,13 +21,16 @@ mod test_parse {
use roc_parse::ast::CommentOrNewline::*; use roc_parse::ast::CommentOrNewline::*;
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::Pattern::{self, *}; use roc_parse::ast::Pattern::{self, *};
use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrLiteral::{self, *};
use roc_parse::ast::StrSegment::*; use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{ use roc_parse::ast::{
self, Attempting, Def, EscapedChar, InterfaceHeader, Spaceable, TypeAnnotation, WhenBranch, self, Attempting, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch,
}; };
use roc_parse::header::ModuleName; use roc_parse::header::{
use roc_parse::module::{interface_header, module_defs}; AppHeader, Effects, ExposesEntry, InterfaceHeader, ModuleName, PackageEntry, PackageName,
PackageOrPath, PlatformHeader, To,
};
use roc_parse::module::{app_header, interface_header, module_defs, platform_header};
use roc_parse::parser::{Fail, FailReason, Parser, State}; use roc_parse::parser::{Fail, FailReason, Parser, State};
use roc_parse::test_helpers::parse_expr_with; use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -1486,7 +1489,7 @@ mod test_parse {
arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))),
arena.alloc(Located::new(1, 1, 2, 3, Num("5"))), arena.alloc(Located::new(1, 1, 2, 3, Num("5"))),
); );
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(1, 1, 0, 3, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(3, 3, 0, 2, ret); let loc_ret = Located::new(3, 3, 0, 2, ret);
@ -1516,7 +1519,7 @@ mod test_parse {
arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))),
arena.alloc(Located::new(1, 1, 4, 5, Num("5"))), arena.alloc(Located::new(1, 1, 4, 5, Num("5"))),
); );
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(1, 1, 0, 5, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(3, 3, 0, 2, ret); let loc_ret = Located::new(3, 3, 0, 2, ret);
@ -1547,7 +1550,7 @@ mod test_parse {
arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))),
arena.alloc(Located::new(1, 1, 4, 5, Num("5"))), arena.alloc(Located::new(1, 1, 4, 5, Num("5"))),
); );
let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 1, def1)); let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 5, def1));
let def2 = Def::SpaceBefore( let def2 = Def::SpaceBefore(
&*arena.alloc(Def::Body( &*arena.alloc(Def::Body(
arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))), arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))),
@ -1591,7 +1594,7 @@ mod test_parse {
arena.alloc(Located::new(1, 1, 1, 8, RecordDestructure(&fields))), arena.alloc(Located::new(1, 1, 1, 8, RecordDestructure(&fields))),
arena.alloc(Located::new(1, 1, 11, 12, Num("5"))), arena.alloc(Located::new(1, 1, 11, 12, Num("5"))),
); );
let loc_def1 = &*arena.alloc(Located::new(1, 1, 1, 8, def1)); let loc_def1 = &*arena.alloc(Located::new(1, 1, 1, 12, def1));
let def2 = Def::SpaceBefore( let def2 = Def::SpaceBefore(
&*arena.alloc(Def::Body( &*arena.alloc(Def::Body(
arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))), arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))),
@ -1679,7 +1682,7 @@ mod test_parse {
Located::new(0, 0, 6, 33, as_ann), Located::new(0, 0, 6, 33, as_ann),
); );
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 33, signature));
let defs = &[loc_ann]; let defs = &[loc_ann];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(2, 2, 0, 2, ret); let loc_ret = Located::new(2, 2, 0, 2, ret);
@ -1715,7 +1718,7 @@ mod test_parse {
ann: Located::new(0, 0, 11, 26, applied_alias), ann: Located::new(0, 0, 11, 26, applied_alias),
}; };
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 4, signature)); let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 26, signature));
let defs = &[loc_ann]; let defs = &[loc_ann];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(2, 2, 0, 2, ret); let loc_ret = Located::new(2, 2, 0, 2, ret);
@ -1733,6 +1736,83 @@ mod test_parse {
); );
} }
#[test]
fn multiline_type_signature() {
assert_parses_to(
"f :\n {}\n\n42",
Defs(
&[&Located::new(
0,
1,
0,
6,
Def::Annotation(
Located::new(0, 0, 0, 1, Pattern::Identifier("f")),
Located::new(
1,
1,
4,
6,
TypeAnnotation::SpaceBefore(
&TypeAnnotation::Record {
fields: &[],
ext: None,
final_comments: &[],
},
&[Newline],
),
),
),
)],
&Located::new(
3,
3,
0,
2,
Expr::SpaceBefore(&Expr::Num("42"), &[Newline, Newline]),
),
),
);
}
#[test]
fn multiline_type_signature_with_comment() {
assert_parses_to(
"f :# comment\n {}\n\n42",
Defs(
&[&Located::new(
0,
1,
0,
6,
Def::Annotation(
Located::new(0, 0, 0, 1, Pattern::Identifier("f")),
Located::new(
1,
1,
4,
6,
TypeAnnotation::SpaceBefore(
&TypeAnnotation::Record {
fields: &[],
ext: None,
final_comments: &[],
},
&[LineComment(" comment")],
),
),
),
)],
&Located::new(
3,
3,
0,
2,
Expr::SpaceBefore(&Expr::Num("42"), &[Newline, Newline]),
),
),
);
}
// #[test] // #[test]
// fn type_signature_function_def() { // fn type_signature_function_def() {
// use TypeAnnotation; // use TypeAnnotation;
@ -2200,7 +2280,237 @@ mod test_parse {
// MODULE // MODULE
#[test] #[test]
fn empty_module() { fn empty_app_header() {
let arena = Bump::new();
let packages = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
provides,
to: Located::new(0, 0, 53, 57, To::ExistingPackage("blah")),
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
};
let src = indoc!(
r#"
app "test-app" packages {} imports [] provides [] to blah
"#
);
let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
#[test]
fn minimal_app_header() {
use PackageOrPath::Path;
let arena = Bump::new();
let packages = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
provides,
to: Located::new(0, 0, 30, 38, To::NewPackage(Path(PlainLine("./blah")))),
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
};
let src = indoc!(
r#"
app "test-app" provides [] to "./blah"
"#
);
let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
#[test]
fn full_app_header() {
use ExposesEntry::Exposed;
use PackageOrPath::Path;
let pkg_entry = PackageEntry::Entry {
shorthand: "base",
spaces_after_shorthand: &[],
package_or_path: Located::new(0, 0, 33, 45, Path(PlainLine("./platform"))),
};
let loc_pkg_entry = Located::new(0, 0, 27, 45, pkg_entry);
let arena = Bump::new();
let packages = bumpalo::vec![in &arena; loc_pkg_entry];
let imports = Vec::new_in(&arena);
let provide_entry = Located::new(0, 0, 59, 68, Exposed("quicksort"));
let provides = bumpalo::vec![in &arena; provide_entry];
let module_name = StrLiteral::PlainLine("quicksort");
let expected = AppHeader {
name: Located::new(0, 0, 4, 15, module_name),
packages,
imports,
provides,
to: Located::new(0, 0, 74, 78, To::ExistingPackage("base")),
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
};
let src = indoc!(
r#"
app "quicksort" packages { base: "./platform" } provides [ quicksort ] to base
"#
);
let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
#[test]
fn empty_platform_header() {
let pkg_name = PackageName {
account: "rtfeldman",
pkg: "blah",
};
let arena = Bump::new();
let effects = Effects {
type_name: "Blah",
entries: Vec::new_in(&arena),
spaces_before_effects_keyword: &[],
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
name: Located::new(0, 0, 9, 23, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
packages: Vec::new_in(&arena),
imports: Vec::new_in(&arena),
provides: Vec::new_in(&arena),
effects,
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
};
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects Blah {}";
let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
#[test]
fn nonempty_platform_header() {
use ExposesEntry::Exposed;
use PackageOrPath::Path;
let newlines = &[Newline];
let pkg_name = PackageName {
account: "foo",
pkg: "barbaz",
};
let pkg_entry = PackageEntry::Entry {
shorthand: "foo",
spaces_after_shorthand: &[],
package_or_path: Located::new(3, 3, 20, 27, Path(PlainLine("./foo"))),
};
let loc_pkg_entry = Located::new(3, 3, 15, 27, pkg_entry);
let arena = Bump::new();
let packages = bumpalo::vec![in &arena; loc_pkg_entry];
let imports = Vec::new_in(&arena);
let provide_entry = Located::new(5, 5, 15, 26, Exposed("mainForHost"));
let provides = bumpalo::vec![in &arena; provide_entry];
let effects = Effects {
type_name: "Effect",
entries: Vec::new_in(&arena),
spaces_before_effects_keyword: newlines,
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
name: Located::new(0, 0, 9, 19, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
packages,
imports,
provides,
effects,
after_platform_keyword: &[],
before_requires: newlines,
after_requires: &[],
before_exposes: newlines,
after_exposes: &[],
before_packages: newlines,
after_packages: &[],
before_imports: newlines,
after_imports: &[],
before_provides: newlines,
after_provides: &[],
};
let src = indoc!(
r#"
platform foo/barbaz
requires {}
exposes []
packages { foo: "./foo" }
imports []
provides [ mainForHost ]
effects Effect {}
"#
);
let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
#[test]
fn empty_interface_header() {
let arena = Bump::new(); let arena = Bump::new();
let exposes = Vec::new_in(&arena); let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena); let imports = Vec::new_in(&arena);
@ -2320,7 +2630,7 @@ mod test_parse {
arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))), arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))),
arena.alloc(Located::new(1, 1, 4, 5, Expr::SpaceBefore(num, &[Newline]))), arena.alloc(Located::new(1, 1, 4, 5, Expr::SpaceBefore(num, &[Newline]))),
); );
let loc_def = &*arena.alloc(Located::new(0, 0, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(0, 1, 0, 5, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines);
let loc_ret = Located::new(3, 3, 0, 2, ret); let loc_ret = Located::new(3, 3, 0, 2, ret);
@ -2349,7 +2659,7 @@ mod test_parse {
arena.alloc(Located::new(6, 6, 0, 1, Identifier("x"))), arena.alloc(Located::new(6, 6, 0, 1, Identifier("x"))),
arena.alloc(Located::new(6, 6, 4, 5, Num("5"))), arena.alloc(Located::new(6, 6, 4, 5, Num("5"))),
); );
let loc_def = &*arena.alloc(Located::new(6, 6, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(6, 6, 0, 5, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines);
let loc_ret = Located::new(8, 8, 0, 2, ret); let loc_ret = Located::new(8, 8, 0, 2, ret);
@ -2392,7 +2702,7 @@ mod test_parse {
arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))), arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))),
arena.alloc(Located::new(4, 4, 4, 5, Num("5"))), arena.alloc(Located::new(4, 4, 4, 5, Num("5"))),
); );
let loc_def = &*arena.alloc(Located::new(4, 4, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(4, 4, 0, 5, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines);
let loc_ret = Located::new(6, 6, 0, 2, ret); let loc_ret = Located::new(6, 6, 0, 2, ret);
@ -2431,7 +2741,7 @@ mod test_parse {
arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))), arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))),
arena.alloc(Located::new(4, 4, 4, 5, Num("5"))), arena.alloc(Located::new(4, 4, 4, 5, Num("5"))),
); );
let loc_def = &*arena.alloc(Located::new(4, 4, 0, 1, def)); let loc_def = &*arena.alloc(Located::new(4, 4, 0, 5, def));
let defs = &[loc_def]; let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines);
let loc_ret = Located::new(6, 6, 0, 2, ret); let loc_ret = Located::new(6, 6, 0, 2, ret);

View file

@ -1624,8 +1624,8 @@ fn to_diff<'b>(
_ => false, _ => false,
}; };
let is_float = |t: &ErrorType| match t { let is_float = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_FLOAT, _) => true, ErrorType::Type(Symbol::NUM_F64, _) => true,
ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true, ErrorType::Alias(Symbol::NUM_F64, _, _) => true,
_ => false, _ => false,
}; };

View file

@ -417,6 +417,44 @@ mod test_reporting {
) )
} }
#[test]
fn unused_undefined_argument() {
report_problem_as(
indoc!(
r#"
foo = { x: 1 == 1, y: 0x4 }
baz = 3
main : Str
main =
when foo.y is
4 -> bar baz "yay"
_ -> "nay"
main
"#
),
indoc!(
r#"
SYNTAX PROBLEM
I cannot find a `bar` value
8 4 -> bar baz "yay"
^^^
these names seem close though:
baz
Map
Str
main
"#
),
)
}
#[test] #[test]
fn report_precedence_problem_multiline() { fn report_precedence_problem_multiline() {
report_problem_as( report_problem_as(
@ -1016,7 +1054,7 @@ mod test_reporting {
This `Blue` global tag application has the type: This `Blue` global tag application has the type:
[ Blue Float ]a [ Blue F64 ]a
But `f` needs the 1st argument to be: But `f` needs the 1st argument to be:
@ -1054,7 +1092,7 @@ mod test_reporting {
The 1st branch is a float of type: The 1st branch is a float of type:
Float F64
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
@ -1093,7 +1131,7 @@ mod test_reporting {
This `when`expression produces: This `when`expression produces:
Float F64
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
@ -1129,7 +1167,7 @@ mod test_reporting {
The body is a float of type: The body is a float of type:
Float F64
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
@ -1335,8 +1373,8 @@ mod test_reporting {
Bool Bool
Int Int
F64
Num Num
Map
"# "#
), ),
) )
@ -1463,7 +1501,7 @@ mod test_reporting {
The body is a record of type: The body is a record of type:
{ x : Float } { x : F64 }
But the type annotation says it should be: But the type annotation says it should be:
@ -1606,7 +1644,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x : { a : Int, b : Float, c : Bool } x : { a : Int, b : F64, c : Bool }
x = { b: 4.0 } x = { b: 4.0 }
x x
@ -1618,17 +1656,17 @@ mod test_reporting {
Something is off with the body of the `x` definition: Something is off with the body of the `x` definition:
1 x : { a : Int, b : Float, c : Bool } 1 x : { a : Int, b : F64, c : Bool }
2 x = { b: 4.0 } 2 x = { b: 4.0 }
^^^^^^^^^^ ^^^^^^^^^^
The body is a record of type: The body is a record of type:
{ b : Float } { b : F64 }
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
{ a : Int, b : Float, c : Bool } { a : Int, b : F64, c : Bool }
Tip: Looks like the c and a fields are missing. Tip: Looks like the c and a fields are missing.
"# "#
@ -1791,8 +1829,8 @@ mod test_reporting {
f f
Int Int
F64
Num Num
Map
"# "#
), ),
) )
@ -2081,7 +2119,7 @@ mod test_reporting {
This argument is a float of type: This argument is a float of type:
Float F64
But `add` needs the 2nd argument to be: But `add` needs the 2nd argument to be:
@ -2563,7 +2601,7 @@ mod test_reporting {
This argument is a record of type: This argument is a record of type:
{ y : Float } { y : F64 }
But `f` needs the 1st argument to be: But `f` needs the 1st argument to be:
@ -2797,7 +2835,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
a : { foo : Int, bar : Float, foo : Str } a : { foo : Int, bar : F64, foo : Str }
a = { bar: 3.0, foo: "foo" } a = { bar: 3.0, foo: "foo" }
a a
@ -2809,12 +2847,12 @@ mod test_reporting {
This record type defines the `.foo` field twice! This record type defines the `.foo` field twice!
1 a : { foo : Int, bar : Float, foo : Str } 1 a : { foo : Int, bar : F64, foo : Str }
^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
In the rest of the program, I will only use the latter definition: In the rest of the program, I will only use the latter definition:
1 a : { foo : Int, bar : Float, foo : Str } 1 a : { foo : Int, bar : F64, foo : Str }
^^^^^^^^^ ^^^^^^^^^
For clarity, remove the previous `.foo` definitions from this record For clarity, remove the previous `.foo` definitions from this record
@ -2829,7 +2867,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
a : [ Foo Int, Bar Float, Foo Str ] a : [ Foo Int, Bar F64, Foo Str ]
a = Foo "foo" a = Foo "foo"
a a
@ -2841,12 +2879,12 @@ mod test_reporting {
This tag union type defines the `Foo` tag twice! This tag union type defines the `Foo` tag twice!
1 a : [ Foo Int, Bar Float, Foo Str ] 1 a : [ Foo Int, Bar F64, Foo Str ]
^^^^^^^ ^^^^^^^ ^^^^^^^ ^^^^^^^
In the rest of the program, I will only use the latter definition: In the rest of the program, I will only use the latter definition:
1 a : [ Foo Int, Bar Float, Foo Str ] 1 a : [ Foo Int, Bar F64, Foo Str ]
^^^^^^^ ^^^^^^^
For clarity, remove the previous `Foo` definitions from this tag union For clarity, remove the previous `Foo` definitions from this tag union
@ -2940,7 +2978,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
a : Num Int Float a : Num Int F64
a = 3 a = 3
a a
@ -2952,8 +2990,8 @@ mod test_reporting {
The `Num` alias expects 1 type argument, but it got 2 instead: The `Num` alias expects 1 type argument, but it got 2 instead:
1 a : Num Int Float 1 a : Num Int F64
^^^^^^^^^^^^^ ^^^^^^^^^^^
Are there missing parentheses? Are there missing parentheses?
"# "#
@ -2966,7 +3004,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
f : Bool -> Num Int Float f : Bool -> Num Int F64
f = \_ -> 3 f = \_ -> 3
f f
@ -2978,8 +3016,8 @@ mod test_reporting {
The `Num` alias expects 1 type argument, but it got 2 instead: The `Num` alias expects 1 type argument, but it got 2 instead:
1 f : Bool -> Num Int Float 1 f : Bool -> Num Int F64
^^^^^^^^^^^^^ ^^^^^^^^^^^
Are there missing parentheses? Are there missing parentheses?
"# "#

View file

@ -128,7 +128,8 @@ mod solve_expr {
} }
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n"); let mut buffer =
String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
for line in src.lines() { for line in src.lines() {
// indent the body! // indent the body!
@ -168,7 +169,7 @@ mod solve_expr {
#[test] #[test]
fn float_literal() { fn float_literal() {
infer_eq("0.5", "Float"); infer_eq("0.5", "F64");
} }
#[test] #[test]
@ -195,6 +196,18 @@ mod solve_expr {
); );
} }
#[test]
fn string_starts_with() {
infer_eq_without_problem(
indoc!(
r#"
Str.startsWith
"#
),
"Str, Str -> Bool",
);
}
// #[test] // #[test]
// fn block_string_literal() { // fn block_string_literal() {
// infer_eq( // infer_eq(
@ -749,7 +762,7 @@ mod solve_expr {
(\a -> a) 3.14 (\a -> a) 3.14
"# "#
), ),
"Float", "F64",
); );
} }
@ -881,7 +894,7 @@ mod solve_expr {
// \l r -> l / r // \l r -> l / r
// "# // "#
// ), // ),
// "Float, Float -> Float", // "F64, F64 -> F64",
// ); // );
// } // }
@ -893,7 +906,7 @@ mod solve_expr {
// 1 / 2 // 1 / 2
// "# // "#
// ), // ),
// "Float", // "F64",
// ); // );
// } // }
@ -1013,7 +1026,7 @@ mod solve_expr {
#[test] #[test]
fn two_field_record() { fn two_field_record() {
infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float }"); infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : F64 }");
} }
#[test] #[test]
@ -1401,12 +1414,12 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
float : Float float : F64
float float
"# "#
), ),
"Float", "F64",
); );
} }
@ -1420,7 +1433,7 @@ mod solve_expr {
float float
"# "#
), ),
"Float", "F64",
); );
} }
@ -1429,13 +1442,13 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
float : Num.Float float : Num.F64
float = 5.5 float = 5.5
float float
"# "#
), ),
"Float", "F64",
); );
} }
@ -1444,13 +1457,13 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
float : Float float : F64
float = 5.5 float = 5.5
float float
"# "#
), ),
"Float", "F64",
); );
} }
@ -1465,7 +1478,7 @@ mod solve_expr {
float float
"# "#
), ),
"Float", "F64",
); );
} }
@ -1565,7 +1578,7 @@ mod solve_expr {
float float
"# "#
), ),
"Float", "F64",
); );
} }
@ -1600,7 +1613,7 @@ mod solve_expr {
{ numIdentity, x : numIdentity 42, y } { numIdentity, x : numIdentity 42, y }
"# "#
), ),
"{ numIdentity : Num a -> Num a, x : Num a, y : Float }", "{ numIdentity : Num a -> Num a, x : Num a, y : F64 }",
); );
} }
@ -1778,7 +1791,7 @@ mod solve_expr {
threePointZero threePointZero
"# "#
), ),
"Float", "F64",
); );
} }
@ -2042,7 +2055,7 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Peano : [ S Peano, Z ] Peano : [ S Peano, Z ]
@ -2126,7 +2139,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
@ -2182,7 +2195,7 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
map = map =
\peano -> \peano ->
@ -2510,7 +2523,7 @@ mod solve_expr {
Num.toFloat Num.toFloat
"# "#
), ),
"Num * -> Float", "Num * -> F64",
); );
} }
@ -2522,7 +2535,7 @@ mod solve_expr {
Num.pow Num.pow
"# "#
), ),
"Float, Float -> Float", "F64, F64 -> F64",
); );
} }
@ -2534,7 +2547,7 @@ mod solve_expr {
Num.ceiling Num.ceiling
"# "#
), ),
"Float -> Int", "F64 -> Int",
); );
} }
@ -2546,7 +2559,7 @@ mod solve_expr {
Num.floor Num.floor
"# "#
), ),
"Float -> Int", "F64 -> Int",
); );
} }
@ -2570,7 +2583,7 @@ mod solve_expr {
Num.atan Num.atan
"# "#
), ),
"Float -> Float", "F64 -> F64",
); );
} }
@ -2620,7 +2633,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
boom = \_ -> boom {} boom = \_ -> boom {}
@ -2837,7 +2850,7 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 } negatePoint { x: 1, y: 2.1, z: 0x3 }
"# "#
), ),
"{ x : Num a, y : Float, z : Int }", "{ x : Num a, y : F64, z : Int }",
); );
} }
@ -2854,7 +2867,7 @@ mod solve_expr {
{ a, b } { a, b }
"# "#
), ),
"{ a : { x : Num a, y : Float, z : c }, b : { blah : Str, x : Num a, y : Float, z : c } }", "{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }",
); );
} }
@ -2915,11 +2928,11 @@ mod solve_expr {
} }
#[test] #[test]
fn list_walk_right() { fn list_walk_backwards() {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
List.walkRight List.walkBackwards
"# "#
), ),
"List a, (a, b -> b), b -> b", "List a, (a, b -> b), b -> b",
@ -2927,7 +2940,7 @@ mod solve_expr {
} }
#[test] #[test]
fn list_walk_right_example() { fn list_walk_backwards_example() {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
@ -2935,7 +2948,7 @@ mod solve_expr {
empty = empty =
[] []
List.walkRight empty (\a, b -> a + b) 0 List.walkBackwards empty (\a, b -> a + b) 0
"# "#
), ),
"Int", "Int",
@ -2966,7 +2979,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
main : List x main : List x
@ -2986,7 +2999,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
main = main =
@ -3007,7 +3020,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Bar : [ Bar ] Bar : [ Bar ]
Foo : [ Foo Bar Int, Empty ] Foo : [ Foo Bar Int, Empty ]
@ -3033,7 +3046,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Foo : [ @Foo [ @Bar ] Int, @Empty ] Foo : [ @Foo [ @Bar ] Int, @Empty ]
@ -3058,7 +3071,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
State a : { count : Int, x : a } State a : { count : Int, x : a }
@ -3083,7 +3096,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
# The color of a node. Leaves are considered Black. # The color of a node. Leaves are considered Black.
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -3116,7 +3129,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Dict k : [ Node k (Dict k), Empty ] Dict k : [ Node k (Dict k), Empty ]
@ -3141,7 +3154,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -3371,7 +3384,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Dict k : [ Node k (Dict k) (Dict k), Empty ] Dict k : [ Node k (Dict k) (Dict k), Empty ]
@ -3411,7 +3424,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]
@ -3487,7 +3500,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ partitionHelp ] imports [] app "test" provides [ partitionHelp ] to "./platform"
swap : Int, Int, List a -> List a swap : Int, Int, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
@ -3525,7 +3538,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Dict k : [ Node k (Dict k) (Dict k), Empty ] Dict k : [ Node k (Dict k) (Dict k), Empty ]
@ -3547,7 +3560,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
Dict k : [ Node k (Dict k) (Dict k), Empty ] Dict k : [ Node k (Dict k) (Dict k), Empty ]
@ -3571,7 +3584,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports [] app "test" provides [ main ] to "./platform"
NodeColor : [ Red, Black ] NodeColor : [ Red, Black ]

View file

@ -75,7 +75,7 @@ mod solve_uniq_expr {
#[test] #[test]
fn float_literal() { fn float_literal() {
infer_eq("0.5", "Attr * Float"); infer_eq("0.5", "Attr * F64");
} }
#[test] #[test]
@ -640,7 +640,7 @@ mod solve_uniq_expr {
(\a -> a) 3.14 (\a -> a) 3.14
"# "#
), ),
"Attr * Float", "Attr * F64",
); );
} }
@ -773,7 +773,7 @@ mod solve_uniq_expr {
// \l r -> l / r // \l r -> l / r
// "# // "#
// ), // ),
// "Float, Float -> Float", // "F64, F64 -> F64",
// ); // );
// } // }
@ -785,7 +785,7 @@ mod solve_uniq_expr {
// 1 / 2 // 1 / 2
// "# // "#
// ), // ),
// "Float", // "F64",
// ); // );
// } // }
@ -1211,7 +1211,7 @@ mod solve_uniq_expr {
{ numIdentity, p, q } { numIdentity, p, q }
"# "#
), ),
"Attr * { numIdentity : Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p))), p : Attr * (Num (Attr * p)), q : Attr * Float }" "Attr * { numIdentity : Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p))), p : Attr * (Num (Attr * p)), q : Attr * F64 }"
); );
} }
@ -1831,7 +1831,7 @@ mod solve_uniq_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
Foo : { x : Str, y : Float } Foo : { x : Str, y : F64 }
{ x, y } : Foo { x, y } : Foo
{ x, y } = { x : "foo", y : 3.14 } { x, y } = { x : "foo", y : 3.14 }
@ -1848,7 +1848,7 @@ mod solve_uniq_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
Foo : { x : Str, y : Float } Foo : { x : Str, y : F64 }
Bar : Foo Bar : Foo
@ -2113,7 +2113,7 @@ mod solve_uniq_expr {
Num.maxFloat / Num.maxFloat Num.maxFloat / Num.maxFloat
"# "#
), ),
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
); );
} }
@ -2125,7 +2125,7 @@ mod solve_uniq_expr {
3.0 / 4.0 3.0 / 4.0
"# "#
), ),
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
); );
} }
@ -2137,7 +2137,7 @@ mod solve_uniq_expr {
3.0 / Num.maxFloat 3.0 / Num.maxFloat
"# "#
), ),
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))", "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
); );
} }
@ -2236,11 +2236,11 @@ mod solve_uniq_expr {
} }
#[test] #[test]
fn list_walk_right_sum() { fn list_walk_backwards_sum() {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
sum = \list -> List.walkRight list Num.add 0 sum = \list -> List.walkBackwards list Num.add 0
sum sum
"# "#
@ -2321,11 +2321,11 @@ mod solve_uniq_expr {
} }
#[test] #[test]
fn list_walk_right_reverse() { fn list_walk_backwards_reverse() {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
reverse = \list -> List.walkRight list (\e, l -> List.append l e) [] reverse = \list -> List.walkBackwards list (\e, l -> List.append l e) []
reverse reverse
"# "#
@ -2772,11 +2772,11 @@ mod solve_uniq_expr {
r#" r#"
Model position : { evaluated : Set position Model position : { evaluated : Set position
, openSet : Set position , openSet : Set position
, costs : Map.Map position Float , costs : Map.Map position F64
, cameFrom : Map.Map position position , cameFrom : Map.Map position position
} }
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
cheapestOpen = \costFunction, model -> cheapestOpen = \costFunction, model ->
folder = \position, resSmallestSoFar -> folder = \position, resSmallestSoFar ->
@ -2802,7 +2802,7 @@ mod solve_uniq_expr {
cheapestOpen cheapestOpen
"# "#
), ),
"Attr * (Attr * (Attr Shared position -> Attr * Float), Attr (* | * | a | b) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))" "Attr * (Attr * (Attr Shared position -> Attr * F64), Attr (* | * | a | b) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))"
) )
}); });
} }
@ -2815,7 +2815,7 @@ mod solve_uniq_expr {
r#" r#"
Model position : { evaluated : Set position Model position : { evaluated : Set position
, openSet : Set position , openSet : Set position
, costs : Map.Map position Float , costs : Map.Map position F64
, cameFrom : Map.Map position position , cameFrom : Map.Map position position
} }
@ -2867,7 +2867,7 @@ mod solve_uniq_expr {
r#" r#"
Model position : { evaluated : Set position Model position : { evaluated : Set position
, openSet : Set position , openSet : Set position
, costs : Map.Map position Float , costs : Map.Map position F64
, cameFrom : Map.Map position position , cameFrom : Map.Map position position
} }
@ -2881,7 +2881,7 @@ mod solve_uniq_expr {
} }
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* cheapestOpen : (position -> F64), Model position -> Result position [ KeyNotFound ]*
cheapestOpen = \costFunction, model -> cheapestOpen = \costFunction, model ->
folder = \position, resSmallestSoFar -> folder = \position, resSmallestSoFar ->
@ -2941,12 +2941,12 @@ mod solve_uniq_expr {
model model
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]* findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
findPath = \{ costFunction, moveFunction, start, end } -> findPath = \{ costFunction, moveFunction, start, end } ->
astar costFunction moveFunction end (initialModel start) astar costFunction moveFunction end (initialModel start)
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]* astar : (position, position -> F64), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
astar = \costFn, moveFn, goal, model -> astar = \costFn, moveFn, goal, model ->
when cheapestOpen (\position -> costFn goal position) model is when cheapestOpen (\position -> costFn goal position) model is
Err _ -> Err _ ->
@ -2972,7 +2972,7 @@ mod solve_uniq_expr {
findPath findPath
"# "#
), ),
"Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * Float), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))" "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
) )
}); });
} }
@ -3071,7 +3071,7 @@ mod solve_uniq_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 } negatePoint { x: 1, y: 2.1, z: 0x3 }
"# "#
), ),
"Attr * { x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * Int }", "Attr * { x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * Int }",
); );
} }
@ -3088,7 +3088,7 @@ mod solve_uniq_expr {
{ a, b } { a, b }
"# "#
), ),
"Attr * { a : Attr * { x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * c }, b : Attr * { blah : Attr * Str, x : Attr * (Num (Attr * a)), y : Attr * Float, z : Attr * c } }" "Attr * { a : Attr * { x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * c }, b : Attr * { blah : Attr * Str, x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * c } }"
); );
} }
@ -3133,11 +3133,11 @@ mod solve_uniq_expr {
} }
#[test] #[test]
fn list_walk_right() { fn list_walk_backwards() {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
List.walkRight List.walkBackwards
"# "#
), ),
"Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)", "Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
@ -3145,7 +3145,7 @@ mod solve_uniq_expr {
} }
#[test] #[test]
fn list_walk_right_example() { fn list_walk_backwards_example() {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
@ -3153,7 +3153,7 @@ mod solve_uniq_expr {
empty = empty =
[] []
List.walkRight empty (\a, b -> a + b) 0 List.walkBackwards empty (\a, b -> a + b) 0
"# "#
), ),
"Attr a Int", "Attr a Int",

View file

@ -70,7 +70,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
// Float : Num FloatingPoint // Float : Num FloatingPoint
add_alias( add_alias(
Symbol::NUM_FLOAT, Symbol::NUM_F64,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
@ -143,11 +143,7 @@ fn floatingpoint_alias_content() -> SolvedType {
#[inline(always)] #[inline(always)]
pub fn float_type() -> SolvedType { pub fn float_type() -> SolvedType {
SolvedType::Alias( SolvedType::Alias(Symbol::NUM_F64, Vec::new(), Box::new(float_alias_content()))
Symbol::NUM_FLOAT,
Vec::new(),
Box::new(float_alias_content()),
)
} }
#[inline(always)] #[inline(always)]

View file

@ -331,7 +331,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
match &content { match &content {
Alias(nested, _, _) => match *nested { Alias(nested, _, _) => match *nested {
Symbol::NUM_INTEGER => buf.push_str("Int"), Symbol::NUM_INTEGER => buf.push_str("Int"),
Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"), Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
buf.push_str("Num "); buf.push_str("Num ");
@ -344,7 +344,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
match &attr_content { match &attr_content {
Alias(nested, _, _) => match *nested { Alias(nested, _, _) => match *nested {
Symbol::NUM_INTEGER => buf.push_str("Int"), Symbol::NUM_INTEGER => buf.push_str("Int"),
Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"), Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
buf.push_str("Num "); buf.push_str("Num ");
write_content(env, content, subs, buf, parens); write_content(env, content, subs, buf, parens);
@ -757,7 +757,7 @@ fn write_apply(
buf.push_str("Int"); buf.push_str("Int");
} }
Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => { Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => {
buf.push_str("Float"); buf.push_str("F64");
} }
Symbol::ATTR_ATTR => match nested_args Symbol::ATTR_ATTR => match nested_args
.get(1) .get(1)
@ -771,7 +771,7 @@ fn write_apply(
buf.push_str("Int"); buf.push_str("Int");
} }
Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => { Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => {
buf.push_str("Float"); buf.push_str("F64");
} }
_ => default_case(subs, arg_content), _ => default_case(subs, arg_content),
}, },

View file

@ -1160,7 +1160,7 @@ fn write_error_type_help(
buf.push_str("Int"); buf.push_str("Int");
} }
Type(Symbol::NUM_FLOATINGPOINT, _) => { Type(Symbol::NUM_FLOATINGPOINT, _) => {
buf.push_str("Float"); buf.push_str("F64");
} }
other => { other => {
let write_parens = parens == Parens::InTypeParam; let write_parens = parens == Parens::InTypeParam;
@ -1278,7 +1278,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
buf.push_str("Int"); buf.push_str("Int");
} }
Type(Symbol::NUM_FLOATINGPOINT, _) => { Type(Symbol::NUM_FLOATINGPOINT, _) => {
buf.push_str("Float"); buf.push_str("F64");
} }
other => { other => {
let write_parens = parens == Parens::InTypeParam; let write_parens = parens == Parens::InTypeParam;

View file

@ -30,7 +30,7 @@ pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region)
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let float_type = builtin_type(Symbol::NUM_FLOAT, vec![]); let float_type = builtin_type(Symbol::NUM_F64, vec![]);
let expected_literal = ForReason(reason, float_type, region); let expected_literal = ForReason(reason, float_type, region);
exists( exists(

View file

@ -57,6 +57,7 @@ zerocopy = "0.3"
env_logger = "0.7" env_logger = "0.7"
futures = "0.3" futures = "0.3"
wgpu_glyph = "0.10" wgpu_glyph = "0.10"
bytemuck = "1.4"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -32,9 +32,11 @@ These are potentially inspirational resources for the editor's design.
* [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer) * [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer)
* [Algorithm visualization for javascript](https://algorithm-visualizer.org) * [Algorithm visualization for javascript](https://algorithm-visualizer.org)
* [godbolt.org Compiler Explorer](https://godbolt.org/)
### Structured Editing ### Structured Editing
* [Greenfoot](https://www.youtube.com/watch?v=uUVA7nTh0XY)
* [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others * [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others
* [Fructure: A Structured Editing Engine in Racket](https://youtu.be/CnbVCNIh1NA) by Andrew Blinn * [Fructure: A Structured Editing Engine in Racket](https://youtu.be/CnbVCNIh1NA) by Andrew Blinn
* [Hazel: A Live FP Environment with Typed Holes](https://youtu.be/UkDSL0U9ndQ) by [Cyrus Omar](https://web.eecs.umich.edu/~comar/) * [Hazel: A Live FP Environment with Typed Holes](https://youtu.be/UkDSL0U9ndQ) by [Cyrus Omar](https://web.eecs.umich.edu/~comar/)

View file

@ -11,15 +11,20 @@
// re-enable this when working on performance optimizations than have it block PRs. // re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
use crate::rect::Rect;
use crate::vertex::Vertex;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use wgpu::util::DeviceExt;
use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text}; use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text};
use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
pub mod ast; pub mod ast;
mod rect;
pub mod text_state; pub mod text_state;
mod vertex;
/// The editor is actually launched from the CLI if you pass it zero arguments, /// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch. /// or if you provide it 1 or more files or directories to open on launch.
@ -117,7 +122,7 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
depth_stencil_state: None, depth_stencil_state: None,
vertex_state: wgpu::VertexStateDescriptor { vertex_state: wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint16, index_format: wgpu::IndexFormat::Uint16,
vertex_buffers: &[], vertex_buffers: &[Vertex::buffer_descriptor()],
}, },
sample_count: 1, sample_count: 1,
sample_mask: !0, sample_mask: !0,
@ -216,6 +221,42 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
.expect("Failed to acquire next swap chain texture") .expect("Failed to acquire next swap chain texture")
.output; .output;
// Test Rectangle
let test_rect_1 = Rect {
top: 0.9,
left: -0.8,
width: 0.2,
height: 0.3,
color: [0.0, 1.0, 1.0],
};
let test_rect_2 = Rect {
top: 0.0,
left: 0.0,
width: 0.5,
height: 0.5,
color: [1.0, 1.0, 0.0],
};
let mut rectangles = Vec::new();
rectangles.extend_from_slice(&test_rect_1.as_array());
rectangles.extend_from_slice(&test_rect_2.as_array());
let mut rect_index_buffers = Vec::new();
rect_index_buffers.extend_from_slice(&Rect::INDEX_BUFFER);
rect_index_buffers.extend_from_slice(&Rect::INDEX_BUFFER);
// Vertex Buffer for drawing rectangles
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&rectangles),
usage: wgpu::BufferUsage::VERTEX,
});
// Index Buffer for drawing rectangles
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&rect_index_buffers),
usage: wgpu::BufferUsage::INDEX,
});
// Clear frame // Clear frame
{ {
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@ -236,7 +277,19 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
}); });
render_pass.set_pipeline(&triangle_pipeline); render_pass.set_pipeline(&triangle_pipeline);
render_pass.draw(0..3, 0..1);
render_pass.set_vertex_buffer(
0, // The buffer slot to use for this vertex buffer.
vertex_buffer.slice(..), // Use the entire buffer.
);
render_pass.set_index_buffer(index_buffer.slice(..));
render_pass.draw_indexed(
0..((&rect_index_buffers).len() as u32), // Draw all of the vertices from our test data.
0, // Base Vertex
0..1, // Instances
);
} }
glyph_brush.queue(Section { glyph_brush.queue(Section {

35
editor/src/rect.rs Normal file
View file

@ -0,0 +1,35 @@
use crate::vertex::Vertex;
pub struct Rect {
pub top: f32,
pub left: f32,
pub width: f32,
pub height: f32,
pub color: [f32; 3],
}
impl Rect {
pub fn as_array(&self) -> [Vertex; 4] {
[
Vertex {
position: [self.left, self.top, 0.0],
color: self.color,
},
Vertex {
position: [self.left + self.width, self.top, 0.0],
color: self.color,
},
Vertex {
position: [self.left + self.width, self.top - self.height, 0.0],
color: self.color,
},
Vertex {
position: [self.left, self.top - self.height, 0.0],
color: self.color,
},
]
}
// Currently broken - needs to be offset when additional rectangles are appended
pub const INDEX_BUFFER: [u16; 6] = [0, 1, 3, 1, 2, 3];
}

View file

@ -1,7 +1,11 @@
#version 450 #version 450
layout(location = 0) out vec4 outColor; // The fragment shader's "in" values come from the "out" values of the vertex shader.
layout(location=0) in vec3 color;
// The actual color that is rendered to the screen based on the vertex.
layout(location=0) out vec4 f_color;
void main() { void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(color, 1.0);
} }

View file

@ -1,10 +1,14 @@
#version 450 #version 450
out gl_PerVertex { // Layout value labelled "in" acquire data from the vertex buffer,
vec4 gl_Position; // as defined in the buffer descriptor for this shader.
}; layout(location=0) in vec3 position;
layout(location=1) in vec3 color;
// Layout values labelled "out" send their data to the fragment shader.
layout(location=0) out vec3 v_color;
void main() { void main() {
vec2 position = vec2(gl_VertexIndex, (gl_VertexIndex & 1) * 2) - 1; v_color = color;
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 1.0);
} }

33
editor/src/vertex.rs Normal file
View file

@ -0,0 +1,33 @@
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Vertex {
pub position: [f32; 3],
pub color: [f32; 3],
}
unsafe impl bytemuck::Pod for Vertex {}
unsafe impl bytemuck::Zeroable for Vertex {}
impl Vertex {
// Defines how the shader will use this data structure.
pub fn buffer_descriptor<'a>() -> wgpu::VertexBufferDescriptor<'a> {
wgpu::VertexBufferDescriptor {
stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[
// position
wgpu::VertexAttributeDescriptor {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float3,
},
// color
wgpu::VertexAttributeDescriptor {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float3,
},
],
}
}
}

6
examples/.gitignore vendored
View file

@ -1,5 +1,3 @@
app app
host.o *.o
c_host.o *.dSYM
roc_app.o
app.dSYM

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