Compare commits
12 Commits
master
...
f6bf3dd639
| Author | SHA1 | Date | |
|---|---|---|---|
| f6bf3dd639 | |||
| c31be288ad | |||
| 4362d82034 | |||
| e9fb4fcd0f | |||
| 24e6bf6318 | |||
| 8657c009c8 | |||
| e250a49703 | |||
| 1baeae1bf0 | |||
| 129bf3c204 | |||
| 768b27a8f6 | |||
| 8e6ac7ecbd | |||
| a663d8f1fb |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -6,3 +6,9 @@
|
||||
hsrc/Syntax/Lexer.hs
|
||||
hsrc/Syntax/Parser.hs
|
||||
bang
|
||||
|
||||
|
||||
# Added by cargo
|
||||
/proptest-regressions
|
||||
/target
|
||||
.aider*
|
||||
|
||||
452
Cargo.lock
generated
Normal file
452
Cargo.lock
generated
Normal file
@@ -0,0 +1,452 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[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 = [
|
||||
"codespan",
|
||||
"codespan-reporting",
|
||||
"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 = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "codespan"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e4b418d52c9206820a56fc1aa28db73d67e346ba8ba6aa90987e8d6becef7e4"
|
||||
dependencies = [
|
||||
"codespan-reporting",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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 = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[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 = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags",
|
||||
"lazy_static",
|
||||
"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.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
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 = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.227"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.227"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.227"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
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",
|
||||
]
|
||||
|
||||
[[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.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[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.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[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",
|
||||
]
|
||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "bang"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
codespan = "0.12.0"
|
||||
codespan-reporting = "0.12.0"
|
||||
proptest = "1.7.0"
|
||||
proptest-derive = "0.6.0"
|
||||
thiserror = "2.0.12"
|
||||
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;
|
||||
227
src/syntax.rs
Normal file
227
src/syntax.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
mod error;
|
||||
mod location;
|
||||
mod name;
|
||||
mod parse;
|
||||
#[cfg(test)]
|
||||
mod parser_tests;
|
||||
pub mod tokens;
|
||||
|
||||
pub use location::{Located, Location};
|
||||
pub use name::Name;
|
||||
use proptest_derive::Arbitrary;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module {
|
||||
definitions: Vec<Definition>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Definition {
|
||||
location: Location,
|
||||
export: ExportClass,
|
||||
type_restrictions: TypeRestrictions,
|
||||
definition: Def,
|
||||
}
|
||||
|
||||
impl Located for Definition {
|
||||
fn location(&self) -> Location {
|
||||
self.location.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Def {
|
||||
Enumeration(EnumerationDef),
|
||||
Structure(StructureDef),
|
||||
Function(FunctionDef),
|
||||
Value(ValueDef),
|
||||
}
|
||||
|
||||
impl Located for Def {
|
||||
fn location(&self) -> Location {
|
||||
match self {
|
||||
Def::Enumeration(def) => def.location.clone(),
|
||||
Def::Structure(def) => def.location.clone(),
|
||||
Def::Function(def) => def.location.clone(),
|
||||
Def::Value(def) => def.location.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnumerationDef {
|
||||
name: String,
|
||||
location: Location,
|
||||
variants: Vec<EnumerationVariant>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnumerationVariant {
|
||||
location: Location,
|
||||
name: String,
|
||||
argument: Option<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StructureDef {
|
||||
name: String,
|
||||
location: Location,
|
||||
fields: Vec<StructureField>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StructureField {
|
||||
location: Location,
|
||||
export: ExportClass,
|
||||
name: String,
|
||||
field_type: Option<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionDef {
|
||||
name: String,
|
||||
location: Location,
|
||||
arguments: Vec<FunctionArg>,
|
||||
return_type: Option<Type>,
|
||||
body: Vec<Statement>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionArg {
|
||||
name: String,
|
||||
arg_type: Option<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ValueDef {
|
||||
name: String,
|
||||
location: Location,
|
||||
value: Expression,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExportClass {
|
||||
Public,
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Statement {
|
||||
Binding(BindingStmt),
|
||||
Expression(Expression),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BindingStmt {
|
||||
location: Location,
|
||||
mutable: bool,
|
||||
variable: Name,
|
||||
value: Expression,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Expression {
|
||||
Value(ConstantValue),
|
||||
Reference(Name),
|
||||
EnumerationValue(Name, Name, Option<Box<Expression>>),
|
||||
StructureValue(Name, Vec<FieldValue>),
|
||||
Conditional(ConditionalExpr),
|
||||
Call(Box<Expression>, CallKind, Vec<Expression>),
|
||||
Block(Location, Vec<Statement>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConditionalExpr {
|
||||
location: Location,
|
||||
test: Box<Expression>,
|
||||
consequent: Box<Expression>,
|
||||
alternative: Option<Box<Expression>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CallKind {
|
||||
Infix,
|
||||
Normal,
|
||||
Postfix,
|
||||
Prefix,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FieldValue {
|
||||
field: Name,
|
||||
value: Expression,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeRestrictions {
|
||||
restrictions: Vec<TypeRestriction>,
|
||||
}
|
||||
|
||||
impl TypeRestrictions {
|
||||
fn empty() -> Self {
|
||||
TypeRestrictions {
|
||||
restrictions: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeRestriction {
|
||||
constructor: Type,
|
||||
arguments: Vec<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Type {
|
||||
Constructor(Location, String),
|
||||
Variable(Location, String),
|
||||
Primitive(Location, String),
|
||||
Application(Box<Type>, Vec<Type>),
|
||||
Function(Vec<Type>, Box<Type>),
|
||||
}
|
||||
|
||||
impl Located for Type {
|
||||
fn location(&self) -> Location {
|
||||
match self {
|
||||
Type::Constructor(l, _) => l.clone(),
|
||||
Type::Variable(l, _) => l.clone(),
|
||||
Type::Primitive(l, _) => l.clone(),
|
||||
Type::Application(t1, ts) => {
|
||||
let mut result = t1.location();
|
||||
if let Some(last) = ts.last() {
|
||||
result = result.extend_to(&last.location());
|
||||
}
|
||||
result
|
||||
}
|
||||
Type::Function(args, ret) => {
|
||||
if let Some(first) = args.first() {
|
||||
first.location().extend_to(&ret.location())
|
||||
} else {
|
||||
ret.location()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConstantValue {
|
||||
Integer(Location, IntegerWithBase),
|
||||
Character(Location, char),
|
||||
String(Location, String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Arbitrary)]
|
||||
pub struct IntegerWithBase {
|
||||
#[proptest(strategy = "proptest::prop_oneof![ \
|
||||
proptest::strategy::Just(None), \
|
||||
proptest::strategy::Just(Some(2)), \
|
||||
proptest::strategy::Just(Some(8)), \
|
||||
proptest::strategy::Just(Some(10)), \
|
||||
proptest::strategy::Just(Some(16)), \
|
||||
]")]
|
||||
base: Option<u8>,
|
||||
value: u64,
|
||||
}
|
||||
127
src/syntax/error.rs
Normal file
127
src/syntax/error.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
//use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use crate::syntax::tokens::Token;
|
||||
use std::ops::Range;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParserError {
|
||||
#[error("Lexer error at {file_id}: {error}")]
|
||||
LexerError { file_id: usize, error: LexerError },
|
||||
|
||||
#[error("Unacceptable end of file at {file_id} while {place}")]
|
||||
UnacceptableEof { file_id: usize, place: &'static str },
|
||||
|
||||
#[error("Unexpected token at {file_id}: expected {expected}, saw {token}")]
|
||||
UnexpectedToken {
|
||||
file_id: usize,
|
||||
span: Range<usize>,
|
||||
token: Token,
|
||||
expected: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
#[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")),
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
48
src/syntax/location.rs
Normal file
48
src/syntax/location.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use codespan_reporting::diagnostic::Label;
|
||||
use std::cmp::{max, min};
|
||||
use std::ops::Range;
|
||||
|
||||
pub trait Located {
|
||||
fn location(&self) -> Location;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Location {
|
||||
file_id: usize,
|
||||
span: Range<usize>,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn new(file_id: usize, span: Range<usize>) -> Self {
|
||||
Location { file_id, span }
|
||||
}
|
||||
|
||||
pub fn extend_to(&self, other: &Location) -> Location {
|
||||
assert_eq!(self.file_id, other.file_id);
|
||||
Location {
|
||||
file_id: self.file_id,
|
||||
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
|
||||
}
|
||||
|
||||
pub fn file_id(&self) -> usize {
|
||||
self.file_id
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Range<usize> {
|
||||
self.span.clone()
|
||||
}
|
||||
|
||||
pub fn primary_label(&self) -> Label<usize> {
|
||||
Label::primary(self.file_id, self.span.clone())
|
||||
}
|
||||
|
||||
pub fn secondary_label(&self) -> Label<usize> {
|
||||
Label::secondary(self.file_id, self.span.clone())
|
||||
}
|
||||
}
|
||||
60
src/syntax/name.rs
Normal file
60
src/syntax/name.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::syntax::Location;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::hash;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static IDENTIFIER_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(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::Hash for Name {
|
||||
fn hash<H: hash::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()
|
||||
}
|
||||
}
|
||||
1250
src/syntax/parse.rs
Normal file
1250
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),
|
||||
}
|
||||
930
src/syntax/parser_tests.rs
Normal file
930
src/syntax/parser_tests.rs
Normal file
@@ -0,0 +1,930 @@
|
||||
use crate::syntax::error::ParserError;
|
||||
use crate::syntax::parse::Parser;
|
||||
use crate::syntax::tokens::{Lexer, Token};
|
||||
use crate::syntax::*;
|
||||
|
||||
#[test]
|
||||
fn constants() {
|
||||
let parse_constant = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_constant()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_constant("16"),
|
||||
Ok(ConstantValue::Integer(
|
||||
_,
|
||||
IntegerWithBase {
|
||||
base: None,
|
||||
value: 16,
|
||||
}
|
||||
))
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_constant("0x10"),
|
||||
Ok(ConstantValue::Integer(
|
||||
_,
|
||||
IntegerWithBase {
|
||||
base: Some(16),
|
||||
value: 16,
|
||||
}
|
||||
))
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_constant("0o20"),
|
||||
Ok(ConstantValue::Integer(
|
||||
_,
|
||||
IntegerWithBase {
|
||||
base: Some(8),
|
||||
value: 16,
|
||||
}
|
||||
))
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_constant("0b10000"),
|
||||
Ok(ConstantValue::Integer(
|
||||
_,
|
||||
IntegerWithBase {
|
||||
base: Some(2),
|
||||
value: 16,
|
||||
}
|
||||
))
|
||||
));
|
||||
assert!(
|
||||
matches!(parse_constant("\"foo\""), Ok(ConstantValue::String(_, x))
|
||||
if x == "foo")
|
||||
);
|
||||
assert!(matches!(
|
||||
parse_constant("'f'"),
|
||||
Ok(ConstantValue::Character(_, 'f'))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn types() {
|
||||
let parse_type = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_type()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_type("Cons"),
|
||||
Ok(Type::Application(cons, empty)) if
|
||||
matches!(cons.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
|
||||
empty.is_empty()
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_type("cons"),
|
||||
Ok(Type::Variable(_, c)) if c == "cons"
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_type("Cons a b"),
|
||||
Ok(Type::Application(a, b))
|
||||
if matches!(a.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
|
||||
matches!(b.as_slice(), [Type::Variable(_, b1), Type::Variable(_, b2)]
|
||||
if b1 == "a" && b2 == "b")
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_type("a -> z"),
|
||||
Ok(Type::Function(a, z))
|
||||
if matches!(a.as_slice(), [Type::Variable(_, a1)] if a1 == "a") &&
|
||||
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
|
||||
));
|
||||
println!("-------------");
|
||||
println!("{:?}", parse_type("(a -> z)"));
|
||||
println!("-------------");
|
||||
assert!(matches!(
|
||||
parse_type("(a -> z)"),
|
||||
Ok(Type::Function(a, z))
|
||||
if matches!(a.as_slice(), [Type::Variable(_, a1)] if a1 == "a") &&
|
||||
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_type("a b -> z"),
|
||||
Ok(Type::Function(a, z))
|
||||
if matches!(a.as_slice(), [Type::Variable(_, a1), Type::Variable(_, b1)]
|
||||
if a1 == "a" && b1 == "b") &&
|
||||
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_type("Cons a b -> z"),
|
||||
Ok(Type::Function(a, z))
|
||||
if matches!(a.as_slice(), [Type::Application(cons, appargs)]
|
||||
if matches!(cons.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
|
||||
matches!(appargs.as_slice(), [Type::Variable(_, b1), Type::Variable(_, b2)]
|
||||
if b1 == "a" && b2 == "b")) &&
|
||||
matches!(z.as_ref(), Type::Variable(_, z1) if z1 == "z")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_restrictions() {
|
||||
let parse_tr = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_type_restrictions()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_tr("restrict()"),
|
||||
Ok(TypeRestrictions{ restrictions }) if restrictions.is_empty()
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
parse_tr("restrict(Cons a b)"),
|
||||
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 1 &&
|
||||
matches!(&restrictions[0], TypeRestriction {
|
||||
constructor,
|
||||
arguments,
|
||||
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
|
||||
arguments.len() == 2 &&
|
||||
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
|
||||
matches!(&arguments[1], Type::Variable(_, x) if x == "b"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_tr("restrict(Cons a b,)"),
|
||||
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 1 &&
|
||||
matches!(&restrictions[0], TypeRestriction {
|
||||
constructor,
|
||||
arguments,
|
||||
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
|
||||
arguments.len() == 2 &&
|
||||
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
|
||||
matches!(&arguments[1], Type::Variable(_, x) if x == "b"))));
|
||||
|
||||
assert!(matches!(parse_tr("restrict(,Cons a b,)"), Err(_)));
|
||||
|
||||
assert!(matches!(
|
||||
parse_tr("restrict(Cons a b, Monad m)"),
|
||||
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 2 &&
|
||||
matches!(&restrictions[0], TypeRestriction {
|
||||
constructor,
|
||||
arguments,
|
||||
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
|
||||
arguments.len() == 2 &&
|
||||
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
|
||||
matches!(&arguments[1], Type::Variable(_, x) if x == "b")) &&
|
||||
matches!(&restrictions[1], TypeRestriction {
|
||||
constructor,
|
||||
arguments,
|
||||
} if matches!(constructor, Type::Constructor(_, x) if x == "Monad") &&
|
||||
arguments.len() == 1 &&
|
||||
matches!(&arguments[0], Type::Variable(_, x) if x == "m"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_tr("restrict(Cons a b, Monad m,)"),
|
||||
Ok(TypeRestrictions { restrictions }) if restrictions.len() == 2 &&
|
||||
matches!(&restrictions[0], TypeRestriction {
|
||||
constructor,
|
||||
arguments,
|
||||
} if matches!(constructor, Type::Constructor(_, x) if x == "Cons") &&
|
||||
arguments.len() == 2 &&
|
||||
matches!(&arguments[0], Type::Variable(_, x) if x == "a") &&
|
||||
matches!(&arguments[1], Type::Variable(_, x) if x == "b")) &&
|
||||
matches!(&restrictions[1], TypeRestriction {
|
||||
constructor,
|
||||
arguments,
|
||||
} if matches!(constructor, Type::Constructor(_, x) if x == "Monad") &&
|
||||
arguments.len() == 1 &&
|
||||
matches!(&arguments[0], Type::Variable(_, x) if x == "m"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_definition() {
|
||||
let parse_fd = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_field_definition()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_fd("foo"), Err(_),));
|
||||
assert!(matches!(
|
||||
parse_fd("foo,"),
|
||||
Ok(Some(StructureField{ name, export: ExportClass::Private, field_type: None, .. }))
|
||||
if name == "foo"
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_fd("foo}"),
|
||||
Ok(Some(StructureField{ name, export: ExportClass::Private, field_type: None, .. }))
|
||||
if name == "foo"
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
parse_fd("foo: Word8,"),
|
||||
Ok(Some(StructureField{ name, field_type, .. }))
|
||||
if name == "foo" &&
|
||||
matches!(&field_type, Some(Type::Application(c, args))
|
||||
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
|
||||
args.is_empty())));
|
||||
|
||||
assert!(matches!(
|
||||
parse_fd("foo: Cons a b,"),
|
||||
Ok(Some(StructureField{ name, field_type, .. }))
|
||||
if name == "foo" &&
|
||||
matches!(&field_type, Some(Type::Application(c, args))
|
||||
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Cons") &&
|
||||
matches!(&args.as_slice(), &[Type::Variable(_, v1), Type::Variable(_, v2)]
|
||||
if v1 == "a" && v2 == "b"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_fd("foo: a -> b,"),
|
||||
Ok(Some(StructureField{ name, field_type, .. }))
|
||||
if name == "foo" &&
|
||||
matches!(&field_type, Some(Type::Function(args, ret))
|
||||
if matches!(&args.as_slice(), &[Type::Variable(_, a)] if a == "a") &&
|
||||
matches!(ret.as_ref(), Type::Variable(_, b) if b == "b"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_fd("export foo: a -> b,"),
|
||||
Ok(Some(StructureField{ name, export: ExportClass::Public, field_type, .. }))
|
||||
if name == "foo" &&
|
||||
matches!(&field_type, Some(Type::Function(args, ret))
|
||||
if matches!(&args.as_slice(), &[Type::Variable(_, a)] if a == "a") &&
|
||||
matches!(ret.as_ref(), Type::Variable(_, b) if b == "b"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structures() {
|
||||
let parse_st = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_structure()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_st("structure { }"), Err(_)));
|
||||
assert!(matches!(parse_st("structure {"), Err(_)));
|
||||
assert!(matches!(parse_st("structure foo {}"), Err(_)));
|
||||
|
||||
assert!(matches!(
|
||||
parse_st("structure Foo {}"),
|
||||
Ok(StructureDef { name, fields, .. })
|
||||
if name == "Foo" && fields.is_empty()));
|
||||
|
||||
assert!(matches!(
|
||||
parse_st("structure Foo { bar }"),
|
||||
Ok(StructureDef { name, fields, .. })
|
||||
if name == "Foo" &&
|
||||
matches!(fields.as_slice(), &[StructureField { ref name, ref field_type, .. }]
|
||||
if name == "bar" && matches!(field_type, None))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_st("structure Foo { bar: Word8 }"),
|
||||
Ok(StructureDef { name, fields, .. })
|
||||
if name == "Foo" &&
|
||||
matches!(fields.as_slice(), &[StructureField { ref name, ref field_type, .. }]
|
||||
if name == "bar" &&
|
||||
matches!(field_type, Some(Type::Application(c, args))
|
||||
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
|
||||
args.is_empty()))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_st("structure Foo { bar: Word8, goo }"),
|
||||
Ok(StructureDef { name, fields, .. })
|
||||
if name == "Foo" &&
|
||||
matches!(fields.as_slice(),
|
||||
&[StructureField { ref name, ref field_type, .. },
|
||||
StructureField { name: ref name2, field_type: None, .. }]
|
||||
if name == "bar" &&
|
||||
name2 == "goo" &&
|
||||
matches!(field_type, Some(Type::Application(c, args))
|
||||
if matches!(c.as_ref(), Type::Constructor(_, c) if c == "Word8") &&
|
||||
args.is_empty()))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_st("structure Foo { bar: b c -> a, goo }"),
|
||||
Ok(StructureDef { name, fields, .. })
|
||||
if name == "Foo" &&
|
||||
matches!(fields.as_slice(),
|
||||
&[StructureField { ref name, ref field_type, .. },
|
||||
StructureField { name: ref name2, field_type: None, .. }]
|
||||
if name == "bar" &&
|
||||
name2 == "goo" &&
|
||||
matches!(field_type, Some(Type::Function(args, ret))
|
||||
if matches!(&args.as_slice(), &[Type::Variable(_, b), Type::Variable(_, c)]
|
||||
if b == "b" && c == "c") &&
|
||||
matches!(ret.as_ref(), Type::Variable(_, a) if a == "a")))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_st("structure Foo { bar: b c -> a, goo, }"),
|
||||
Ok(StructureDef { name, fields, .. })
|
||||
if name == "Foo" &&
|
||||
matches!(fields.as_slice(),
|
||||
&[StructureField { ref name, ref field_type, .. },
|
||||
StructureField { name: ref name2, field_type: None, .. }]
|
||||
if name == "bar" &&
|
||||
name2 == "goo" &&
|
||||
matches!(field_type, Some(Type::Function(args, ret))
|
||||
if matches!(&args.as_slice(), &[Type::Variable(_, b), Type::Variable(_, c)]
|
||||
if b == "b" && c == "c") &&
|
||||
matches!(ret.as_ref(), Type::Variable(_, a) if a == "a")))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_variant() {
|
||||
let parse_ev = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_enum_variant()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_ev("foo"), Err(_),));
|
||||
assert!(matches!(parse_ev("foo,"), Err(_),));
|
||||
assert!(matches!(parse_ev("Cons foo,"), Err(_),));
|
||||
assert!(matches!(parse_ev(""), Err(_)));
|
||||
|
||||
assert!(matches!(parse_ev("}"), Ok(None)));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ev("Cons,"),
|
||||
Ok(Some(EnumerationVariant { name, argument, .. }))
|
||||
if name == "Cons" && argument.is_none()));
|
||||
assert!(matches!(
|
||||
parse_ev("Cons }"),
|
||||
Ok(Some(EnumerationVariant { name, argument, .. }))
|
||||
if name == "Cons" && argument.is_none()));
|
||||
assert!(matches!(
|
||||
parse_ev("Cons, }"),
|
||||
Ok(Some(EnumerationVariant { name, argument, .. }))
|
||||
if name == "Cons" && argument.is_none()));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ev("Cons(Pair a),"),
|
||||
Ok(Some(EnumerationVariant { name, ref argument, .. }))
|
||||
if name == "Cons" &&
|
||||
matches!(argument, Some(Type::Application(typef, args))
|
||||
if matches!(typef.as_ref(), Type::Constructor(_, name)
|
||||
if name == "Pair") &&
|
||||
matches!(&args.as_slice(), &[Type::Variable(_, argname)]
|
||||
if argname == "a"))));
|
||||
assert!(matches!(
|
||||
parse_ev("Cons(Pair a) }"),
|
||||
Ok(Some(EnumerationVariant { name, ref argument, .. }))
|
||||
if name == "Cons" &&
|
||||
matches!(argument, Some(Type::Application(typef, args))
|
||||
if matches!(typef.as_ref(), Type::Constructor(_, name)
|
||||
if name == "Pair") &&
|
||||
matches!(&args.as_slice(), &[Type::Variable(_, argname)]
|
||||
if argname == "a"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ev("Cons(a b -> c) }"),
|
||||
Ok(Some(EnumerationVariant { name, ref argument, .. }))
|
||||
if name == "Cons" &&
|
||||
matches!(argument, Some(Type::Function(args, ret))
|
||||
if matches!(&args.as_slice(), &[Type::Variable(_, a), Type::Variable(_, b)]
|
||||
if a == "a" && b == "b") &&
|
||||
matches!(ret.as_ref(), Type::Variable(_, c) if c == "c"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enumerations() {
|
||||
let parse_en = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_enumeration()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_en("enumeration { }"), Err(_)));
|
||||
assert!(matches!(parse_en("enumeration {"), Err(_)));
|
||||
assert!(matches!(parse_en("enumeration"), Err(_)));
|
||||
|
||||
assert!(matches!(
|
||||
parse_en("enumeration Empty { }"),
|
||||
Ok(EnumerationDef { name, variants, .. })
|
||||
if name == "Empty" && variants.is_empty()));
|
||||
assert!(matches!(
|
||||
parse_en("enumeration Alternates { A, B }"),
|
||||
Ok(EnumerationDef { name, variants, .. })
|
||||
if name == "Alternates" &&
|
||||
matches!(&variants.as_slice(), &[
|
||||
EnumerationVariant { name: name1, argument: arg1, ..},
|
||||
EnumerationVariant { name: name2, argument: arg2, ..},
|
||||
] if name1 == "A" && arg1.is_none() &&
|
||||
name2 == "B" && arg2.is_none())));
|
||||
assert!(matches!(
|
||||
parse_en("enumeration Alternates { A, B, }"),
|
||||
Ok(EnumerationDef { name, variants, .. })
|
||||
if name == "Alternates" &&
|
||||
matches!(&variants.as_slice(), &[
|
||||
EnumerationVariant { name: name1, argument: arg1, ..},
|
||||
EnumerationVariant { name: name2, argument: arg2, ..},
|
||||
] if name1 == "A" && arg1.is_none() &&
|
||||
name2 == "B" && arg2.is_none())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expressions() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_ex(""), Err(_)));
|
||||
assert!(matches!(
|
||||
parse_ex("x"),
|
||||
Ok(Expression::Reference(n)) if n.as_printed() == "x"));
|
||||
assert!(matches!(
|
||||
parse_ex("(x)"),
|
||||
Ok(Expression::Reference(n)) if n.as_printed() == "x"));
|
||||
assert!(matches!(
|
||||
parse_ex("'c'"),
|
||||
Ok(Expression::Value(ConstantValue::Character(_, _)))
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_ex("\"c\""),
|
||||
Ok(Expression::Value(ConstantValue::String(_, _)))
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_ex("1"),
|
||||
Ok(Expression::Value(ConstantValue::Integer(_, _)))
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_ex("(1)"),
|
||||
Ok(Expression::Value(ConstantValue::Integer(_, _)))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enumeration_values() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_ex("Hello::world"), Err(_)));
|
||||
assert!(matches!(
|
||||
parse_ex("Hello::World"),
|
||||
Ok(Expression::EnumerationValue(t, v, None))
|
||||
if t.as_printed() == "Hello" &&
|
||||
v.as_printed() == "World"));
|
||||
assert!(matches!(
|
||||
parse_ex("Hello::World(a)"),
|
||||
Ok(Expression::EnumerationValue(t, v, Some(_)))
|
||||
if t.as_printed() == "Hello" &&
|
||||
v.as_printed() == "World"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structure_value() {
|
||||
let parse_st = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(parse_st("Foo{ , }"), Err(_)));
|
||||
assert!(matches!(parse_st("Foo{ foo, }"), Err(_)));
|
||||
assert!(matches!(parse_st("Foo{ foo: , }"), Err(_)));
|
||||
assert!(matches!(parse_st("Foo{ , foo: 1, }"), Err(_)));
|
||||
assert!(matches!(
|
||||
parse_st("Foo{ foo: 1 }"),
|
||||
Ok(Expression::StructureValue(sname, values))
|
||||
if sname.as_printed() == "Foo" &&
|
||||
matches!(values.as_slice(), [FieldValue{ field, value }]
|
||||
if field.as_printed() == "foo" &&
|
||||
matches!(value, Expression::Value(ConstantValue::Integer(_,_))))));
|
||||
assert!(matches!(
|
||||
parse_st("Foo{ foo: 1, }"),
|
||||
Ok(Expression::StructureValue(sname, values))
|
||||
if sname.as_printed() == "Foo" &&
|
||||
matches!(values.as_slice(), [FieldValue{ field, value }]
|
||||
if field.as_printed() == "foo" &&
|
||||
matches!(value, Expression::Value(ConstantValue::Integer(_,_))))));
|
||||
assert!(matches!(
|
||||
parse_st("Foo{ foo: 1, bar: \"foo\" }"),
|
||||
Ok(Expression::StructureValue(sname, values))
|
||||
if sname.as_printed() == "Foo" &&
|
||||
matches!(values.as_slice(), [FieldValue{ field: f1, value: v1 },
|
||||
FieldValue{ field: f2, value: v2 }]
|
||||
if f1.as_printed() == "foo" &&
|
||||
f2.as_printed() == "bar" &&
|
||||
matches!(v1, Expression::Value(ConstantValue::Integer(_,_))) &&
|
||||
matches!(v2, Expression::Value(ConstantValue::String(_,_))))));
|
||||
assert!(matches!(
|
||||
parse_st("Foo{ foo: 1, bar: \"foo\", }"),
|
||||
Ok(Expression::StructureValue(sname, values))
|
||||
if sname.as_printed() == "Foo" &&
|
||||
matches!(values.as_slice(), [FieldValue{ field: f1, value: v1 },
|
||||
FieldValue{ field: f2, value: v2 }]
|
||||
if f1.as_printed() == "foo" &&
|
||||
f2.as_printed() == "bar" &&
|
||||
matches!(v1, Expression::Value(ConstantValue::Integer(_,_))) &&
|
||||
matches!(v2, Expression::Value(ConstantValue::String(_,_))))));
|
||||
assert!(matches!(parse_st("Foo{ foo: 1,, bar: \"foo\", }"), Err(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infix_and_precedence() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.add_infix_precedence("+", parse::Associativity::Left, 6);
|
||||
result.add_infix_precedence("*", parse::Associativity::Right, 7);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("0"),
|
||||
Ok(Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value, .. })))
|
||||
if value == 0));
|
||||
assert!(matches!(
|
||||
parse_ex("(0)"),
|
||||
Ok(Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value, .. })))
|
||||
if value == 0));
|
||||
assert!(matches!(
|
||||
parse_ex("((0))"),
|
||||
Ok(Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value, .. })))
|
||||
if value == 0));
|
||||
assert!(matches!(
|
||||
parse_ex("1 + 2"),
|
||||
Ok(Expression::Call(plus, CallKind::Infix, args))
|
||||
if matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v1, .. })),
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v2, .. }))
|
||||
] if *v1 == 1 && *v2 == 2)));
|
||||
assert!(matches!(
|
||||
parse_ex("1 + 2 + 3"),
|
||||
Ok(Expression::Call(plus, CallKind::Infix, args))
|
||||
if matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Call(innerplus, CallKind::Infix, inner_args),
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v3, .. }))
|
||||
] if *v3 == 3 &&
|
||||
matches!(innerplus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(inner_args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v1, .. })),
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v2, .. }))
|
||||
] if *v1 == 1 && *v2 == 2))));
|
||||
assert!(matches!(
|
||||
parse_ex("1 * 2 * 3"),
|
||||
Ok(Expression::Call(times, CallKind::Infix, args))
|
||||
if matches!(times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v1, .. })),
|
||||
Expression::Call(innertimes, CallKind::Infix, inner_args),
|
||||
] if *v1 == 1 &&
|
||||
matches!(innertimes.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(inner_args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v2, .. })),
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: v3, .. }))
|
||||
] if *v2 == 2 && *v3 == 3))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("1 + 2 * 3 + 4"),
|
||||
Ok(Expression::Call(plus_right, CallKind::Infix, outer_args)) if
|
||||
matches!(plus_right.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(outer_args.as_slice(), [
|
||||
Expression::Call(plus_left, CallKind::Infix, left_args),
|
||||
Expression::Value(ConstantValue::Integer(_, v4))
|
||||
] if
|
||||
matches!(v4, IntegerWithBase{ value: 4, .. }) &&
|
||||
matches!(plus_left.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(left_args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, v1)),
|
||||
Expression::Call(times, CallKind::Infix, times_args)
|
||||
] if
|
||||
matches!(v1, IntegerWithBase{ value: 1, .. }) &&
|
||||
matches!(times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(times_args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, v2)),
|
||||
Expression::Value(ConstantValue::Integer(_, v3))
|
||||
] if
|
||||
matches!(v2, IntegerWithBase{ value: 2, .. }) &&
|
||||
matches!(v3, IntegerWithBase{ value: 3, .. }))))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("1 * 2 + 3 * 4"),
|
||||
Ok(Expression::Call(plus, CallKind::Infix, outer_args)) if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(outer_args.as_slice(), [
|
||||
Expression::Call(left_times, CallKind::Infix, left_args),
|
||||
Expression::Call(right_times, CallKind::Infix, right_args)
|
||||
] if
|
||||
matches!(left_times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(right_times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(left_args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, v1)),
|
||||
Expression::Value(ConstantValue::Integer(_, v2)),
|
||||
] if
|
||||
matches!(v1, IntegerWithBase { value: 1, .. }) &&
|
||||
matches!(v2, IntegerWithBase { value: 2, .. })) &&
|
||||
matches!(right_args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, v3)),
|
||||
Expression::Value(ConstantValue::Integer(_, v4)),
|
||||
] if
|
||||
matches!(v3, IntegerWithBase { value: 3, .. }) &&
|
||||
matches!(v4, IntegerWithBase { value: 4, .. })))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calls() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.add_infix_precedence("+", parse::Associativity::Left, 6);
|
||||
result.add_infix_precedence("*", parse::Associativity::Right, 7);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("f()"),
|
||||
Ok(Expression::Call(f, CallKind::Normal, args)) if
|
||||
matches!(f.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
args.is_empty()));
|
||||
assert!(matches!(
|
||||
parse_ex("f(a)"),
|
||||
Ok(Expression::Call(f, CallKind::Normal, args)) if
|
||||
matches!(f.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "a")));
|
||||
assert!(matches!(
|
||||
parse_ex("f(a,b)"),
|
||||
Ok(Expression::Call(f, CallKind::Normal, args)) if
|
||||
matches!(f.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Reference(a),
|
||||
Expression::Reference(b),
|
||||
] if a.as_printed() == "a" && b.as_printed() == "b")));
|
||||
assert!(matches!(
|
||||
parse_ex("f(a,b,)"),
|
||||
Ok(Expression::Call(f, CallKind::Normal, args)) if
|
||||
matches!(f.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Reference(a),
|
||||
Expression::Reference(b),
|
||||
] if a.as_printed() == "a" && b.as_printed() == "b")));
|
||||
assert!(matches!(parse_ex("f(,a,b,)"), Err(_)));
|
||||
assert!(matches!(parse_ex("f(a,,b,)"), Err(_)));
|
||||
assert!(matches!(parse_ex("f(a,b,,)"), Err(_)));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("f()()"),
|
||||
Ok(Expression::Call(f, CallKind::Normal, args)) if
|
||||
matches!(f.as_ref(), Expression::Call(inner, CallKind::Normal, inner_args) if
|
||||
matches!(inner.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
inner_args.is_empty()) &&
|
||||
args.is_empty()));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("f() + 1"),
|
||||
Ok(Expression::Call(plus, CallKind::Infix, args)) if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Call(subcall, CallKind::Normal, subargs),
|
||||
Expression::Value(ConstantValue::Integer(_, v1))
|
||||
] if
|
||||
matches!(v1, IntegerWithBase{ value: 1, .. }) &&
|
||||
matches!(subcall.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
subargs.is_empty())));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("f(a + b, c*d)"),
|
||||
Ok(Expression::Call(eff, CallKind::Normal, args)) if
|
||||
matches!(eff.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Call(plus, CallKind::Infix, pargs),
|
||||
Expression::Call(times, CallKind::Infix, targs),
|
||||
] if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(pargs.as_slice(), [ Expression::Reference(a), Expression::Reference(b) ] if
|
||||
a.as_printed() == "a" && b.as_printed() == "b") &&
|
||||
matches!(targs.as_slice(), [ Expression::Reference(c), Expression::Reference(d) ] if
|
||||
c.as_printed() == "c" && d.as_printed() == "d"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("f(a + b, c*d,)"),
|
||||
Ok(Expression::Call(eff, CallKind::Normal, args)) if
|
||||
matches!(eff.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Call(plus, CallKind::Infix, pargs),
|
||||
Expression::Call(times, CallKind::Infix, targs),
|
||||
] if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(pargs.as_slice(), [ Expression::Reference(a), Expression::Reference(b) ] if
|
||||
a.as_printed() == "a" && b.as_printed() == "b") &&
|
||||
matches!(targs.as_slice(), [ Expression::Reference(c), Expression::Reference(d) ] if
|
||||
c.as_printed() == "c" && d.as_printed() == "d"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("3 + f(1 + 2)"),
|
||||
Ok(Expression::Call(plus, CallKind::Infix, args)) if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, v3)),
|
||||
Expression::Call(eff, CallKind::Normal, fargs)
|
||||
] if
|
||||
matches!(v3, IntegerWithBase{ value: 3, .. }) &&
|
||||
matches!(eff.as_ref(), Expression::Reference(n) if n.as_printed() == "f") &&
|
||||
matches!(fargs.as_slice(), [Expression::Call(p, CallKind::Infix, pargs)] if
|
||||
matches!(p.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(pargs.as_slice(), [Expression::Value(v1), Expression::Value(v2)] if
|
||||
matches!(v1, ConstantValue::Integer(_, IntegerWithBase { value: 1, .. })) &&
|
||||
matches!(v2, ConstantValue::Integer(_, IntegerWithBase { value: 2, .. })))))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("(f . g)(1 + 2)"),
|
||||
Ok(Expression::Call(fg, CallKind::Normal, args)) if
|
||||
matches!(fg.as_ref(), Expression::Call(dot, CallKind::Infix, fgargs) if
|
||||
matches!(dot.as_ref(), Expression::Reference(n) if n.as_printed() == ".") &&
|
||||
matches!(fgargs.as_slice(), [Expression::Reference(f), Expression::Reference(g)] if
|
||||
f.as_printed() == "f" && g.as_printed() == "g")) &&
|
||||
matches!(args.as_slice(), [Expression::Call(plus, CallKind::Infix, pargs)] if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(pargs.as_slice(), [Expression::Value(v1), Expression::Value(v2)] if
|
||||
matches!(v1, ConstantValue::Integer(_, IntegerWithBase{ value: 1, .. })) &&
|
||||
matches!(v2, ConstantValue::Integer(_, IntegerWithBase{ value: 2, .. }))))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("a + b(2 + 3) * c"),
|
||||
Ok(Expression::Call(plus, CallKind::Infix, pargs)) if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(pargs.as_slice(), [
|
||||
Expression::Reference(a),
|
||||
Expression::Call(times, CallKind::Infix, targs)
|
||||
] if a.as_printed() == "a" &&
|
||||
matches!(times.as_ref(), Expression::Reference(n) if n.as_printed() == "*") &&
|
||||
matches!(targs.as_slice(), [
|
||||
Expression::Call(b, CallKind::Normal, bargs),
|
||||
Expression::Reference(c),
|
||||
] if c.as_printed() == "c" &&
|
||||
matches!(b.as_ref(), Expression::Reference(n) if n.as_printed() == "b") &&
|
||||
matches!(bargs.as_slice(), [Expression::Call(plus, CallKind::Infix, pargs)] if
|
||||
matches!(plus.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(pargs.as_slice(), [
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: 2, .. })),
|
||||
Expression::Value(ConstantValue::Integer(_, IntegerWithBase{ value: 3, .. }))
|
||||
]))))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_and_postfix() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.add_infix_precedence("+", parse::Associativity::Left, 4);
|
||||
result.add_infix_precedence("*", parse::Associativity::Left, 8);
|
||||
result.add_prefix_precedence("++", 6);
|
||||
result.add_postfix_precedence("++", 6);
|
||||
result.add_prefix_precedence("--", 7);
|
||||
result.add_postfix_precedence("--", 7);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("++a"),
|
||||
Ok(Expression::Call(pp, CallKind::Prefix, args)) if
|
||||
matches!(pp.as_ref(), Expression::Reference(n) if n.as_printed() == "++") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "a")));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("a--"),
|
||||
Ok(Expression::Call(pp, CallKind::Postfix, args)) if
|
||||
matches!(pp.as_ref(), Expression::Reference(n) if n.as_printed() == "--") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "a")));
|
||||
|
||||
// the prefix is weaker than the postfix, so it should be the outside
|
||||
// operatotr
|
||||
assert!(matches!(
|
||||
parse_ex("++a--"),
|
||||
Ok(Expression::Call(pp, CallKind::Prefix, args)) if
|
||||
matches!(pp.as_ref(), Expression::Reference(n) if n.as_printed() == "++") &&
|
||||
matches!(args.as_slice(), [Expression::Call(mm, CallKind::Postfix, args)] if
|
||||
matches!(mm.as_ref(), Expression::Reference(n) if n.as_printed() == "--") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "a"))));
|
||||
|
||||
// the prefix is stronger than the postfix, so it should be the inside
|
||||
// operator
|
||||
assert!(matches!(
|
||||
parse_ex("--a++"),
|
||||
Ok(Expression::Call(pp, CallKind::Postfix, args)) if
|
||||
matches!(pp.as_ref(), Expression::Reference(n) if n.as_printed() == "++") &&
|
||||
matches!(args.as_slice(), [Expression::Call(mm, CallKind::Prefix, args)] if
|
||||
matches!(mm.as_ref(), Expression::Reference(n) if n.as_printed() == "--") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "a"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("a++ + b"),
|
||||
Ok(Expression::Call(p, CallKind::Infix, args)) if
|
||||
matches!(p.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Call(mm, CallKind::Postfix, args),
|
||||
Expression::Reference(n)
|
||||
] if n.as_printed() == "b" &&
|
||||
matches!(mm.as_ref(), Expression::Reference(n) if n.as_printed() == "++") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "a"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("a + ++ b"),
|
||||
Ok(Expression::Call(p, CallKind::Infix, args)) if
|
||||
matches!(p.as_ref(), Expression::Reference(n) if n.as_printed() == "+") &&
|
||||
matches!(args.as_slice(), [
|
||||
Expression::Reference(n),
|
||||
Expression::Call(mm, CallKind::Prefix, args),
|
||||
] if n.as_printed() == "a" &&
|
||||
matches!(mm.as_ref(), Expression::Reference(n) if n.as_printed() == "++") &&
|
||||
matches!(args.as_slice(), [Expression::Reference(n)] if n.as_printed() == "b"))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("a * ++ b"),
|
||||
Err(ParserError::UnexpectedToken{ token: Token::OperatorName(pp), .. })
|
||||
if pp == "++"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("{}"),
|
||||
Ok(Expression::Block(_, void)) if
|
||||
matches!(void.as_slice(), [Statement::Expression(call)] if
|
||||
matches!(call, Expression::Call(void, CallKind::Normal, vargs) if
|
||||
matches!(void.as_ref(), Expression::Reference(n) if
|
||||
n.as_printed() == "%prim%void") &&
|
||||
vargs.is_empty()))));
|
||||
assert!(matches!(
|
||||
parse_ex("{ x }"),
|
||||
Ok(Expression::Block(_, x)) if
|
||||
matches!(x.as_slice(), [Statement::Expression(Expression::Reference(n))] if
|
||||
n.as_printed() == "x")));
|
||||
assert!(matches!(
|
||||
parse_ex("{ x; }"),
|
||||
Ok(Expression::Block(_, x)) if
|
||||
matches!(x.as_slice(), [
|
||||
Statement::Expression(Expression::Reference(n)),
|
||||
Statement::Expression(Expression::Call(primv, CallKind::Normal, vargs)),
|
||||
] if n.as_printed() == "x" && vargs.is_empty() &&
|
||||
matches!(primv.as_ref(), Expression::Reference(n) if
|
||||
n.as_printed() == "%prim%void"))));
|
||||
assert!(matches!(
|
||||
parse_ex("{ x; y }"),
|
||||
Ok(Expression::Block(_, x)) if
|
||||
matches!(x.as_slice(), [
|
||||
Statement::Expression(Expression::Reference(x)),
|
||||
Statement::Expression(Expression::Reference(y)),
|
||||
] if x.as_printed() == "x" && y.as_printed() == "y")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindings() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("{ let x = y; }"),
|
||||
Ok(Expression::Block(_, x)) if
|
||||
matches!(x.as_slice(), [Statement::Binding(b), Statement::Expression(_)] if
|
||||
!b.mutable &&
|
||||
b.variable.as_printed() == "x" &&
|
||||
matches!(b.value, Expression::Reference(ref n) if n.as_printed() == "y"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditionals() {
|
||||
let parse_ex = |str| {
|
||||
let lexer = Lexer::from(str);
|
||||
let mut result = Parser::new(0, lexer);
|
||||
result.parse_expression()
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("if x { y } else { z }"),
|
||||
Ok(Expression::Conditional(cond)) if
|
||||
matches!(cond.test.as_ref(), Expression::Reference(n) if n.as_printed() == "x") &&
|
||||
matches!(cond.consequent.as_ref(), Expression::Block(_, cs) if
|
||||
matches!(cs.as_slice(), [Statement::Expression(Expression::Reference(n))] if
|
||||
n.as_printed() == "y")) &&
|
||||
matches!(cond.alternative.as_ref(), Some(expr) if
|
||||
matches!(expr.as_ref(), Expression::Block(_, ast) if
|
||||
matches!(ast.as_slice(), [Statement::Expression(Expression::Reference(n))] if
|
||||
n.as_printed() == "z")))));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("if x { y }"),
|
||||
Ok(Expression::Conditional(cond)) if
|
||||
matches!(cond.test.as_ref(), Expression::Reference(n) if n.as_printed() == "x") &&
|
||||
matches!(cond.consequent.as_ref(), Expression::Block(_, cs) if
|
||||
matches!(cs.as_slice(), [Statement::Expression(Expression::Reference(n))] if
|
||||
n.as_printed() == "y")) &&
|
||||
cond.alternative.is_none()));
|
||||
|
||||
assert!(matches!(parse_ex("if x v { z }"), Err(_)));
|
||||
|
||||
assert!(matches!(
|
||||
parse_ex("if x + y { z }"),
|
||||
Ok(Expression::Conditional(cond)) if
|
||||
matches!(cond.test.as_ref(), Expression::Call(_, CallKind::Infix, _))));
|
||||
}
|
||||
709
src/syntax/tokens.rs
Normal file
709
src/syntax/tokens.rs
Normal file
@@ -0,0 +1,709 @@
|
||||
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,
|
||||
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::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(usize),
|
||||
}
|
||||
|
||||
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::Working(LexerState {
|
||||
stream: value.char_indices(),
|
||||
buffer: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(state.stream.offset());
|
||||
None
|
||||
}
|
||||
|
||||
Ok(Some(ltoken)) => Some(Ok(ltoken)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LexerState<'a> {
|
||||
fn next_char(&mut self) -> Option<(usize, char)> {
|
||||
let result = self.buffer.take().or_else(|| self.stream.next());
|
||||
result
|
||||
}
|
||||
|
||||
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::Colon),
|
||||
',' => 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),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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 = 0;
|
||||
|
||||
while let Some((idx, char)) = self.next_char() {
|
||||
if let Some(digit) = char.to_digit(16) {
|
||||
value = (value * 16) + digit;
|
||||
continue;
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
.expect(format!("Can get at least one token from {s:?}").as_str())
|
||||
.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: 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 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());
|
||||
}
|
||||
Reference in New Issue
Block a user