Compare commits

..

10 Commits

Author SHA1 Message Date
acbc62a170 Try to get a builder going.
Some checks failed
Matrix / beta (push) Failing after 5s
Matrix / nightly (push) Failing after 0s
Matrix / stable (push) Failing after 0s
Matrix / beta (pull_request) Failing after 2s
Matrix / nightly (pull_request) Failing after 1s
Matrix / stable (pull_request) Failing after 6s
2025-11-24 18:36:29 -08:00
2ef9ae8bdc Stuff and bother. 2025-11-24 18:31:44 -08:00
90c5d6fef8 Tests. 2025-11-11 20:41:58 -08:00
1bc560f684 Almost ... there. 2025-11-11 14:20:28 -08:00
c795172692 All the unimplementeds are gone! 2025-11-11 13:42:19 -08:00
45e49a4c84 This is now tidy for the bits that exst. 2025-11-11 11:07:29 -08:00
05d7284551 Tidy, tidy, tidy. 2025-11-05 21:30:03 -08:00
7bd242a641 Pattern parsing seems working. 2025-10-23 09:26:15 -07:00
9ea6868938 Shifting and naming. 2025-10-11 14:46:02 -07:00
55df27de98 Recovered. 2025-10-11 13:47:41 -07:00
14 changed files with 3588 additions and 1105 deletions

26
.github/workflows/builder.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Matrix
on:
- pull_request
- push
jobs:
main:
strategy:
matrix:
rust:
- stable
- beta
- nightly
name: ${{matrix.rust}}
runs-on: x86_64-linux
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{matrix.rust}}
components: rustfmt, clippy
- run: rustup --version
- run: rustc -vV
- run: cargo clippy -- --deny clippy::pedantic
- run: cargo fmt --all -- --check
- run: cargo test

422
Cargo.lock generated
View File

