Merge branch 'master' of ../simplexpr into config_rework

This commit is contained in:
elkowar 2021-07-21 19:21:15 +02:00
commit 810dbb1368
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
42 changed files with 1927 additions and 0 deletions

656
crates/simplexpr/Cargo.lock generated Normal file
View file

@ -0,0 +1,656 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ascii-canvas"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
dependencies = [
"term",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "beef"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409"
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"terminal_size",
"winapi",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "ena"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "insta"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244"
dependencies = [
"console",
"lazy_static",
"serde",
"serde_json",
"serde_yaml",
"similar",
"uuid",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "lalrpop"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988"
dependencies = [
"ascii-canvas",
"atty",
"bit-set",
"diff",
"ena",
"itertools",
"lalrpop-util",
"petgraph",
"pico-args",
"regex",
"regex-syntax",
"string_cache",
"term",
"tiny-keccak",
"unicode-xid",
]
[[package]]
name = "lalrpop-util"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278"
dependencies = [
"regex",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "logos"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax",
"syn",
"utf8-ranges",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
dependencies = [
"dtoa",
"linked-hash-map",
"serde",
"yaml-rust",
]
[[package]]
name = "similar"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
[[package]]
name = "simplexpr"
version = "0.1.0"
dependencies = [
"insta",
"itertools",
"lalrpop",
"lalrpop-util",
"logos",
"maplit",
"regex",
"serde",
"serde_json",
"strum",
"thiserror",
]
[[package]]
name = "siphasher"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27"
[[package]]
name = "string_cache"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"precomputed-hash",
]
[[package]]
name = "strum"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "thiserror"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "utf8-ranges"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View file

@ -0,0 +1,29 @@
[package]
name = "simplexpr"
version = "0.1.0"
edition = "2018"
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
build = "build.rs"
[dependencies]
lalrpop-util = "0.19.5"
regex = "1"
itertools = "0.10"
thiserror = "1.0"
maplit = "1.0"
logos = "0.12"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
strum = { version = "0.21", features = ["derive"] }
[build-dependencies]
lalrpop = "0.19.5"
[dev-dependencies]
insta = "1.7"

View file

@ -0,0 +1,6 @@
# simplexpr
simplexpr is a parser and interpreter for a simple expression syntax that can be embedded into other applications or crates.
It is being developed to be used in [eww](https://github.com/elkowar/eww), but may also other uses.
For now, this is highly experimental, unstable, and ugly. You most definitely do not want to use this crate.

View file

@ -0,0 +1,4 @@
extern crate lalrpop;
fn main() {
lalrpop::Configuration::new().log_verbose().process_current_dir().unwrap();
}

View file

@ -0,0 +1 @@
nightly

View file

@ -0,0 +1,14 @@
unstable_features = true
fn_single_line = false
max_width = 130
reorder_impl_items = true
merge_imports = true
normalize_comments = true
use_field_init_shorthand = true
#wrap_comments = true
combine_control_expr = false
condense_wildcard_suffixes = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
use_small_heuristics = "Max"

View file

View file

@ -0,0 +1,93 @@
use crate::dynval::DynVal;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
/// stores the left and right end of a span, and a given file identifier.
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct Span(pub usize, pub usize, pub usize);
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.0, self.1)
}
}
impl std::fmt::Debug for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.0, self.1)
}
}
#[rustfmt::skip]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)]
pub enum BinOp {
#[strum(serialize = "+") ] Plus,
#[strum(serialize = "-") ] Minus,
#[strum(serialize = "*") ] Times,
#[strum(serialize = "/") ] Div,
#[strum(serialize = "%") ] Mod,
#[strum(serialize = "==")] Equals,
#[strum(serialize = "!=")] NotEquals,
#[strum(serialize = "&&")] And,
#[strum(serialize = "||")] Or,
#[strum(serialize = ">") ] GT,
#[strum(serialize = "<") ] LT,
#[strum(serialize = "?:")] Elvis,
#[strum(serialize = "=~")] RegexMatch,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)]
pub enum UnaryOp {
#[strum(serialize = "!")]
Not,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SimplExpr {
Literal(Span, DynVal),
VarRef(Span, String),
BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),
UnaryOp(Span, UnaryOp, Box<SimplExpr>),
IfElse(Span, Box<SimplExpr>, Box<SimplExpr>, Box<SimplExpr>),
JsonAccess(Span, Box<SimplExpr>, Box<SimplExpr>),
FunctionCall(Span, String, Vec<SimplExpr>),
}
impl std::fmt::Display for SimplExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SimplExpr::VarRef(_, x) => write!(f, "{}", x),
SimplExpr::Literal(_, x) => write!(f, "\"{}\"", x),
SimplExpr::BinOp(_, l, op, r) => write!(f, "({} {} {})", l, op, r),
SimplExpr::UnaryOp(_, op, x) => write!(f, "{}{}", op, x),
SimplExpr::IfElse(_, a, b, c) => write!(f, "(if {} then {} else {})", a, b, c),
SimplExpr::JsonAccess(_, value, index) => write!(f, "{}[{}]", value, index),
SimplExpr::FunctionCall(_, function_name, args) => {
write!(f, "{}({})", function_name, args.iter().join(", "))
}
}
}
}
impl SimplExpr {
pub fn literal(span: Span, s: String) -> Self {
Self::Literal(span, DynVal(s, Some(span)))
}
pub fn span(&self) -> Span {
match self {
SimplExpr::Literal(span, _) => *span,
SimplExpr::VarRef(span, _) => *span,
SimplExpr::BinOp(span, ..) => *span,
SimplExpr::UnaryOp(span, ..) => *span,
SimplExpr::IfElse(span, ..) => *span,
SimplExpr::JsonAccess(span, ..) => *span,
SimplExpr::FunctionCall(span, ..) => *span,
}
}
}
impl std::fmt::Debug for SimplExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}

