summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock641
-rw-r--r--crates/rebel-lang/Cargo.toml24
-rw-r--r--crates/rebel-lang/benches/recipe.rs104
-rw-r--r--crates/rebel-lang/examples/repl.rs141
-rw-r--r--crates/rebel-lang/src/func.rs33
-rw-r--r--crates/rebel-lang/src/lib.rs36
-rw-r--r--crates/rebel-lang/src/scope.rs277
-rw-r--r--crates/rebel-lang/src/typing.rs791
-rw-r--r--crates/rebel-lang/src/value.rs713
-rw-r--r--crates/rebel-parse/Cargo.toml5
-rw-r--r--crates/rebel-parse/examples/parse-string.rs12
-rw-r--r--crates/rebel-parse/src/ast.rs223
-rw-r--r--crates/rebel-parse/src/ast/expr.rs333
-rw-r--r--crates/rebel-parse/src/ast/mod.rs187
-rw-r--r--crates/rebel-parse/src/ast/pat.rs57
-rw-r--r--crates/rebel-parse/src/ast/typ.rs28
-rw-r--r--crates/rebel-parse/src/grammar/recipe.rs245
-rw-r--r--crates/rebel-parse/src/grammar/tokenize.rs77
-rw-r--r--crates/rebel-parse/src/token.rs37
-rw-r--r--examples/recipes/gmp/build.recipe42
20 files changed, 3605 insertions, 401 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5f256e7..39b7cbd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,48 +3,64 @@
version = 3
[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "anstream"
-version = "0.6.13"
+version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
+ "is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
-version = "1.0.6"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.2"
+version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
@@ -63,6 +79,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -82,6 +104,9 @@ name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+dependencies = [
+ "serde",
+]
[[package]]
name = "blake3"
@@ -107,6 +132,12 @@ dependencies = [
]
[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
name = "capctl"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -119,9 +150,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.95"
+version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
[[package]]
name = "cfg-if"
@@ -136,6 +167,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -164,10 +208,10 @@ version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
- "heck",
+ "heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.61",
]
[[package]]
@@ -178,9 +222,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "condtype"
@@ -195,6 +239,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -204,6 +254,32 @@ dependencies = [
]
[[package]]
+name = "crossterm"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+dependencies = [
+ "bitflags 2.5.0",
+ "crossterm_winapi",
+ "libc",
+ "mio",
+ "parking_lot",
+ "serde",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -220,6 +296,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b390250ca2b6862735ef39a7b37b0266a8d5cd9d1e579260c95b1dc27761d6ad"
[[package]]
+name = "derive-into-owned"
+version = "0.2.0"
+source = "git+https://github.com/neocturne/derive-into-owned.git?branch=more-types#e692ae6da9220ff3a45b0a1c380e1f4a90c5b890"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+]
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -252,10 +338,16 @@ checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.61",
]
[[package]]
+name = "either"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
+
+[[package]]
name = "enum-kinds"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -283,6 +375,17 @@ dependencies = [
]
[[package]]
+name = "fd-lock"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
+dependencies = [
+ "cfg-if",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -290,7 +393,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall",
+ "redox_syscall 0.4.1",
"windows-sys 0.52.0",
]
@@ -320,9 +423,15 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.14.3"
+version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
@@ -340,6 +449,29 @@ dependencies = [
]
[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -356,12 +488,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -369,9 +525,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.153"
+version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "linux-raw-sys"
@@ -380,6 +536,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -392,6 +558,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -404,6 +582,24 @@ dependencies = [
]
[[package]]
+name = "nu-ansi-term"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "olpc-cjson"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -421,9 +617,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
+name = "parking_lot"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.1",
+ "smallvec",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
name = "peg"
-version = "0.8.2"
-source = "git+https://github.com/kevinmehall/rust-peg.git#2fc1cadaa1efcf47c867715d063ac2d7296945c6"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a625d12ad770914cbf7eff6f9314c3ef803bfe364a1b20bc36ddf56673e71e5"
dependencies = [
"peg-macros",
"peg-runtime",
@@ -431,8 +651,9 @@ dependencies = [
[[package]]
name = "peg-macros"
-version = "0.8.2"
-source = "git+https://github.com/kevinmehall/rust-peg.git#2fc1cadaa1efcf47c867715d063ac2d7296945c6"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f241d42067ed3ab6a4fece1db720838e1418f36d868585a27931f95d6bc03582"
dependencies = [
"peg-runtime",
"proc-macro2",
@@ -441,14 +662,15 @@ dependencies = [
[[package]]
name = "peg-runtime"
-version = "0.8.2"
-source = "git+https://github.com/kevinmehall/rust-peg.git#2fc1cadaa1efcf47c867715d063ac2d7296945c6"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a"
[[package]]
name = "pest"
-version = "2.7.9"
+version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
+checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
dependencies = [
"memchr",
"thiserror",
@@ -457,9 +679,9 @@ dependencies = [
[[package]]
name = "pest_derive"
-version = "2.7.9"
+version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
+checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
dependencies = [
"pest",
"pest_generator",
@@ -467,22 +689,22 @@ dependencies = [
[[package]]
name = "pest_generator"
-version = "2.7.9"
+version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
+checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.61",
]
[[package]]
name = "pest_meta"
-version = "2.7.9"
+version = "2.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
+checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
dependencies = [
"once_cell",
"pest",
@@ -490,10 +712,52 @@ dependencies = [
]
[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
name = "proc-macro2"
-version = "1.0.81"
+version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
@@ -508,6 +772,21 @@ dependencies = [
]
[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
name = "rebel"
version = "0.1.0"
dependencies = [
@@ -534,13 +813,29 @@ dependencies = [
]
[[package]]
+name = "rebel-lang"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "divan",
+ "enum-kinds",
+ "rebel-common",
+ "rebel-parse",
+ "reedline",
+ "rustc-hash",
+]
+
+[[package]]
name = "rebel-parse"
version = "0.1.0"
dependencies = [
"clap",
+ "derive-into-owned",
"divan",
"peg",
+ "phf",
"rebel-common",
+ "rustc-hash",
]
[[package]]
@@ -583,12 +878,47 @@ dependencies = [
]
[[package]]
+name = "redox_syscall"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
+dependencies = [
+ "bitflags 2.5.0",
+]
+
+[[package]]
+name = "reedline"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abf59e4c97b5049ba96b052cdb652368305a2eddcbce9bf1c16f9d003139eeea"
+dependencies = [
+ "chrono",
+ "crossterm",
+ "fd-lock",
+ "itertools",
+ "nu-ansi-term",
+ "serde",
+ "strip-ansi-escapes",
+ "strum",
+ "strum_macros",
+ "thiserror",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
name = "regex-lite"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e"
[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -602,10 +932,16 @@ dependencies = [
]
[[package]]
+name = "rustversion"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0"
+
+[[package]]
name = "ryu"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
@@ -617,23 +953,29 @@ dependencies = [
]
[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
name = "serde"
-version = "1.0.198"
+version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
+checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.198"
+version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
+checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.61",
]
[[package]]
@@ -672,12 +1014,82 @@ dependencies = [
]
[[package]]
+name = "signal-hook"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa"
+dependencies = [
+ "vte",
+]
+
+[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
+name = "strum"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
+
+[[package]]
+name = "strum_macros"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.61",
+]
+
+[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -696,9 +1108,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.60"
+version = "2.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
dependencies = [
"proc-macro2",
"quote",
@@ -734,22 +1146,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.59"
+version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
+checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.59"
+version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
+checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.60",
+ "syn 2.0.61",
]
[[package]]
@@ -804,6 +1216,18 @@ dependencies = [
]
[[package]]
+name = "unicode-segmentation"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
+
+[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -822,6 +1246,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
+name = "vte"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
+dependencies = [
+ "utf8parse",
+ "vte_generate_state_changes",
+]
+
+[[package]]
+name = "vte_generate_state_changes"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -832,6 +1276,82 @@ dependencies = [
]
[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.61",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[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-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -841,6 +1361,21 @@ dependencies = [
]
[[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 = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/rebel-lang/Cargo.toml b/crates/rebel-lang/Cargo.toml
new file mode 100644
index 0000000..a0a04a6
--- /dev/null
+++ b/crates/rebel-lang/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "rebel-lang"
+version = "0.1.0"
+authors = ["Matthias Schiffer <mschiffer@universe-factory.net>"]
+license = "MIT"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+rebel-common = { path = "../rebel-common" }
+rebel-parse = { path = "../rebel-parse" }
+
+enum-kinds = "0.5.1"
+rustc-hash = "1.1.0"
+
+[dev-dependencies]
+clap = { version = "4.0.0", features = ["derive"] }
+divan = "0.1.14"
+reedline = "0.32.0"
+
+[[bench]]
+name = "recipe"
+harness = false
diff --git a/crates/rebel-lang/benches/recipe.rs b/crates/rebel-lang/benches/recipe.rs
new file mode 100644
index 0000000..fe017f5
--- /dev/null
+++ b/crates/rebel-lang/benches/recipe.rs
@@ -0,0 +1,104 @@
+use rebel_lang::{
+ scope::Scope,
+ typing::{self, Type, VarType},
+ value::{self, Value},
+};
+use rebel_parse::ast;
+
+fn main() {
+ divan::main();
+}
+
+const RECIPE: &str = include_str!("../../../examples/recipes/gmp/build.recipe");
+
+fn recipe() -> ast::Recipe<'static> {
+ let tokens = rebel_parse::tokenize::token_stream(RECIPE).unwrap();
+ rebel_parse::recipe::recipe(&tokens).unwrap()
+}
+
+fn type_scope() -> Box<Scope<VarType>> {
+ let mut scope = Box::<Scope<_>>::default();
+
+ scope.defs.insert_value("workdir", VarType::new(Type::Str));
+ scope.initialize("workdir");
+ scope.defs.insert_value("name", VarType::new(Type::Str));
+ scope.initialize("name");
+
+ scope
+}
+
+fn value_scope() -> Box<Scope<Value>> {
+ let mut scope = Box::<Scope<_>>::default();
+
+ scope
+ .defs
+ .insert_value("workdir", Value::Str("workdir".to_owned()));
+ scope
+ .defs
+ .insert_value("name", Value::Str("gpm".to_owned()));
+
+ scope
+}
+
+#[divan::bench]
+fn validate(bencher: divan::Bencher) {
+ let recipe = recipe();
+
+ bencher.bench(|| {
+ for stmt in divan::black_box(&recipe) {
+ stmt.validate().unwrap();
+ }
+ });
+}
+
+#[divan::bench]
+fn typecheck(bencher: divan::Bencher) {
+ let recipe = recipe();
+ let scope = type_scope();
+
+ for stmt in &recipe {
+ stmt.validate().unwrap();
+ }
+
+ bencher
+ .with_inputs(|| scope.clone())
+ .bench_local_values(|mut scope| {
+ let mut ctx = typing::Context(&mut scope);
+
+ for stmt in divan::black_box(&recipe) {
+ let ast::RecipeStmt::BlockStmt(stmt) = stmt else {
+ // TODO: Check other statements
+ continue;
+ };
+
+ ctx.type_block_stmt(stmt).unwrap();
+ }
+ scope
+ });
+}
+
+#[divan::bench]
+fn execute(bencher: divan::Bencher) {
+ let recipe = recipe();
+ let scope = value_scope();
+
+ for stmt in &recipe {
+ stmt.validate().unwrap();
+ }
+
+ bencher
+ .with_inputs(|| scope.clone())
+ .bench_local_values(|mut scope| {
+ let mut ctx = value::Context(&mut scope);
+
+ for stmt in divan::black_box(&recipe) {
+ let ast::RecipeStmt::BlockStmt(stmt) = stmt else {
+ // TODO: Execute other statements
+ continue;
+ };
+
+ ctx.eval_block_stmt(stmt).unwrap();
+ }
+ scope
+ });
+}
diff --git a/crates/rebel-lang/examples/repl.rs b/crates/rebel-lang/examples/repl.rs
new file mode 100644
index 0000000..3e0c319
--- /dev/null
+++ b/crates/rebel-lang/examples/repl.rs
@@ -0,0 +1,141 @@
+use std::rc::Rc;
+
+use rebel_lang::{
+ func::{Func, FuncDef, FuncType},
+ scope::{MethodMap, Scope},
+ typing::{self, Type, TypeFamily, VarType},
+ value::{self, Value},
+ Error, Result,
+};
+use rebel_parse::{recipe, tokenize};
+use reedline::{DefaultPrompt, DefaultPromptSegment, Reedline, Signal, ValidationResult};
+
+fn intrinsic_array_len(params: &[Value]) -> Result<Value> {
+ assert!(params.len() == 1);
+ let Value::Array(array) = &params[0] else {
+ panic!();
+ };
+ Ok(Value::Int(
+ array
+ .len()
+ .try_into()
+ .or(Err(Error::eval("array length out of bounds")))?,
+ ))
+}
+fn intrinsic_string_len(params: &[Value]) -> Result<Value> {
+ assert!(params.len() == 1);
+ let Value::Str(string) = &params[0] else {
+ panic!();
+ };
+ Ok(Value::Int(
+ string
+ .chars()
+ .count()
+ .try_into()
+ .or(Err(Error::eval("string length out of bounds")))?,
+ ))
+}
+
+struct Validator;
+
+impl reedline::Validator for Validator {
+ fn validate(&self, line: &str) -> ValidationResult {
+ if tokenize::token_stream(line).is_ok() {
+ ValidationResult::Complete
+ } else {
+ ValidationResult::Incomplete
+ }
+ }
+}
+
+fn main() {
+ let mut methods = MethodMap::default();
+ methods.entry(TypeFamily::Array).or_default().insert(
+ "len",
+ Func {
+ typ: FuncType {
+ params: vec![Type::Array(Box::new(Type::Free))],
+ ret: Type::Int,
+ },
+ def: FuncDef::Intrinsic(intrinsic_array_len),
+ },
+ );
+ methods.entry(TypeFamily::Str).or_default().insert(
+ "len",
+ Func {
+ typ: FuncType {
+ params: vec![Type::Str],
+ ret: Type::Int,
+ },
+ def: FuncDef::Intrinsic(intrinsic_string_len),
+ },
+ );
+ let methods = Rc::new(methods);
+
+ let mut type_scope = Box::<Scope<VarType>>::default();
+ type_scope.methods = methods.clone();
+ let mut value_scope = Box::<Scope<Value>>::default();
+ value_scope.methods = methods.clone();
+
+ let mut rl = Reedline::create().with_validator(Box::new(Validator));
+ let prompt = DefaultPrompt::new(DefaultPromptSegment::Empty, DefaultPromptSegment::Empty);
+
+ 'repl: loop {
+ let input = match rl.read_line(&prompt).unwrap() {
+ Signal::Success(input) => input,
+ Signal::CtrlC => continue,
+ Signal::CtrlD => break,
+ };
+
+ let tokens = match tokenize::token_stream(&input) {
+ Ok(value) => value,
+ Err(err) => {
+ println!("Tokenize error: {err}");
+ continue;
+ }
+ };
+ let block = match recipe::block(&tokens) {
+ Ok(value) => value,
+ Err(err) => {
+ println!("Parse error: {err}");
+ continue;
+ }
+ };
+
+ let mut type_scope_tmp = type_scope.clone();
+ let mut value_scope_tmp = value_scope.clone();
+
+ let mut typ = Type::unit();
+ let mut value = Value::unit();
+
+ for stmt in &block.0 {
+ if let Err(err) = stmt.validate() {
+ println!("Validation error: {err:?}");
+ continue 'repl;
+ }
+
+ typ = match typing::Context(&mut type_scope_tmp).type_block_stmt(stmt) {
+ Ok(typ) => typ,
+ Err(err) => {
+ println!("Type checking failed: {err}");
+ continue 'repl;
+ }
+ };
+
+ value = match value::Context(&mut value_scope_tmp).eval_block_stmt(stmt) {
+ Ok(value) => value,
+ Err(err) => {
+ println!("Evaluation failed: {err}");
+ continue 'repl;
+ }
+ };
+ }
+
+ if value != Value::unit() {
+ println!("{value}: {typ}");
+ }
+
+ type_scope = type_scope_tmp;
+ value_scope = value_scope_tmp;
+ }
+}
diff --git a/crates/rebel-lang/src/func.rs b/crates/rebel-lang/src/func.rs
new file mode 100644
index 0000000..19d3ea0
--- /dev/null
+++ b/crates/rebel-lang/src/func.rs
@@ -0,0 +1,33 @@
+use std::fmt::Display;
+
+use rebel_parse::ast::Block;
+
+use crate::{typing::Type, value::Value, Result};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Func {
+ pub typ: FuncType,
+ pub def: FuncDef,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FuncType {
+ pub params: Vec<Type>,
+ pub ret: Type,
+}
+
+impl Display for FuncType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ // TODO
+ write!(f, "fn( ... ) -> {}", self.ret)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum FuncDef {
+ Intrinsic(fn(&[Value]) -> Result<Value>),
+ Body {
+ param_names: Vec<String>,
+ block: Block<'static>,
+ },
+}
diff --git a/crates/rebel-lang/src/lib.rs b/crates/rebel-lang/src/lib.rs
new file mode 100644
index 0000000..001bb79
--- /dev/null
+++ b/crates/rebel-lang/src/lib.rs
@@ -0,0 +1,36 @@
+use std::fmt::Display;
+
+pub mod func;
+pub mod scope;
+pub mod typing;
+pub mod value;
+
+#[derive(Debug)]
+pub struct Error(pub &'static str, pub &'static str);
+
+impl Error {
+ pub fn lookup(text: &'static str) -> Self {
+ Self("Lookup", text)
+ }
+
+ pub fn typ(text: &'static str) -> Self {
+ Self("Type", text)
+ }
+
+ pub fn eval(text: &'static str) -> Self {
+ Self("Eval", text)
+ }
+
+ pub fn bug(text: &'static str) -> Self {
+ Self("BUG", text)
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let Error(cat, text) = self;
+ write!(f, "{cat} error: {text}")
+ }
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/crates/rebel-lang/src/scope.rs b/crates/rebel-lang/src/scope.rs
new file mode 100644
index 0000000..decb427
--- /dev/null
+++ b/crates/rebel-lang/src/scope.rs
@@ -0,0 +1,277 @@
+use std::{borrow::Cow, mem, rc::Rc};
+
+use rebel_parse::ast::{self, pat};
+use rustc_hash::{FxHashMap, FxHashSet};
+
+use crate::{
+ func::Func,
+ typing::{Type, TypeFamily},
+ Error, Result,
+};
+
+pub type MethodMap = FxHashMap<TypeFamily, FxHashMap<&'static str, Func>>;
+
+#[derive(Debug, Clone)]
+pub struct Scope<T> {
+ pub defs: Module<T>,
+ pub initialized: Rc<FxHashSet<String>>,
+ pub methods: Rc<MethodMap>,
+ pub parent: Option<Box<Scope<T>>>,
+ pub upvalue_initialized: FxHashSet<String>,
+}
+
+impl<T> Default for Scope<T> {
+ fn default() -> Self {
+ let mut ret = Scope {
+ defs: Module::default(),
+ initialized: Rc::default(),
+ methods: Rc::default(),
+ parent: None,
+ upvalue_initialized: FxHashSet::default(),
+ };
+
+ // Type "prelude"
+ ret.defs.insert_type("bool", Type::Bool);
+ ret.defs.insert_type("int", Type::Int);
+ ret.defs.insert_type("str", Type::Str);
+
+ ret
+ }
+}
+
+impl<T> Scope<T> {
+ pub fn is_initialized(&self, path: &ast::Path) -> bool {
+ // TODO: What to do about other paths?
+ if let [ident] = &path.components[..] {
+ self.initialized.contains(ident.name.as_ref())
+ } else {
+ true
+ }
+ }
+
+ pub fn initialize(&mut self, ident: &str) {
+ if !self.initialized.contains(ident) {
+ Rc::make_mut(&mut self.initialized).insert(ident.to_owned());
+ }
+ if !self.defs.values.contains_key(ident) {
+ self.upvalue_initialized.insert(ident.to_owned());
+ }
+ }
+
+ pub fn initialize_all(&mut self, idents: impl IntoIterator<Item = impl AsRef<str>>) {
+ for ident in idents {
+ self.initialize(ident.as_ref());
+ }
+ }
+
+ pub fn func(&self) -> Self {
+ // TODO: Upvalues
+ Scope {
+ defs: Module::default(),
+ initialized: Rc::default(),
+ methods: self.methods.clone(),
+ parent: None,
+ upvalue_initialized: FxHashSet::default(),
+ }
+ }
+
+ pub fn scoped<F, R>(self: &mut Box<Self>, f: F) -> (R, FxHashSet<String>)
+ where
+ F: FnOnce(&mut Box<Self>) -> R,
+ {
+ let child = Box::new(Scope {
+ defs: Module::default(),
+ initialized: self.initialized.clone(),
+ methods: self.methods.clone(),
+ parent: None,
+ upvalue_initialized: FxHashSet::default(),
+ });
+ let scope = mem::replace(self, child);
+ self.parent = Some(scope);
+
+ let ret = f(self);
+
+ let parent = self.parent.take().unwrap();
+ let child = mem::replace(self, parent);
+ let Scope {
+ upvalue_initialized,
+ ..
+ } = *child;
+
+ (ret, upvalue_initialized)
+ }
+
+ pub fn lookup_value(&self, path: &ast::Path) -> Result<&T> {
+ if path.root != ast::PathRoot::Relative {
+ return Err(Error::lookup("invalid path"));
+ }
+
+ let mut lookup_scope = Some(self);
+ while let Some(scope) = lookup_scope {
+ if scope.defs.contains_value(&path.components) {
+ return scope
+ .defs
+ .lookup_value(&path.components)
+ .ok_or(Error::lookup("unresolved path"));
+ }
+ lookup_scope = scope.parent.as_deref();
+ }
+
+ Err(Error::lookup("undefined variable"))
+ }
+
+ pub fn lookup_type(&self, path: &ast::Path) -> Result<&Type> {
+ if path.root != ast::PathRoot::Relative {
+ return Err(Error::lookup("invalid path"));
+ }
+ if path.components
+ == [ast::Ident {
+ name: Cow::Borrowed("_"),
+ }] {
+ return Ok(&Type::Free);
+ }
+
+ let mut lookup_scope = Some(self);
+ while let Some(scope) = lookup_scope {
+ if scope.defs.contains_type(&path.components) {
+ return scope
+ .defs
+ .lookup(&path.components)
+ .and_then(|def| def.typ.as_deref())
+ .ok_or(Error::lookup("unresolved path"));
+ }
+ lookup_scope = scope.parent.as_deref();
+ }
+
+ Err(Error::lookup("undefined type"))
+ }
+}
+
+impl<T: Clone> Scope<T> {
+ pub fn lookup_var_mut<'a>(&mut self, path: &ast::Path<'a>) -> Result<(&mut T, ast::Ident<'a>)> {
+ if path.root != ast::PathRoot::Relative {
+ return Err(Error::lookup("invalid path"));
+ }
+
+ let mut lookup_scope = Some(self);
+ while let Some(scope) = lookup_scope {
+ if scope.defs.contains_value(&path.components) {
+ return scope
+ .defs
+ .lookup_value_mut(&path.components)
+ .ok_or(Error::lookup("unresolved path"));
+ }
+ lookup_scope = scope.parent.as_deref_mut();
+ }
+
+ Err(Error::lookup("undefined variable"))
+ }
+}
+
+pub fn pat_ident<'a>(pat: &pat::Pat<'a>) -> ast::Ident<'a> {
+ match pat {
+ pat::Pat::Paren(subpat) => pat_ident(subpat),
+ pat::Pat::Ident(ident) => ident.clone(),
+ }
+}
+
+pub fn is_wildcard_destr_pat(pat: &pat::DestrPat) -> bool {
+ match pat {
+ pat::DestrPat::Index { .. } => false,
+ pat::DestrPat::Field { .. } => false,
+ pat::DestrPat::Paren(pat) => is_wildcard_destr_pat(pat),
+ pat::DestrPat::Path(path) => {
+ path.root == ast::PathRoot::Relative
+ && path.components
+ == [ast::Ident {
+ name: Cow::Borrowed("_"),
+ }]
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Module<T> {
+ pub values: FxHashMap<String, Rc<T>>,
+ pub typ: Option<Rc<Type>>,
+ pub children: Rc<FxHashMap<String, Module<T>>>,
+}
+
+impl<T> Module<T> {
+ pub fn contains_value(&self, path: &[ast::Ident<'_>]) -> bool {
+ match path {
+ [] => true,
+ [ident] => self.values.contains_key(ident.name.as_ref()),
+ [ident, ..] => self.children.contains_key(ident.name.as_ref()),
+ }
+ }
+
+ pub fn contains_type(&self, path: &[ast::Ident<'_>]) -> bool {
+ match path {
+ [] => true,
+ [ident, ..] => self.children.contains_key(ident.name.as_ref()),
+ }
+ }
+
+ pub fn insert_value(&mut self, ident: &str, value: T) {
+ self.values.insert(ident.to_owned(), Rc::new(value));
+ }
+
+ pub fn insert_type(&mut self, ident: &str, typ: Type) {
+ Rc::make_mut(&mut self.children)
+ .entry(ident.to_owned())
+ .or_default()
+ .typ = Some(Rc::new(typ));
+ }
+
+ pub fn lookup(&self, path: &[ast::Ident<'_>]) -> Option<&Module<T>> {
+ let Some((ident, rest)) = path.split_first() else {
+ return Some(self);
+ };
+
+ self.children.get(ident.name.as_ref())?.lookup(rest)
+ }
+
+ pub fn lookup_value(&self, path: &[ast::Ident<'_>]) -> Option<&T> {
+ let (ident, prefix) = path.split_last()?;
+ let module = self.lookup(prefix)?;
+ module.values.get(ident.name.as_ref()).map(Rc::as_ref)
+ }
+}
+
+impl<T: Clone> Module<T> {
+ pub fn lookup_value_mut<'a>(
+ &mut self,
+ path: &[ast::Ident<'a>],
+ ) -> Option<(&mut T, ast::Ident<'a>)> {
+ if let [ident] = path {
+ return Some((
+ self.values.get_mut(ident.name.as_ref()).map(Rc::make_mut)?,
+ ident.clone(),
+ ));
+ }
+
+ // TODO: Decide how to handle this case
+ None
+ }
+}
+
+impl<T> Default for Module<T> {
+ fn default() -> Self {
+ Self {
+ values: Default::default(),
+ typ: Default::default(),
+ children: Default::default(),
+ }
+ }
+}
+
+impl<T> Clone for Module<T> {
+ fn clone(&self) -> Self {
+ Self {
+ values: self.values.clone(),
+ typ: self.typ.clone(),
+ children: self.children.clone(),
+ }
+ }
+}
diff --git a/crates/rebel-lang/src/typing.rs b/crates/rebel-lang/src/typing.rs
new file mode 100644
index 0000000..34280cd
--- /dev/null
+++ b/crates/rebel-lang/src/typing.rs
@@ -0,0 +1,791 @@
+use std::{fmt::Display, iter, rc::Rc};
+
+use enum_kinds::EnumKind;
+
+use rebel_parse::ast::{self, expr, pat, typ};
+use rustc_hash::{FxHashMap, FxHashSet};
+
+use crate::{
+ func::FuncType,
+ scope::{self, Scope},
+ Error, Result,
+};
+
+#[derive(Debug)]
+pub struct Context<'scope>(pub &'scope mut Box<Scope<VarType>>);
+
+#[derive(Debug)]
+pub struct TypeContext<'scope, T>(pub &'scope Scope<T>);
+
+#[derive(Debug, Clone)]
+pub struct VarType {
+ pub explicit_type: Type,
+ pub inferred_type: Type,
+}
+
+impl VarType {
+ pub fn new(explicit_type: Type) -> Self {
+ VarType {
+ inferred_type: explicit_type.clone(),
+ explicit_type,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Coerce {
+ Common,
+ Compare,
+ Assign,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, EnumKind)]
+#[enum_kind(TypeFamily, derive(Hash))]
+pub enum Type {
+ Free,
+ Bool,
+ Int,
+ Str,
+ Option(Box<Type>),
+ Tuple(Vec<Type>),
+ Array(Box<Type>),
+ Map(OrdType, Box<Type>),
+ Struct(FxHashMap<String, Type>),
+ Fn(Box<FuncType>),
+}
+
+impl Type {
+ pub fn unit() -> Self {
+ Type::Tuple(Vec::new())
+ }
+
+ pub fn unify(self, other: Type, coerce: Coerce) -> Result<Type> {
+ use Type::*;
+
+ Ok(match (self, other) {
+ (Free, typ) => typ,
+ (typ, Free) => typ,
+ (Bool, Bool) => Bool,
+ (Int, Int) => Int,
+ (Str, Str) => Str,
+ (Option(self_inner), Option(other_inner)) => {
+ Option(Box::new(self_inner.unify(*other_inner, coerce)?))
+ }
+ (Option(self_inner), other_typ) => {
+ Option(Box::new(self_inner.unify(other_typ, coerce)?))
+ }
+ (self_typ, Option(other_inner)) if coerce != Coerce::Assign => {
+ Option(Box::new(self_typ.unify(*other_inner, coerce)?))
+ }
+ (Tuple(self_elems), Tuple(other_elems)) if self_elems.len() == other_elems.len() => {
+ Tuple(
+ self_elems
+ .into_iter()
+ .zip(other_elems.into_iter())
+ .map(|(t1, t2)| t1.unify(t2, coerce))
+ .collect::<Result<_>>()?,
+ )
+ }
+ (Array(self_inner), Array(other_inner)) => {
+ Array(Box::new(self_inner.unify(*other_inner, coerce)?))
+ }
+ (Map(self_key, self_value), Map(other_key, other_value)) if self_key == other_key => {
+ Map(self_key, Box::new(self_value.unify(*other_value, coerce)?))
+ }
+ (Struct(self_entries), Struct(mut other_entries)) => {
+ if self_entries.len() != other_entries.len() {
+ return Err(Error::typ("conflicting struct types"));
+ }
+ Struct(
+ self_entries
+ .into_iter()
+ .map(|(k, v)| {
+ let Some(v2) = other_entries.remove(&k) else {
+ return Err(Error::typ("conflicting struct types"));
+ };
+ Ok((k, v.unify(v2, coerce)?))
+ })
+ .collect::<Result<_>>()?,
+ )
+ }
+ _ => return Err(Error::typ("type conflict")),
+ })
+ }
+}
+
+impl From<OrdType> for Type {
+ fn from(value: OrdType) -> Self {
+ match value {
+ OrdType::Free => Type::Free,
+ OrdType::Int => Type::Int,
+ OrdType::Str => Type::Str,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum OrdType {
+ Free,
+ Int,
+ Str,
+}
+
+impl OrdType {
+ pub fn unify(self, other: OrdType, _coerce: Coerce) -> Result<OrdType> {
+ use OrdType::*;
+
+ Ok(match (self, other) {
+ (Free, typ) => typ,
+ (typ, Free) => typ,
+ (Int, Int) => Int,
+ (Str, Str) => Str,
+ _ => return Err(Error::typ("type conflict")),
+ })
+ }
+}
+
+impl Display for OrdType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ OrdType::Free => f.write_str("_"),
+ OrdType::Int => f.write_str("int"),
+ OrdType::Str => f.write_str("str"),
+ }
+ }
+}
+
+impl TryFrom<Type> for OrdType {
+ type Error = Error;
+
+ fn try_from(value: Type) -> Result<Self> {
+ Ok(match value {
+ Type::Free => OrdType::Free,
+ Type::Int => OrdType::Int,
+ Type::Str => OrdType::Str,
+ _ => return Err(Error::typ("invalid type for set/map key")),
+ })
+ }
+}
+
+impl<'scope> Context<'scope> {
+ pub fn type_block_stmt(&mut self, stmt: &ast::BlockStmt) -> Result<Type> {
+ Ok(match stmt {
+ ast::BlockStmt::Let { dest, expr } => {
+ let ast::TypedPat { pat, typ } = dest.as_ref();
+
+ let dest_ident = scope::pat_ident(pat);
+
+ let explicit_type = if let Some(typ) = typ {
+ let type_ctx = TypeContext(self.0);
+ type_ctx.type_type(typ)?
+ } else {
+ Type::Free
+ };
+
+ let expr_type = expr
+ .as_ref()
+ .map(|expr| {
+ let expr_type = self.type_expr(expr)?;
+ explicit_type
+ .clone()
+ .unify(expr_type.clone(), Coerce::Assign)?;
+ Ok(expr_type)
+ })
+ .transpose()?;
+
+ if dest_ident.name == "_" {
+ return Ok(Type::unit());
+ }
+
+ self.0
+ .defs
+ .insert_value(dest_ident.name.as_ref(), VarType::new(explicit_type));
+ Rc::make_mut(&mut self.0.initialized).remove(dest_ident.name.as_ref());
+
+ let Some(expr_type) = expr_type else {
+ return Ok(Type::unit());
+ };
+
+ self.assign_destr_pat_type(&pat::DestrPat::from(pat.borrowed()), expr_type)?
+ }
+ ast::BlockStmt::Assign { dest, expr } => {
+ let expr_type = self.type_expr(expr)?;
+ self.assign_destr_pat_type(dest, expr_type)?
+ }
+ ast::BlockStmt::Fn {
+ ident,
+ params,
+ ret,
+ block,
+ } => {
+ let typ = Type::Fn(Box::new(self.type_fn_def(params, ret.as_deref(), block)?));
+
+ // TODO: Reject in validation?
+ if ident.name == "_" {
+ return Ok(Type::unit());
+ }
+ self.0
+ .defs
+ .insert_value(ident.name.as_ref(), VarType::new(typ.clone()));
+ Rc::make_mut(&mut self.0.initialized).insert(ident.name.as_ref().to_owned());
+
+ typ
+ }
+ ast::BlockStmt::Expr { expr } => self.type_expr(expr)?,
+ ast::BlockStmt::Empty => Type::unit(),
+ })
+ }
+
+ fn type_fn_def(
+ &mut self,
+ params: &[ast::FuncParam],
+ ret: Option<&typ::Type>,
+ block: &ast::Block,
+ ) -> Result<FuncType> {
+ let type_ctx = TypeContext(self.0);
+ let typ = type_ctx.type_fn_def(params, ret)?;
+
+ let mut scope = Box::new(self.0.func());
+
+ {
+ let defs = &mut scope.defs;
+ let initialized = Rc::make_mut(&mut scope.initialized);
+
+ for (param, param_type) in params.iter().zip(&typ.params) {
+ defs.insert_value(param.name.name.as_ref(), VarType::new(param_type.clone()));
+ initialized.insert(param.name.name.as_ref().to_owned());
+ }
+ }
+
+ typ.ret
+ .clone()
+ .unify(Context(&mut scope).type_block(block)?, Coerce::Assign)?;
+
+ Ok(typ)
+ }
+
+ pub fn type_expr(&mut self, expr: &expr::Expr<'_>) -> Result<Type> {
+ use expr::Expr::*;
+
+ match expr {
+ Binary { left, op, right } => self.type_binary_op(left, *op, right),
+ Unary { op, expr } => self.type_unary_op(*op, expr),
+ Apply { expr, params } => self.type_apply(expr, params),
+ Method {
+ expr,
+ method,
+ params,
+ } => self.type_method(expr, method, params),
+ Index { base, index } => self.type_index(base, index),
+ Field { base, field } => self.type_field(base, field),
+ Block(block) => self.type_block(block),
+ IfElse {
+ if_blocks,
+ else_block,
+ } => self.type_ifelse(if_blocks, else_block),
+ Paren(subexpr) => self.type_expr(subexpr),
+ Path(path) => self.type_path(path),
+ Literal(lit) => self.type_literal(lit),
+ }
+ }
+
+ fn type_binary_op(
+ &mut self,
+ left: &expr::Expr<'_>,
+ op: expr::OpBinary,
+ right: &expr::Expr<'_>,
+ ) -> Result<Type> {
+ use expr::OpBinary::*;
+ use Type::*;
+
+ let tl = self.type_expr(left)?;
+ let tr = self.type_expr(right)?;
+
+ Ok(match (tl, op, tr) {
+ (Str, Add, Str) => Str,
+ (Int, Add, Int) => Int,
+ (Array(t1), Add, Array(t2)) => Array(Box::new(t1.unify(*t2, Coerce::Common)?)),
+ (Int, Sub, Int) => Int,
+ (Array(t1), Sub, Array(t2)) => {
+ (*t1).clone().unify(*t2, Coerce::Compare)?;
+ Array(t1)
+ }
+ (Int, Mul, Int) => Int,
+ (Int, Div, Int) => Int,
+ (Int, Rem, Int) => Int,
+ (Bool, And, Bool) => Bool,
+ (Bool, Or, Bool) => Bool,
+ (l, Eq, r) => {
+ l.unify(r, Coerce::Compare)?;
+ Bool
+ }
+ (l, Ne, r) => {
+ l.unify(r, Coerce::Compare)?;
+ Bool
+ }
+ (Int, Lt, Int) => Bool,
+ (Int, Le, Int) => Bool,
+ (Int, Ge, Int) => Bool,
+ (Int, Gt, Int) => Bool,
+ (Str, Lt, Str) => Bool,
+ (Str, Le, Str) => Bool,
+ (Str, Ge, Str) => Bool,
+ (Str, Gt, Str) => Bool,
+ _ => return Err(Error::typ("invalid types for operation")),
+ })
+ }
+
+ fn type_unary_op(&mut self, op: expr::OpUnary, expr: &expr::Expr<'_>) -> Result<Type> {
+ use expr::OpUnary::*;
+ use Type::*;
+
+ let typ = self.type_expr(expr)?;
+
+ Ok(match (op, typ) {
+ (Not, Bool) => Bool,
+ (Neg, Int) => Int,
+ _ => return Err(Error::typ("invalid type for operation")),
+ })
+ }
+
+ fn type_index(&mut self, base: &expr::Expr<'_>, index: &expr::Expr<'_>) -> Result<Type> {
+ use Type::*;
+
+ let base_type = self.type_expr(base)?;
+ let index_type = self.type_expr(index)?;
+
+ if let Array(elem_type) = base_type {
+ if index_type == Int {
+ return Ok(*elem_type);
+ }
+ } else if let Map(key_type, value_type) = base_type {
+ if Type::from(key_type)
+ .unify(index_type, Coerce::Assign)
+ .is_ok()
+ {
+ return Ok(*value_type);
+ }
+ }
+
+ Err(Error::typ("invalid types index operation"))
+ }
+
+ fn type_func(&mut self, func: FuncType, call_param_types: Vec<Result<Type>>) -> Result<Type> {
+ if func.params.len() != call_param_types.len() {
+ return Err(Error::typ("incorrect number of parameters"));
+ }
+
+ for (func_param_type, call_param_type) in func.params.iter().zip(call_param_types) {
+ func_param_type
+ .clone()
+ .unify(call_param_type?, Coerce::Assign)?;
+ }
+
+ Ok(func.ret)
+ }
+
+ fn type_apply(&mut self, expr: &expr::Expr<'_>, params: &[expr::Expr]) -> Result<Type> {
+ use Type::*;
+
+ let expr_type = self.type_expr(expr)?;
+
+ let Fn(func) = expr_type else {
+ return Err(Error::typ("invalid type for function call"));
+ };
+
+ let param_types = params.iter().map(|param| self.type_expr(param)).collect();
+
+ self.type_func(*func, param_types)
+ }
+
+ fn type_method(
+ &mut self,
+ expr: &expr::Expr<'_>,
+ method: &ast::Ident<'_>,
+ params: &[expr::Expr],
+ ) -> Result<Type> {
+ let self_type = self.type_expr(expr)?;
+ let type_family = TypeFamily::from(&self_type);
+
+ let method = self
+ .0
+ .methods
+ .get(&type_family)
+ .and_then(|methods| methods.get(method.name.as_ref()))
+ .ok_or(Error::lookup("undefined method"))?
+ .typ
+ .clone();
+
+ let param_types = iter::once(Ok(self_type))
+ .chain(params.iter().map(|param| self.type_expr(param)))
+ .collect();
+
+ self.type_func(method, param_types)
+ }
+
+ fn type_field(&mut self, base: &expr::Expr<'_>, field: &ast::Ident<'_>) -> Result<Type> {
+ use Type::*;
+
+ let base_type = self.type_expr(base)?;
+ let name = field.name.as_ref();
+
+ Ok(match base_type {
+ Tuple(elems) => {
+ let index: usize = name.parse().or(Err(Error::typ("no such field")))?;
+ elems
+ .into_iter()
+ .nth(index)
+ .ok_or(Error::typ("no such field"))?
+ }
+ Struct(mut entries) => entries.remove(name).ok_or(Error::typ("no such field"))?,
+ _ => return Err(Error::typ("invalid field access base type")),
+ })
+ }
+
+ fn type_scope(&mut self, stmts: &[ast::BlockStmt]) -> Result<(Type, FxHashSet<String>)> {
+ let (ret, upvalues) = self.scoped(|ctx| {
+ let mut ret = Type::unit();
+ for stmt in stmts {
+ ret = ctx.type_block_stmt(stmt)?;
+ }
+ Ok(ret)
+ });
+ Ok((ret?, upvalues))
+ }
+
+ fn type_block(&mut self, block: &ast::Block) -> Result<Type> {
+ if let [ast::BlockStmt::Expr { expr }] = &block.0[..] {
+ return self.type_expr(expr);
+ }
+
+ let (ret, upvalues) = self.type_scope(&block.0)?;
+ self.0.initialize_all(upvalues);
+ Ok(ret)
+ }
+
+ fn type_ifelse(
+ &mut self,
+ if_blocks: &[(expr::Expr, ast::Block)],
+ else_block: &Option<Box<ast::Block>>,
+ ) -> Result<Type> {
+ let (mut ret, mut common_upvalues) = if let Some(block) = else_block {
+ self.type_scope(&block.0)?
+ } else {
+ (Type::unit(), FxHashSet::default())
+ };
+
+ for (cond, block) in if_blocks {
+ Type::Bool.unify(self.type_expr(cond)?, Coerce::Assign)?;
+
+ let (typ, upvalues) = self.type_scope(&block.0)?;
+ ret = ret.unify(typ, Coerce::Common)?;
+ common_upvalues = &common_upvalues & &upvalues;
+ }
+
+ self.0.initialize_all(common_upvalues);
+
+ Ok(ret)
+ }
+
+ fn type_path(&self, path: &ast::Path<'_>) -> Result<Type> {
+ let var = self.0.lookup_value(path)?;
+ if !self.0.is_initialized(path) {
+ return Err(Error::typ("uninitialized variable"));
+ }
+ Ok(var.inferred_type.clone())
+ }
+
+ fn check_string_interp_type(typ: Type, kind: expr::StrKind) -> Result<()> {
+ match (typ, kind) {
+ (Type::Free, _) => Ok(()),
+ (Type::Bool, _) => Ok(()),
+ (Type::Int, _) => Ok(()),
+ (Type::Str, _) => Ok(()),
+ (Type::Option(inner), expr::StrKind::Script) => {
+ Self::check_string_interp_type(*inner, kind)
+ }
+ (Type::Tuple(elems), expr::StrKind::Script) => {
+ for elem in elems {
+ Self::check_string_interp_type(elem, kind)?;
+ }
+ Ok(())
+ }
+ (Type::Array(inner), expr::StrKind::Script) => {
+ Self::check_string_interp_type(*inner, kind)
+ }
+ _ => Err(Error::typ("invalid type for string interpolation")),
+ }
+ }
+
+ fn check_string_piece(&mut self, piece: &expr::StrPiece, kind: expr::StrKind) -> Result<()> {
+ let typ = match piece {
+ expr::StrPiece::Chars(_) => return Ok(()),
+ expr::StrPiece::Escape(_) => return Ok(()),
+ expr::StrPiece::Interp(expr) => self.type_expr(expr)?,
+ };
+ Self::check_string_interp_type(typ, kind)
+ }
+
+ fn type_literal(&mut self, lit: &expr::Literal<'_>) -> Result<Type> {
+ use expr::Literal;
+ use Type::*;
+
+ Ok(match lit {
+ Literal::Unit => Type::unit(),
+ Literal::None => Type::Option(Box::new(Type::Free)),
+ Literal::Bool(_) => Bool,
+ Literal::Int(_) => Int,
+ Literal::Str { pieces, kind } => {
+ for piece in pieces {
+ self.check_string_piece(piece, *kind)?;
+ }
+ Str
+ }
+ Literal::Tuple(elems) => Tuple(
+ elems
+ .iter()
+ .map(|elem| self.type_expr(elem))
+ .collect::<Result<_>>()?,
+ ),
+ Literal::Array(elems) => Array(Box::new(
+ elems.iter().try_fold(Type::Free, |acc, elem| {
+ acc.unify(self.type_expr(elem)?, Coerce::Common)
+ })?,
+ )),
+ Literal::Map(entries) => {
+ let (key, value) = entries.iter().try_fold(
+ (Type::Free, Type::Free),
+ |(acc_key, acc_value), expr::MapEntry { key, value }| {
+ Ok((
+ acc_key.unify(self.type_expr(key)?, Coerce::Common)?,
+ acc_value.unify(self.type_expr(value)?, Coerce::Common)?,
+ ))
+ },
+ )?;
+ Type::Map(key.try_into()?, Box::new(value))
+ }
+ Literal::Struct(entries) => {
+ if entries.is_empty() {
+ return Ok(Type::unit());
+ }
+ Struct(
+ entries
+ .iter()
+ .map(|expr::StructField { name, value }| {
+ Ok((name.as_ref().to_owned(), self.type_expr(value)?))
+ })
+ .collect::<Result<_>>()?,
+ )
+ }
+ })
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn assign_destr_pat_type_with<'a, 'r, R: 'r>(
+ &mut self,
+ pat: &pat::DestrPat<'a>,
+ f: Box<dyn FnOnce(&mut Type, ast::Ident<'a>) -> Result<R> + 'r>,
+ ) -> Result<R> {
+ let initialized = self.0.initialized.clone();
+ match pat {
+ pat::DestrPat::Index { base, index } => {
+ let index_type = self.type_expr(index)?;
+ self.assign_destr_pat_type_with(
+ base,
+ Box::new(|base_type, ident| {
+ if !initialized.contains(ident.name.as_ref()) {
+ return Err(Error::typ(
+ "tried to assign field of uninitialized variable",
+ ));
+ }
+ match (base_type, index_type) {
+ (Type::Array(inner), Type::Int) => f(inner.as_mut(), ident),
+ (Type::Map(key_type, value_type), index_type) => {
+ *key_type = key_type
+ .clone()
+ .unify(OrdType::try_from(index_type)?, Coerce::Common)?;
+ f(value_type.as_mut(), ident)
+ }
+ _ => Err(Error::typ("invalid types for index operation")),
+ }
+ }),
+ )
+ }
+ pat::DestrPat::Field { base, field } => self.assign_destr_pat_type_with(
+ base,
+ Box::new(|typ, ident| {
+ if !initialized.contains(ident.name.as_ref()) {
+ return Err(Error::typ(
+ "tried to assign field of uninitialized variable",
+ ));
+ }
+ match typ {
+ Type::Tuple(elems) => {
+ let index: Option<usize> = field.name.parse().ok();
+ f(
+ index
+ .and_then(|index| elems.get_mut(index))
+ .ok_or(Error::typ("no such field"))?,
+ ident,
+ )
+ }
+ Type::Struct(entries) => f(
+ entries
+ .get_mut(field.name.as_ref())
+ .ok_or(Error::typ("no such field"))?,
+ ident,
+ ),
+ _ => Err(Error::typ("invalid field access base type")),
+ }
+ }),
+ ),
+ pat::DestrPat::Paren(subpat) => self.assign_destr_pat_type_with(subpat, f),
+ pat::DestrPat::Path(path) => {
+ let (var, ident) = self.0.lookup_var_mut(path)?;
+ f(&mut var.inferred_type, ident)
+ }
+ }
+ }
+
+ fn assign_destr_pat_type(&mut self, dest: &pat::DestrPat, typ: Type) -> Result<Type> {
+ if scope::is_wildcard_destr_pat(dest) {
+ return Ok(Type::unit());
+ }
+
+ let (inferred_type, ident) = self.assign_destr_pat_type_with(
+ dest,
+ Box::new(|dest_type, ident| {
+ let inferred_type = dest_type.clone().unify(typ, Coerce::Common)?;
+ *dest_type = inferred_type.clone();
+
+ Ok((inferred_type, ident))
+ }),
+ )?;
+
+ self.0.initialize(ident.name.as_ref());
+
+ // TODO: Check explicit type
+
+ Ok(inferred_type)
+ }
+
+ fn scoped<F, R>(&mut self, f: F) -> (R, FxHashSet<String>)
+ where
+ F: FnOnce(&mut Context) -> R,
+ {
+ self.0.scoped(|scope| f(&mut Context(scope)))
+ }
+}
+
+impl<'scope, T> TypeContext<'scope, T> {
+ pub fn type_type(&self, typ: &typ::Type<'_>) -> Result<Type> {
+ use typ::Type::*;
+
+ Ok(match typ {
+ Paren(subtyp) => self.type_type(subtyp)?,
+ Option(subtyp) => Type::Option(Box::new(self.type_type(subtyp)?)),
+ Path(path) => self.type_type_path(path)?,
+ Literal(lit) => self.type_type_literal(lit)?,
+ })
+ }
+
+ fn type_type_path(&self, path: &ast::Path<'_>) -> Result<Type> {
+ self.0.lookup_type(path).cloned()
+ }
+
+ fn type_type_literal(&self, lit: &typ::Literal<'_>) -> Result<Type> {
+ use typ::Literal;
+ use Type::*;
+
+ Ok(match lit {
+ Literal::Unit => Type::unit(),
+ Literal::Tuple(elems) => Tuple(
+ elems
+ .iter()
+ .map(|elem| self.type_type(elem))
+ .collect::<Result<_>>()?,
+ ),
+ Literal::Array(typ) => Array(Box::new(self.type_type(typ)?)),
+ Literal::Map(key, value) => Map(
+ self.type_type(key)?.try_into()?,
+ Box::new(self.type_type(value)?),
+ ),
+ Literal::Struct(entries) => {
+ if entries.is_empty() {
+ return Ok(Type::unit());
+ }
+ Struct(
+ entries
+ .iter()
+ .map(|typ::StructField { name, typ }| {
+ Ok((name.as_ref().to_owned(), self.type_type(typ)?))
+ })
+ .collect::<Result<_>>()?,
+ )
+ }
+ })
+ }
+
+ pub fn type_fn_def(
+ &self,
+ params: &[ast::FuncParam],
+ ret: Option<&typ::Type>,
+ ) -> Result<FuncType> {
+ let param_types: Vec<_> = params
+ .iter()
+ .map(|param| self.type_type(&param.typ))
+ .collect::<Result<_>>()?;
+ let ret_type = ret
+ .map(|typ| self.type_type(typ))
+ .transpose()?
+ .unwrap_or(Type::unit());
+
+ Ok(FuncType {
+ params: param_types,
+ ret: ret_type,
+ })
+ }
+}
+
+impl Display for Type {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Type::Free => f.write_str("_"),
+ Type::Bool => f.write_str("bool"),
+ Type::Int => f.write_str("int"),
+ Type::Str => f.write_str("str"),
+ Type::Option(inner) => write!(f, "{inner}?"),
+ Type::Tuple(elems) => {
+ let mut first = true;
+ f.write_str("(")?;
+ for elem in elems {
+ if !first {
+ f.write_str(", ")?;
+ }
+ first = false;
+ elem.fmt(f)?;
+ }
+ if elems.len() == 1 {
+ f.write_str(",")?;
+ }
+ f.write_str(")")
+ }
+ Type::Array(typ) => write!(f, "[{typ}]"),
+ Type::Map(key, value) => write!(f, "map{{{key} => {value}}}"),
+ Type::Struct(entries) => {
+ let mut first = true;
+ f.write_str("{")?;
+ for (key, typ) in entries {
+ if !first {
+ f.write_str(", ")?;
+ }
+ first = false;
+ write!(f, "{key}: {typ}")?;
+ }
+ f.write_str("}")
+ }
+ /* TODO */
+ Type::Fn(func) => write!(f, "{func}"),
+ }
+ }
+}
diff --git a/crates/rebel-lang/src/value.rs b/crates/rebel-lang/src/value.rs
new file mode 100644
index 0000000..bca3fee
--- /dev/null
+++ b/crates/rebel-lang/src/value.rs
@@ -0,0 +1,713 @@
+use std::{
+ cell::RefCell,
+ fmt::{Display, Write},
+ iter,
+};
+
+use rebel_parse::ast::{self, expr, pat, typ};
+use rustc_hash::{FxHashMap, FxHashSet};
+
+use crate::{
+ func::{Func, FuncDef},
+ scope::{self, Scope},
+ typing::{Coerce, OrdType, Type, TypeContext, TypeFamily},
+ Error, Result,
+};
+
+#[derive(Debug)]
+pub struct Context<'scope>(pub &'scope mut Box<Scope<Value>>);
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Value {
+ Uninitialized,
+ None,
+ Bool(bool),
+ Int(i64),
+ Str(String),
+ Tuple(Vec<Value>),
+ Map(FxHashMap<OrdValue, Value>),
+ Array(Vec<Value>),
+ Struct(FxHashMap<String, Value>),
+ Fn(Box<Func>),
+}
+
+impl Value {
+ pub fn unit() -> Self {
+ Value::Tuple(Vec::new())
+ }
+
+ pub fn typ(&self) -> Result<Type> {
+ Ok(match self {
+ Value::Uninitialized => return Err(Error::typ("uninitialized value")),
+ Value::None => Type::Option(Box::new(Type::Free)),
+ Value::Bool(_) => Type::Bool,
+ Value::Int(_) => Type::Int,
+ Value::Str(_) => Type::Str,
+ Value::Tuple(elems) => {
+ Type::Tuple(elems.iter().map(|elem| elem.typ()).collect::<Result<_>>()?)
+ }
+ Value::Array(elems) => Type::Array(Box::new(Self::common_type(elems)?)),
+ Value::Map(entries) => Type::Map(
+ Self::common_ord_type(entries.keys())?,
+ Box::new(Self::common_type(entries.values())?),
+ ),
+ Value::Struct(entries) => Type::Struct(
+ entries
+ .iter()
+ .map(|(k, v)| Ok((k.clone(), v.typ()?)))
+ .collect::<Result<_>>()?,
+ ),
+ Value::Fn(func) => Type::Fn(Box::new(func.typ.clone())),
+ })
+ }
+
+ fn common_type<'a>(values: impl IntoIterator<Item = &'a Value>) -> Result<Type> {
+ values.into_iter().try_fold(Type::Free, |acc, value| {
+ acc.unify(value.typ()?, Coerce::Common)
+ })
+ }
+
+ fn common_ord_type<'a>(values: impl IntoIterator<Item = &'a OrdValue>) -> Result<OrdType> {
+ values.into_iter().try_fold(OrdType::Free, |acc, value| {
+ acc.unify(value.typ()?, Coerce::Common)
+ })
+ }
+}
+
+impl From<OrdValue> for Value {
+ fn from(value: OrdValue) -> Self {
+ match value {
+ OrdValue::Int(v) => Value::Int(v),
+ OrdValue::Str(v) => Value::Str(v),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum OrdValue {
+ Int(i64),
+ Str(String),
+}
+
+impl OrdValue {
+ pub fn typ(&self) -> Result<OrdType> {
+ Ok(match self {
+ OrdValue::Int(_) => OrdType::Int,
+ OrdValue::Str(_) => OrdType::Str,
+ })
+ }
+}
+
+impl Display for OrdValue {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ OrdValue::Int(value) => value.fmt(f),
+ OrdValue::Str(value) => write!(f, "{value:?}"),
+ }
+ }
+}
+
+impl TryFrom<Value> for OrdValue {
+ type Error = Error;
+
+ fn try_from(value: Value) -> Result<Self> {
+ Ok(match value {
+ Value::Int(v) => OrdValue::Int(v),
+ Value::Str(v) => OrdValue::Str(v),
+ _ => return Err(Error::bug("OrdValue from unordered value")),
+ })
+ }
+}
+
+impl<'scope> Context<'scope> {
+ pub fn eval_block_stmt(&mut self, stmt: &ast::BlockStmt) -> Result<Value> {
+ Ok(match stmt {
+ ast::BlockStmt::Let { dest, expr } => {
+ let ast::TypedPat { pat, typ: _ } = dest.as_ref();
+
+ let dest_ident = scope::pat_ident(pat);
+
+ let value = expr.as_ref().map(|expr| self.eval_expr(expr)).transpose()?;
+
+ if dest_ident.name == "_" {
+ return Ok(Value::unit());
+ }
+
+ self.0
+ .defs
+ .insert_value(dest_ident.name.as_ref(), Value::Uninitialized);
+
+ let Some(value) = value else {
+ return Ok(Value::unit());
+ };
+
+ self.assign_destr_pat_value(&pat::DestrPat::from(pat.borrowed()), value)?
+ }
+ ast::BlockStmt::Assign { dest, expr } => {
+ let value = self.eval_expr(expr)?;
+ self.assign_destr_pat_value(dest, value)?
+ }
+ ast::BlockStmt::Fn {
+ ident,
+ params,
+ ret,
+ block,
+ } => {
+ let value = Value::Fn(Box::new(self.eval_fn_def(params, ret.as_deref(), block)?));
+
+ // TODO: Reject in validation?
+ if ident.name == "_" {
+ return Ok(Value::unit());
+ }
+ self.0.defs.insert_value(ident.name.as_ref(), value.clone());
+
+ value
+ }
+ ast::BlockStmt::Expr { expr } => self.eval_expr(expr)?,
+ ast::BlockStmt::Empty => Value::unit(),
+ })
+ }
+
+ fn eval_fn_def(
+ &mut self,
+ params: &[ast::FuncParam],
+ ret: Option<&typ::Type>,
+ block: &ast::Block,
+ ) -> Result<Func> {
+ let type_ctx = TypeContext(self.0);
+ let typ = type_ctx.type_fn_def(params, ret)?;
+
+ let param_names: Vec<_> = params
+ .iter()
+ .map(|param| param.name.name.as_ref().to_owned())
+ .collect();
+
+ Ok(Func {
+ typ,
+ def: FuncDef::Body {
+ param_names,
+ block: block.borrowed().into_owned(),
+ },
+ })
+ }
+
+ pub fn eval_expr(&mut self, expr: &expr::Expr<'_>) -> Result<Value> {
+ use expr::Expr::*;
+
+ match expr {
+ Binary { left, op, right } => self.eval_binary_op(left, *op, right),
+ Unary { op, expr } => self.eval_unary_op(*op, expr),
+ Apply { expr, params } => self.eval_apply(expr, params),
+ Method {
+ expr,
+ method,
+ params,
+ } => self.eval_method(expr, method, params),
+ Index { base, index } => self.eval_index(base, index),
+ Field { base, field } => self.eval_field(base, field),
+ Block(block) => self.eval_block(block),
+ IfElse {
+ if_blocks,
+ else_block,
+ } => self.eval_ifelse(if_blocks, else_block),
+ Paren(subexpr) => self.eval_expr(subexpr),
+ Path(path) => self.eval_path(path),
+ Literal(lit) => self.eval_literal(lit),
+ }
+ }
+
+ fn eval_binary_op(
+ &mut self,
+ left: &expr::Expr<'_>,
+ op: expr::OpBinary,
+ right: &expr::Expr<'_>,
+ ) -> Result<Value> {
+ use expr::OpBinary::*;
+ use Value::*;
+
+ let tl = self.eval_expr(left)?;
+ let tr = self.eval_expr(right)?;
+
+ Ok(match (tl, op, tr) {
+ (Str(s1), Add, Str(s2)) => Str(s1 + &s2),
+ (Int(i1), Add, Int(i2)) => Int(i1
+ .checked_add(i2)
+ .ok_or(Error::eval("integer over- or underflow"))?),
+ (Array(elems1), Add, Array(elems2)) => Array([elems1, elems2].concat()),
+ (Int(i1), Sub, Int(i2)) => Int(i1
+ .checked_sub(i2)
+ .ok_or(Error::eval("integer over- or underflow"))?),
+ (Array(elems1), Sub, Array(elems2)) => Array(
+ elems1
+ .into_iter()
+ .filter(|elem| !elems2.contains(elem))
+ .collect(),
+ ),
+ (Int(i1), Mul, Int(i2)) => Int(i1
+ .checked_mul(i2)
+ .ok_or(Error::eval("integer over- or underflow"))?),
+ (Int(i1), Div, Int(i2)) => {
+ Int(i1.checked_div(i2).ok_or(Error::eval("division by zero"))?)
+ }
+ (Int(i1), Rem, Int(i2)) => {
+ Int(i1.checked_rem(i2).ok_or(Error::eval("division by zero"))?)
+ }
+ (Bool(b1), And, Bool(b2)) => Bool(b1 && b2),
+ (Bool(b1), Or, Bool(b2)) => Bool(b1 || b2),
+ (l, Eq, r) => Bool(l == r),
+ (l, Ne, r) => Bool(l != r),
+ (Int(i1), Lt, Int(i2)) => Bool(i1 < i2),
+ (Int(i1), Le, Int(i2)) => Bool(i1 <= i2),
+ (Int(i1), Ge, Int(i2)) => Bool(i1 >= i2),
+ (Int(i1), Gt, Int(i2)) => Bool(i1 > i2),
+ (Str(i1), Lt, Str(i2)) => Bool(i1 < i2),
+ (Str(i1), Le, Str(i2)) => Bool(i1 <= i2),
+ (Str(i1), Ge, Str(i2)) => Bool(i1 >= i2),
+ (Str(i1), Gt, Str(i2)) => Bool(i1 > i2),
+ _ => return Err(Error::typ("invalid types for operation")),
+ })
+ }
+
+ fn eval_unary_op(&mut self, op: expr::OpUnary, expr: &expr::Expr<'_>) -> Result<Value> {
+ use expr::OpUnary::*;
+ use Value::*;
+
+ let typ = self.eval_expr(expr)?;
+
+ Ok(match (op, typ) {
+ (Not, Bool(val)) => Bool(!val),
+ (Neg, Int(val)) => Int(val
+ .checked_neg()
+ .ok_or(Error::eval("integer over- or underflow"))?),
+ _ => return Err(Error::typ("invalid type for operation")),
+ })
+ }
+
+ fn eval_index(&mut self, base: &expr::Expr<'_>, index: &expr::Expr<'_>) -> Result<Value> {
+ use Value::*;
+
+ let base_value = self.eval_expr(base)?;
+ let index_value = self.eval_expr(index)?;
+
+ if let Array(elems) = base_value {
+ if let Int(index) = index_value {
+ return usize::try_from(index)
+ .ok()
+ .and_then(|index| elems.into_iter().nth(index))
+ .ok_or(Error::eval("array index out of bounds"));
+ }
+ } else if let Map(entries) = base_value {
+ return entries
+ .get(&index_value.try_into()?)
+ .cloned()
+ .ok_or(Error::eval("map key not found"));
+ }
+
+ Err(Error::typ("invalid types index operation"))
+ }
+
+ fn eval_func(&mut self, func: Func, call_param_values: Vec<Value>) -> Result<Value> {
+ match func.def {
+ FuncDef::Intrinsic(f) => f(&call_param_values),
+ FuncDef::Body { param_names, block } => {
+ let mut scope = Box::new(self.0.func());
+
+ for (param_name, param_value) in param_names.iter().zip(call_param_values) {
+ scope.defs.insert_value(param_name, param_value);
+ }
+
+ Context(&mut scope).eval_block(&block)
+ }
+ }
+ }
+
+ fn eval_apply(&mut self, expr: &expr::Expr<'_>, params: &[expr::Expr]) -> Result<Value> {
+ use Value::*;
+
+ let value = self.eval_expr(expr)?;
+
+ let Fn(func) = value else {
+ return Err(Error::typ("invalid type for function call"));
+ };
+
+ let param_values: Vec<_> = params
+ .iter()
+ .map(|param| self.eval_expr(param))
+ .collect::<Result<_>>()?;
+
+ self.eval_func(*func, param_values)
+ }
+
+ fn eval_method(
+ &mut self,
+ expr: &expr::Expr<'_>,
+ method: &ast::Ident<'_>,
+ params: &[expr::Expr],
+ ) -> Result<Value> {
+ let self_value = self.eval_expr(expr)?;
+ // TODO: Use inferred type for method lookup
+ let self_type = self_value.typ()?;
+ let type_family = TypeFamily::from(&self_type);
+
+ let method = self
+ .0
+ .methods
+ .get(&type_family)
+ .and_then(|methods| methods.get(method.name.as_ref()))
+ .ok_or(Error::lookup("undefined method"))?
+ .clone();
+
+ let param_values: Vec<_> = iter::once(Ok(self_value))
+ .chain(params.iter().map(|param| self.eval_expr(param)))
+ .collect::<Result<_>>()?;
+
+ self.eval_func(method, param_values)
+ }
+
+ fn eval_field(&mut self, base: &expr::Expr<'_>, field: &ast::Ident<'_>) -> Result<Value> {
+ use Value::*;
+
+ let base_value = self.eval_expr(base)?;
+ let name = field.name.as_ref();
+
+ Ok(match base_value {
+ Tuple(elems) => {
+ let index: usize = name.parse().or(Err(Error::typ("no such field")))?;
+ elems
+ .into_iter()
+ .nth(index)
+ .ok_or(Error::typ("no such field"))?
+ }
+ Struct(mut entries) => entries.remove(name).ok_or(Error::typ("no such field"))?,
+ _ => return Err(Error::typ("invalid field access base type")),
+ })
+ }
+
+ fn eval_scope(&mut self, stmts: &[ast::BlockStmt]) -> Result<(Value, FxHashSet<String>)> {
+ let (ret, upvalues) = self.scoped(|ctx| {
+ let mut ret = Value::unit();
+ for stmt in stmts {
+ ret = ctx.eval_block_stmt(stmt)?;
+ }
+ Ok(ret)
+ });
+ Ok((ret?, upvalues))
+ }
+
+ fn eval_block(&mut self, block: &ast::Block) -> Result<Value> {
+ if let [ast::BlockStmt::Expr { expr }] = &block.0[..] {
+ return self.eval_expr(expr);
+ }
+
+ let (ret, upvalues) = self.eval_scope(&block.0)?;
+ self.0.initialize_all(upvalues);
+ Ok(ret)
+ }
+
+ fn eval_ifelse(
+ &mut self,
+ if_blocks: &[(expr::Expr, ast::Block)],
+ else_block: &Option<Box<ast::Block>>,
+ ) -> Result<Value> {
+ for (cond, block) in if_blocks {
+ if self.eval_expr(cond)? == Value::Bool(true) {
+ return self.eval_block(block);
+ }
+ }
+ if let Some(block) = else_block {
+ self.eval_block(block)
+ } else {
+ Ok(Value::unit())
+ }
+ }
+
+ fn eval_path(&self, path: &ast::Path<'_>) -> Result<Value> {
+ Ok(self.0.lookup_value(path)?.clone())
+ }
+
+ fn eval_literal(&mut self, lit: &expr::Literal<'_>) -> Result<Value> {
+ use expr::Literal;
+ use Value::*;
+
+ Ok(match lit {
+ Literal::Unit => Value::unit(),
+ Literal::None => Value::None,
+ Literal::Bool(val) => Bool(*val),
+ Literal::Int(val) => Int(*val),
+ Literal::Str { pieces, kind } => Str(StrDisplay {
+ pieces,
+ kind: *kind,
+ ctx: RefCell::new(self),
+ }
+ .to_string()),
+ Literal::Tuple(elems) => Tuple(
+ elems
+ .iter()
+ .map(|elem| self.eval_expr(elem))
+ .collect::<Result<_>>()?,
+ ),
+ Literal::Array(elems) => Array(
+ elems
+ .iter()
+ .map(|elem| self.eval_expr(elem))
+ .collect::<Result<_>>()?,
+ ),
+ Literal::Map(entries) => {
+ let map: FxHashMap<_, _> = entries
+ .iter()
+ .map(|expr::MapEntry { key, value }| {
+ Ok((self.eval_expr(key)?.try_into()?, self.eval_expr(value)?))
+ })
+ .collect::<Result<_>>()?;
+ if map.len() != entries.len() {
+ return Err(Error::eval("duplicate map key"));
+ }
+ Map(map)
+ }
+ Literal::Struct(entries) => {
+ if entries.is_empty() {
+ return Ok(Value::unit());
+ }
+ Struct(
+ entries
+ .iter()
+ .map(|expr::StructField { name, value }| {
+ Ok((name.as_ref().to_owned(), self.eval_expr(value)?))
+ })
+ .collect::<Result<_>>()?,
+ )
+ }
+ })
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn assign_destr_pat_value_with<'r, R: 'r>(
+ &mut self,
+ pat: &pat::DestrPat,
+ f: Box<dyn FnOnce(&mut Value) -> Result<R> + 'r>,
+ ) -> Result<R> {
+ match pat {
+ pat::DestrPat::Index { base, index } => {
+ let index_value = self.eval_expr(index)?;
+ self.assign_destr_pat_value_with(
+ base,
+ Box::new(|base_value| match (base_value, index_value) {
+ (Value::Array(inner), Value::Int(index)) => f(usize::try_from(index)
+ .ok()
+ .and_then(|index| inner.get_mut(index))
+ .ok_or(Error::eval("array index out of bounds"))?),
+ (Value::Map(entries), key) => {
+ use std::collections::hash_map::Entry;
+ match entries.entry(OrdValue::try_from(key)?) {
+ Entry::Occupied(mut entry) => f(entry.get_mut()),
+ Entry::Vacant(entry) => {
+ let mut tmp = Value::Uninitialized;
+ let ret =
+ f(&mut tmp).or(Err(Error::eval("map key not found")))?;
+ entry.insert(tmp);
+ Ok(ret)
+ }
+ }
+ }
+ _ => Err(Error::typ("invalid types for index operation")),
+ }),
+ )
+ }
+ pat::DestrPat::Field { base, field } => self.assign_destr_pat_value_with(
+ base,
+ Box::new(|value| match value {
+ Value::Tuple(elems) => {
+ let index: Option<usize> = field.name.parse().ok();
+ f(index
+ .and_then(|index| elems.get_mut(index))
+ .ok_or(Error::typ("no such field"))?)
+ }
+ Value::Struct(value_entries) => f(value_entries
+ .get_mut(field.name.as_ref())
+ .ok_or(Error::typ("no such field"))?),
+ _ => Err(Error::typ("invalid field access base type")),
+ }),
+ ),
+ pat::DestrPat::Paren(subpat) => self.assign_destr_pat_value_with(subpat, f),
+ pat::DestrPat::Path(path) => f(self.0.lookup_var_mut(path)?.0),
+ }
+ }
+
+ fn assign_destr_pat_value(&mut self, dest: &pat::DestrPat, value: Value) -> Result<Value> {
+ if scope::is_wildcard_destr_pat(dest) {
+ return Ok(Value::unit());
+ }
+
+ self.assign_destr_pat_value_with(
+ dest,
+ Box::new(|dest_value| {
+ *dest_value = value.clone();
+ Ok(())
+ }),
+ )?;
+
+ Ok(value)
+ }
+
+ fn scoped<F, R>(&mut self, f: F) -> (R, FxHashSet<String>)
+ where
+ F: FnOnce(&mut Context) -> R,
+ {
+ self.0.scoped(|scope| f(&mut Context(scope)))
+ }
+}
+
+impl Display for Value {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Value::Uninitialized => f.write_str("_"),
+ Value::None => f.write_str("none"),
+ Value::Bool(value) => value.fmt(f),
+ Value::Int(value) => value.fmt(f),
+ Value::Str(value) => write!(f, "{value:?}"),
+ Value::Tuple(elems) => {
+ let mut first = true;
+ f.write_str("(")?;
+ for elem in elems {
+ if !first {
+ f.write_str(", ")?;
+ }
+ first = false;
+ elem.fmt(f)?;
+ }
+ if elems.len() == 1 {
+ f.write_str(",")?;
+ }
+ f.write_str(")")
+ }
+ Value::Array(elems) => {
+ let mut first = true;
+ f.write_str("[")?;
+ for elem in elems {
+ if !first {
+ f.write_str(", ")?;
+ }
+ first = false;
+ elem.fmt(f)?;
+ }
+ f.write_str("]")
+ }
+ Value::Map(entries) => {
+ let mut first = true;
+ let mut entries: Vec<_> = entries.iter().collect();
+ entries.sort_unstable_by_key(|(key, _)| *key);
+ f.write_str("map{")?;
+ for (key, value) in entries {
+ if !first {
+ f.write_str(", ")?;
+ }
+ first = false;
+ write!(f, "{key} => {value}")?;
+ }
+ f.write_str("}")
+ }
+ Value::Struct(entries) => {
+ let mut first = true;
+ f.write_str("{")?;
+ for (key, value) in entries {
+ if !first {
+ f.write_str(", ")?;
+ }
+ first = false;
+ write!(f, "{key}: {value}")?;
+ }
+ f.write_str("}")
+ }
+ Value::Fn(func) => func.typ.fmt(f),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct Stringify<'a>(&'a Value);
+
+impl<'a> Display for Stringify<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self.0 {
+ Value::Bool(value) => value.fmt(f),
+ Value::Int(value) => value.fmt(f),
+ Value::Str(value) => value.fmt(f),
+ _ => Err(std::fmt::Error),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ScriptStringify<'a>(&'a Value);
+
+impl<'a> ScriptStringify<'a> {
+ fn fmt_list(elems: &'a [Value], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mut first = true;
+ for elem in elems {
+ if !first {
+ f.write_char(' ')?;
+ }
+ ScriptStringify(elem).fmt(f)?;
+ first = false;
+ }
+ Ok(())
+ }
+}
+
+impl<'a> Display for ScriptStringify<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self.0 {
+ Value::Bool(value) => {
+ value.fmt(f)?;
+ }
+ Value::Int(value) => {
+ value.fmt(f)?;
+ }
+ Value::Str(value) => {
+ f.write_char('\'')?;
+ f.write_str(&value.replace('\'', "'\\''"))?;
+ f.write_char('\'')?;
+ }
+ Value::None => {}
+ Value::Array(elems) => {
+ Self::fmt_list(elems, f)?;
+ }
+ Value::Tuple(elems) => {
+ Self::fmt_list(elems, f)?;
+ }
+ _ => return Err(std::fmt::Error),
+ };
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+struct StrDisplay<'a, 'scope> {
+ pieces: &'a [expr::StrPiece<'a>],
+ kind: expr::StrKind,
+ ctx: RefCell<&'a mut Context<'scope>>,
+}
+
+impl<'a, 'scope> Display for StrDisplay<'a, 'scope> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for piece in self.pieces {
+ match piece {
+ expr::StrPiece::Chars(chars) => f.write_str(chars)?,
+ expr::StrPiece::Escape(c) => f.write_char(*c)?,
+ expr::StrPiece::Interp(expr) => {
+ let val = self
+ .ctx
+ .borrow_mut()
+ .eval_expr(expr)
+ .or(Err(std::fmt::Error))?;
+ match self.kind {
+ expr::StrKind::Regular => Stringify(&val).fmt(f),
+ expr::StrKind::Raw => unreachable!(),
+ expr::StrKind::Script => ScriptStringify(&val).fmt(f),
+ }?;
+ }
+ };
+ }
+ Ok(())
+ }
+}
diff --git a/crates/rebel-parse/Cargo.toml b/crates/rebel-parse/Cargo.toml
index 0233af8..d116736 100644
--- a/crates/rebel-parse/Cargo.toml
+++ b/crates/rebel-parse/Cargo.toml
@@ -8,8 +8,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-peg = { git = "https://github.com/kevinmehall/rust-peg.git", version = "0.8.2" }
+derive-into-owned = { git = "https://github.com/neocturne/derive-into-owned.git", branch = "more-types" }
+peg = "0.8.3"
+phf = { version = "0.11.2", features = ["macros"] }
rebel-common = { path = "../rebel-common" }
+rustc-hash = "1.1.0"
[dev-dependencies]
clap = { version = "4.0.0", features = ["derive"] }
diff --git a/crates/rebel-parse/examples/parse-string.rs b/crates/rebel-parse/examples/parse-string.rs
index 190d0fa..9750a87 100644
--- a/crates/rebel-parse/examples/parse-string.rs
+++ b/crates/rebel-parse/examples/parse-string.rs
@@ -9,9 +9,11 @@ enum Rule {
Tokenize,
Recipe,
RecipeStmt,
- Body,
- BodyStmt,
+ Block,
+ BlockStmt,
Expr,
+ Type,
+ Pat,
}
#[derive(Clone, Debug, Parser)]
@@ -46,9 +48,11 @@ fn main() {
Rule::Tokenize => Ok(as_debug(tokens)),
Rule::Recipe => recipe::recipe(&tokens).map(as_debug),
Rule::RecipeStmt => recipe::recipe_stmt(&tokens).map(as_debug),
- Rule::Body => recipe::body(&tokens).map(as_debug),
- Rule::BodyStmt => recipe::body_stmt(&tokens).map(as_debug),
+ Rule::Block => recipe::block(&tokens).map(as_debug),
+ Rule::BlockStmt => recipe::block_stmt(&tokens).map(as_debug),
Rule::Expr => recipe::expr(&tokens).map(as_debug),
+ Rule::Type => recipe::typ(&tokens).map(as_debug),
+ Rule::Pat => recipe::pat(&tokens).map(as_debug),
};
if opts.rule != Rule::Tokenize {
let dur = Instant::now().duration_since(start);
diff --git a/crates/rebel-parse/src/ast.rs b/crates/rebel-parse/src/ast.rs
deleted file mode 100644
index 648a79e..0000000
--- a/crates/rebel-parse/src/ast.rs
+++ /dev/null
@@ -1,223 +0,0 @@
-use crate::token;
-
-pub type Recipe<'a> = Vec<RecipeStmt<'a>>;
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum RecipeStmt<'a> {
- BodyStmt(BodyStmt<'a>),
- Fetch {
- name: Ident<'a>,
- body: Body<'a>,
- },
- Task {
- name: Ident<'a>,
- params: Vec<FuncParam<'a>>,
- body: Body<'a>,
- },
-}
-
-pub type Body<'a> = Vec<BodyStmt<'a>>;
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum BodyStmt<'a> {
- Assign {
- left: Box<TypedExpr<'a>>,
- op: Option<OpBinary>,
- right: Box<Expr<'a>>,
- },
-}
-
-impl<'a> BodyStmt<'a> {
- pub(crate) fn assign(left: TypedExpr<'a>, op: Option<OpBinary>, right: Expr<'a>) -> Self {
- BodyStmt::Assign {
- left: Box::new(left),
- op,
- right: Box::new(right),
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Expr<'a> {
- Binary {
- left: Box<Expr<'a>>,
- op: OpBinary,
- right: Box<Expr<'a>>,
- },
- Unary {
- op: OpUnary,
- expr: Box<Expr<'a>>,
- },
- Apply {
- expr: Box<Expr<'a>>,
- params: Vec<Expr<'a>>,
- },
- Method {
- expr: Box<Expr<'a>>,
- method: Ident<'a>,
- params: Vec<Expr<'a>>,
- },
- Index {
- expr: Box<Expr<'a>>,
- index: Box<Expr<'a>>,
- },
- Field {
- expr: Box<Expr<'a>>,
- field: Ident<'a>,
- },
- Paren(Box<Expr<'a>>),
- Path(Path<'a>),
- Literal(Literal<'a>),
-}
-
-impl<'a> Expr<'a> {
- pub(crate) fn binary(left: Expr<'a>, op: OpBinary, right: Expr<'a>) -> Self {
- Expr::Binary {
- left: Box::new(left),
- op,
- right: Box::new(right),
- }
- }
-
- pub(crate) fn unary(op: OpUnary, expr: Expr<'a>) -> Self {
- Expr::Unary {
- op,
- expr: Box::new(expr),
- }
- }
-
- pub(crate) fn apply(expr: Expr<'a>, params: Vec<Expr<'a>>) -> Self {
- Expr::Apply {
- expr: Box::new(expr),
- params,
- }
- }
-
- pub(crate) fn method(expr: Expr<'a>, method: Ident<'a>, params: Vec<Expr<'a>>) -> Self {
- Expr::Method {
- expr: Box::new(expr),
- method,
- params,
- }
- }
-
- pub(crate) fn index(expr: Expr<'a>, index: Expr<'a>) -> Self {
- Expr::Index {
- expr: Box::new(expr),
- index: Box::new(index),
- }
- }
-
- pub(crate) fn field(expr: Expr<'a>, field: Ident<'a>) -> Self {
- Expr::Field {
- expr: Box::new(expr),
- field,
- }
- }
-
- pub(crate) fn paren(expr: Expr<'a>) -> Self {
- Expr::Paren(Box::new(expr))
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct TypedExpr<'a> {
- pub expr: Expr<'a>,
- pub typ: Option<Expr<'a>>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct FuncParam<'a> {
- pub name: Ident<'a>,
- pub typ: Expr<'a>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Literal<'a> {
- Unit,
- Boolean(bool),
- Integer(u64),
- String(Vec<StringPiece<'a>>),
- Tuple(Vec<Expr<'a>>),
- Array(Vec<Expr<'a>>),
- Map(Vec<MapEntry<'a>>),
-}
-
-impl<'a> Literal<'a> {
- pub(crate) fn number(s: &'a str) -> Result<Self, &'static str> {
- let (radix, rest) = if let Some(rest) = s.strip_prefix("0x") {
- (16, rest)
- } else if let Some(rest) = s.strip_prefix("0o") {
- (8, rest)
- } else if let Some(rest) = s.strip_prefix("0b") {
- (2, rest)
- } else {
- (10, s)
- };
- let digits = rest.replace('_', "");
- let value = u64::from_str_radix(&digits, radix).or(Err("number"))?;
- Ok(Literal::Integer(value))
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum StringPiece<'a> {
- Chars(&'a str),
- Escape(char),
- Interp(Expr<'a>),
-}
-
-impl<'a> TryFrom<&token::StringPiece<'a>> for StringPiece<'a> {
- type Error = &'static str;
-
- fn try_from(value: &token::StringPiece<'a>) -> Result<Self, Self::Error> {
- use crate::recipe;
-
- Ok(match value {
- token::StringPiece::Chars(chars) => StringPiece::Chars(chars),
- token::StringPiece::Escape(c) => StringPiece::Escape(*c),
- token::StringPiece::Interp(tokens) => StringPiece::Interp(
- recipe::expr(tokens).or(Err("Invalid expression in string interpolation"))?,
- ),
- })
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct MapEntry<'a> {
- pub key: &'a str,
- pub value: Expr<'a>,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum OpUnary {
- Not,
- Neg,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum OpBinary {
- Add,
- Sub,
- Mul,
- Div,
- Rem,
- And,
- Or,
- Eq,
- Lt,
- Le,
- Ne,
- Ge,
- Gt,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Path<'a> {
- pub components: Vec<Ident<'a>>,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub struct Ident<'a> {
- pub name: &'a str,
-}
diff --git a/crates/rebel-parse/src/ast/expr.rs b/crates/rebel-parse/src/ast/expr.rs
new file mode 100644
index 0000000..a35a9af
--- /dev/null
+++ b/crates/rebel-parse/src/ast/expr.rs
@@ -0,0 +1,333 @@
+use std::borrow::Cow;
+
+use super::{Block, DestrPat, Ident, Path, PathRoot, ValidationError};
+use crate::token;
+use derive_into_owned::{Borrowed, IntoOwned};
+use rustc_hash::FxHashSet;
+
+pub use token::StrKind;
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum Expr<'a> {
+ Binary {
+ left: Box<Expr<'a>>,
+ op: OpBinary,
+ right: Box<Expr<'a>>,
+ },
+ Unary {
+ op: OpUnary,
+ expr: Box<Expr<'a>>,
+ },
+ Apply {
+ expr: Box<Expr<'a>>,
+ params: Vec<Expr<'a>>,
+ },
+ Method {
+ expr: Box<Expr<'a>>,
+ method: Ident<'a>,
+ params: Vec<Expr<'a>>,
+ },
+ Index {
+ base: Box<Expr<'a>>,
+ index: Box<Expr<'a>>,
+ },
+ Field {
+ base: Box<Expr<'a>>,
+ field: Ident<'a>,
+ },
+ Block(Block<'a>),
+ IfElse {
+ if_blocks: Vec<(Expr<'a>, Block<'a>)>,
+ else_block: Option<Box<Block<'a>>>,
+ },
+ Paren(Box<Expr<'a>>),
+ Path(Path<'a>),
+ Literal(Literal<'a>),
+}
+
+impl<'a> Expr<'a> {
+ pub(crate) fn binary(left: Expr<'a>, op: OpBinary, right: Expr<'a>) -> Self {
+ Expr::Binary {
+ left: Box::new(left),
+ op,
+ right: Box::new(right),
+ }
+ }
+
+ pub(crate) fn unary(op: OpUnary, expr: Expr<'a>) -> Self {
+ Expr::Unary {
+ op,
+ expr: Box::new(expr),
+ }
+ }
+
+ pub(crate) fn apply(expr: Expr<'a>, params: Vec<Expr<'a>>) -> Self {
+ Expr::Apply {
+ expr: Box::new(expr),
+ params,
+ }
+ }
+
+ pub(crate) fn method(expr: Expr<'a>, method: Ident<'a>, params: Vec<Expr<'a>>) -> Self {
+ Expr::Method {
+ expr: Box::new(expr),
+ method,
+ params,
+ }
+ }
+
+ pub(crate) fn index(base: Expr<'a>, index: Expr<'a>) -> Self {
+ Expr::Index {
+ base: Box::new(base),
+ index: Box::new(index),
+ }
+ }
+
+ pub(crate) fn field(base: Expr<'a>, field: Ident<'a>) -> Self {
+ Expr::Field {
+ base: Box::new(base),
+ field,
+ }
+ }
+
+ pub(crate) fn paren(expr: Expr<'a>) -> Self {
+ Expr::Paren(Box::new(expr))
+ }
+
+ pub fn validate(&self) -> Result<(), ValidationError> {
+ match self {
+ Expr::Binary { left, op, right } => {
+ left.validate()?;
+ right.validate()?;
+
+ if op.is_comparision()
+ && (left.is_binary_comparison() || right.is_binary_comparison())
+ {
+ return Err(ValidationError::NeedsParens);
+ }
+ Ok(())
+ }
+ Expr::Unary { op: _, expr } => expr.validate(),
+ Expr::Apply { expr, params } => {
+ for param in params {
+ param.validate()?;
+ }
+ expr.validate()
+ }
+ Expr::Method {
+ expr,
+ method: _,
+ params,
+ } => {
+ for param in params {
+ param.validate()?;
+ }
+ expr.validate()
+ }
+ Expr::Index { base, index } => {
+ index.validate()?;
+ base.validate()
+ }
+ Expr::Field { base, field: _ } => base.validate(),
+ Expr::Block(block) => {
+ for stmt in &block.0 {
+ stmt.validate()?;
+ }
+ Ok(())
+ }
+ Expr::IfElse {
+ if_blocks,
+ else_block,
+ } => {
+ for (cond, block) in if_blocks {
+ cond.validate()?;
+ block.validate()?;
+ }
+ if let Some(block) = else_block {
+ block.validate()?;
+ }
+ Ok(())
+ }
+ Expr::Paren(expr) => expr.validate(),
+ Expr::Path(_) => Ok(()),
+ Expr::Literal(lit) => lit.validate(),
+ }
+ }
+
+ fn is_binary_comparison(&self) -> bool {
+ let Expr::Binary {
+ left: _,
+ op,
+ right: _,
+ } = self
+ else {
+ return false;
+ };
+
+ op.is_comparision()
+ }
+}
+
+impl<'a> From<DestrPat<'a>> for Expr<'a> {
+ fn from(value: DestrPat<'a>) -> Self {
+ match value {
+ DestrPat::Index { base, index } => Expr::Index {
+ base: Box::new((*base).into()),
+ index: index.clone(),
+ },
+ DestrPat::Field { base, field } => Expr::Field {
+ base: Box::new((*base).into()),
+ field: field.clone(),
+ },
+ DestrPat::Paren(pat) => Expr::Paren(Box::new((*pat).into())),
+ DestrPat::Path(path) => Expr::Path(path.clone()),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum Literal<'a> {
+ Unit,
+ None,
+ Bool(bool),
+ Int(i64),
+ Str {
+ pieces: Vec<StrPiece<'a>>,
+ kind: StrKind,
+ },
+ Tuple(Vec<Expr<'a>>),
+ Array(Vec<Expr<'a>>),
+ Map(Vec<MapEntry<'a>>),
+ Struct(Vec<StructField<'a>>),
+}
+
+impl<'a> Literal<'a> {
+ fn validate(&self) -> Result<(), ValidationError> {
+ match self {
+ Literal::Unit => Ok(()),
+ Literal::None => Ok(()),
+ Literal::Bool(_) => Ok(()),
+ Literal::Int(_) => Ok(()),
+ Literal::Str { pieces, kind: _ } => {
+ for piece in pieces {
+ match piece {
+ StrPiece::Chars(_) => {}
+ StrPiece::Escape(_) => {}
+ StrPiece::Interp(expr) => expr.validate()?,
+ }
+ }
+ Ok(())
+ }
+ Literal::Tuple(elems) => {
+ for elem in elems {
+ elem.validate()?;
+ }
+ Ok(())
+ }
+ Literal::Array(elems) => {
+ for elem in elems {
+ elem.validate()?;
+ }
+ Ok(())
+ }
+ Literal::Map(entries) => {
+ for MapEntry { key, value } in entries {
+ key.validate()?;
+ value.validate()?;
+ }
+ Ok(())
+ }
+ Literal::Struct(entries) => {
+ let mut fields = FxHashSet::default();
+ for StructField { name, value } in entries {
+ if !fields.insert(name) {
+ return Err(ValidationError::DuplicateKey);
+ }
+ value.validate()?;
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum StrPiece<'a> {
+ Chars(Cow<'a, str>),
+ Escape(char),
+ Interp(Expr<'a>),
+}
+
+impl<'a> TryFrom<&token::StrPiece<'a>> for StrPiece<'a> {
+ type Error = &'static str;
+
+ fn try_from(value: &token::StrPiece<'a>) -> Result<Self, Self::Error> {
+ use crate::recipe;
+
+ Ok(match value {
+ token::StrPiece::Chars(chars) => StrPiece::Chars(Cow::Borrowed(chars)),
+ token::StrPiece::Escape(c) => StrPiece::Escape(*c),
+ token::StrPiece::Interp(tokens) => StrPiece::Interp(
+ recipe::expr(tokens).or(Err("Invalid expression in string interpolation"))?,
+ ),
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct MapEntry<'a> {
+ pub key: Expr<'a>,
+ pub value: Expr<'a>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct StructField<'a> {
+ pub name: Cow<'a, str>,
+ pub value: Expr<'a>,
+}
+
+impl<'a> StructField<'a> {
+ pub(crate) fn new(field: Ident<'a>, value: Option<Expr<'a>>) -> Self {
+ let value = value.unwrap_or_else(|| {
+ Expr::Path(Path {
+ root: PathRoot::Relative,
+ components: vec![field.clone()],
+ })
+ });
+ StructField {
+ name: field.name,
+ value,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OpUnary {
+ Not,
+ Neg,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OpBinary {
+ Add,
+ Sub,
+ Mul,
+ Div,
+ Rem,
+ And,
+ Or,
+ Eq,
+ Lt,
+ Le,
+ Ne,
+ Ge,
+ Gt,
+}
+
+impl OpBinary {
+ fn is_comparision(self) -> bool {
+ use OpBinary::*;
+
+ matches!(self, Eq | Lt | Le | Ne | Ge | Gt)
+ }
+}
diff --git a/crates/rebel-parse/src/ast/mod.rs b/crates/rebel-parse/src/ast/mod.rs
new file mode 100644
index 0000000..0cdc808
--- /dev/null
+++ b/crates/rebel-parse/src/ast/mod.rs
@@ -0,0 +1,187 @@
+use std::borrow::Cow;
+
+use derive_into_owned::{Borrowed, IntoOwned};
+use rustc_hash::FxHashSet;
+
+pub mod expr;
+pub mod pat;
+pub mod typ;
+
+use expr::{Expr, StructField};
+use pat::{DestrPat, Pat};
+use typ::Type;
+
+pub type Recipe<'a> = Vec<RecipeStmt<'a>>;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum RecipeStmt<'a> {
+ BlockStmt(BlockStmt<'a>),
+ Fetch {
+ name: Ident<'a>,
+ entries: Vec<StructField<'a>>,
+ },
+ Task {
+ name: Ident<'a>,
+ params: Vec<FuncParam<'a>>,
+ block: Block<'a>,
+ },
+}
+
+impl<'a> RecipeStmt<'a> {
+ pub fn validate(&self) -> Result<(), ValidationError> {
+ match self {
+ RecipeStmt::BlockStmt(stmt) => stmt.validate(),
+ RecipeStmt::Fetch { name: _, entries } => {
+ let mut fields = FxHashSet::default();
+ for StructField { name, value } in entries {
+ if !fields.insert(name) {
+ return Err(ValidationError::DuplicateKey);
+ }
+ value.validate()?;
+ }
+ Ok(())
+ }
+ RecipeStmt::Task {
+ name: _,
+ params: _,
+ block,
+ } => {
+ // TODO: Validate params?
+ block.validate()
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct Block<'a>(pub Vec<BlockStmt<'a>>);
+
+impl<'a> Block<'a> {
+ pub fn validate(&self) -> Result<(), ValidationError> {
+ for stmt in &self.0 {
+ stmt.validate()?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum BlockStmt<'a> {
+ Let {
+ dest: Box<TypedPat<'a>>,
+ expr: Option<Box<Expr<'a>>>,
+ },
+ Assign {
+ dest: Box<DestrPat<'a>>,
+ expr: Box<Expr<'a>>,
+ },
+ Fn {
+ ident: Ident<'a>,
+ params: Vec<FuncParam<'a>>,
+ ret: Option<Box<Type<'a>>>,
+ block: Block<'a>,
+ },
+ Expr {
+ expr: Box<Expr<'a>>,
+ },
+ Empty,
+}
+
+impl<'a> BlockStmt<'a> {
+ pub(crate) fn let_assign(dest: TypedPat<'a>, expr: Option<Expr<'a>>) -> Self {
+ BlockStmt::Let {
+ dest: Box::new(dest),
+ expr: expr.map(Box::new),
+ }
+ }
+
+ pub(crate) fn assign(
+ dest: DestrPat<'a>,
+ op: Option<expr::OpBinary>,
+ swapped: bool,
+ expr: Expr<'a>,
+ ) -> Self {
+ let expr = match op {
+ Some(op) => {
+ let dest_expr = Expr::from(dest.clone());
+ if swapped {
+ Expr::binary(expr, op, dest_expr)
+ } else {
+ Expr::binary(dest_expr, op, expr)
+ }
+ }
+ None => expr,
+ };
+ BlockStmt::Assign {
+ dest: Box::new(dest),
+ expr: Box::new(expr),
+ }
+ }
+
+ pub fn validate(&self) -> Result<(), ValidationError> {
+ match self {
+ BlockStmt::Let { dest, expr } => {
+ let TypedPat { pat, typ: _ } = dest.as_ref();
+ pat.validate()?;
+ if let Some(expr) = expr {
+ expr.validate()?;
+ }
+ Ok(())
+ }
+ BlockStmt::Assign { dest, expr } => {
+ dest.validate()?;
+ expr.validate()?;
+ Ok(())
+ }
+ BlockStmt::Fn {
+ ident: _,
+ params: _,
+ ret: _,
+ block,
+ } => {
+ // TODO: Validate params?
+ block.validate()
+ }
+ BlockStmt::Expr { expr } => expr.validate(),
+ BlockStmt::Empty => Ok(()),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct TypedPat<'a> {
+ pub pat: Pat<'a>,
+ pub typ: Option<Type<'a>>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct FuncParam<'a> {
+ pub name: Ident<'a>,
+ pub typ: Type<'a>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PathRoot {
+ Absolute,
+ Relative,
+ Recipe,
+ Task,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct Path<'a> {
+ pub root: PathRoot,
+ pub components: Vec<Ident<'a>>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct Ident<'a> {
+ pub name: Cow<'a, str>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum ValidationError {
+ DuplicateKey,
+ NeedsParens,
+ InvalidLet,
+}
diff --git a/crates/rebel-parse/src/ast/pat.rs b/crates/rebel-parse/src/ast/pat.rs
new file mode 100644
index 0000000..c85f625
--- /dev/null
+++ b/crates/rebel-parse/src/ast/pat.rs
@@ -0,0 +1,57 @@
+use super::*;
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum Pat<'a> {
+ Paren(Box<Pat<'a>>),
+ Ident(Ident<'a>),
+}
+
+impl<'a> Pat<'a> {
+ pub fn validate(&self) -> Result<(), ValidationError> {
+ match self {
+ Pat::Paren(pat) => pat.validate(),
+ Pat::Ident(_) => Ok(()),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum DestrPat<'a> {
+ Index {
+ base: Box<DestrPat<'a>>,
+ index: Box<Expr<'a>>,
+ },
+ Field {
+ base: Box<DestrPat<'a>>,
+ field: Ident<'a>,
+ },
+ Paren(Box<DestrPat<'a>>),
+ Path(Path<'a>),
+}
+
+impl<'a> DestrPat<'a> {
+ pub fn validate(&self) -> Result<(), ValidationError> {
+ match self {
+ DestrPat::Index { base, index } => {
+ base.validate()?;
+ index.validate()?;
+ Ok(())
+ }
+ DestrPat::Field { base, field: _ } => base.validate(),
+ DestrPat::Paren(pat) => pat.validate(),
+ DestrPat::Path(_) => Ok(()),
+ }
+ }
+}
+
+impl<'a> From<Pat<'a>> for DestrPat<'a> {
+ fn from(value: Pat<'a>) -> Self {
+ match value {
+ Pat::Paren(pat) => DestrPat::Paren(Box::new((*pat).into())),
+ Pat::Ident(ident) => DestrPat::Path(Path {
+ root: PathRoot::Relative,
+ components: vec![ident],
+ }),
+ }
+ }
+}
diff --git a/crates/rebel-parse/src/ast/typ.rs b/crates/rebel-parse/src/ast/typ.rs
new file mode 100644
index 0000000..54ab1b9
--- /dev/null
+++ b/crates/rebel-parse/src/ast/typ.rs
@@ -0,0 +1,28 @@
+use std::borrow::Cow;
+
+use derive_into_owned::{Borrowed, IntoOwned};
+
+use super::Path;
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum Type<'a> {
+ Paren(Box<Type<'a>>),
+ Option(Box<Type<'a>>),
+ Path(Path<'a>),
+ Literal(Literal<'a>),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub enum Literal<'a> {
+ Unit,
+ Tuple(Vec<Type<'a>>),
+ Array(Box<Type<'a>>),
+ Map(Box<Type<'a>>, Box<Type<'a>>),
+ Struct(Vec<StructField<'a>>),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, IntoOwned, Borrowed)]
+pub struct StructField<'a> {
+ pub name: Cow<'a, str>,
+ pub typ: Type<'a>,
+}
diff --git a/crates/rebel-parse/src/grammar/recipe.rs b/crates/rebel-parse/src/grammar/recipe.rs
index 49b1689..81b47c9 100644
--- a/crates/rebel-parse/src/grammar/recipe.rs
+++ b/crates/rebel-parse/src/grammar/recipe.rs
@@ -1,46 +1,126 @@
-use crate::ast::{self, Expr};
-use crate::token::*;
+use std::borrow::Cow;
+
+use crate::{
+ ast::{
+ self,
+ expr::{self, Expr},
+ pat::{DestrPat, Pat},
+ typ::{self, Type},
+ },
+ token::*,
+};
pub use rules::*;
peg::parser! {
pub grammar rules<'a>() for TokenStream<'a> {
- use ast::OpBinary::*;
- use ast::OpUnary::*;
+ use expr::OpBinary::*;
+ use expr::OpUnary::*;
pub rule recipe() -> ast::Recipe<'a>
= recipe:recipe_stmt()* { recipe }
pub rule recipe_stmt() -> ast::RecipeStmt<'a>
- = keyword_fetch() name:ident() p('{') body:body() p('}') {
- ast::RecipeStmt::Fetch { name, body: Vec::new() }
+ = [Token::Keyword(Keyword::Fetch)] name:ident() p('{') entries:delimited(<struct_field()>, <p(',')>) p('}') {
+ ast::RecipeStmt::Fetch { name, entries }
}
- / keyword_task() name:ident() p('(') params:func_params() p(')')
- p('{') body:body() p('}') {
- ast::RecipeStmt::Task { name, params, body }
+ / [Token::Keyword(Keyword::Task)] name:ident() p('(') params:func_params() p(')')
+ p('{') block:block() p('}') {
+ ast::RecipeStmt::Task { name, params, block }
}
- / stmt:body_stmt() {
- ast::RecipeStmt::BodyStmt(stmt)
+ / stmt:block_stmt() p(';') {
+ ast::RecipeStmt::BlockStmt(stmt)
}
- pub rule body() -> ast::Body<'a>
- = body:body_stmt()* { body }
+ pub rule block() -> ast::Block<'a>
+ = block:block_stmt() ++ p(';') { ast::Block(block) }
- pub rule body_stmt() -> ast::BodyStmt<'a>
- = left:typed_expr() op:assign_op() right:expr() p(';') {
- ast::BodyStmt::assign(left, op, right)
+ pub rule block_stmt() -> ast::BlockStmt<'a>
+ = [Token::Keyword(Keyword::Let)] dest:typed_pat() p('=') expr:expr() {
+ ast::BlockStmt::let_assign(dest, Some(expr))
+ }
+ / [Token::Keyword(Keyword::Let)] dest:typed_pat() {
+ ast::BlockStmt::let_assign(dest, None)
+ }
+ / [Token::Keyword(Keyword::Fn)] ident:ident() p('(') params:func_params() p(')')
+ ret:tagged(<p2('-', '>')>, <typ()>)? p('{') block:block() p('}')
+ {
+ ast::BlockStmt::Fn {
+ ident,
+ params,
+ ret: ret.map(Box::new),
+ block,
+ }
+ }
+ / dest:destr_pat() op:assign_op() expr:expr() {
+ ast::BlockStmt::assign(dest, op, false, expr)
+ }
+ / dest:destr_pat() p2('=', '+') expr:expr() {
+ ast::BlockStmt::assign(dest, Some(Add), true, expr)
}
+ / expr:expr() {
+ ast::BlockStmt::Expr { expr: Box::new(expr) }
+ }
+ / { ast::BlockStmt::Empty }
- rule assign_op() -> Option<ast::OpBinary>
- = p2('+', '=') { Some(Add) }
+ rule assign_op() -> Option<expr::OpBinary>
+ = p('=') { None }
+ / p2('+', '=') { Some(Add) }
/ p2('-', '=') { Some(Sub) }
/ p2('*', '=') { Some(Mul) }
/ p2('/', '=') { Some(Div) }
/ p2('%', '=') { Some(Rem) }
- / p('=') { None }
- rule typed_expr() -> ast::TypedExpr<'a>
- = expr:expr() typ:tagged(<p(':')>, <expr()>)? { ast::TypedExpr { expr, typ } }
+ rule typed_pat() -> ast::TypedPat<'a>
+ = pat:pat() typ:tagged(<p(':')>, <typ()>)? { ast::TypedPat { pat, typ } }
+
+ pub rule typ() -> Type<'a> = precedence! {
+ t:@ p('?') { Type::Option(Box::new(t)) }
+ --
+ t:typ_atom() { t }
+ }
+
+ rule typ_atom() -> Type<'a>
+ = p('(') t:typ() p(')') { Type::Paren(Box::new(t)) }
+ / lit:typ_literal() { Type::Literal(lit) }
+ / path:path() { Type::Path(path) }
+
+ rule typ_literal() -> typ::Literal<'a>
+ = p('(') p(')') { typ::Literal::Unit }
+ / p('(') elements:(typ() ++ p(',')) p(',')? p(')') {
+ typ::Literal::Tuple(elements)
+ }
+ / p('[') typ:typ() p(']') {
+ typ::Literal::Array(Box::new(typ))
+ }
+ / [Token::Keyword(Keyword::Map)] p('{') key:typ() p2('=', '>') value:typ() p('}') {
+ typ::Literal::Map(Box::new(key), Box::new(value))
+ }
+ / p('{') entries:delimited(<struct_field_typ()>, <p(',')>) p('}') {
+ typ::Literal::Struct(entries)
+ }
+
+ pub rule pat() -> ast::pat::Pat<'a>
+ = p('(') pat:pat() p(')') { Pat::Paren(Box::new(pat)) }
+ / ident:ident() { Pat::Ident(ident) }
+
+ pub rule destr_pat() -> DestrPat<'a> = precedence! {
+ base:@ p('[') index:expr() p(']') {
+ DestrPat::Index { base: Box::new(base), index: Box::new(index) }
+ }
+ --
+ base:@ p('.') field:field() {
+ DestrPat::Field { base: Box::new(base), field }
+ }
+ --
+ p('(') pat:destr_pat() p(')') { DestrPat::Paren(Box::new(pat)) }
+ path:path() { DestrPat::Path(path) }
+ }
+
+ rule struct_field_typ() -> typ::StructField<'a>
+ = field:field() p(':') typ:typ() {
+ typ::StructField { name: field.name, typ }
+ }
pub rule expr() -> Expr<'a> = precedence! {
left:(@) p2('|', '|') right:@ { Expr::binary(left, Or, right) }
@@ -67,63 +147,112 @@ peg::parser! {
expr:@ p('(') params:call_params() p(')') {
Expr::apply(expr, params)
}
- expr:@ p('[') index:expr() p(']') { Expr::index(expr, index) }
+ base:@ p('[') index:expr() p(']') { Expr::index(base, index) }
--
expr:@ p('.') method:field() p('(') params:call_params() p(')') {
Expr::method(expr, method, params)
}
- expr:@ p('.') field:field() { Expr::field(expr, field) }
+ base:@ p('.') field:field() { Expr::field(base, field) }
--
- p('(') e:expr() p(')') { Expr::paren(e) }
e:atom() { e }
}
rule atom() -> Expr<'a>
- = lit:literal() { Expr::Literal(lit) }
+ = p('(') e:expr() p(')') { Expr::paren(e) }
+ / [Token::Keyword(Keyword::If)]
+ if_blocks:(cond_block() ++ ([Token::Keyword(Keyword::Else)] [Token::Keyword(Keyword::If)]))
+ else_block:([Token::Keyword(Keyword::Else)] p('{') block:block() p('}') { Box::new(block) })?
+ {
+ Expr::IfElse { if_blocks, else_block }
+ }
+ / lit:literal() { Expr::Literal(lit) }
+ / p('{') block:block() p('}') { Expr::Block(block) }
/ path:path() { Expr::Path(path) }
- rule call_params() -> Vec<ast::Expr<'a>>
+ rule cond_block() -> (Expr<'a>, ast::Block<'a>)
+ = cond:expr() p('{') block:block() p('}') { (cond, block) }
+
+ rule call_params() -> Vec<expr::Expr<'a>>
= args:delimited(<expr()>, <p(',')>) { args }
rule func_params() -> Vec<ast::FuncParam<'a>>
= params:delimited(<func_param()>, <p(',')>) { params }
rule func_param() -> ast::FuncParam<'a>
- = name:ident() p(':') typ:expr() { ast::FuncParam { name, typ } }
+ = name:ident() p(':') typ:typ() { ast::FuncParam { name, typ } }
- rule literal() -> ast::Literal<'a>
- = keyword_true() { ast::Literal::Boolean(true) }
- / keyword_false() { ast::Literal::Boolean(false) }
- / [Token::Number(content)] { ?
- ast::Literal::number(content)
- }
- / [Token::String(String { pieces, .. })] { ?
- let ast_pieces = pieces.iter().map(|piece| piece.try_into()).collect::<Result<_, _>>()?;
- Ok(ast::Literal::String(ast_pieces))
+ rule literal() -> expr::Literal<'a>
+ = [Token::Keyword(Keyword::True)] { expr::Literal::Bool(true) }
+ / [Token::Keyword(Keyword::False)] { expr::Literal::Bool(false) }
+ / [Token::Keyword(Keyword::None)] { expr::Literal::None }
+ / n:number() { expr::Literal::Int(n) }
+ / [Token::Str(Str { pieces, kind })] { ?
+ let pieces = pieces
+ .iter()
+ .map(|piece| piece.try_into())
+ .collect::<Result<_, _>>()?;
+ Ok(expr::Literal::Str{ pieces, kind: *kind })
}
- / p('(') p(')') { ast::Literal::Unit }
- / p('(') elements:(expr() ** p(',')) p(',')? p(')') {
- ast::Literal::Tuple(elements)
+ / p('(') p(')') { expr::Literal::Unit }
+ / p('(') elements:(expr() ++ p(',')) p(',')? p(')') {
+ expr::Literal::Tuple(elements)
}
/ p('[') elements:delimited(<expr()>, <p(',')>) p(']') {
- ast::Literal::Array(elements)
+ expr::Literal::Array(elements)
}
- / p('{') entries:delimited(<map_entry()>, <p(',')>) p('}') {
- ast::Literal::Map(entries)
+ / [Token::Keyword(Keyword::Map)] p('{') entries:delimited(<map_entry()>, <p(',')>) p('}') {
+ expr::Literal::Map(entries)
+ }
+ / p('{') entries:delimited(<struct_field()>, <p(',')>) p('}') {
+ expr::Literal::Struct(entries)
+ }
+
+ rule map_entry() -> expr::MapEntry<'a>
+ = key:expr() p2('=', '>') value:expr() {
+ expr::MapEntry { key, value }
}
- rule map_entry() -> ast::MapEntry<'a>
- = key:field() p('=') value:expr() {
- ast::MapEntry { key: key.name, value }
+ rule struct_field() -> expr::StructField<'a>
+ = field:field() value:tagged(<p(':')>, <expr()>)? {
+ expr::StructField::new(field, value)
}
rule path() -> ast::Path<'a>
- = components:ident() ++ p2(':', ':') { ast::Path { components } }
+ = components:(ident() ++ p2(':', ':')) {
+ ast::Path { root: ast::PathRoot::Relative, components }
+ }
+ / components:(p2(':', ':') ident:ident() { ident })+ {
+ ast::Path { root: ast::PathRoot::Absolute, components }
+ }
+ / [Token::Keyword(Keyword::Recipe)] components:(p2(':', ':') ident:ident() { ident })* {
+ ast::Path { root: ast::PathRoot::Recipe, components }
+ }
+ / [Token::Keyword(Keyword::Task)] components:(p2(':', ':') ident:ident() { ident })* {
+ ast::Path { root: ast::PathRoot::Task, components }
+ }
rule field() -> ast::Ident<'a>
= ident()
/ [Token::Number(content)] {
- ast::Ident { name: content }
+ ast::Ident { name: Cow::Borrowed(content) }
+ }
+
+ rule number() -> i64
+ = neg:p('-')? [Token::Number(s)] { ?
+ let (radix, rest) = if let Some(rest) = s.strip_prefix("0x") {
+ (16, rest)
+ } else if let Some(rest) = s.strip_prefix("0o") {
+ (8, rest)
+ } else if let Some(rest) = s.strip_prefix("0b") {
+ (2, rest)
+ } else {
+ (10, *s)
+ };
+ let mut digits = rest.replace('_', "");
+ if neg.is_some() {
+ digits = format!("-{digits}");
+ }
+ i64::from_str_radix(&digits, radix).or(Err("number"))
}
rule p_(ch: char)
@@ -132,29 +261,11 @@ peg::parser! {
rule p(ch: char) -> ()
= [Token::Punct(Punct(c, _)) if *c == ch] {}
- rule p2(ch1: char, ch2: char)
+ rule p2(ch1: char, ch2: char) -> ()
= p_(ch1) p(ch2)
rule ident() -> ast::Ident<'a>
- = !keyword() [Token::Ident(name)] { ast::Ident { name } }
-
- rule keyword()
- = keyword_true()
- / keyword_false()
- / keyword_fetch()
- / keyword_task()
-
- rule keyword_true()
- = const_ident("true")
- rule keyword_false()
- = const_ident("false")
- rule keyword_fetch()
- = const_ident("fetch")
- rule keyword_task()
- = const_ident("task")
-
- rule const_ident(keyword: &str)
- = [Token::Ident(name) if *name == keyword]
+ = [Token::Ident(name)] { ast::Ident { name: Cow::Borrowed(name) } }
rule delimited<T>(expr: rule<T>, delim: rule<()>) -> Vec<T>
= values:(expr() ++ delim()) delim()? { values }
diff --git a/crates/rebel-parse/src/grammar/tokenize.rs b/crates/rebel-parse/src/grammar/tokenize.rs
index 8ca9d59..eb8a900 100644
--- a/crates/rebel-parse/src/grammar/tokenize.rs
+++ b/crates/rebel-parse/src/grammar/tokenize.rs
@@ -2,6 +2,23 @@ use crate::token::*;
pub use rules::*;
+static KEYWORDS: phf::Map<&'static str, Keyword> = phf::phf_map! {
+ "else" => Keyword::Else,
+ "false" => Keyword::False,
+ "fetch" => Keyword::Fetch,
+ "fn" => Keyword::Fn,
+ "for" => Keyword::For,
+ "if" => Keyword::If,
+ "let" => Keyword::Let,
+ "map" => Keyword::Map,
+ "mut" => Keyword::Mut,
+ "none" => Keyword::None,
+ "recipe" => Keyword::Recipe,
+ "set" => Keyword::Set,
+ "task" => Keyword::Task,
+ "true" => Keyword::True,
+};
+
peg::parser! {
pub grammar rules() for str {
pub rule token_stream() -> TokenStream<'input>
@@ -9,21 +26,32 @@ peg::parser! {
pub rule token() -> Token<'input>
= number:number() { Token::Number(number) }
- / string:string() { Token::String(string) }
- / ident:ident() { Token::Ident(ident) }
+ / string:string() { Token::Str(string) }
+ / token:ident_or_keyword() { token }
/ punct:punct() { Token::Punct(punct) }
- rule ident() -> &'input str
- = $(
+ rule ident_or_keyword() -> Token<'input>
+ = s:$(
['a'..='z' | 'A' ..='Z' | '_' ]
['a'..='z' | 'A' ..='Z' | '_' | '0'..='9']*
- )
+ ) {
+ if let Some(kw) = KEYWORDS.get(s) {
+ Token::Keyword(*kw)
+ } else {
+ Token::Ident(s)
+ }
+ }
rule punct() -> Punct
= ch:punct_char() spacing:spacing() { Punct(ch, spacing) }
rule punct_char() -> char
- = !number() !string() !ident() !__ ch:[_] { ch }
+ = !comment_start() ch:[
+ | '~' | '!' | '@' | '#' | '$' | '%' | '^' | '&'
+ | '*' | '-' | '=' | '+' | '|' | ';' | ':' | ','
+ | '<' | '.' | '>' | '/' | '\'' | '?' | '(' | ')'
+ | '[' | ']' | '{' | '}'
+ ] { ch }
rule spacing() -> Spacing
= &punct_char() { Spacing::Joint }
@@ -32,29 +60,29 @@ peg::parser! {
rule number() -> &'input str
= $(['0'..='9'] ['0'..='9' | 'a'..='z' | 'A'..='Z' | '_']*)
- rule string() -> String<'input>
+ rule string() -> Str<'input>
= "\"" pieces:string_piece()* "\"" {
- String {
+ Str {
pieces,
- kind: StringKind::String,
+ kind: StrKind::Regular,
}
}
/ "r\"" chars:$([^'"']*) "\"" {
- String {
- pieces: vec![StringPiece::Chars(chars)],
- kind: StringKind::RawString,
+ Str {
+ pieces: vec![StrPiece::Chars(chars)],
+ kind: StrKind::Raw,
}
}
/ "```" newline() pieces:script_string_piece()* "```" {
- String {
+ Str {
pieces,
- kind: StringKind::ScriptString,
+ kind: StrKind::Script,
}
}
- rule string_piece() -> StringPiece<'input>
- = chars:$((!"{{" [^'"' | '\\'])+) { StringPiece::Chars(chars) }
- / "\\" escape:string_escape() { StringPiece::Escape(escape) }
+ rule string_piece() -> StrPiece<'input>
+ = chars:$((!"{{" [^'"' | '\\'])+) { StrPiece::Chars(chars) }
+ / "\\" escape:string_escape() { StrPiece::Escape(escape) }
/ string_interp()
rule string_escape() -> char
@@ -63,6 +91,7 @@ peg::parser! {
/ "t" { '\t' }
/ "\\" { '\\' }
/ "\"" { '"' }
+ / "{" { '{' }
/ "0" { '\0' }
/ "x" digits:$(['0'..='7'] hex_digit()) {
u8::from_str_radix(digits, 16).unwrap().into()
@@ -71,13 +100,13 @@ peg::parser! {
u32::from_str_radix(digits, 16).unwrap().try_into().or(Err("Invalid unicode escape"))
}
- rule script_string_piece() -> StringPiece<'input>
- = chars:$((!"{{" !"```" [_])+) { StringPiece::Chars(chars) }
+ rule script_string_piece() -> StrPiece<'input>
+ = chars:$((!"{{" !"```" [_])+) { StrPiece::Chars(chars) }
/ string_interp()
- rule string_interp() -> StringPiece<'input>
- = "{{" tokens:subtoken()* "}}" {
- StringPiece::Interp(TokenStream(tokens))
+ rule string_interp() -> StrPiece<'input>
+ = "{{" _ tokens:(subtoken() ++ _) _ "}}" {
+ StrPiece::Interp(TokenStream(tokens))
}
rule subtoken() -> Token<'input>
@@ -94,6 +123,10 @@ peg::parser! {
rule _
= quiet!{__?}
+ rule comment_start()
+ = "//"
+ / "/*"
+
rule comment()
= "//" (!newline() [_])* (newline() / ![_])
/ "/*" (!"*/" [_])* "*/"
diff --git a/crates/rebel-parse/src/token.rs b/crates/rebel-parse/src/token.rs
index 2f2f849..444b5a8 100644
--- a/crates/rebel-parse/src/token.rs
+++ b/crates/rebel-parse/src/token.rs
@@ -1,12 +1,31 @@
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Token<'a> {
+ Keyword(Keyword),
Ident(&'a str),
Punct(Punct),
- String(String<'a>),
+ Str(Str<'a>),
Number(&'a str),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Keyword {
+ Else,
+ False,
+ Fetch,
+ Fn,
+ For,
+ If,
+ Let,
+ Map,
+ Mut,
+ None,
+ Recipe,
+ Set,
+ Task,
+ True,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Punct(pub char, pub Spacing);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -16,23 +35,23 @@ pub enum Spacing {
}
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct String<'a> {
- pub pieces: Vec<StringPiece<'a>>,
- pub kind: StringKind,
+pub struct Str<'a> {
+ pub pieces: Vec<StrPiece<'a>>,
+ pub kind: StrKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum StringPiece<'a> {
+pub enum StrPiece<'a> {
Chars(&'a str),
Escape(char),
Interp(TokenStream<'a>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum StringKind {
- String,
- RawString,
- ScriptString,
+pub enum StrKind {
+ Regular,
+ Raw,
+ Script,
}
#[derive(Clone, Debug, PartialEq, Eq)]
diff --git a/examples/recipes/gmp/build.recipe b/examples/recipes/gmp/build.recipe
index 44e87d6..19a25f6 100644
--- a/examples/recipes/gmp/build.recipe
+++ b/examples/recipes/gmp/build.recipe
@@ -7,63 +7,61 @@
// build_depend: (task: TaskID) -> TaskDep
// host_depend: (task: TaskID) -> TaskDep
-version = "6.3.0";
-sourcedir = "{{workdir}}/{{name}}-{{version}}";
-builddir = "{{workdir}}/{{name}}-build";
+let version = "6.3.0";
+let sourcedir = "{{workdir}}/{{name}}-{{version}}";
+let builddir = "{{workdir}}/{{name}}-build";
fetch source {
- url = ["https://invalid/{{name}}-{{version}}.tar.xz"];
+ url: ["https://invalid/{{name}}-{{version}}.tar.xz"],
// TODO: Move to lockfile
- sha256 = "a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898";
+ sha256: "a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898",
}
task unpack() {
- depends = [source];
+ task.depends = [source];
- run = ```
+ ```
tar xf {{source.path}}
- ```;
+ ```
}
task configure(host: Platform) {
- parent = unpack();
- depends = [
+ task.parent = unpack();
+ task.depends = [
build_depend(toolchain::build_depends),
host_depend(toolchain::depends),
];
- run = ```
+ ```
mkdir {{builddir}}
cd {{builddir}}
{{sourcedir}}/configure \
--build={{build.gnu_triplet}} \
--host={{host.gnu_triplet}} \
--prefix={{host.prefix}}
- ```;
+ ```
}
task compile(host: Platform) {
- parent = configure(host);
+ task.parent = configure(host);
- run = ```
+ ```
cd {{builddir}}
make
- ```;
+ ```
}
task install(host: Platform) {
- parent = compile(host);
+ task.parent = compile(host);
- output = {
- default = {
- runtime_depends = [host_depend(toolchain::depends)],
- },
+ task.output["default"] = {
+ runtime_depends: [host_depend(toolchain::depends)],
};
- run = ```
+ ```
cd {{builddir}}
make DESTDIR={{destdir}} install
rm {{destdir}}{{host.prefix}}/lib/*.a
rm {{destdir}}{{host.prefix}}/lib/*.la
- ```;
+ ```
}