@@ -2,6 +2,42 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "ariadne"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f5e3dca4e09a6f340a61a0e9c7b61e030c69fc27bf29d73218f7e5e3b7638f"
dependencies = [
"concolor",
"unicode-width 0.1.14",
"yansi",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -12,8 +48,11 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
name = "bang"
version = "0.1.0"
dependencies = [
"codespan",
"codespan-reporting",
"ariadne",
"internment",
"itertools",
"memmap2",
"pretty",
"proptest",
"proptest-derive",
"thiserror",
@@ -36,37 +75,67 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.9.4"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "codespan"
version = "0.12.0"
name = "concolor"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4b418d52c9206820a56fc1aa28db73d67e346ba8ba6aa90987e8d6becef7e4"
checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3"
dependencies = [
"codespan-reporting",
"serde",
"bitflags 1.3.2",
"concolor-query",
"is-terminal",
]
[[package]]
name = "codespan-reporting"
version = "0.12.0"
name = "concolor-query"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
dependencies = [
"serde",
"termcolor",
"unicode-width",
"windows-sys 0.45.0",
]
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
@@ -74,7 +143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -90,28 +159,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.3.3"
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
"wasip2",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "internment"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "636d4b0f6a39fd684effe2a73f5310df16a3fa7954c26d36833e98f44d1977a2"
dependencies = [
"ahash",
"dashmap",
"hashbrown 0.15.5",
"once_cell",
]
[[package]]
name = "is-terminal"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.176"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "linux-raw-sys"
@@ -119,6 +243,24 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "memmap2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
dependencies = [
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -134,6 +276,19 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -144,24 +299,35 @@ dependencies = [
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
name = "pretty"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156"
dependencies = [
"arrayvec",
"termcolor",
"typed-arena",
"unicode-width 0.2.2",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
version = "1.8.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce"
checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
dependencies = [
"bit-set",
"bit-vec",
"bitflags",
"lazy_static",
"bitflags 2.10.0",
"num-traits",
"rand",
"rand_chacha",
@@ -191,9 +357,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.40"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
@@ -243,10 +409,19 @@ dependencies = [
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rustix"
@@ -254,18 +429,18 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rusty-fork"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [
"fnv",
"quick-error",
@@ -274,40 +449,22 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.227"
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245"
dependencies = [
"serde_core",
"serde_derive",
]
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde_core"
version = "1.0.227"
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.227"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.106"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
@@ -324,7 +481,7 @@ dependencies = [
"getrandom",
"once_cell",
"rustix",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -338,24 +495,30 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.16"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "unarray"
version = "0.1.4"
@@ -364,15 +527,27 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.19"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-width"
version = "0.2.1"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wait-timeout"
@@ -383,15 +558,6 @@ dependencies = [
"libc",
]
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
@@ -407,30 +573,102 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "windows-link"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.1"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.8.27"

View File

@@ -4,8 +4,14 @@ version = "0.1.0"
edition = "2024"
[dependencies]
codespan = "0.12.0"
codespan-reporting = "0.12.0"
ariadne = { version = "0.5.1", features = ["auto-color"] }
internment = { version = "0.8.6", features = ["arc", "arena"] }
itertools = "0.14.0"
memmap2 = "0.9.8"
pretty = { version = "0.12.5", features = ["termcolor"] }
proptest = "1.7.0"
proptest-derive = "0.6.0"
thiserror = "2.0.12"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)'] }

View File

@@ -1,227 +1,17 @@
mod arbitrary;
mod ast;
mod error;
mod location;
mod name;
mod parse;
#[cfg(test)]
mod parser_tests;
pub mod tokens;
mod print;
mod tokens;
mod universe;
pub use crate::syntax::error::ParserError;
pub use ast::*;
pub use location::{Located, Location};
pub use name::Name;
use proptest_derive::Arbitrary;
use std::fmt::Debug;
use std::ops::Range;
#[derive(Debug)]
pub struct Module {
definitions: Vec<Definition>,
}
#[derive(Debug)]
pub struct Definition {
location: Location,
export: ExportClass,
type_restrictions: TypeRestrictions,
definition: Def,
}
impl Located for Definition {
fn location(&self) -> Location {
self.location.clone()
}
}
#[derive(Debug)]
pub enum Def {
Enumeration(EnumerationDef),
Structure(StructureDef),
Function(FunctionDef),
Value(ValueDef),
}
impl Located for Def {
fn location(&self) -> Location {
match self {
Def::Enumeration(def) => def.location.clone(),
Def::Structure(def) => def.location.clone(),
Def::Function(def) => def.location.clone(),
Def::Value(def) => def.location.clone(),
}
}
}
#[derive(Debug)]
pub struct EnumerationDef {
name: String,
location: Location,
variants: Vec<EnumerationVariant>,
}
#[derive(Debug)]
pub struct EnumerationVariant {
location: Location,
name: String,
argument: Option<Type>,
}
#[derive(Debug)]
pub struct StructureDef {
name: String,
location: Location,
fields: Vec<StructureField>,
}
#[derive(Debug)]
pub struct StructureField {
location: Location,
export: ExportClass,
name: String,
field_type: Option<Type>,
}
#[derive(Debug)]
pub struct FunctionDef {
name: String,
location: Location,
arguments: Vec<FunctionArg>,
return_type: Option<Type>,
body: Vec<Statement>,
}
#[derive(Debug)]
pub struct FunctionArg {
name: String,
arg_type: Option<Type>,
}
#[derive(Debug)]
pub struct ValueDef {
name: String,
location: Location,
value: Expression,
}
#[derive(Debug)]
pub enum ExportClass {
Public,
Private,
}
#[derive(Debug)]
pub enum Statement {
Binding(BindingStmt),
Expression(Expression),
}
#[derive(Debug)]
pub struct BindingStmt {
location: Location,
mutable: bool,
variable: Name,
value: Expression,
}
#[derive(Debug)]
pub enum Expression {
Value(ConstantValue),
Reference(Name),
EnumerationValue(Name, Name, Option<Box<Expression>>),
StructureValue(Name, Vec<FieldValue>),
Conditional(ConditionalExpr),
Call(Box<Expression>, CallKind, Vec<Expression>),
Block(Location, Vec<Statement>),
}
#[derive(Debug)]
pub struct ConditionalExpr {
location: Location,
test: Box<Expression>,
consequent: Box<Expression>,
alternative: Option<Box<Expression>>,
}
#[derive(Debug)]
pub enum CallKind {
Infix,
Normal,
Postfix,
Prefix,
}
#[derive(Debug)]
pub struct FieldValue {
field: Name,
value: Expression,
}
#[derive(Debug)]
pub struct TypeRestrictions {
restrictions: Vec<TypeRestriction>,
}
impl TypeRestrictions {
fn empty() -> Self {
TypeRestrictions {
restrictions: vec![],
}
}
}
#[derive(Debug)]
pub struct TypeRestriction {
constructor: Type,
arguments: Vec<Type>,
}
#[derive(Debug)]
pub enum Type {
Constructor(Location, String),
Variable(Location, String),
Primitive(Location, String),
Application(Box<Type>, Vec<Type>),
Function(Vec<Type>, Box<Type>),
}
impl Located for Type {
fn location(&self) -> Location {
match self {
Type::Constructor(l, _) => l.clone(),
Type::Variable(l, _) => l.clone(),
Type::Primitive(l, _) => l.clone(),
Type::Application(t1, ts) => {
let mut result = t1.location();
if let Some(last) = ts.last() {
result = result.extend_to(&last.location());
}
result
}
Type::Function(args, ret) => {
if let Some(first) = args.first() {
first.location().extend_to(&ret.location())
} else {
ret.location()
}
}
}
}
}
#[derive(Debug)]
pub enum ConstantValue {
Integer(Location, IntegerWithBase),
Character(Location, char),
String(Location, String),
}
#[derive(Clone, Debug, PartialEq, Eq, Arbitrary)]
pub struct IntegerWithBase {
#[proptest(strategy = "proptest::prop_oneof![ \
proptest::strategy::Just(None), \
proptest::strategy::Just(Some(2)), \
proptest::strategy::Just(Some(8)), \
proptest::strategy::Just(Some(10)), \
proptest::strategy::Just(Some(16)), \
]")]
base: Option<u8>,
value: u64,
}
pub use universe::*;

548
src/syntax/arbitrary.rs Normal file
View File

@@ -0,0 +1,548 @@
use std::fmt::Arguments;
use crate::syntax::ast::{ConstantValue, IntegerWithBase, Type};
use crate::syntax::location::Location;
use crate::syntax::name::Name;
use itertools::Itertools;
use proptest::arbitrary::Arbitrary;
use proptest::prelude::{BoxedStrategy, Rng};
use proptest::prop_oneof;
use proptest::strategy::{NewTree, Strategy, ValueTree};
use proptest::test_runner::TestRunner;
const MAXIMUM_TYPE_DEPTH: usize = 5;
const MAXIMUM_TYPE_WIDTH: usize = 5;
const MAXIMUM_STRING_SIZE: usize = 32;
const PRIMITIVE_TYPES: &[&str] = &[
"Char", "String", "I8", "I16", "I32", "I64", "U8", "U16", "U32", "U64",
];
#[derive(Debug, Default)]
pub struct TypeGenerationContext {
available_constructors: Vec<Name>,
available_variables: Vec<Name>,
}
impl TypeGenerationContext {
fn generate_type(&mut self, runner: &mut TestRunner, depth: usize) -> Type {
let mut leaf_options = vec![];
if !self.available_constructors.is_empty() {
for name in self.available_constructors.iter() {
leaf_options.push(Type::Constructor(
Location::manufactured(),
name.clone(),
));
}
}
if !self.available_variables.is_empty() {
for name in self.available_variables.iter() {
leaf_options.push(Type::Variable(
Location::manufactured(),
name.clone(),
));
}
}
for prim in PRIMITIVE_TYPES.iter() {
leaf_options.push(Type::Primitive(
Location::manufactured(),
Name::new(Location::manufactured(), prim.to_string()),
));
}
if depth < MAXIMUM_TYPE_DEPTH && runner.rng().random_bool(0.5) {
}
let index = runner.rng().random_range(0..leaf_options.len());
leaf_options.remove(index)
}
}
#[derive(Clone)]
pub struct TypeGenerationTree {
current_value: Type,
parent: Option<Box<TypeGenerationTree>>,
untried_simplified_items: Option<Vec<TypeGenerationTree>>,
}
impl TypeGenerationTree {
/// Create a new type generation tree based on the given
/// initial value.
pub fn new(initial_value: Type) -> TypeGenerationTree {
TypeGenerationTree {
current_value: initial_value,
parent: None,
untried_simplified_items: None,
}
}
}
fn generate_powerset(_: &[Type]) -> Vec<Vec<Type>> {
vec![]
}
fn simplify_type(incoming: &Type) -> Vec<Type> {
match incoming {
Type::Primitive(_, _) => vec![],
Type::Constructor(_, _) => vec![],
Type::Variable(_, _) => vec![],
Type::Function(arg_types, ret_type) => {
let simplified_return_types = simplify_type(ret_type.as_ref());
// we do the following as a set of steps, choosing to go deep rather than
// broad immediately. So this works as follows:
//
// 1. If there are simplifications for the return type, then just
// return variations with the simplified return type.
// 2. If there are simplifications for the first argument, then
// just return variations with the first argument simplified.
// 3. Repeat for each of the arguments.
// 4. At this point, all the subtypes are as simple as they can
// be, so return a series of function types with fewer arguments.
// 5. If we are a function with no arguments, then just return
// the return type.
if !simplified_return_types.is_empty() {
return simplified_return_types
.into_iter()
.map(|ret| Type::Function(arg_types.clone(), Box::new(ret)))
.collect();
}
// now check the arguments, and see if we can simplify them in a
// better way.
for idx in 0..arg_types.len() {
let simplified_arguments = simplify_type(&arg_types[idx]);
if simplified_arguments.is_empty() {
continue;
}
let mut new_function_types = vec![];
for simplified_arg in simplified_arguments.into_iter() {
let mut new_args = vec![];
for item in &arg_types[0..idx] {
new_args.push(item.clone());
}
new_args.push(simplified_arg);
for item in &arg_types[idx + 1..arg_types.len()] {
new_args.push(item.clone());
}
new_function_types.push(Type::Function(new_args, ret_type.clone()));
}
if !new_function_types.is_empty() {
return new_function_types;
}
}
// ok, all of the arguments and the return type are already as
// simple as they can be, so let's see if we can reduce the number
// of arguments.
let mut new_types = vec![];
for args in arg_types.iter().powerset() {
if args.len() != arg_types.len() {
new_types.push(Type::Function(
args.into_iter().cloned().collect(),
ret_type.clone(),
));
}
}
if new_types.is_empty() {
vec![ret_type.as_ref().clone()]
} else {
new_types
}
}
Type::Application(constructor_type, arg_types) => {
// much like functions, we're going to try to simplify the constructor,
// then we'll try to simplify the arguments, then we'll try to remove
// arguments.
let simplified_constructor = simplify_type(constructor_type.as_ref());
if !simplified_constructor.is_empty() {
return simplified_constructor
.into_iter()
.map(|c| Type::Application(Box::new(c), arg_types.clone()))
.collect();
}
// now check the arguments, and see if we can simplify them in a
// better way.
for idx in 0..arg_types.len() {
let simplified_arguments = simplify_type(&arg_types[idx]);
if simplified_arguments.is_empty() {
continue;
}
let mut new_appl_types = vec![];
for simplified_arg in simplified_arguments.into_iter() {
let mut new_args = vec![];
for item in &arg_types[0..idx] {
new_args.push(item.clone());
}
new_args.push(simplified_arg);
for item in &arg_types[idx + 1..arg_types.len()] {
new_args.push(item.clone());
}
new_appl_types.push(Type::Application(constructor_type.clone(), new_args));
}
if !new_appl_types.is_empty() {
return new_appl_types;
}
}
// and now we'll try to reduce types.
let mut new_types = vec![];
for args in arg_types.iter().powerset() {
if args.len() != arg_types.len() {
new_types.push(Type::Application(
constructor_type.clone(),
args.into_iter().cloned().collect(),
));
}
}
if new_types.is_empty() {
vec![constructor_type.as_ref().clone()]
} else {
new_types
}
}
}
}
impl ValueTree for TypeGenerationTree {
type Value = Type;
fn current(&self) -> Self::Value {
self.current_value.clone()
}
fn simplify(&mut self) -> bool {
match self.untried_simplified_items.as_mut() {
None => {
let mut simplified = simplify_type(&self.current_value)
.into_iter()
.map(|current_value| TypeGenerationTree {
current_value,
parent: Some(Box::new(self.clone())),
untried_simplified_items: None,
})
.collect::<Vec<_>>();
match simplified.pop() {
None => {
self.untried_simplified_items = Some(simplified);
false
}
Some(next_tree) => {
self.untried_simplified_items = Some(simplified);
*self = next_tree;
true
}
}
}
Some(untried_simplifieds) => match untried_simplifieds.pop() {
None => false,
Some(x) => {
*self = x;
true
}
},
}
}
fn complicate(&mut self) -> bool {
match self.parent.take() {
None => false,
Some(x) => {
*self = *x;
true
}
}
}
}
impl Strategy for TypeGenerationContext {
type Tree = TypeGenerationTree;
type Value = Type;
fn new_tree(&self, _runner: &mut TestRunner) -> NewTree<Self> {
unimplemented!()
}
}
impl Arbitrary for Type {
type Parameters = TypeGenerationContext;
type Strategy = TypeGenerationContext;
fn arbitrary_with(_context: Self::Parameters) -> Self::Strategy {
unimplemented!()
}
}
#[derive(Default)]
pub enum LegalConstantType {
#[default]
Any,
String,
Char,
Number,
}
impl Arbitrary for ConstantValue {
type Parameters = LegalConstantType;
type Strategy = BoxedStrategy<ConstantValue>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
match args {
LegalConstantType::Char => char::arbitrary()
.prop_map(|x| ConstantValue::Character(Location::manufactured(), x))
.boxed(),
LegalConstantType::String => {
proptest::collection::vec(proptest::char::any(), MAXIMUM_STRING_SIZE)
.prop_map(|x| {
ConstantValue::String(Location::manufactured(), String::from_iter(x))
})
.boxed()
}
LegalConstantType::Number => {
let value_strat = u64::arbitrary();
let base_strat = proptest::prop_oneof![
proptest::strategy::Just(None),
proptest::strategy::Just(Some(2)),
proptest::strategy::Just(Some(8)),
proptest::strategy::Just(Some(10)),
proptest::strategy::Just(Some(16)),
];
(value_strat, base_strat)
.prop_map(|(value, base)| {
ConstantValue::Integer(
Location::manufactured(),
IntegerWithBase { base, value },
)
})
.boxed()
}
LegalConstantType::Any => proptest::prop_oneof![
Self::arbitrary_with(LegalConstantType::Char),
Self::arbitrary_with(LegalConstantType::String),
Self::arbitrary_with(LegalConstantType::Number),
]
.boxed(),
}
}
}
#[cfg(test)]
mod simplifiers {
use super::*;
#[test]
fn types() {
let loc = Location::manufactured();
let foo = Name::new(loc.clone(), "Foo");
let primint = Type::Primitive(loc.clone(), Name::new(loc.clone(), "Int"));
let primchar = Type::Primitive(loc.clone(), Name::new(loc.clone(), "Char"));
let primstr = Type::Primitive(loc.clone(), Name::new(loc.clone(), "String"));
assert_eq!(
simplify_type(&Type::Constructor(loc.clone(), foo.clone())),
vec![]
);
assert_eq!(
simplify_type(&Type::Variable(loc.clone(), foo.clone())),
vec![]
);
assert_eq!(
simplify_type(&Type::Primitive(loc.clone(), foo.clone())),
vec![]
);
assert_eq!(
simplify_type(&Type::Function(vec![], Box::new(primint.clone()))),
vec![primint.clone()]
);
assert_eq!(
simplify_type(&Type::Function(
vec![primint.clone(), primchar.clone()],
Box::new(primint.clone())
)),
vec![
Type::Function(vec![], Box::new(primint.clone())),
Type::Function(vec![primint.clone()], Box::new(primint.clone())),
Type::Function(vec![primchar.clone()], Box::new(primint.clone())),
]
);
assert_eq!(
simplify_type(&Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(primint.clone())
)),
vec![
Type::Function(vec![], Box::new(primint.clone())),
Type::Function(vec![primint.clone()], Box::new(primint.clone())),
Type::Function(vec![primchar.clone()], Box::new(primint.clone())),
Type::Function(vec![primstr.clone()], Box::new(primint.clone())),
Type::Function(
vec![primint.clone(), primchar.clone()],
Box::new(primint.clone())
),
Type::Function(
vec![primint.clone(), primstr.clone()],
Box::new(primint.clone())
),
Type::Function(
vec![primchar.clone(), primstr.clone()],
Box::new(primint.clone())
),
]
);
assert_eq!(
simplify_type(&Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(Type::Function(vec![], Box::new(primint.clone()))),
)),
vec![Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(primint.clone())
),]
);
assert_eq!(
simplify_type(&Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(Type::Function(
vec![primint.clone(), primchar.clone()],
Box::new(primint.clone())
)),
)),
vec![
Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(Type::Function(vec![], Box::new(primint.clone())))
),
Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(Type::Function(
vec![primint.clone()],
Box::new(primint.clone())
))
),
Type::Function(
vec![primint.clone(), primchar.clone(), primstr.clone()],
Box::new(Type::Function(
vec![primchar.clone()],
Box::new(primint.clone())
))
),
]
);
assert_eq!(
simplify_type(&Type::Function(
vec![
Type::Function(vec![], Box::new(primint.clone())),
primstr.clone()
],
Box::new(primint.clone())
)),
vec![Type::Function(
vec![primint.clone(), primstr.clone()],
Box::new(primint.clone())
)]
);
assert_eq!(
simplify_type(&Type::Function(
vec![
primint.clone(),
Type::Function(vec![], Box::new(primint.clone()))
],
Box::new(primint.clone())
)),
vec![Type::Function(
vec![primint.clone(), primint.clone()],
Box::new(primint.clone())
)]
);
let applied = Type::Application(Box::new(primint.clone()), vec![]);
assert_eq!(
simplify_type(&Type::Application(Box::new(primint.clone()), vec![])),
vec![primint.clone()]
);
assert_eq!(simplify_type(&applied), vec![primint.clone()]);
assert_eq!(
simplify_type(&Type::Application(
Box::new(applied.clone()),
vec![primint.clone()]
)),
vec![Type::Application(
Box::new(primint.clone()),
vec![primint.clone()]
)]
);
assert_eq!(
simplify_type(&Type::Application(
Box::new(primint.clone()),
vec![applied.clone()]
)),
vec![Type::Application(
Box::new(primint.clone()),
vec![primint.clone()]
)]
);
assert_eq!(
simplify_type(&Type::Application(
Box::new(primint.clone()),
vec![primchar.clone(), applied.clone(), primstr.clone()]
)),
vec![Type::Application(
Box::new(primint.clone()),
vec![primchar.clone(), primint.clone(), primstr.clone()]
)]
);
assert_eq!(
simplify_type(&Type::Application(
Box::new(primint.clone()),
vec![primchar.clone(), primint.clone(), primstr.clone()]
)),
vec![
Type::Application(Box::new(primint.clone()), vec![]),
Type::Application(Box::new(primint.clone()), vec![primchar.clone()]),
Type::Application(Box::new(primint.clone()), vec![primint.clone()]),
Type::Application(Box::new(primint.clone()), vec![primstr.clone()]),
Type::Application(
Box::new(primint.clone()),
vec![primchar.clone(), primint.clone()]
),
Type::Application(
Box::new(primint.clone()),
vec![primchar.clone(), primstr.clone()]
),
Type::Application(
Box::new(primint.clone()),
vec![primint.clone(), primstr.clone()]
)
]
);
}
}