View file

@ -0,0 +1,218 @@
use crate::ast::Span;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{fmt, iter::FromIterator, str::FromStr};
pub type Result<T> = std::result::Result<T, ConversionError>;
#[derive(Debug, thiserror::Error)]
#[error("Failed to turn {value} into a value of type {target_type}")]
pub struct ConversionError {
pub value: DynVal,
pub target_type: &'static str,
pub source: Option<Box<dyn std::error::Error>>,
}
impl ConversionError {
fn new(value: DynVal, target_type: &'static str, source: impl std::error::Error + 'static) -> Self {
ConversionError { value, target_type, source: Some(Box::new(source)) }
}
pub fn span(&self) -> Option<Span> {
self.value.1
}
}
#[derive(Clone, Deserialize, Serialize, Default, Eq)]
pub struct DynVal(pub String, pub Option<Span>);
impl From<String> for DynVal {
fn from(s: String) -> Self {
DynVal(s, None)
}
}
impl fmt::Display for DynVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for DynVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", self.0)
}
}
/// Manually implement equality, to allow for values in different formats (i.e. "1" and "1.0") to still be considered as equal.
impl std::cmp::PartialEq<Self> for DynVal {
fn eq(&self, other: &Self) -> bool {
if let (Ok(a), Ok(b)) = (self.as_f64(), other.as_f64()) {
a == b
} else {
self.0 == other.0
}
}
}
impl FromIterator<DynVal> for DynVal {
fn from_iter<T: IntoIterator<Item = DynVal>>(iter: T) -> Self {
DynVal(iter.into_iter().join(""), None)
}
}
impl std::str::FromStr for DynVal {
type Err = ConversionError;
/// parses the value, trying to turn it into a number and a boolean first,
/// before deciding that it is a string.
fn from_str(s: &str) -> Result<Self> {
Ok(DynVal::from_string(s.to_string()))
}
}
pub trait FromDynVal: Sized {
type Err;
fn from_dynval(x: &DynVal) -> std::result::Result<Self, Self::Err>;
}
impl<E, T: FromStr<Err = E>> FromDynVal for T {
type Err = E;
fn from_dynval(x: &DynVal) -> std::result::Result<Self, Self::Err> {
x.0.parse()
}
}
macro_rules! impl_dynval_from {
($($t:ty),*) => {
$(impl From<$t> for DynVal {
fn from(x: $t) -> Self { DynVal(x.to_string(), None) }
})*
};
}
impl_dynval_from!(bool, i32, u32, f32, u8, f64, &str);
impl From<&serde_json::Value> for DynVal {
fn from(v: &serde_json::Value) -> Self {
DynVal(
v.as_str()
.map(|x| x.to_string())
.or_else(|| serde_json::to_string(v).ok())
.unwrap_or_else(|| "<invalid json value>".to_string()),
None,
)
}
}
impl DynVal {
pub fn at(self, span: Span) -> Self {
DynVal(self.0, Some(span))
}
pub fn span(&self) -> Option<Span> {
self.1
}
pub fn from_string(s: String) -> Self {
DynVal(s, None)
}
pub fn read_as<E, T: FromDynVal<Err = E>>(&self) -> std::result::Result<T, E> {
T::from_dynval(self)
}
pub fn into_inner(self) -> String {
self.0
}
/// This will never fail
pub fn as_string(&self) -> Result<String> {
Ok(self.0.to_owned())
}
pub fn as_f64(&self) -> Result<f64> {
self.0.parse().map_err(|e| ConversionError::new(self.clone(), "f64", e))
}
pub fn as_i32(&self) -> Result<i32> {
self.0.parse().map_err(|e| ConversionError::new(self.clone(), "i32", e))
}
pub fn as_bool(&self) -> Result<bool> {
self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", e))
}
pub fn as_duration(&self) -> Result<std::time::Duration> {
use std::time::Duration;
let s = &self.0;
if s.ends_with("ms") {
Ok(Duration::from_millis(
s.trim_end_matches("ms").parse().map_err(|e| ConversionError::new(self.clone(), "integer", e))?,
))
} else if s.ends_with('s') {
Ok(Duration::from_secs(
s.trim_end_matches('s').parse().map_err(|e| ConversionError::new(self.clone(), "integer", e))?,
))
} else if s.ends_with('m') {
Ok(Duration::from_secs(
s.trim_end_matches('m').parse::<u64>().map_err(|e| ConversionError::new(self.clone(), "integer", e))? * 60,
))
} else if s.ends_with('h') {
Ok(Duration::from_secs(
s.trim_end_matches('h').parse::<u64>().map_err(|e| ConversionError::new(self.clone(), "integer", e))? * 60 * 60,
))
} else {
Err(ConversionError { value: self.clone(), target_type: "duration", source: None })
}
}
// pub fn as_vec(&self) -> Result<Vec<String>> {
// match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
// Some(content) => {
// let mut items: Vec<String> = content.split(',').map(|x: &str| x.to_string()).collect();
// let mut removed = 0;
// for times_ran in 0..items.len() {
//// escapes `,` if there's a `\` before em
// if items[times_ran - removed].ends_with('\\') {
// items[times_ran - removed].pop();
// let it = items.remove((times_ran + 1) - removed);
// items[times_ran - removed] += ",";
// items[times_ran - removed] += &it;
// removed += 1;
//}
// Ok(items)
//}
// None => Err(ConversionError { value: self.clone(), target_type: "vec", source: None }),
//}
pub fn as_json_value(&self) -> Result<serde_json::Value> {
serde_json::from_str::<serde_json::Value>(&self.0)
.map_err(|e| ConversionError::new(self.clone(), "json-value", Box::new(e)))
}
}
#[cfg(test)]
mod test {
// use super::*;
// use pretty_assertions::assert_eq;
//#[test]
// fn test_parse_vec() {
// assert_eq!(vec![""], parse_vec("[]".to_string()).unwrap(), "should be able to parse empty lists");
// assert_eq!(vec!["hi"], parse_vec("[hi]".to_string()).unwrap(), "should be able to parse single element list");
// assert_eq!(
// vec!["hi", "ho", "hu"],
// parse_vec("[hi,ho,hu]".to_string()).unwrap(),
//"should be able to parse three element list"
//);
// assert_eq!(vec!["hi,ho"], parse_vec("[hi\\,ho]".to_string()).unwrap(), "should be able to parse list with escaped comma");
// assert_eq!(
// vec!["hi,ho", "hu"],
// parse_vec("[hi\\,ho,hu]".to_string()).unwrap(),
//"should be able to parse two element list with escaped comma"
//);
// assert!(parse_vec("".to_string()).is_err(), "Should fail when parsing empty string");
// assert!(parse_vec("[a,b".to_string()).is_err(), "Should fail when parsing unclosed list");
// assert!(parse_vec("a]".to_string()).is_err(), "Should fail when parsing unopened list");
//}
}

