Compare commits
22 Commits
master
...
acbc62a170
| Author | SHA1 | Date | |
|---|---|---|---|
| acbc62a170 | |||
| 2ef9ae8bdc | |||
| 90c5d6fef8 | |||
| 1bc560f684 | |||
| c795172692 | |||
| 45e49a4c84 | |||
| 05d7284551 | |||
| 7bd242a641 | |||
| 9ea6868938 | |||
| 55df27de98 | |||
| f6bf3dd639 | |||
| c31be288ad | |||
| 4362d82034 | |||
| e9fb4fcd0f | |||
| 24e6bf6318 | |||
| 8657c009c8 | |||
| e250a49703 | |||
| 1baeae1bf0 | |||
| 129bf3c204 | |||
| 768b27a8f6 | |||
| 8e6ac7ecbd | |||
| a663d8f1fb |
26
.github/workflows/builder.yml
vendored
Normal file
26
.github/workflows/builder.yml
vendored
Normal 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
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -6,3 +6,9 @@
|
|||||||
hsrc/Syntax/Lexer.hs
|
hsrc/Syntax/Lexer.hs
|
||||||
hsrc/Syntax/Parser.hs
|
hsrc/Syntax/Parser.hs
|
||||||
bang
|
bang
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
/proptest-regressions
|
||||||
|
/target
|
||||||
|
.aider*
|
||||||
|
|||||||
690
Cargo.lock
generated
Normal file
690
Cargo.lock
generated
Normal file
@@ -0,0 +1,690 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# 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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bang"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ariadne",
|
||||||
|
"internment",
|
||||||
|
"itertools",
|
||||||
|
"memmap2",
|
||||||
|
"pretty",
|
||||||
|
"proptest",
|
||||||
|
"proptest-derive",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concolor"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"concolor-query",
|
||||||
|
"is-terminal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concolor-query"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
|
||||||
|
dependencies = [
|
||||||
|
"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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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.177"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
|
||||||
|
dependencies = [
|
||||||
|
"bit-set",
|
||||||
|
"bit-vec",
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_xorshift",
|
||||||
|
"regex-syntax",
|
||||||
|
"rusty-fork",
|
||||||
|
"tempfile",
|
||||||
|
"unarray",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proptest-derive"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_xorshift"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusty-fork"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"quick-error",
|
||||||
|
"tempfile",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.110"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.1+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "bang"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
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)'] }
|
||||||
1
src/bin/bangc.rs
Normal file
1
src/bin/bangc.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod syntax;
|
||||||
17
src/syntax.rs
Normal file
17
src/syntax.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
mod arbitrary;
|
||||||
|
mod ast;
|
||||||
|
mod error;
|
||||||
|
mod location;
|
||||||
|
mod name;
|
||||||
|
mod parse;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod parser_tests;
|
||||||
|
mod print;
|
||||||
|
mod tokens;
|
||||||
|
mod universe;
|
||||||
|
|
||||||
|
pub use crate::syntax::error::ParserError;
|
||||||
|
pub use ast::*;
|
||||||
|
pub use location::{Located, Location};
|
||||||
|
pub use name::Name;
|
||||||
|
pub use universe::*;
|
||||||
548
src/syntax/arbitrary.rs
Normal file
548
src/syntax/arbitrary.rs
Normal 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
336
src/syntax/ast.rs
Normal 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,
|
||||||
|
}
|
||||||
147
src/syntax/error.rs
Normal file
147
src/syntax/error.rs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
//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}: {error}")]
|
||||||
|
LexerError {
|
||||||
|
file: ArcIntern<PathBuf>,
|
||||||
|
error: LexerError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Unacceptable end of file at {file} while {place}")]
|
||||||
|
UnacceptableEof {
|
||||||
|
file: ArcIntern<PathBuf>,
|
||||||
|
place: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Unexpected token at {file}: expected {expected}, saw {token}")]
|
||||||
|
UnexpectedToken {
|
||||||
|
file: ArcIntern<PathBuf>,
|
||||||
|
span: Range<usize>,
|
||||||
|
token: Token,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
|
pub enum LexerError {
|
||||||
|
#[error("Illegal control character in input stream at offset {offset}")]
|
||||||
|
IllegalControlCharacter { offset: usize },
|
||||||
|
|
||||||
|
#[error("Illegal primitive value/type; it cut off before we could determine which at {span:?}")]
|
||||||
|
IllegalPrimitive { span: Range<usize> },
|
||||||
|
|
||||||
|
#[error("Illegal character in primitive ({char:?}) at {span:?}")]
|
||||||
|
IllegalPrimitiveCharacter { span: Range<usize>, char: char },
|
||||||
|
|
||||||
|
#[error("Unfinished character constant found at {span:?}")]
|
||||||
|
UnfinishedCharacter { span: Range<usize> },
|
||||||
|
|
||||||
|
#[error("Unfinished string constant found at {span:?}")]
|
||||||
|
UnfinishedString { span: Range<usize> },
|
||||||
|
|
||||||
|
#[error("Character {char:?} has some extra bits at the end at {span:?}")]
|
||||||
|
OverlongCharacter { char: char, span: Range<usize> },
|
||||||
|
|
||||||
|
#[error("Unknown escaped character {escaped_char:?} at {span:?}")]
|
||||||
|
UnknownEscapeCharacter {
|
||||||
|
escaped_char: char,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Invalid unicode escape sequence at {span:?}")]
|
||||||
|
InvalidUnicode { span: Range<usize> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LexerError {
|
||||||
|
pub fn to_triple(&self) -> (usize, Result<Token, LexerError>, usize) {
|
||||||
|
match self {
|
||||||
|
LexerError::IllegalControlCharacter { offset } => (*offset, Err(self.clone()), *offset),
|
||||||
|
LexerError::IllegalPrimitive { span } => (span.start, Err(self.clone()), span.end),
|
||||||
|
LexerError::IllegalPrimitiveCharacter { span, .. } => {
|
||||||
|
(span.start, Err(self.clone()), span.end)
|
||||||
|
}
|
||||||
|
LexerError::UnfinishedCharacter { span, .. } => {
|
||||||
|
(span.start, Err(self.clone()), span.end)
|
||||||
|
}
|
||||||
|
LexerError::UnfinishedString { span, .. } => (span.start, Err(self.clone()), span.end),
|
||||||
|
LexerError::OverlongCharacter { span, .. } => (span.start, Err(self.clone()), span.end),
|
||||||
|
LexerError::UnknownEscapeCharacter { span, .. } => {
|
||||||
|
(span.start, Err(self.clone()), span.end)
|
||||||
|
}
|
||||||
|
LexerError::InvalidUnicode { span, .. } => (span.start, Err(self.clone()), span.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl<F> From<LexerError> for Diagnostic<F> {
|
||||||
|
// fn from(value: LexerError) -> Self {
|
||||||
|
// match value {
|
||||||
|
// LexerError::IllegalControlCharacter { file, offset } => Diagnostic::error()
|
||||||
|
// .with_code("E1001")
|
||||||
|
// .with_message("Illegal control character in input stream")
|
||||||
|
// .with_label(Label::primary(file, offset..offset).with_message("illegal character")),
|
||||||
|
//
|
||||||
|
// LexerError::IllegalPrimitive { file, span } => Diagnostic::error()
|
||||||
|
// .with_code("E1002")
|
||||||
|
// .with_message("Illegal primitive; it cut off before it could finish")
|
||||||
|
// .with_label(
|
||||||
|
// Label::primary(file, span)
|
||||||
|
// .with_message("should be at least one character after the %"),
|
||||||
|
// ),
|
||||||
|
//
|
||||||
|
// LexerError::IllegalPrimitiveCharacter { file, span, char } => Diagnostic::error()
|
||||||
|
// .with_code("E1003")
|
||||||
|
// .with_message(format!("Illegal character {char:?} in primitive"))
|
||||||
|
// .with_label(Label::primary(file, span).with_message("illegal character")),
|
||||||
|
//
|
||||||
|
// LexerError::UnfinishedCharacter { file, span } => Diagnostic::error()
|
||||||
|
// .with_code("E1004")
|
||||||
|
// .with_message("Unfinished character in input stream.")
|
||||||
|
// .with_label(Label::primary(file, span).with_message("unfinished character")),
|
||||||
|
//
|
||||||
|
// LexerError::UnfinishedString { file, span } => Diagnostic::error()
|
||||||
|
// .with_code("E1005")
|
||||||
|
// .with_message("Unfinished string in input stream.")
|
||||||
|
// .with_label(Label::primary(file, span).with_message("unfinished string")),
|
||||||
|
//
|
||||||
|
// LexerError::OverlongCharacter { file, char, span } => Diagnostic::error()
|
||||||
|
// .with_code("E1006")
|
||||||
|
// .with_message(format!(
|
||||||
|
// "Character {char:?} has some extra bits at the end of it."
|
||||||
|
// ))
|
||||||
|
// .with_label(Label::primary(file, span).with_message("overlong character")),
|
||||||
|
//
|
||||||
|
// LexerError::UnknownEscapeCharacter {
|
||||||
|
// file,
|
||||||
|
// escaped_char,
|
||||||
|
// span,
|
||||||
|
// } => Diagnostic::error()
|
||||||
|
// .with_code("E1007")
|
||||||
|
// .with_message(format!("Unknown escape character {escaped_char:?}."))
|
||||||
|
// .with_label(Label::primary(file, span).with_message("unknown character")),
|
||||||
|
//
|
||||||
|
// LexerError::InvalidUnicode { file, span } => Diagnostic::error()
|
||||||
|
// .with_code("E1008")
|
||||||
|
// .with_message("Unknown or invalid unicode escape sequence.")
|
||||||
|
// .with_label(Label::primary(file, span).with_message("escape sequence")),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
79
src/syntax/location.rs
Normal file
79
src/syntax/location.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Location {
|
||||||
|
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: &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, other.file);
|
||||||
|
Location {
|
||||||
|
file: self.file.clone(),
|
||||||
|
span: min(self.span.start, other.span.start)..max(self.span.end, other.span.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_span(mut self, span: Range<usize>) -> Location {
|
||||||
|
self.span = min(self.span.start, span.start)..max(self.span.end, span.end);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
|
||||||
|
assert_eq!(loc1.extend_to(&loc2).source(), &file);
|
||||||
|
assert_eq!(loc1.extend_to(&loc2).start(), 1);
|
||||||
|
assert_eq!(loc1.extend_to(&loc2).end(), 8);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
156
src/syntax/name.rs
Normal file
156
src/syntax/name.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
use crate::syntax::Location;
|
||||||
|
#[cfg(test)]
|
||||||
|
use internment::ArcIntern;
|
||||||
|
use std::cmp;
|
||||||
|
use std::fmt;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
static IDENTIFIER_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Name {
|
||||||
|
printable: String,
|
||||||
|
identifier: u64,
|
||||||
|
location: Option<Location>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::PartialEq for Name {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.identifier == other.identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::Eq for Name {}
|
||||||
|
|
||||||
|
impl Hash for Name {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.identifier.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.printable, self.identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Name {
|
||||||
|
pub fn new<S: ToString>(location: Location, s: S) -> Name {
|
||||||
|
let my_id = IDENTIFIER_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Name {
|
||||||
|
printable: s.to_string(),
|
||||||
|
identifier: my_id,
|
||||||
|
location: Some(location),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gensym(base: &'static str) -> Name {
|
||||||
|
let formatted = format!("<{base}>");
|
||||||
|
let my_id = IDENTIFIER_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
Name {
|
||||||
|
printable: formatted,
|
||||||
|
identifier: my_id,
|
||||||
|
location: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
1785
src/syntax/parse.rs
Normal file
1785
src/syntax/parse.rs
Normal file
File diff suppressed because it is too large
Load Diff
70
src/syntax/parser.lalrpop
Normal file
70
src/syntax/parser.lalrpop
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use crate::syntax::*;
|
||||||
|
use crate::syntax::error::ParserError;
|
||||||
|
use crate::syntax::tokens::*;
|
||||||
|
|
||||||
|
grammar(file_id: usize);
|
||||||
|
|
||||||
|
extern {
|
||||||
|
type Location = usize;
|
||||||
|
type Error = ParserError;
|
||||||
|
|
||||||
|
enum Token {
|
||||||
|
"(" => Token::OpenParen,
|
||||||
|
")" => Token::CloseParen,
|
||||||
|
"[" => Token::OpenSquare,
|
||||||
|
"]" => Token::CloseSquare,
|
||||||
|
"{" => Token::OpenBrace,
|
||||||
|
"}" => Token::CloseBrace,
|
||||||
|
";" => Token::Semi,
|
||||||
|
":" => Token::Colon,
|
||||||
|
"," => Token::Comma,
|
||||||
|
"`" => Token::BackTick,
|
||||||
|
"\\" => Token::Lambda(_),
|
||||||
|
"->" => Token::Arrow,
|
||||||
|
|
||||||
|
"<constructor>" => Token::TypeName(<String>),
|
||||||
|
"<value>" => Token::ValueName(<String>),
|
||||||
|
"<op>" => Token::OperatorName(<String>),
|
||||||
|
"<prim_constructor>" => Token::PrimitiveTypeName(<String>),
|
||||||
|
"<prim_value>" => Token::PrimitiveValueName(<String>),
|
||||||
|
"<integer>" => Token::Integer(<IntegerWithBase>),
|
||||||
|
"<char>" => Token::Character(<char>),
|
||||||
|
"<string>" => Token::String(<String>),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub Type: Type = {
|
||||||
|
FunctionType,
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionType: Type = {
|
||||||
|
TypeApplication,
|
||||||
|
<argtype:FunctionType> "->" <ret:TypeApplication> =>
|
||||||
|
Type::Function(Box::new(argtype), Box::new(ret)),
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeApplication: Type = {
|
||||||
|
BaseType,
|
||||||
|
<s:@L> <c:"<constructor>"> <e:@L> <arguments: BaseType*> => {
|
||||||
|
let constructor = Type::Constructor(Location::new(file_id, s..e), c);
|
||||||
|
Type::Application(Box::new(constructor), arguments)
|
||||||
|
},
|
||||||
|
<s:@L> <c:"<prim_constructor>"> <e:@L> <arguments: BaseType*> => {
|
||||||
|
let constructor = Type::Constructor(Location::new(file_id, s..e), c);
|
||||||
|
Type::Application(Box::new(constructor), arguments)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType: Type = {
|
||||||
|
<s:@L> <v:"<value>"> <e:@L> =>
|
||||||
|
Type::Variable(Location::new(file_id, s..e), v),
|
||||||
|
<s:@L> <p: "<prim_value>"> <e:@L> =>
|
||||||
|
Type::Primitive(Location::new(file_id, s..e), p),
|
||||||
|
"(" <t:Type> ")" => t,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub ConstantValue: ConstantValue = {
|
||||||
|
<s:@L> <x:"<integer>"> <e:@L> => ConstantValue::Integer(Location::new(file_id, s..e), x),
|
||||||
|
<s:@L> <x:"<char>"> <e:@L> => ConstantValue::Character(Location::new(file_id, s..e), x),
|
||||||
|
<s:@L> <x:"<string>"> <e:@L> => ConstantValue::String(Location::new(file_id, s..e), x),
|
||||||
|
}
|
||||||
1500
src/syntax/parser_tests.rs
Normal file
1500
src/syntax/parser_tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
70
src/syntax/print.rs
Normal file
70
src/syntax/print.rs
Normal 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);
|
||||||
|
// }
|
||||||
|
}
|
||||||
882
src/syntax/tokens.rs
Normal file
882
src/syntax/tokens.rs
Normal file
@@ -0,0 +1,882 @@
|
|||||||
|
use crate::syntax::IntegerWithBase;
|
||||||
|
use crate::syntax::error::LexerError;
|
||||||
|
use proptest_derive::Arbitrary;
|
||||||
|
use std::fmt;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::str::CharIndices;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LocatedToken {
|
||||||
|
pub token: Token,
|
||||||
|
pub span: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single token of the input stream; used to help the parsing function over
|
||||||
|
/// more concrete things than bytes.
|
||||||
|
///
|
||||||
|
/// The [`std::fmt::Display`] implementation is designed to round-trip, so those
|
||||||
|
/// needing a more regular or descriptive option should consider using the
|
||||||
|
/// [`std::fmt::Debug`] implementation instead.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Arbitrary)]
|
||||||
|
pub enum Token {
|
||||||
|
OpenParen,
|
||||||
|
CloseParen,
|
||||||
|
OpenSquare,
|
||||||
|
CloseSquare,
|
||||||
|
OpenBrace,
|
||||||
|
CloseBrace,
|
||||||
|
Semi,
|
||||||
|
Colon,
|
||||||
|
DoubleColon,
|
||||||
|
Comma,
|
||||||
|
BackTick,
|
||||||
|
Arrow,
|
||||||
|
Lambda(bool),
|
||||||
|
|
||||||
|
TypeName(#[proptest(regex = r"[A-Z][a-zA-Z0-9_]*")] String),
|
||||||
|
ValueName(#[proptest(regex = r"[a-z_][a-zA-Z0-9_]*")] String),
|
||||||
|
OperatorName(
|
||||||
|
#[proptest(
|
||||||
|
regex = r"[\~\!\@\#\$\%\^\&\*\+\-\=\.<>\?\|][\~\!\@\#\$\%\^\&\*\+\-\=\.<>\?\|_]*",
|
||||||
|
filter = "|x| x != \"->\""
|
||||||
|
)]
|
||||||
|
String,
|
||||||
|
),
|
||||||
|
|
||||||
|
PrimitiveTypeName(#[proptest(regex = r"[A-Z][a-zA-Z0-9_]*")] String),
|
||||||
|
PrimitiveValueName(#[proptest(regex = r"[a-z_][a-zA-Z0-9_]*")] String),
|
||||||
|
|
||||||
|
Integer(IntegerWithBase),
|
||||||
|
Character(char),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Token {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Token::OpenParen => write!(f, "("),
|
||||||
|
Token::CloseParen => write!(f, ")"),
|
||||||
|
Token::OpenSquare => write!(f, "["),
|
||||||
|
Token::CloseSquare => write!(f, "]"),
|
||||||
|
Token::OpenBrace => write!(f, "{{"),
|
||||||
|
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, "->"),
|
||||||
|
Token::Lambda(false) => write!(f, "\\"),
|
||||||
|
Token::Lambda(true) => write!(f, "λ"),
|
||||||
|
Token::TypeName(str) => write!(f, "{str}"),
|
||||||
|
Token::ValueName(str) => write!(f, "{str}"),
|
||||||
|
Token::OperatorName(str) => write!(f, "{str}"),
|
||||||
|
Token::PrimitiveTypeName(str) => write!(f, "prim%{str}"),
|
||||||
|
Token::PrimitiveValueName(str) => write!(f, "prim%{str}"),
|
||||||
|
Token::Integer(IntegerWithBase { base, value }) => match base {
|
||||||
|
None => write!(f, "{value}"),
|
||||||
|
Some(2) => write!(f, "0b{value:b}"),
|
||||||
|
Some(8) => write!(f, "0o{value:o}"),
|
||||||
|
Some(10) => write!(f, "0d{value}"),
|
||||||
|
Some(16) => write!(f, "0x{value:x}"),
|
||||||
|
Some(base) => write!(f, "<illegal number token base={base} value={value}>"),
|
||||||
|
},
|
||||||
|
Token::Character(c) => write!(f, "{c:?}"),
|
||||||
|
Token::String(s) => write!(f, "{s:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(private_interfaces)]
|
||||||
|
pub enum Lexer<'a> {
|
||||||
|
Working(LexerState<'a>),
|
||||||
|
Errored(LexerError),
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LexerState<'a> {
|
||||||
|
stream: CharIndices<'a>,
|
||||||
|
buffer: Option<(usize, char)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Lexer<'a> {
|
||||||
|
fn from(value: &'a str) -> Self {
|
||||||
|
Lexer::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Lexer<'a> {
|
||||||
|
pub fn new(stream: &'a str) -> Self {
|
||||||
|
Lexer::Working(LexerState {
|
||||||
|
stream: stream.char_indices(),
|
||||||
|
buffer: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Lexer<'a> {
|
||||||
|
type Item = Result<LocatedToken, LexerError>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
Lexer::Done => None,
|
||||||
|
Lexer::Errored(e) => Some(Err(e.clone())),
|
||||||
|
Lexer::Working(state) => match state.next_token() {
|
||||||
|
Err(e) => {
|
||||||
|
println!("ERROR: {e}");
|
||||||
|
*self = Lexer::Errored(e.clone());
|
||||||
|
Some(Err(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None) => {
|
||||||
|
*self = Lexer::Done;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(ltoken)) => Some(Ok(ltoken)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LexerState<'a> {
|
||||||
|
fn next_char(&mut self) -> Option<(usize, char)> {
|
||||||
|
self.buffer.take().or_else(|| self.stream.next())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stash_char(&mut self, idx: usize, c: char) {
|
||||||
|
assert!(self.buffer.is_none());
|
||||||
|
self.buffer = Some((idx, c));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_token(&mut self) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
while let Some((token_start_offset, char)) = self.next_char() {
|
||||||
|
if char.is_whitespace() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let simple_response = |token| {
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token,
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
match char {
|
||||||
|
'(' => return simple_response(Token::OpenParen),
|
||||||
|
')' => return simple_response(Token::CloseParen),
|
||||||
|
'[' => return simple_response(Token::OpenSquare),
|
||||||
|
']' => return simple_response(Token::CloseSquare),
|
||||||
|
'{' => return simple_response(Token::OpenBrace),
|
||||||
|
'}' => return simple_response(Token::CloseBrace),
|
||||||
|
';' => return simple_response(Token::Semi),
|
||||||
|
',' => return simple_response(Token::Comma),
|
||||||
|
'`' => return simple_response(Token::BackTick),
|
||||||
|
'\\' => return simple_response(Token::Lambda(false)),
|
||||||
|
'λ' => return simple_response(Token::Lambda(true)),
|
||||||
|
|
||||||
|
'0' => return self.starts_with_zero(token_start_offset),
|
||||||
|
'\'' => 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),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = char.to_digit(10) {
|
||||||
|
return self.parse_integer(token_start_offset, 10, None, value as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
if char.is_uppercase() {
|
||||||
|
return self.parse_identifier(
|
||||||
|
token_start_offset,
|
||||||
|
char.into(),
|
||||||
|
|c| c.is_alphanumeric() || c == '_',
|
||||||
|
Token::TypeName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if char.is_alphabetic() || char == '_' {
|
||||||
|
return self.parse_identifier(
|
||||||
|
token_start_offset,
|
||||||
|
char.into(),
|
||||||
|
|c| c.is_alphanumeric() || c == '_',
|
||||||
|
Token::ValueName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !char.is_alphanumeric() && !char.is_whitespace() && !char.is_control() {
|
||||||
|
return self.parse_identifier(
|
||||||
|
token_start_offset,
|
||||||
|
char.into(),
|
||||||
|
|c| !c.is_alphanumeric() && !c.is_whitespace() && !c.is_control(),
|
||||||
|
Token::OperatorName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_zero(
|
||||||
|
&mut self,
|
||||||
|
token_start_offset: usize,
|
||||||
|
) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
match self.next_char() {
|
||||||
|
None => {
|
||||||
|
let token = Token::Integer(IntegerWithBase {
|
||||||
|
base: None,
|
||||||
|
value: 0,
|
||||||
|
});
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token,
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((_, 'b')) => self.parse_integer(token_start_offset, 2, Some(2), 0),
|
||||||
|
Some((_, 'o')) => self.parse_integer(token_start_offset, 8, Some(8), 0),
|
||||||
|
Some((_, 'd')) => self.parse_integer(token_start_offset, 10, Some(10), 0),
|
||||||
|
Some((_, 'x')) => self.parse_integer(token_start_offset, 16, Some(16), 0),
|
||||||
|
|
||||||
|
Some((offset, c)) => {
|
||||||
|
if let Some(value) = c.to_digit(10) {
|
||||||
|
self.parse_integer(token_start_offset, 10, None, value as u64)
|
||||||
|
} else {
|
||||||
|
self.stash_char(offset, c);
|
||||||
|
let token = Token::Integer(IntegerWithBase {
|
||||||
|
base: None,
|
||||||
|
value: 0,
|
||||||
|
});
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token,
|
||||||
|
span: token_start_offset..offset,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_integer(
|
||||||
|
&mut self,
|
||||||
|
token_start_offset: usize,
|
||||||
|
base: u32,
|
||||||
|
provided_base: Option<u8>,
|
||||||
|
mut value: u64,
|
||||||
|
) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
let mut end_offset = self.stream.offset();
|
||||||
|
|
||||||
|
while let Some((offset, c)) = self.next_char() {
|
||||||
|
end_offset = offset;
|
||||||
|
if let Some(digit) = c.to_digit(base) {
|
||||||
|
value = (value * (base as u64)) + (digit as u64);
|
||||||
|
} else {
|
||||||
|
self.stash_char(offset, c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = Token::Integer(IntegerWithBase {
|
||||||
|
base: provided_base,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token,
|
||||||
|
span: token_start_offset..end_offset,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_identifier(
|
||||||
|
&mut self,
|
||||||
|
token_start_offset: usize,
|
||||||
|
mut identifier: String,
|
||||||
|
mut allowed_character: fn(char) -> bool,
|
||||||
|
mut builder: fn(String) -> Token,
|
||||||
|
) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
let mut end_offset = self.stream.offset();
|
||||||
|
|
||||||
|
while let Some((offset, c)) = self.next_char() {
|
||||||
|
end_offset = offset;
|
||||||
|
|
||||||
|
if allowed_character(c) {
|
||||||
|
identifier.push(c);
|
||||||
|
} else if identifier == "prim" && c == '%' {
|
||||||
|
identifier = String::new();
|
||||||
|
allowed_character = |c| c.is_alphanumeric() || c == '_';
|
||||||
|
match self.next_char() {
|
||||||
|
None => {
|
||||||
|
return Err(LexerError::IllegalPrimitive {
|
||||||
|
span: token_start_offset..end_offset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((_, char)) => {
|
||||||
|
if char.is_uppercase() {
|
||||||
|
identifier.push(char);
|
||||||
|
builder = Token::PrimitiveTypeName;
|
||||||
|
} else if char.is_lowercase() || char == '_' {
|
||||||
|
identifier.push(char);
|
||||||
|
builder = Token::PrimitiveValueName;
|
||||||
|
} else {
|
||||||
|
return Err(LexerError::IllegalPrimitiveCharacter {
|
||||||
|
span: token_start_offset..end_offset,
|
||||||
|
char,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.stash_char(offset, c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token: builder(identifier),
|
||||||
|
span: token_start_offset..end_offset,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_single(
|
||||||
|
&mut self,
|
||||||
|
token_start_offset: usize,
|
||||||
|
) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
let Some((_, mut char)) = self.next_char() else {
|
||||||
|
return Err(LexerError::UnfinishedCharacter {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if char == '\\' {
|
||||||
|
char = self.get_escaped_character(token_start_offset)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((idx, finish_char)) = self.next_char() else {
|
||||||
|
return Err(LexerError::UnfinishedCharacter {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if finish_char != '\'' {
|
||||||
|
return Err(LexerError::OverlongCharacter {
|
||||||
|
char,
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token: Token::Character(char),
|
||||||
|
span: token_start_offset..idx,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_escaped_character(&mut self, token_start_offset: usize) -> Result<char, LexerError> {
|
||||||
|
let Some((idx, escaped_char)) = self.next_char() else {
|
||||||
|
return Err(LexerError::UnfinishedCharacter {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
match escaped_char {
|
||||||
|
'0' => Ok('\0'),
|
||||||
|
'a' => Ok('\u{0007}'),
|
||||||
|
'b' => Ok('\u{0008}'),
|
||||||
|
'f' => Ok('\u{000C}'),
|
||||||
|
'n' => Ok('\n'),
|
||||||
|
'r' => Ok('\r'),
|
||||||
|
't' => Ok('\t'),
|
||||||
|
'u' => self.get_unicode_sequence(idx),
|
||||||
|
'v' => Ok('\u{000B}'),
|
||||||
|
'\'' => Ok('\''),
|
||||||
|
'"' => Ok('"'),
|
||||||
|
'\\' => Ok('\\'),
|
||||||
|
_ => Err(LexerError::UnknownEscapeCharacter {
|
||||||
|
escaped_char,
|
||||||
|
span: idx..self.stream.offset(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_unicode_sequence(&mut self, token_start_offset: usize) -> Result<char, LexerError> {
|
||||||
|
let Some((_, char)) = self.next_char() else {
|
||||||
|
return Err(LexerError::InvalidUnicode {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if char != '{' {
|
||||||
|
return Err(LexerError::InvalidUnicode {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut value: u32 = 0;
|
||||||
|
|
||||||
|
while let Some((idx, char)) = self.next_char() {
|
||||||
|
if let Some(digit) = char.to_digit(16) {
|
||||||
|
if let Some(shifted) = value.checked_shl(4) {
|
||||||
|
value = shifted + digit;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return Err(LexerError::InvalidUnicode {
|
||||||
|
span: token_start_offset..idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == '}' {
|
||||||
|
if let Some(char) = char::from_u32(value) {
|
||||||
|
return Ok(char);
|
||||||
|
} else {
|
||||||
|
return Err(LexerError::InvalidUnicode {
|
||||||
|
span: token_start_offset..idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(LexerError::InvalidUnicode {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(LexerError::InvalidUnicode {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_double(
|
||||||
|
&mut self,
|
||||||
|
token_start_offset: usize,
|
||||||
|
) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
while let Some((idx, char)) = self.next_char() {
|
||||||
|
match char {
|
||||||
|
'"' => {
|
||||||
|
return Ok(Some(LocatedToken {
|
||||||
|
token: Token::String(result),
|
||||||
|
span: token_start_offset..idx,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
'\\' => result.push(self.get_escaped_character(idx)?),
|
||||||
|
|
||||||
|
_ => result.push(char),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(LexerError::UnfinishedString {
|
||||||
|
span: token_start_offset..self.stream.offset(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_dash(
|
||||||
|
&mut self,
|
||||||
|
token_start_offset: usize,
|
||||||
|
) -> Result<Option<LocatedToken>, LexerError> {
|
||||||
|
match self.next_char() {
|
||||||
|
None => Ok(Some(LocatedToken {
|
||||||
|
token: Token::OperatorName("-".into()),
|
||||||
|
span: token_start_offset..token_start_offset + 1,
|
||||||
|
})),
|
||||||
|
Some((end, '>')) => {
|
||||||
|
let Some((pbloc, peekaboo)) = self.next_char() else {
|
||||||
|
return Ok(Some(LocatedToken {
|
||||||
|
token: Token::Arrow,
|
||||||
|
span: token_start_offset..end,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let is_operator = !peekaboo.is_alphanumeric()
|
||||||
|
&& !peekaboo.is_whitespace()
|
||||||
|
&& !peekaboo.is_control();
|
||||||
|
|
||||||
|
if is_operator {
|
||||||
|
self.parse_identifier(
|
||||||
|
token_start_offset,
|
||||||
|
format!("->{peekaboo}"),
|
||||||
|
|c| !c.is_alphanumeric() && !c.is_whitespace() && !c.is_control(),
|
||||||
|
Token::OperatorName,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.stash_char(pbloc, peekaboo);
|
||||||
|
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token: Token::Arrow,
|
||||||
|
span: token_start_offset..end,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some((_, c)) if !c.is_alphanumeric() && !c.is_whitespace() && !c.is_control() => self
|
||||||
|
.parse_identifier(
|
||||||
|
token_start_offset,
|
||||||
|
format!("-{c}"),
|
||||||
|
|c| !c.is_alphanumeric() && !c.is_whitespace() && !c.is_control(),
|
||||||
|
Token::OperatorName,
|
||||||
|
),
|
||||||
|
Some((idx, c)) => {
|
||||||
|
self.stash_char(idx, c);
|
||||||
|
Ok(Some(LocatedToken {
|
||||||
|
token: Token::OperatorName("-".into()),
|
||||||
|
span: token_start_offset..idx,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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! {
|
||||||
|
#[test]
|
||||||
|
fn token_string_token(token: Token) {
|
||||||
|
println!("Starting from {token:?}");
|
||||||
|
let string = format!("{token}");
|
||||||
|
let mut tokens = Lexer::from(string.as_str());
|
||||||
|
let initial_token = tokens.next()
|
||||||
|
.expect("Can get a token without an error.")
|
||||||
|
.expect("Can get a valid token.")
|
||||||
|
.token;
|
||||||
|
|
||||||
|
proptest::prop_assert_eq!(token, initial_token);
|
||||||
|
proptest::prop_assert!(tokens.next().is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn parsed_single_token(s: &str) -> Token {
|
||||||
|
let mut tokens = Lexer::from(s);
|
||||||
|
let result = tokens
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| panic!("Can get at least one token from {s:?}"))
|
||||||
|
.expect("Can get a valid token.")
|
||||||
|
.token;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
tokens.next().is_none(),
|
||||||
|
"Should only get one token from {s:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn numbers_work_as_expected() {
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: None,
|
||||||
|
value: 1
|
||||||
|
}),
|
||||||
|
parsed_single_token("1")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(2),
|
||||||
|
value: 1
|
||||||
|
}),
|
||||||
|
parsed_single_token("0b1")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(8),
|
||||||
|
value: 1
|
||||||
|
}),
|
||||||
|
parsed_single_token("0o1")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(10),
|
||||||
|
value: 1
|
||||||
|
}),
|
||||||
|
parsed_single_token("0d1")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(16),
|
||||||
|
value: 1
|
||||||
|
}),
|
||||||
|
parsed_single_token("0x1")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: None,
|
||||||
|
value: 10
|
||||||
|
}),
|
||||||
|
parsed_single_token("10")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(2),
|
||||||
|
value: 2
|
||||||
|
}),
|
||||||
|
parsed_single_token("0b10")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(8),
|
||||||
|
value: 8
|
||||||
|
}),
|
||||||
|
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),
|
||||||
|
value: 10
|
||||||
|
}),
|
||||||
|
parsed_single_token("0d10")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Token::Integer(IntegerWithBase {
|
||||||
|
base: Some(16),
|
||||||
|
value: 16
|
||||||
|
}),
|
||||||
|
parsed_single_token("0x10")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lambda_works() {
|
||||||
|
assert_eq!(Token::Lambda(false), parsed_single_token("\\"));
|
||||||
|
assert_eq!(Token::Lambda(true), parsed_single_token("λ"));
|
||||||
|
assert_eq!(Token::TypeName("Λ".into()), parsed_single_token("Λ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn types_work_as_expected() {
|
||||||
|
assert_eq!(Token::TypeName("Int".into()), parsed_single_token("Int"));
|
||||||
|
assert_eq!(Token::TypeName("Int8".into()), parsed_single_token("Int8"));
|
||||||
|
assert_eq!(Token::TypeName("Γ".into()), parsed_single_token("Γ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn values_work_as_expected() {
|
||||||
|
assert_eq!(
|
||||||
|
Token::ValueName("alpha".into()),
|
||||||
|
parsed_single_token("alpha")
|
||||||
|
);
|
||||||
|
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("-"));
|
||||||
|
assert_eq!(Token::OperatorName("+".into()), parsed_single_token("+"));
|
||||||
|
assert_eq!(Token::OperatorName("*".into()), parsed_single_token("*"));
|
||||||
|
assert_eq!(Token::OperatorName("/".into()), parsed_single_token("/"));
|
||||||
|
assert_eq!(Token::OperatorName("↣".into()), parsed_single_token("↣"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_separate_pieces() {
|
||||||
|
let mut lexer = Lexer::from("a-b");
|
||||||
|
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
|
||||||
|
|
||||||
|
assert_eq!(Some(Token::ValueName("a".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::OperatorName("-".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::ValueName("b".into())), next_token());
|
||||||
|
assert_eq!(None, next_token());
|
||||||
|
|
||||||
|
let mut lexer = Lexer::from("a--b");
|
||||||
|
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
|
||||||
|
|
||||||
|
assert_eq!(Some(Token::ValueName("a".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::OperatorName("--".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::ValueName("b".into())), next_token());
|
||||||
|
assert_eq!(None, next_token());
|
||||||
|
|
||||||
|
let mut lexer = Lexer::from("a - -b");
|
||||||
|
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
|
||||||
|
|
||||||
|
assert_eq!(Some(Token::ValueName("a".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::OperatorName("-".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::OperatorName("-".into())), next_token());
|
||||||
|
assert_eq!(Some(Token::ValueName("b".into())), next_token());
|
||||||
|
assert_eq!(None, next_token());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arrow_requires_nonop() {
|
||||||
|
let mut lexer = Lexer::from("->");
|
||||||
|
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
|
||||||
|
assert_eq!(Some(Token::Arrow), next_token());
|
||||||
|
|
||||||
|
let mut lexer = Lexer::from("->*");
|
||||||
|
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
|
||||||
|
assert_eq!(Some(Token::OperatorName("->*".into())), next_token());
|
||||||
|
|
||||||
|
let mut lexer = Lexer::from("->*x");
|
||||||
|
let mut next_token = move || lexer.next().map(|x| x.expect("Can read valid token").token);
|
||||||
|
assert_eq!(Some(Token::OperatorName("->*".into())), next_token());
|
||||||
|
|
||||||
|
let mut lexer = Lexer::from("->x");
|
||||||
|
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
44
src/syntax/universe.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user