336
src/syntax/ast.rs Normal file
View File

@@ -0,0 +1,336 @@
use crate::syntax::location::{Located, Location};
use crate::syntax::name::Name;
use proptest_derive::Arbitrary;
#[derive(Debug)]
pub struct Module {
pub definitions: Vec<Definition>,
}
#[derive(Debug)]
pub struct Definition {
pub location: Location,
pub export: ExportClass,
pub type_restrictions: TypeRestrictions,
pub definition: Def,
}
impl Located for Definition {
fn location(&self) -> Location {
self.location.clone()
}
}
#[derive(Debug)]
pub enum Def {
Enumeration(EnumerationDef),
Structure(StructureDef),
Function(FunctionDef),
Value(ValueDef),
Operator(OperatorDef),
}
impl Located for Def {
fn location(&self) -> Location {
match self {
Def::Enumeration(def) => def.location.clone(),
Def::Structure(def) => def.location.clone(),
Def::Function(def) => def.location.clone(),
Def::Value(def) => def.location.clone(),
Def::Operator(def) => def.location.clone(),
}
}
}
#[derive(Debug)]
pub struct EnumerationDef {
pub name: Name,
pub location: Location,
pub variants: Vec<EnumerationVariant>,
}
#[derive(Debug)]
pub struct EnumerationVariant {
pub location: Location,
pub name: Name,
pub argument: Option<Type>,
}
#[derive(Debug)]
pub struct StructureDef {
pub name: Name,
pub location: Location,
pub fields: Vec<StructureField>,
}
#[derive(Debug)]
pub struct StructureField {
pub location: Location,
pub export: ExportClass,
pub name: Name,
pub field_type: Option<Type>,
}
#[derive(Debug)]
pub struct FunctionDef {
pub name: Name,
pub location: Location,
pub arguments: Vec<FunctionArg>,
pub return_type: Option<Type>,
pub body: Vec<Statement>,
}
#[derive(Debug)]
pub struct FunctionArg {
pub name: Name,
pub arg_type: Option<Type>,
}
#[derive(Debug)]
pub struct ValueDef {
pub name: Name,
pub location: Location,
pub mtype: Option<Type>,
pub value: Expression,
}
#[derive(Debug)]
pub struct OperatorDef {
pub operator_name: Name,
pub location: Location,
pub function_name: Name,
}
#[derive(Debug)]
pub enum ExportClass {
Public,
Private,
}
#[derive(Debug)]
pub enum Statement {
Binding(BindingStmt),
Expression(Expression),
}
#[derive(Debug)]
pub struct BindingStmt {
pub location: Location,
pub mutable: bool,
pub variable: Name,
pub value: Expression,
}
#[derive(Debug)]
pub enum Expression {
Value(ConstantValue),
Reference(Location, Name),
Enumeration(EnumerationExpr),
Structure(StructureExpr),
Conditional(ConditionalExpr),
Match(MatchExpr),
Call(Box<Expression>, CallKind, Vec<Expression>),
Block(Location, Vec<Statement>),
}
impl Located for Expression {
fn location(&self) -> Location {
match self {
Expression::Value(c) => c.location(),
Expression::Reference(l, _) => l.clone(),
Expression::Enumeration(ev) => ev.location.clone(),
Expression::Structure(sv) => sv.location.clone(),
Expression::Conditional(ce) => ce.location.clone(),
Expression::Match(me) => me.location.clone(),
Expression::Call(_, _, _) => unimplemented!(),
Expression::Block(l, _) => l.clone(),
}
}
}
#[derive(Debug)]
pub struct EnumerationExpr {
pub location: Location,
pub type_name: Name,
pub variant_name: Name,
pub argument: Option<Box<Expression>>,
}
#[derive(Debug)]
pub struct StructureExpr {
pub location: Location,
pub type_name: Name,
pub fields: Vec<FieldValue>,
}
#[derive(Debug)]
pub struct ConditionalExpr {
pub location: Location,
pub test: Box<Expression>,
pub consequent: Box<Expression>,
pub alternative: Option<Box<Expression>>,
}
#[derive(Debug)]
pub struct MatchExpr {
pub location: Location,
pub value: Box<Expression>,
pub cases: Vec<MatchCase>,
}
#[derive(Debug)]
pub struct MatchCase {
pub pattern: Pattern,
pub consequent: Expression,
}
#[derive(Debug)]
pub enum Pattern {
Constant(ConstantValue),
Variable(Name),
EnumerationValue(EnumerationPattern),
Structure(StructurePattern),
}
#[derive(Debug)]
pub struct EnumerationPattern {
pub location: Location,
pub type_name: Name,
pub variant_name: Name,
pub argument: Option<Box<Pattern>>,
}
#[derive(Debug)]
pub struct StructurePattern {
pub location: Location,
pub type_name: Name,
pub fields: Vec<(Name, Option<Pattern>)>,
}
#[derive(Debug)]
pub enum CallKind {
Infix,
Normal,
Postfix,
Prefix,
}
#[derive(Debug)]
pub struct FieldValue {
pub field: Name,
pub value: Expression,
}
#[derive(Debug)]
pub struct TypeRestrictions {
pub restrictions: Vec<TypeRestriction>,
}
impl TypeRestrictions {
pub fn empty() -> Self {
TypeRestrictions {
restrictions: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.restrictions.is_empty()
}
}
#[derive(Debug)]
pub struct TypeRestriction {
pub constructor: Type,
pub arguments: Vec<Type>,
}
#[derive(Clone, Debug)]
pub enum Type {
Constructor(Location, Name),
Variable(Location, Name),
Primitive(Location, Name),
Application(Box<Type>, Vec<Type>),
Function(Vec<Type>, Box<Type>),
}
impl PartialEq for Type {
fn eq(&self, other: &Self) -> bool {
match self {
Type::Constructor(_, x) => matches!(other, Type::Constructor(_, y) if x == y),
Type::Variable(_, x) => matches!(other, Type::Variable(_, y) if x == y),
Type::Primitive(_, x) => matches!(other, Type::Primitive(_, y) if x == y),
Type::Application(con1, args1) => {
matches!(other, Type::Application(con2, args2) if con1 == con2 && args1 == args2)
}
Type::Function(args1, ret1) => {
matches!(other, Type::Function(args2, ret2) if args1 == args2 && ret1 == ret2)
}
}
}
}
impl Located for Type {
fn location(&self) -> Location {
match self {
Type::Constructor(l, _) => l.clone(),
Type::Variable(l, _) => l.clone(),
Type::Primitive(l, _) => l.clone(),
Type::Application(t1, ts) => {
let mut result = t1.location();
if let Some(last) = ts.last() {
result = result.extend_to(&last.location());
}
result
}
Type::Function(args, ret) => {
if let Some(first) = args.first() {
first.location().extend_to(&ret.location())
} else {
ret.location()
}
}
}
}
}
#[derive(Clone, Debug)]
pub enum ConstantValue {
Integer(Location, IntegerWithBase),
Character(Location, char),
String(Location, String),
}
impl Located for ConstantValue {
fn location(&self) -> Location {
match self {
ConstantValue::Integer(l, _) => l.clone(),
ConstantValue::Character(l, _) => l.clone(),
ConstantValue::String(l, _) => l.clone(),
}
}
}
impl PartialEq for ConstantValue {
fn eq(&self, other: &Self) -> bool {
match self {
ConstantValue::Character(_, x) => {
matches!(other, ConstantValue::Character(_, y) if x == y)
}
ConstantValue::String(_, x) => matches!(other, ConstantValue::String(_, y) if x == y),
ConstantValue::Integer(_, x) => matches!(other, ConstantValue::Integer(_, y) if x == y),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Arbitrary)]
pub struct IntegerWithBase {
#[proptest(strategy = "proptest::prop_oneof![ \
proptest::strategy::Just(None), \
proptest::strategy::Just(Some(2)), \
proptest::strategy::Just(Some(8)), \
proptest::strategy::Just(Some(10)), \
proptest::strategy::Just(Some(16)), \
]")]
pub base: Option<u8>,
pub value: u64,
}

View File

@@ -1,22 +1,42 @@
//use codespan_reporting::diagnostic::{Diagnostic, Label};
use crate::syntax::tokens::Token;
use internment::ArcIntern;
use std::ops::Range;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParserError {
#[error("Lexer error at {file_id}: {error}")]
LexerError { file_id: usize, error: LexerError },
#[error("Lexer error at {file}: {error}")]
LexerError {
file: ArcIntern<PathBuf>,
error: LexerError,
},
#[error("Unacceptable end of file at {file_id} while {place}")]
UnacceptableEof { file_id: usize, place: &'static str },
#[error("Unacceptable end of file at {file} while {place}")]
UnacceptableEof {
file: ArcIntern<PathBuf>,
place: String,
},
#[error("Unexpected token at {file_id}: expected {expected}, saw {token}")]
#[error("Unexpected token at {file}: expected {expected}, saw {token}")]
UnexpectedToken {
file_id: usize,
file: ArcIntern<PathBuf>,
span: Range<usize>,
token: Token,
expected: &'static str,
expected: String,
},
#[error("Unexpected problem opening file {file}: {error}")]
OpenError { file: String, error: std::io::Error },
#[error("Unexpected problem reading file {file}: {error}")]
ReadError { file: String, error: std::io::Error },
#[error("UTF-8 problem reading file {file}: {error}")]
Utf8Error {
file: String,
error: std::str::Utf8Error,
},
}

View File

@@ -1,6 +1,8 @@
use codespan_reporting::diagnostic::Label;
use ariadne::Span;
use internment::ArcIntern;
use std::cmp::{max, min};
use std::ops::Range;
use std::path::PathBuf;
pub trait Located {
fn location(&self) -> Location;
@@ -8,19 +10,45 @@ pub trait Located {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Location {
file_id: usize,
file: ArcIntern<PathBuf>,
span: Range<usize>,
}
impl Span for Location {
type SourceId = ArcIntern<PathBuf>;
fn source(&self) -> &Self::SourceId {
&self.file
}
fn start(&self) -> usize {
self.span.start
}
fn end(&self) -> usize {
self.span.end
}
}
impl Location {
pub fn new(file_id: usize, span: Range<usize>) -> Self {
Location { file_id, span }
pub fn new(file: &ArcIntern<PathBuf>, span: Range<usize>) -> Self {
Location {
file: file.clone(),
span,
}
}
pub fn manufactured() -> Self {
Location {
file: ArcIntern::new("<manufactured>".into()),
span: 0..0,
}
}
pub fn extend_to(&self, other: &Location) -> Location {
assert_eq!(self.file_id, other.file_id);
assert_eq!(self.file, other.file);
Location {
file_id: self.file_id,
file: self.file.clone(),
span: min(self.span.start, other.span.start)..max(self.span.end, other.span.end),
}
}
@@ -29,20 +57,23 @@ impl Location {
self.span = min(self.span.start, span.start)..max(self.span.end, span.end);
self
}
pub fn file_id(&self) -> usize {
self.file_id
}
pub fn span(&self) -> Range<usize> {
self.span.clone()
}
#[test]
fn extension_and_merge() {
let file = ArcIntern::new("/foo/bar.txt".into());
let loc1 = Location::new(&file, 1..4);
let loc2 = Location::new(&file, 4..8);
pub fn primary_label(&self) -> Label<usize> {
Label::primary(self.file_id, self.span.clone())
}
assert_eq!(loc1.extend_to(&loc2).source(), &file);
assert_eq!(loc1.extend_to(&loc2).start(), 1);
assert_eq!(loc1.extend_to(&loc2).end(), 8);
pub fn secondary_label(&self) -> Label<usize> {
Label::secondary(self.file_id, self.span.clone())
}
let loc3 = Location::new(&file, 12..16);
assert_eq!(loc1.extend_to(&loc3).source(), &file);
assert_eq!(loc1.extend_to(&loc3).start(), 1);
assert_eq!(loc1.extend_to(&loc3).end(), 16);
assert_eq!(loc1.clone().merge_span(0..1).start(), 0);
assert_eq!(loc1.merge_span(0..1).end(), 4);
}

View File

@@ -1,12 +1,14 @@
use crate::syntax::Location;
#[cfg(test)]
use internment::ArcIntern;
use std::cmp;
use std::fmt;
use std::hash;
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU64, Ordering};
static IDENTIFIER_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Name {
printable: String,
identifier: u64,
@@ -21,8 +23,8 @@ impl cmp::PartialEq for Name {
impl cmp::Eq for Name {}
impl hash::Hash for Name {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
impl Hash for Name {
fn hash<H: Hasher>(&self, state: &mut H) {
self.identifier.hash(state);
}
}
@@ -57,4 +59,98 @@ impl Name {
pub fn as_printed(&self) -> &str {
self.printable.as_str()
}
pub fn bind_to(&mut self, other: &Name) {
self.identifier = other.identifier;
}
pub fn location(&self) -> Option<&Location> {
self.location.as_ref()
}
}
#[test]
fn equality() {
let file = ArcIntern::new("/foo.bang".into());
let loc1 = Location::new(&file, 0..3);
let loc2 = Location::new(&file, 9..12);
assert_ne!(Name::gensym("x"), Name::gensym("x"));
assert_ne!(Name::new(loc1.clone(), "x"), Name::new(loc1.clone(), "x"));
assert_eq!(
Name {
printable: "x".into(),
identifier: 5,
location: Some(loc1.clone())
},
Name {
printable: "x".into(),
identifier: 5,
location: Some(loc2.clone())
}
);
assert_eq!(
Name {
printable: "x".into(),
identifier: 5,
location: Some(loc1.clone())
},
Name {
printable: "x".into(),
identifier: 5,
location: None
}
);
assert_eq!(
Name {
printable: "x".into(),
identifier: 5,
location: Some(loc1.clone())
},
Name {
printable: "y".into(),
identifier: 5,
location: None
}
);
}
#[test]
fn hashing() {
let file = ArcIntern::new("/foo.bang".into());
let loc1 = Location::new(&file, 0..3);
let loc2 = Location::new(&file, 9..12);
let x1 = Name {
printable: "x".into(),
identifier: 1,
location: Some(loc1),
};
let mut x2 = Name {
printable: "x".into(),
identifier: 2,
location: Some(loc2),
};
let y1 = Name {
printable: "y".into(),
identifier: 1,
location: None,
};
let run_hash = |name: &Name| {
let mut hash = std::hash::DefaultHasher::new();
name.hash(&mut hash);
hash.finish()
};
let hash_x1 = run_hash(&x1);
let hash_x2 = run_hash(&x2);
let hash_y1 = run_hash(&y1);
assert_ne!(hash_x1, hash_x2);
assert_eq!(hash_x1, hash_y1);
x2.bind_to(&x1);
let rehashed_x2 = run_hash(&x2);
assert_eq!(hash_x1, rehashed_x2);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

70
src/syntax/print.rs Normal file
View File

@@ -0,0 +1,70 @@
use crate::syntax::ast::{ConstantValue, Type};
#[cfg(test)]
use crate::syntax::parse::Parser;
#[cfg(test)]
use crate::syntax::tokens::Lexer;
use pretty::{DocAllocator, Pretty};
impl<'a, D: ?Sized + DocAllocator<'a, A>, A: 'a> Pretty<'a, D, A> for Type {
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
Type::Constructor(_, n) => allocator.as_string(n),
Type::Variable(_, n) => allocator.as_string(n),
Type::Primitive(_, n) => allocator.text("prim%").append(allocator.as_string(n)),
Type::Application(c, args) => c
.pretty(allocator)
.append(allocator.space())
.append(allocator.intersperse(args, " ")),
Type::Function(args, ret) => allocator
.intersperse(args, " ")
.append(allocator.space())
.append(ret.pretty(allocator)),
}
}
}
impl<'a, D: ?Sized + DocAllocator<'a, A>, A: 'a> Pretty<'a, D, A> for ConstantValue {
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
ConstantValue::String(_, x) => allocator.text(format!("{x:?}")),
ConstantValue::Character(_, c) => allocator.text(format!("{c:?}")),
ConstantValue::Integer(_, iwb) => match iwb.base {
None => allocator.as_string(iwb.value),
Some(2) => allocator.text(format!("0b{:b}", iwb.value)),
Some(8) => allocator.text(format!("0o{:o}", iwb.value)),
Some(10) => allocator.text(format!("0d{}", iwb.value)),
Some(16) => allocator.text(format!("0x{:x}", iwb.value)),
Some(x) => panic!("Illegal base {x} for integer constant."),
},
}
}
}
proptest::proptest! {
#[test]
fn constants(x: ConstantValue) {
let allocator: pretty::Arena = pretty::Arena::new();
let docbuilder = x.clone().pretty(&allocator);
let mut string_version = String::new();
docbuilder.render_fmt(80, &mut string_version).expect("can render to string");
let lexer = Lexer::from(string_version.as_str());
let mut parser = Parser::new("test", lexer);
let roundtripped = parser.parse_constant().expect("can parse constant");
proptest::prop_assert_eq!(x, roundtripped);
}
// #[test]
// fn types(x: Type) {
// let allocator: pretty::Arena = pretty::Arena::new();
// let docbuilder = x.clone().pretty(&allocator);
// let mut string_version = String::new();
// docbuilder.render_fmt(80, &mut string_version).expect("can render to string");
// println!("String version: {string_version:?}");
// let lexer = Lexer::from(string_version.as_str());
// let mut parser = Parser::new("test", lexer);
// let roundtripped = parser.parse_type().expect("can parse constant");
// proptest::prop_assert_eq!(x, roundtripped);
// }
}

View File

@@ -27,6 +27,7 @@ pub enum Token {
CloseBrace,
Semi,
Colon,
DoubleColon,
Comma,
BackTick,
Arrow,
@@ -61,6 +62,7 @@ impl fmt::Display for Token {
Token::CloseBrace => write!(f, "}}"),
Token::Semi => write!(f, ";"),
Token::Colon => write!(f, ":"),
Token::DoubleColon => write!(f, "::"),
Token::Comma => write!(f, ","),
Token::BackTick => write!(f, "`"),
Token::Arrow => write!(f, "->"),
@@ -89,7 +91,7 @@ impl fmt::Display for Token {
pub enum Lexer<'a> {
Working(LexerState<'a>),
Errored(LexerError),
Done(usize),
Done,
}
struct LexerState<'a> {
@@ -99,10 +101,7 @@ struct LexerState<'a> {
impl<'a> From<&'a str> for Lexer<'a> {
fn from(value: &'a str) -> Self {
Lexer::Working(LexerState {
stream: value.char_indices(),
buffer: None,
})
Lexer::new(value)
}
}
@@ -120,7 +119,7 @@ impl<'a> Iterator for Lexer<'a> {
fn next(&mut self) -> Option<Self::Item> {
match self {
Lexer::Done(_) => None,
Lexer::Done => None,
Lexer::Errored(e) => Some(Err(e.clone())),
Lexer::Working(state) => match state.next_token() {
Err(e) => {
@@ -130,7 +129,7 @@ impl<'a> Iterator for Lexer<'a> {
}
Ok(None) => {
*self = Lexer::Done(state.stream.offset());
*self = Lexer::Done;
None
}
@@ -142,8 +141,7 @@ impl<'a> Iterator for Lexer<'a> {
impl<'a> LexerState<'a> {
fn next_char(&mut self) -> Option<(usize, char)> {
let result = self.buffer.take().or_else(|| self.stream.next());
result
self.buffer.take().or_else(|| self.stream.next())
}
fn stash_char(&mut self, idx: usize, c: char) {
@@ -172,7 +170,6 @@ impl<'a> LexerState<'a> {
'{' => return simple_response(Token::OpenBrace),
'}' => return simple_response(Token::CloseBrace),
';' => return simple_response(Token::Semi),
':' => return simple_response(Token::Colon),
',' => return simple_response(Token::Comma),
'`' => return simple_response(Token::BackTick),
'\\' => return simple_response(Token::Lambda(false)),
@@ -182,6 +179,7 @@ impl<'a> LexerState<'a> {
'\'' => return self.starts_with_single(token_start_offset),
'\"' => return self.starts_with_double(token_start_offset),
'-' => return self.starts_with_dash(token_start_offset),
':' => return self.starts_with_colon(token_start_offset),
_ => {}
}
@@ -413,12 +411,18 @@ impl<'a> LexerState<'a> {
});
}
let mut value = 0;
let mut value: u32 = 0;
while let Some((idx, char)) = self.next_char() {
if let Some(digit) = char.to_digit(16) {
value = (value * 16) + digit;
if let Some(shifted) = value.checked_shl(4) {
value = shifted + digit;
continue;
} else {
return Err(LexerError::InvalidUnicode {
span: token_start_offset..idx,
});
}
}
if char == '}' {
@@ -519,6 +523,31 @@ impl<'a> LexerState<'a> {
}
}
}
fn starts_with_colon(
&mut self,
token_start_offset: usize,
) -> Result<Option<LocatedToken>, LexerError> {
match self.next_char() {
None => Ok(Some(LocatedToken {
token: Token::Colon,
span: token_start_offset..token_start_offset + 1,
})),
Some((pos, ':')) => Ok(Some(LocatedToken {
token: Token::DoubleColon,
span: token_start_offset..pos,
})),
Some((pos, char)) => {
self.stash_char(pos, char);
Ok(Some(LocatedToken {
token: Token::Colon,
span: token_start_offset..token_start_offset + 1,
}))
}
}
}
}
proptest::proptest! {
@@ -542,7 +571,7 @@ fn parsed_single_token(s: &str) -> Token {
let mut tokens = Lexer::from(s);
let result = tokens
.next()
.expect(format!("Can get at least one token from {s:?}").as_str())
.unwrap_or_else(|| panic!("Can get at least one token from {s:?}"))
.expect("Can get a valid token.")
.token;
@@ -613,6 +642,13 @@ fn numbers_work_as_expected() {
}),
parsed_single_token("0o10")
);
assert_eq!(
Token::Integer(IntegerWithBase {
base: None,
value: 10
}),
parsed_single_token("0010")
);
assert_eq!(
Token::Integer(IntegerWithBase {
base: Some(10),
@@ -652,6 +688,20 @@ fn values_work_as_expected() {
assert_eq!(Token::ValueName("ɑ".into()), parsed_single_token("ɑ"));
}
#[test]
fn primitives() {
assert_eq!(
Token::PrimitiveValueName("add_u8".into()),
parsed_single_token("prim%add_u8"),
);
assert_eq!(
Token::PrimitiveTypeName("U8".into()),
parsed_single_token("prim%U8"),
);
assert!(Lexer::from("prim%").next().unwrap().is_err());
assert!(Lexer::from("prim%%").next().unwrap().is_err());
}
#[test]
fn operators_work_as_expected() {
assert_eq!(Token::OperatorName("-".into()), parsed_single_token("-"));
@@ -707,3 +757,126 @@ fn arrow_requires_nonop() {
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
assert_eq!(Some(Token::Arrow), next_token());
}
#[test]
fn unicode() {
let mut lexer = Lexer::from("'\\u{00BE}'");
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
assert_eq!(Some(Token::Character('¾')), next_token());
let mut lexer = Lexer::from("'\\u{11111111111111111111111111111}'");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'\\u{00BE'");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'\\u00BE}'");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'\\u");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'\\u{00Z}'");
assert!(lexer.next().unwrap().is_err());
}
#[test]
fn character_string_errors() {
let mut lexer = Lexer::from("'");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'-\\");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("''");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'ab'");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'\\x'");
assert!(lexer.next().unwrap().is_err());
let mut lexer = Lexer::from("'a'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character('a'),
..
}))
));
let mut lexer = Lexer::from("'\\0'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character('\0'),
..
}))
));
let mut lexer = Lexer::from("'\\a'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\b'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\f'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\n'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\r'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\t'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\v'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character(_),
..
}))
));
let mut lexer = Lexer::from("'\\''");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character('\''),
..
}))
));
let mut lexer = Lexer::from("'\\\\'");
assert!(matches!(
lexer.next(),
Some(Ok(LocatedToken {
token: Token::Character('\\'),
..
}))
));
let mut lexer = Lexer::from("\"foo");
assert!(lexer.next().unwrap().is_err());
}