View file

@ -0,0 +1,66 @@
use crate::{
ast::Span,
dynval,
parser::lexer::{self, LexicalError},
};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Parse error: {source}")]
ParseError { file_id: usize, source: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError> },
#[error("Type error: {0}")]
ConversionError(#[from] dynval::ConversionError),
#[error("{1}")]
Spanned(Span, Box<dyn std::error::Error>),
#[error(transparent)]
Eval(#[from] crate::eval::EvalError),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error>),
}
impl Error {
pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>) -> Self {
Error::ParseError { file_id, source: err }
}
pub fn at(self, span: Span) -> Self {
Self::Spanned(span, Box::new(self))
}
pub fn get_span(&self) -> Option<Span> {
match self {
Self::ParseError { file_id, source } => get_parse_error_span(*file_id, source),
Self::Spanned(span, _) => Some(*span),
Self::Eval(err) => err.span(),
Self::ConversionError(err) => err.span(),
_ => None,
}
}
}
fn get_parse_error_span(
file_id: usize,
err: &lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>,
) -> Option<Span> {
match err {
lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location, file_id)),
lalrpop_util::ParseError::UnrecognizedEOF { location, expected: _ } => Some(Span(*location, *location, file_id)),
lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Some(Span(token.0, token.2, file_id)),
lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2, file_id)),
lalrpop_util::ParseError::User { error: LexicalError(l, r) } => Some(Span(*l, *r, file_id)),
}
}
#[macro_export]
macro_rules! spanned {
($err:ty, $span:expr, $block:expr) => {{
let span = $span;
let result: Result<_, $err> = try { $block };
result.at(span)
}};
}

View file

@ -0,0 +1,223 @@
use itertools::Itertools;
use crate::{
ast::{BinOp, SimplExpr, Span, UnaryOp},
dynval::{ConversionError, DynVal},
};
use std::collections::HashMap;
#[derive(Debug, thiserror::Error)]
pub enum EvalError {
#[error("Tried to reference variable `{0}`, but we cannot access variables here")]
NoVariablesAllowed(String),
#[error("Invalid regex: {0}")]
InvalidRegex(#[from] regex::Error),
#[error("got unresolved variable `{0}`")]
UnresolvedVariable(VarName),
#[error("Type error: {0}")]
ConversionError(#[from] ConversionError),
#[error("Incorrect number of arguments given to function: {0}")]
WrongArgCount(String),
#[error("Unknown function {0}")]
UnknownFunction(String),
#[error("Unknown variable {0}")]
UnknownVariable(String),
#[error("Unable to index into value {0}")]
CannotIndex(String),
#[error("{1}")]
Spanned(Span, Box<EvalError>),
}
impl EvalError {
pub fn span(&self) -> Option<Span> {
match self {
EvalError::Spanned(span, _) => Some(*span),
EvalError::ConversionError(err) => err.span(),
_ => None,
}
}
pub fn at(self, span: Span) -> Self {
Self::Spanned(span, Box::new(self))
}
}
type VarName = String;
pub trait FunctionSource {
type Err;
fn run_fn(&self, name: &str, args: &[DynVal]) -> Result<DynVal, Self::Err>;
}
impl SimplExpr {
pub fn map_terminals_into(self, f: impl Fn(Self) -> Self) -> Self {
use SimplExpr::*;
match self {
BinOp(span, box a, op, box b) => BinOp(span, box f(a), op, box f(b)),
UnaryOp(span, op, box a) => UnaryOp(span, op, box f(a)),
IfElse(span, box a, box b, box c) => IfElse(span, box f(a), box f(b), box f(c)),
JsonAccess(span, box a, box b) => JsonAccess(span, box f(a), box f(b)),
FunctionCall(span, name, args) => FunctionCall(span, name, args.into_iter().map(f).collect()),
other => f(other),
}
}
/// resolve variable references in the expression. Fails if a variable cannot be resolved.
pub fn resolve_refs(self, variables: &HashMap<VarName, DynVal>) -> Result<Self, EvalError> {
use SimplExpr::*;
match self {
// Literal(x) => Ok(Literal(AttrValue::from_primitive(x.resolve_fully(&variables)?))),
Literal(span, x) => Ok(Literal(span, x)),
BinOp(span, box a, op, box b) => Ok(BinOp(span, box a.resolve_refs(variables)?, op, box b.resolve_refs(variables)?)),
UnaryOp(span, op, box x) => Ok(UnaryOp(span, op, box x.resolve_refs(variables)?)),
IfElse(span, box a, box b, box c) => {
Ok(IfElse(span, box a.resolve_refs(variables)?, box b.resolve_refs(variables)?, box c.resolve_refs(variables)?))
}
JsonAccess(span, box a, box b) => {
Ok(JsonAccess(span, box a.resolve_refs(variables)?, box b.resolve_refs(variables)?))
}
FunctionCall(span, function_name, args) => Ok(FunctionCall(
span,
function_name,
args.into_iter().map(|a| a.resolve_refs(variables)).collect::<Result<_, EvalError>>()?,
)),
VarRef(span, ref name) => match variables.get(name) {
Some(value) => Ok(Literal(span, value.clone())),
None => Err(EvalError::UnknownVariable(name.to_string()).at(span)),
},
}
}
pub fn var_refs(&self) -> Vec<&String> {
use SimplExpr::*;
match self {
Literal(..) => Vec::new(),
VarRef(_, name) => vec![name],
BinOp(_, box a, _, box b) | JsonAccess(_, box a, box b) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs
}
UnaryOp(_, _, box x) => x.var_refs(),
IfElse(_, box a, box b, box c) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs.append(&mut c.var_refs());
refs
}
FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs()).collect_vec(),
}
}
pub fn eval_no_vars(&self) -> Result<DynVal, EvalError> {
match self.eval(&HashMap::new()) {
Ok(x) => Ok(x),
Err(EvalError::UnknownVariable(name)) => Err(EvalError::NoVariablesAllowed(name)),
Err(x) => Err(x),
}
}
pub fn eval(&self, values: &HashMap<VarName, DynVal>) -> Result<DynVal, EvalError> {
let span = self.span();
let value = match self {
SimplExpr::Literal(_, x) => Ok(x.clone()),
SimplExpr::VarRef(span, ref name) => {
Ok(values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(*span))?.at(*span))
}
SimplExpr::BinOp(_, a, op, b) => {
let a = a.eval(values)?;
let b = b.eval(values)?;
Ok(match op {
BinOp::Equals => DynVal::from(a == b),
BinOp::NotEquals => DynVal::from(a != b),
BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?),
BinOp::Or => DynVal::from(a.as_bool()? || b.as_bool()?),
BinOp::Plus => match a.as_f64() {
Ok(num) => DynVal::from(num + b.as_f64()?),
Err(_) => DynVal::from(format!("{}{}", a.as_string()?, b.as_string()?)),
},
BinOp::Minus => DynVal::from(a.as_f64()? - b.as_f64()?),
BinOp::Times => DynVal::from(a.as_f64()? * b.as_f64()?),
BinOp::Div => DynVal::from(a.as_f64()? / b.as_f64()?),
BinOp::Mod => DynVal::from(a.as_f64()? % b.as_f64()?),
BinOp::GT => DynVal::from(a.as_f64()? > b.as_f64()?),
BinOp::LT => DynVal::from(a.as_f64()? < b.as_f64()?),
#[allow(clippy::useless_conversion)]
BinOp::Elvis => DynVal::from(if a.0.is_empty() { b } else { a }),
BinOp::RegexMatch => {
let regex = regex::Regex::new(&b.as_string()?)?;
DynVal::from(regex.is_match(&a.as_string()?))
}
})
}
SimplExpr::UnaryOp(_, op, a) => {
let a = a.eval(values)?;
Ok(match op {
UnaryOp::Not => DynVal::from(!a.as_bool()?),
})
}
SimplExpr::IfElse(_, cond, yes, no) => {
if cond.eval(values)?.as_bool()? {
yes.eval(values)
} else {
no.eval(values)
}
}
SimplExpr::JsonAccess(span, val, index) => {
let val = val.eval(values)?;
let index = index.eval(values)?;
match val.as_json_value()? {
serde_json::Value::Array(val) => {
let index = index.as_i32()?;
let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null);
Ok(DynVal::from(indexed_value))
}
serde_json::Value::Object(val) => {
let indexed_value = val
.get(&index.as_string()?)
.or_else(|| val.get(&index.as_i32().ok()?.to_string()))
.unwrap_or(&serde_json::Value::Null);
Ok(DynVal::from(indexed_value))
}
_ => Err(EvalError::CannotIndex(format!("{}", val)).at(*span)),
}
}
SimplExpr::FunctionCall(span, function_name, args) => {
let args = args.into_iter().map(|a| a.eval(values)).collect::<Result<_, EvalError>>()?;
call_expr_function(&function_name, args).map_err(|e| e.at(*span))
}
};
Ok(value?.at(span))
}
}
fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError> {
match name {
"round" => match args.as_slice() {
[num, digits] => {
let num = num.as_f64()?;
let digits = digits.as_i32()?;
Ok(DynVal::from(format!("{:.1$}", num, digits as usize)))
}
_ => Err(EvalError::WrongArgCount(name.to_string())),
},
"replace" => match args.as_slice() {
[string, pattern, replacement] => {
let string = string.as_string()?;
let pattern = regex::Regex::new(&pattern.as_string()?)?;
let replacement = replacement.as_string()?;
Ok(DynVal::from(pattern.replace_all(&string, replacement.replace("$", "$$").replace("\\", "$")).into_owned()))
}
_ => Err(EvalError::WrongArgCount(name.to_string())),
},
_ => Err(EvalError::UnknownFunction(name.to_string())),
}
}