44
src/syntax/universe.rs Normal file
View File

@@ -0,0 +1,44 @@
use crate::syntax::ast::*;
use crate::syntax::error::ParserError;
use crate::syntax::parse::Parser;
use crate::syntax::tokens::Lexer;
use memmap2::Mmap;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Default)]
pub struct Universe {
pub files: HashMap<PathBuf, Mmap>,
pub modules: HashMap<PathBuf, Module>,
}
impl Universe {
/// Add a file to this universe.
///
/// This may result in other files being loaded on behalf of the file, if
/// (for example) the given file has imports.
pub fn add_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ParserError> {
let filename = file.as_ref().to_string_lossy().into_owned();
let file_handle = std::fs::File::open(&file).map_err(|e| ParserError::OpenError {
file: filename.clone(),
error: e,
})?;
let contents = unsafe { Mmap::map(&file_handle) }.map_err(|e| ParserError::ReadError {
file: filename.clone(),
error: e,
})?;
let string_contents =
std::str::from_utf8(&contents).map_err(|e| ParserError::Utf8Error {
file: filename.clone(),
error: e,
})?;
let lexer = Lexer::from(string_contents);
let mut parser = Parser::new(&file, lexer);
let module = parser.parse_module()?;
self.modules.insert(file.as_ref().to_path_buf(), module);
Ok(())
}
}