View file

@ -0,0 +1,21 @@
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(try_blocks)]
pub mod ast;
pub mod dynval;
pub mod error;
pub mod eval;
pub mod parser;
pub use ast::{SimplExpr, Span};
use lalrpop_util::lalrpop_mod;
lalrpop_mod!(
#[allow(clippy::all)]
pub simplexpr_parser
);
pub fn parse_string(file_id: usize, s: &str) -> Result<SimplExpr, error::Error> {
parser::parse_string(file_id, s)
}

View file

@ -0,0 +1,3 @@
pub fn b<T>(x: T) -> Box<T> {
Box::new(x)
}

View file

@ -0,0 +1,78 @@
use logos::Logos;
#[rustfmt::skip]
#[derive(Logos, Debug, PartialEq, Eq, Clone, strum::Display, strum::EnumString)]
pub enum Token {
#[strum(serialize = "+") ] #[token("+") ] Plus,
#[strum(serialize = "-") ] #[token("-") ] Minus,
#[strum(serialize = "*") ] #[token("*") ] Times,
#[strum(serialize = "/") ] #[token("/") ] Div,
#[strum(serialize = "%") ] #[token("%") ] Mod,
#[strum(serialize = "==")] #[token("==")] Equals,
#[strum(serialize = "!=")] #[token("!=")] NotEquals,
#[strum(serialize = "&&")] #[token("&&")] And,
#[strum(serialize = "||")] #[token("||")] Or,
#[strum(serialize = ">") ] #[token(">") ] GT,
#[strum(serialize = "<") ] #[token("<") ] LT,
#[strum(serialize = "?:")] #[token("?:")] Elvis,
#[strum(serialize = "=~")] #[token("=~")] RegexMatch,
#[strum(serialize = "!") ] #[token("!") ] Not,
#[strum(serialize = ",") ] #[token(",") ] Comma,
#[strum(serialize = "?") ] #[token("?") ] Question,
#[strum(serialize = ":") ] #[token(":") ] Colon,
#[strum(serialize = "(") ] #[token("(") ] LPren,
#[strum(serialize = ")") ] #[token(")") ] RPren,
#[strum(serialize = "[") ] #[token("[") ] LBrack,
#[strum(serialize = "]") ] #[token("]") ] RBrack,
#[strum(serialize = ".") ] #[token(".") ] Dot,
#[strum(serialize = "true") ] #[token("true") ] True,
#[strum(serialize = "false")] #[token("false")] False,
#[regex(r"[a-zA-Z_-]+", |x| x.slice().to_string())]
Ident(String),
#[regex(r"[+-]?(?:[0-9]+[.])?[0-9]+", |x| x.slice().to_string())]
NumLit(String),
#[regex(r#""(?:[^"\\]|\\.)*""#, |x| x.slice().to_string())]
StrLit(String),
#[error]
#[regex(r"[ \t\n\f]+", logos::skip)]
Error,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct LexicalError(pub usize, pub usize);
impl std::fmt::Display for LexicalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Lexical error at {}..{}", self.0, self.1)
}
}
pub type SpannedResult<Tok, Loc, Error> = Result<(Loc, Tok, Loc), Error>;
pub struct Lexer<'input> {
lexer: logos::SpannedIter<'input, Token>,
}
impl<'input> Lexer<'input> {
pub fn new(text: &'input str) -> Self {
Lexer { lexer: logos::Lexer::new(text).spanned() }
}
}
impl<'input> Iterator for Lexer<'input> {
type Item = SpannedResult<Token, usize, LexicalError>;
fn next(&mut self) -> Option<Self::Item> {
let (token, range) = self.lexer.next()?;
if token == Token::Error {
Some(Err(LexicalError(range.start, range.end)))
} else {
Some(Ok((range.start, token, range.end)))
}
}
}

View file

@ -0,0 +1,47 @@
pub mod lalrpop_helpers;
pub mod lexer;
use crate::{
ast::SimplExpr,
error::{Error, Result},
};
pub fn parse_string(file_id: usize, s: &str) -> Result<SimplExpr> {
let lexer = lexer::Lexer::new(s);
let parser = crate::simplexpr_parser::ExprParser::new();
parser.parse(file_id, lexer).map_err(|e| Error::from_parse_error(file_id, e))
}
#[cfg(test)]
mod tests {
macro_rules! test_parser {
($($text:literal),* $(,)?) => {{
let p = crate::simplexpr_parser::ExprParser::new();
use crate::parser::lexer::Lexer;
::insta::with_settings!({sort_maps => true}, {
$(
::insta::assert_debug_snapshot!(p.parse(0, Lexer::new($text)));
)*
});
}}
}
#[test]
fn test() {
test_parser!(
"1",
"2 + 5",
"2 * 5 + 1 * 1 + 3",
"(1 + 2) * 2",
"1 + true ? 2 : 5",
"1 + true ? 2 : 5 + 2",
"1 + (true ? 2 : 5) + 2",
"foo(1, 2)",
"! false || ! true",
"\"foo\" + 12.4",
"hi[\"ho\"]",
"foo.bar.baz",
"foo.bar[2 + 2] * asdf[foo.bar]",
);
}
}

View file

@ -0,0 +1,121 @@
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
use crate::parser::lexer::{Token, LexicalError};
use crate::parser::lalrpop_helpers::*;
use lalrpop_util::ParseError;
grammar(fid: usize);
extern {
type Location = usize;
type Error = LexicalError;
enum Token {
"+" => Token::Plus,
"-" => Token::Minus,
"*" => Token::Times,
"/" => Token::Div,
"%" => Token::Mod,
"==" => Token::Equals,
"!=" => Token::NotEquals,
"&&" => Token::And,
"||" => Token::Or,
">" => Token::GT,
"<" => Token::LT,
"?:" => Token::Elvis,
"=~" => Token::RegexMatch,
"!" => Token::Not,
"," => Token::Comma,
"?" => Token::Question,
":" => Token::Colon,
"(" => Token::LPren,
")" => Token::RPren,
"[" => Token::LBrack,
"]" => Token::RBrack,
"." => Token::Dot,
"true" => Token::True,
"false" => Token::False,
"identifier" => Token::Ident(<String>),
"number" => Token::NumLit(<String>),
"string" => Token::StrLit(<String>),
"lexer_error" => Token::Error,
}
}
Comma<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T?> => match e {
None => v,
Some(e) => {
v.push(e);
v
}
}
};
pub Expr: SimplExpr = {
#[precedence(level="0")]
<l:@L> "lexer_error" <r:@R> =>? {
Err(ParseError::User { error: LexicalError(l, r) })
},
<Literal>,
<l:@L> <ident:"identifier"> <r:@R> => VarRef(Span(l, r, fid), ident.to_string()),
"(" <ExprReset> ")",
#[precedence(level="1")] #[assoc(side="right")]
<l:@L> <ident:"identifier"> "(" <args: Comma<ExprReset>> ")" <r:@R> => FunctionCall(Span(l, r, fid), ident, args),
<l:@L> <value:Expr> "[" <index: ExprReset> "]" <r:@R> => JsonAccess(Span(l, r, fid), b(value), b(index)),
<l:@L> <value:Expr> "." <lit_l:@L> <index:"identifier"> <r:@R> => {
JsonAccess(Span(l, r, fid), b(value), b(Literal(Span(lit_l, r, fid), index.into())))
},
#[precedence(level="2")] #[assoc(side="right")]
<l:@L> "!" <e:Expr> <r:@R> => UnaryOp(Span(l, r, fid), Not, b(e)),
#[precedence(level="3")] #[assoc(side="left")]
<l:@L> <le:Expr> "*" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Times, b(re)),
<l:@L> <le:Expr> "/" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Div, b(re)),
<l:@L> <le:Expr> "%" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Mod, b(re)),
#[precedence(level="4")] #[assoc(side="left")]
<l:@L> <le:Expr> "+" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Plus, b(re)),
<l:@L> <le:Expr> "-" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Minus, b(re)),
#[precedence(level="5")] #[assoc(side="left")]
<l:@L> <le:Expr> "==" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Equals, b(re)),
<l:@L> <le:Expr> "!=" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), NotEquals, b(re)),
<l:@L> <le:Expr> "<" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), GT, b(re)),
<l:@L> <le:Expr> ">" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), LT, b(re)),
<l:@L> <le:Expr> "=~" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), RegexMatch, b(re)),
#[precedence(level="6")] #[assoc(side="left")]
<l:@L> <le:Expr> "&&" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), And, b(re)),
<l:@L> <le:Expr> "||" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Or, b(re)),
<l:@L> <le:Expr> "?:" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Elvis, b(re)),
#[precedence(level="7")] #[assoc(side="right")]
<l:@L> <cond:Expr> "?" <then:ExprReset> ":" <els:Expr> <r:@R> => {
IfElse(Span(l, r, fid), b(cond), b(then), b(els))
},
};
ExprReset = <Expr>;
Literal: SimplExpr = {
<l:@L> <x:StrLit> <r:@R> => SimplExpr::literal(Span(l, r, fid), x),
<l:@L> <x:"number"> <r:@R> => SimplExpr::literal(Span(l, r, fid), x),
<l:@L> "true" <r:@R> => SimplExpr::literal(Span(l, r, fid), "true".into()),
<l:@L> "false" <r:@R> => SimplExpr::literal(Span(l, r, fid), "false".into()),
}
StrLit: String = {
<x:"string"> => x[1..x.len() - 1].to_owned(),
};

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"\\\"foo\\\" + 12.4\"))"
---
Ok(
("foo" + "12.4"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"hi[\\\"ho\\\"]\"))"
---
Ok(
hi["ho"],
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"foo.bar.baz\"))"
---
Ok(
foo["bar"]["baz"],
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\"))"
---
Ok(
(foo["bar"][("2" + "2")] * asdf[foo["bar"]]),
)

View file

@ -0,0 +1,30 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"1 + (true ? 2 : 5) + 2\"))"
---
Ok(
BinOp(
BinOp(
Literal(
"1",
),
Plus,
IfElse(
Literal(
"true",
),
Literal(
"2",
),
Literal(
"5",
),
),
),
Plus,
Literal(
"2",
),
),
)

View file

@ -0,0 +1,13 @@
---
source: src/lib.rs
expression: "Lexer::new(\"foo(1, 2)\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x) |\n Token::StrLit(x) =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::<Vec<_>>()"
---
[
"foo",
"LPren",
"1",
"Comma",
"2",
"RPren",
]

View file

@ -0,0 +1,18 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"foo(1, 2)\"))"
---
Ok(
FunctionCall(
"foo",
[
Literal(
"1",
),
Literal(
"2",
),
],
),
)

View file

@ -0,0 +1,12 @@
---
source: src/lib.rs
expression: "Lexer::new(\"! false || ! true\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x =>\n format!(\"{}\",\n x),\n }).collect::<Vec<_>>()"
---
[
"!",
"False",
"||",
"!",
"True",
]

View file

@ -0,0 +1,22 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"! false || ! true\"))"
---
Ok(
BinOp(
UnaryOp(
Not,
Literal(
"false",
),
),
Or,
UnaryOp(
Not,
Literal(
"true",
),
),
),
)

View file

@ -0,0 +1,10 @@
---
source: src/lib.rs
expression: "Lexer::new(\"\\\"foo\\\" + 12.4\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x =>\n format!(\"{}\",\n x),\n }).collect::<Vec<_>>()"
---
[
"\"foo\"",
"+",
"12.4",
]

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"2 + 5\"))"
---
Ok(
("2" + "5"),
)

View file

@ -0,0 +1,16 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"\\\"foo\\\" + 12.4\"))"
---
Ok(
BinOp(
Literal(
"foo",
),
Plus,
Literal(
"12.4",
),
),
)

View file

@ -0,0 +1,11 @@
---
source: src/lib.rs
expression: "Lexer::new(\"hi[\\\"ho\\\"]\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x) |\n Token::StrLit(x)\n =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::<Vec<_>>()"
---
[
"hi",
"LBrack",
"\"ho\"",
"RBrack",
]

View file

@ -0,0 +1,15 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"hi[\\\"ho\\\"]\"))"
---
Ok(
JsonAccess(
VarRef(
"hi",
),
Literal(
"ho",
),
),
)

View file

@ -0,0 +1,12 @@
---
source: src/lib.rs
expression: "Lexer::new(\"foo.bar.baz\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::<Vec<_>>()"
---
[
"foo",
"Dot",
"bar",
"Dot",
"baz",
]

View file

@ -0,0 +1,20 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"foo.bar.baz\"))"
---
Ok(
JsonAccess(
JsonAccess(
VarRef(
"foo",
),
Literal(
"bar",
),
),
Literal(
"baz",
),
),
)

View file

@ -0,0 +1,22 @@
---
source: src/lib.rs
expression: "Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\").filter_map(|x|\n x.ok()).map(|(_,\n x,\n _)|\n match x\n {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x\n =>\n format!(\"{}\",\n x),\n }).collect::<Vec<_>>()"
---
[
"foo",
"Dot",
"bar",
"LBrack",
"2",
"+",
"2",
"RBrack",
"*",
"asdf",
"LBrack",
"foo",
"Dot",
"bar",
"RBrack",
]

View file

@ -0,0 +1,42 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\"))"
---
Ok(
BinOp(
JsonAccess(
JsonAccess(
VarRef(
"foo",
),
Literal(
"bar",
),
),
BinOp(
Literal(
"2",
),
Plus,
Literal(
"2",
),
),
),
Times,
JsonAccess(
VarRef(
"asdf",
),
JsonAccess(
VarRef(
"foo",
),
Literal(
"bar",
),
),
),
),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"2 * 5 + 1 * 1 + 3\"))"
---
Ok(
((("2" * "5") + ("1" * "1")) + "3"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"(1 + 2) * 2\"))"
---
Ok(
(("1" + "2") * "2"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"1 + true ? 2 : 5\"))"
---
Ok(
(if ("1" + "true") then "2" else "5"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"1 + true ? 2 : 5 + 2\"))"
---
Ok(
(if ("1" + "true") then "2" else ("5" + "2")),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"1 + (true ? 2 : 5) + 2\"))"
---
Ok(
(("1" + (if "true" then "2" else "5")) + "2"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"foo(1, 2)\"))"
---
Ok(
foo("1", "2"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"! false || ! true\"))"
---
Ok(
(!"false" || !"true"),
)

View file

@ -0,0 +1,8 @@
---
source: src/lib.rs
expression: "p.parse(Lexer::new(\"1\"))"
---
Ok(
"1",
)