From 12c0431e692ce43491362658a650afb25212698f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20K=C3=B6hring?= Date: Sat, 5 Jul 2025 12:58:22 +0200 Subject: [PATCH] some more structure and grammar parser fixes --- Cargo.lock | 1117 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 + src/emitter/js.rs | 35 ++ src/emitter/mod.rs | 1 + src/main.rs | 591 ++------------------- src/parser/ast.rs | 612 ++++++++++++++++++++++ src/parser/mod.rs | 76 +++ src/solace.pest | 40 +- src/transformer/js.rs | 14 + src/transformer/mod.rs | 4 + test.short.solace | 3 + 11 files changed, 1919 insertions(+), 579 deletions(-) create mode 100644 src/emitter/js.rs create mode 100644 src/emitter/mod.rs create mode 100644 src/parser/ast.rs create mode 100644 src/parser/mod.rs create mode 100644 src/transformer/js.rs create mode 100644 src/transformer/mod.rs create mode 100644 test.short.solace diff --git a/Cargo.lock b/Cargo.lock index 312c704..45acab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,100 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "ast_node" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337b95505078e10730624c8d182b8564c745ba4b2ce398e626a4df3f8c9e6899" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "better_scoped_tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" +dependencies = [ + "scoped-tls", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -11,12 +105,59 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytes-str" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +dependencies = [ + "bytes", + "serde", +] + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -36,6 +177,22 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "digest" version = "0.10.7" @@ -46,6 +203,49 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "from_variant" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accfe8b52dc15c1bace718020831f72ce91a4c096709a4d733868f4f4034e22a" +dependencies = [ + "proc-macro2", + "swc_macros_common", + "syn", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -56,6 +256,177 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hstr" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1638d2018a21b9ff65d7fc28c2271c76a5af6ff4f621b204d032bc649763a4" +dependencies = [ + "hashbrown", + "new_debug_unreachable", + "once_cell", + "phf", + "rustc-hash", + "triomphe", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "is-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -68,12 +439,77 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.8.1" @@ -118,6 +554,63 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -127,6 +620,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.40" @@ -136,6 +649,118 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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 = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -147,13 +772,221 @@ dependencies = [ "digest", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "solace" version = "0.1.0" dependencies = [ + "anyhow", "lazy_static", "pest", "pest_derive", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen", + "thiserror", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_enum" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b0e5369ebc6ec5fadbc400599467eb6ba5a614c03de094fcb233dddac2f5f4" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "swc_allocator" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b926f0d94bbb34031fe5449428cfa1268cdc0b31158d6ad9c97e0fc1e79dd" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown", + "ptr_meta", + "rustc-hash", + "triomphe", +] + +[[package]] +name = "swc_atoms" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf4c40238f7224596754940676547dab6bbf8f33d9f4560b966fc66f2fe00db" +dependencies = [ + "hstr", + "once_cell", + "rustc-hash", + "serde", +] + +[[package]] +name = "swc_common" +version = "13.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc28daa84b57762ecf28d8fdf6a3204ff04c8b8d98d35b9e8b9f56d4a7fafffa" +dependencies = [ + "anyhow", + "ast_node", + "better_scoped_tls", + "bytes-str", + "cfg-if", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash", + "serde", + "siphasher 0.3.11", + "swc_allocator", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_visit", + "tracing", + "unicode-width", + "url", +] + +[[package]] +name = "swc_ecma_ast" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ddc264ed13ae03aa30e1c89798502f9ddbe765a4ad695054add1074ffbc5cb" +dependencies = [ + "bitflags", + "is-macro", + "num-bigint", + "once_cell", + "phf", + "rustc-hash", + "scoped-tls", + "string_enum", + "swc_atoms", + "swc_common", + "swc_visit", + "unicode-id-start", +] + +[[package]] +name = "swc_ecma_codegen" +version = "15.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1719b3bb5bff1c99cfb6fbd2129e7a7a363d3ddf50e22b95143c1877559d872a" +dependencies = [ + "ascii", + "compact_str", + "memchr", + "num-bigint", + "once_cell", + "regex", + "rustc-hash", + "ryu-js", + "serde", + "swc_allocator", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen_macros", + "swc_sourcemap", + "tracing", +] + +[[package]] +name = "swc_ecma_codegen_macros" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845c8312c82545780f837992bb15fff1dc3464f644465d5ed0abd1196cd090d3" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "swc_eq_ignore_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "swc_macros_common" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "swc_sourcemap" +version = "9.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9755c673c6a83c461e98fa018f681adb8394a3f44f89a06f27e80fd4fe4fa1e4" +dependencies = [ + "base64-simd", + "bitvec", + "bytes-str", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + +[[package]] +name = "swc_visit" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" +dependencies = [ + "either", + "new_debug_unreachable", ] [[package]] @@ -167,6 +1000,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "2.0.12" @@ -187,6 +1037,57 @@ dependencies = [ "syn", ] +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "typenum" version = "1.18.0" @@ -199,14 +1100,230 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicode-id-start" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 1d5191d..cc3e0be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.98" lazy_static = "1.5.0" pest = "2.8.1" pest_derive = "2.8.1" +swc_common = "13.0.2" +swc_ecma_ast = "13.0.0" +swc_ecma_codegen = "15.0.1" +thiserror = "2.0.12" diff --git a/src/emitter/js.rs b/src/emitter/js.rs new file mode 100644 index 0000000..2170180 --- /dev/null +++ b/src/emitter/js.rs @@ -0,0 +1,35 @@ +use swc_ecma_ast::Module; +use swc_ecma_codegen::Config; +// use swc_ecma_codegen::{text_writer::JsWriter, Config, Emitter}; +// use swc_common::{sync::Lrc, SourceMap}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum EmitError { + #[error("Failed to emit JavaScript: {0}")] + EmitFailed(String), +} + +pub struct JsEmitter { + config: Config, +} + +impl JsEmitter { + pub fn new() -> Self { + Self { + config: Config::default(), + } + } + + pub fn with_minify(mut self) -> Self { + self.config.minify = true; + self + } + + pub fn emit(&self, _module: Module) -> Result { + // SWC codegen here + return Err(EmitError::EmitFailed( + "Emitter not yet implemented!".to_owned(), + )); + } +} diff --git a/src/emitter/mod.rs b/src/emitter/mod.rs new file mode 100644 index 0000000..cb71828 --- /dev/null +++ b/src/emitter/mod.rs @@ -0,0 +1 @@ +pub mod js; diff --git a/src/main.rs b/src/main.rs index e4cce2b..19fd5ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,561 +1,40 @@ -use std::fs; -use pest::iterators::{Pair, Pairs}; -use pest::pratt_parser::{Assoc, Op, PrattParser}; -use pest::Parser; -use pest_derive::Parser; +mod emitter; +mod parser; +mod transformer; -#[derive(Parser)] -#[grammar = "solace.pest"] -pub struct SolaceParser; +use anyhow::Result; +use std::{env, fs}; +use thiserror::Error; -#[derive(Debug, Clone)] -pub enum Expr { - // Literals - Number(f64), - String(String), - Boolean(bool), - None, - Undefined, - Underscore, - - // Variables and calls - Identifier(String), - MemberAccess(Box, String), - Index(Box, Box), - Call(Box, Vec), - - // Operators - Binary(BinaryOp, Box, Box), - Unary(UnaryOp, Box), - Ternary(Box, Box, Box), - Assignment(String, Box), - - // Control flow - If(Box, Box, Option>), - Match(Option>, Vec), - - // Collections - Array(Vec), - - // Postfix - PostIncrement(Box), - PostDecrement(Box), +#[derive(Error, Debug)] +pub enum ArgumentError { + #[error("Usage: {0} ./path/to/file.solace")] + MissingSourceFile(String), } -#[derive(Debug, Clone)] -pub enum BinaryOp { - Add, Sub, Mul, Div, Mod, - Eq, Ne, Lt, Gt, Le, Ge, - And, Or, - RangeInclusive, RangeExclusive, - Is, +fn main() -> Result<()> { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + return Err(ArgumentError::MissingSourceFile(args[0].clone()).into()); + } + + let file_path = &args[1]; + let input = fs::read_to_string(file_path).expect(&format!("Cannot read file '{}'!", file_path)); + + // Parse Solace Code + let ast = parser::parse(&input, /* debug */ true)?; + + // Transform from Solace AST to SWC AST + let js_transformer = transformer::js::JsTransformer::new(); + let js_ast = js_transformer.transform(ast); + + // Emit JavaScript + let js_emitter = emitter::js::JsEmitter::new(); + let js_code = js_emitter.emit(js_ast)?; + + // Write Output to stdout + println!("{}", js_code); + + Ok(()) } - -#[derive(Debug, Clone)] -pub enum UnaryOp { - Not, Neg, PreIncrement, PreDecrement, -} - -#[derive(Debug, Clone)] -pub struct MatchArm { - pattern: MatchPattern, - body: Expr, -} - -#[derive(Debug, Clone)] -pub enum MatchPattern { - Wildcard, - Expression(Expr), - Condition(Vec<(BinaryOp, Expr)>), -} - -#[derive(Debug, Clone)] -pub struct Block { - statements: Vec, -} - -#[derive(Debug, Clone)] -pub enum ReturnType { - Simple(Type), - Named { - name: String, - type_annotation: Type, - default_value: Option, - } -} - -#[derive(Debug, Clone)] -pub enum Statement { - Import { name: String, from: String }, - Function { - name: String, - params: Vec, - return_type: Option, - extends: Option, - body: Block, - }, - Variable { - kind: VarKind, - name: String, - type_annotation: Option, - value: Expr, - }, - Defer { - condition: Option, - binding: Option, - body: Block, - }, - Watch { - target: String, - body: Block, - }, - Return(Option), - If(Expr, Box, Option>), - For { - var: String, - index: Option, - iterable: Expr, - body: Box, - }, - While(Expr, Box), - Expression(Expr), - Block(Block), -} - -#[derive(Debug, Clone)] -pub enum VarKind { - Const, Var, Live, -} - -#[derive(Debug, Clone)] -pub struct Param { - name: String, - type_annotation: Option, -} - -#[derive(Debug, Clone)] -pub enum Type { - Primitive(String), - Array(Box), - Optional(Box), - ErrorUnion(Option, Box), - Named(String), -} - -pub struct SolacePrattParser { - pratt: PrattParser, -} - -impl SolacePrattParser { - pub fn new() -> Self { - let pratt = PrattParser::new() - .op(Op::infix(Rule::assign, Assoc::Right)) - .op(Op::infix(Rule::question, Assoc::Right) | Op::infix(Rule::colon, Assoc::Right)) - .op(Op::infix(Rule::or, Assoc::Left)) - .op(Op::infix(Rule::and, Assoc::Left)) - .op(Op::infix(Rule::eq, Assoc::Left) | Op::infix(Rule::ne, Assoc::Left) | Op::infix(Rule::is_kw, Assoc::Left)) - .op(Op::infix(Rule::lt, Assoc::Left) | Op::infix(Rule::gt, Assoc::Left) | - Op::infix(Rule::le, Assoc::Left) | Op::infix(Rule::ge, Assoc::Left)) - .op(Op::infix(Rule::range_inclusive, Assoc::Left) | - Op::infix(Rule::range_exclusive, Assoc::Left)) - .op(Op::infix(Rule::plus, Assoc::Left) | Op::infix(Rule::minus, Assoc::Left)) - .op(Op::infix(Rule::multiply, Assoc::Left) | Op::infix(Rule::divide, Assoc::Left) | - Op::infix(Rule::modulo, Assoc::Left)) - .op(Op::prefix(Rule::not) | Op::prefix(Rule::minus) | - Op::prefix(Rule::increment) | Op::prefix(Rule::decrement)) - .op(Op::postfix(Rule::increment) | Op::postfix(Rule::decrement)); - - SolacePrattParser { pratt } - } - - pub fn parse_expr(&self, pairs: Pairs) -> Result { - self.pratt - .map_primary(|primary| self.parse_primary(primary)) - .map_infix(|lhs, op, rhs| self.parse_infix(lhs, op, rhs)) - .map_prefix(|op, rhs| self.parse_prefix(op, rhs)) - .map_postfix(|lhs, op| self.parse_postfix(lhs, op)) - .parse(pairs) - } - - fn parse_primary(&self, pair: Pair) -> Result { - match pair.as_rule() { - Rule::number_literal => { - let num = pair.as_str().parse::() - .map_err(|_| "Invalid number")?; - Ok(Expr::Number(num)) - } - Rule::string_literal => { - let s = pair.as_str(); - Ok(Expr::String(s[1..s.len()-1].to_string())) - } - Rule::boolean_literal => { - Ok(Expr::Boolean(pair.as_str() == "true")) - } - Rule::none_kw => Ok(Expr::None), - Rule::undefined_kw => Ok(Expr::Undefined), - Rule::underscore => Ok(Expr::Underscore), - Rule::identifier => Ok(Expr::Identifier(pair.as_str().to_string())), - Rule::array_literal => { - let mut elements = vec![]; - for inner in pair.into_inner() { - if inner.as_rule() == Rule::expression { - elements.push(self.parse_expr(inner.into_inner())?); - } - } - Ok(Expr::Array(elements)) - } - Rule::if_expr => self.parse_if_expr(pair), - Rule::match_expr => self.parse_match_expr(pair), - _ => Err(format!("Unexpected primary: {:?}", pair.as_rule())) - } - } - - fn parse_infix(&self, lhs: Result, op: Pair, rhs: Result) -> Result { - let lhs = lhs?; - let rhs = rhs?; - - match op.as_rule() { - Rule::plus => Ok(Expr::Binary(BinaryOp::Add, Box::new(lhs), Box::new(rhs))), - Rule::minus => Ok(Expr::Binary(BinaryOp::Sub, Box::new(lhs), Box::new(rhs))), - Rule::multiply => Ok(Expr::Binary(BinaryOp::Mul, Box::new(lhs), Box::new(rhs))), - Rule::divide => Ok(Expr::Binary(BinaryOp::Div, Box::new(lhs), Box::new(rhs))), - Rule::modulo => Ok(Expr::Binary(BinaryOp::Mod, Box::new(lhs), Box::new(rhs))), - Rule::eq => Ok(Expr::Binary(BinaryOp::Eq, Box::new(lhs), Box::new(rhs))), - Rule::ne => Ok(Expr::Binary(BinaryOp::Ne, Box::new(lhs), Box::new(rhs))), - Rule::lt => Ok(Expr::Binary(BinaryOp::Lt, Box::new(lhs), Box::new(rhs))), - Rule::gt => Ok(Expr::Binary(BinaryOp::Gt, Box::new(lhs), Box::new(rhs))), - Rule::le => Ok(Expr::Binary(BinaryOp::Le, Box::new(lhs), Box::new(rhs))), - Rule::ge => Ok(Expr::Binary(BinaryOp::Ge, Box::new(lhs), Box::new(rhs))), - Rule::and => Ok(Expr::Binary(BinaryOp::And, Box::new(lhs), Box::new(rhs))), - Rule::or => Ok(Expr::Binary(BinaryOp::Or, Box::new(lhs), Box::new(rhs))), - Rule::is_kw => Ok(Expr::Binary(BinaryOp::Is, Box::new(lhs), Box::new(rhs))), - Rule::range_inclusive => Ok(Expr::Binary(BinaryOp::RangeInclusive, Box::new(lhs), Box::new(rhs))), - Rule::range_exclusive => Ok(Expr::Binary(BinaryOp::RangeExclusive, Box::new(lhs), Box::new(rhs))), - Rule::assign => { - if let Expr::Identifier(name) = lhs { - Ok(Expr::Assignment(name, Box::new(rhs))) - } else { - Err("Left side of assignment must be identifier".to_string()) - } - } - Rule::question => { - // Handle ternary - need to parse the rest - // This is simplified - in practice you'd need more complex handling - Ok(Expr::Ternary(Box::new(lhs), Box::new(rhs), Box::new(Expr::None))) - } - _ => Err(format!("Unexpected infix operator: {:?}", op.as_rule())) - } - } - - fn parse_prefix(&self, op: Pair, rhs: Result) -> Result { - let rhs = rhs?; - - match op.as_rule() { - Rule::not => Ok(Expr::Unary(UnaryOp::Not, Box::new(rhs))), - Rule::minus => Ok(Expr::Unary(UnaryOp::Neg, Box::new(rhs))), - Rule::increment => Ok(Expr::Unary(UnaryOp::PreIncrement, Box::new(rhs))), - Rule::decrement => Ok(Expr::Unary(UnaryOp::PreDecrement, Box::new(rhs))), - _ => Err(format!("Unexpected prefix operator: {:?}", op.as_rule())) - } - } - - fn parse_postfix(&self, lhs: Result, op: Pair) -> Result { - let lhs = lhs?; - - match op.as_rule() { - Rule::increment => Ok(Expr::PostIncrement(Box::new(lhs))), - Rule::decrement => Ok(Expr::PostDecrement(Box::new(lhs))), - _ => Err(format!("Unexpected postfix operator: {:?}", op.as_rule())) - } - } - - fn parse_if_expr(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - let condition = self.parse_expr(inner.next().unwrap().into_inner())?; - let then_block = self.parse_block(inner.next().unwrap())?; - let else_block = inner.next().map(|p| self.parse_block(p)).transpose()?; - - Ok(Expr::If(Box::new(condition), Box::new(then_block), else_block.map(Box::new))) - } - - fn parse_match_expr(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - let target = if let Some(p) = inner.peek() { - if p.as_rule() == Rule::expression { - Some(Box::new(self.parse_expr(inner.next().unwrap().into_inner())?)) - } else { - None - } - } else { - None - }; - - let mut arms = vec![]; - for arm_pair in inner { - if arm_pair.as_rule() == Rule::match_arm { - arms.push(self.parse_match_arm(arm_pair)?); - } - } - - Ok(Expr::Match(target, arms)) - } - - fn parse_match_arm(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - let pattern = self.parse_match_pattern(inner.next().unwrap())?; - let body = self.parse_expr(inner.next().unwrap().into_inner())?; - - Ok(MatchArm { pattern, body }) - } - - fn parse_match_pattern(&self, pair: Pair) -> Result { - // Simplified pattern parsing - match pair.as_rule() { - Rule::underscore => Ok(MatchPattern::Wildcard), - _ => Ok(MatchPattern::Expression(self.parse_expr(pair.into_inner())?)) - } - } - - fn parse_block(&self, pair: Pair) -> Result { - let mut statements = vec![]; - for stmt in pair.into_inner() { - statements.push(self.parse_statement(stmt)?); - } - Ok(Block { statements }) - } - - fn parse_statement(&self, pair: Pair) -> Result { - match pair.as_rule() { - Rule::expression_stmt => { - let expr = self.parse_expr(pair.into_inner())?; - Ok(Statement::Expression(expr)) - } - Rule::return_stmt => { - let mut inner = pair.into_inner(); - let expr = inner.next().map(|p| self.parse_expr(p.into_inner())).transpose()?; - Ok(Statement::Return(expr)) - } - // Add other statement parsing here - _ => Err(format!("Unimplemented statement type: {:?}", pair.as_rule())) - } - } - - fn parse_type(&self, pair: Pair) -> Result { - match pair.as_rule() { - Rule::type_annotation => { - // Type annotation starts with colon, skip it - let mut inner = pair.into_inner(); - inner.next(); // skip colon - self.parse_type_expr(inner.next().unwrap()) - } - Rule::type_expr => { - self.parse_type_expr(pair) - } - _ => Err(format!("Expected type annotation or type expression, got {:?}", pair.as_rule())) - } - } - - fn parse_type_expr(&self, pair: Pair) -> Result { - let inner = pair.into_inner(); - let mut current_type = None; - let mut is_optional = false; - let mut error_type = None; - let mut array_depth = 0; - - for part in inner { - match part.as_rule() { - Rule::optional_prefix => { - is_optional = true; - } - Rule::error_union_prefix => { - // Could be just "!" or "ErrorType!" - let prefix_inner = part.into_inner(); - if let Some(error_name) = prefix_inner.peek() { - error_type = Some(error_name.as_str().to_string()); - } else { - error_type = Some("Error".to_string()); // Default error type - } - } - Rule::base_type => { - current_type = Some(self.parse_base_type(part)?); - } - Rule::array_suffix => { - array_depth += 1; - } - _ => {} - } - } - - let mut result_type = current_type.ok_or("No base type found")?; - - // Apply array suffixes - for _ in 0..array_depth { - result_type = Type::Array(Box::new(result_type)); - } - - // Apply error union - if let Some(err_type) = error_type { - result_type = Type::ErrorUnion(Some(err_type), Box::new(result_type)); - } - - // Apply optional - if is_optional { - result_type = Type::Optional(Box::new(result_type)); - } - - Ok(result_type) - } - - fn parse_base_type(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - let type_part = inner.next().unwrap(); - - match type_part.as_rule() { - Rule::primitive_type => { - Ok(Type::Primitive(type_part.as_str().to_string())) - } - Rule::identifier => { - Ok(Type::Named(type_part.as_str().to_string())) - } - _ => Err(format!("Unexpected base type: {:?}", type_part.as_rule())) - } - } - - fn parse_param_list(&self, pair: Pair) -> Result, String> { - let mut params = vec![]; - - for inner in pair.into_inner() { - if inner.as_rule() == Rule::param { - params.push(self.parse_param(inner)?); - } - } - - Ok(params) - } - - fn parse_param(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - let name = inner.next().unwrap().as_str().to_string(); - - let type_annotation = if let Some(type_pair) = inner.next() { - Some(self.parse_type(type_pair)?) - } else { - None - }; - - Ok(Param { name, type_annotation }) - } - - fn parse_return_type(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - let first = inner.next().unwrap(); - - match first.as_rule() { - Rule::arrow_fat => { - // Named return variable: => (name: Type = default?) - let name = inner.next().unwrap().as_str().to_string(); - let type_annotation = self.parse_type(inner.next().unwrap())?; - let default_value = if let Some(expr_pair) = inner.next() { - Some(self.parse_expr(expr_pair.into_inner())?) - } else { - None - }; - - Ok(ReturnType::Named { - name, - type_annotation, - default_value, - }) - } - Rule::type_annotation => { - // Simple return type: : Type - Ok(ReturnType::Simple(self.parse_type(first)?)) - } - _ => Err(format!("Unexpected return type: {:?}", first.as_rule())) - } - } - - fn parse_function_decl(&self, pair: Pair) -> Result { - let mut inner = pair.into_inner(); - - // Skip 'fn' keyword - inner.next(); - - let name = inner.next().unwrap().as_str().to_string(); - let params = self.parse_param_list(inner.next().unwrap())?; - - let mut extends = None; - let mut return_type = None; - let mut body = None; - - while let Some(remaining) = inner.next() { - match remaining.as_rule() { - Rule::extends_kw => { - extends = Some(inner.next().unwrap().as_str().to_string()); - } - Rule::return_type => { - return_type = Some(self.parse_return_type(remaining)?); - } - Rule::block => { - body = Some(self.parse_block(remaining)?); - } - _ => {} - } - } - - Ok(Statement::Function { - name, - params, - return_type, - extends, - body: body.ok_or("Function body required")?, - }) - } -} - -/* Usage example -pub fn parse_solace_code(input: &str) -> Result, String> { - let pairs = SolaceParser::parse(Rule::program, input) - .map_err(|e| format!("Parse error: {}", e))?; - - let parser = SolacePrattParser::new(); - let mut statements = vec![]; - - for pair in pairs { - if pair.as_rule() == Rule::program { - for stmt_pair in pair.into_inner() { - if stmt_pair.as_rule() != Rule::EOI { - statements.push(parser.parse_statement(stmt_pair)?); - } - } - } - } - - Ok(statements) -} -*/ - -fn main() { - let unparsed_file = fs::read_to_string("test.solace").expect("Cannot read test file"); - - match SolaceParser::parse(Rule::program, &unparsed_file) { - Ok(mut pairs) => { - let program = pairs.next().unwrap(); - println!("Parsing was successful."); - print_parse_tree(program, 0); - } - Err(err) => { - println!("Parse error: {}", err); - } - } -} - -fn print_parse_tree(pair: Pair, indent: usize) { - let indent_str = " ".repeat(indent); - println!("{}{:?}: \"{}\"", indent_str, pair.as_rule(), pair.as_str()); - - for inner_pair in pair.into_inner() { - print_parse_tree(inner_pair, indent + 1) - } -} - diff --git a/src/parser/ast.rs b/src/parser/ast.rs new file mode 100644 index 0000000..773ba14 --- /dev/null +++ b/src/parser/ast.rs @@ -0,0 +1,612 @@ +use super::{ParseError, Rule}; +use pest::iterators::{Pair, Pairs}; +use pest::pratt_parser::{Assoc, Op, PrattParser}; + +#[derive(Debug, Clone)] +pub enum Expr { + //Empty + Empty, + // Literals + Number(f64), + String(String), + Boolean(bool), + None, + Undefined, + Underscore, + + // Variables and calls + Identifier(String), + MemberAccess(Box, String), + Index(Box, Box), + Call(Box, Vec), + + // Operators + Binary(BinaryOp, Box, Box), + Unary(UnaryOp, Box), + Ternary(Box, Box, Box), + Assignment(String, Box), + + // Control flow + If(Box, Box, Option>), + Match(Option>, Vec), + + // Collections + Array(Vec), + + // Postfix + PostIncrement(Box), + PostDecrement(Box), +} + +#[derive(Debug, Clone)] +pub enum BinaryOp { + Add, + Sub, + Mul, + Div, + Mod, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, + And, + Or, + RangeInclusive, + RangeExclusive, + Is, +} + +#[derive(Debug, Clone)] +pub enum UnaryOp { + Not, + Neg, + PreIncrement, + PreDecrement, +} + +#[derive(Debug, Clone)] +pub struct MatchArm { + pattern: MatchPattern, + body: Expr, +} + +#[derive(Debug, Clone)] +pub enum MatchPattern { + Wildcard, + Expression(Expr), + Condition(Vec<(BinaryOp, Expr)>), +} + +#[derive(Debug, Clone)] +pub struct Block { + statements: Vec, +} + +#[derive(Debug, Clone)] +pub enum ReturnType { + Simple(Type), + Named { + name: String, + type_annotation: Type, + default_value: Option, + }, +} + +#[derive(Debug, Clone)] +pub enum Statement { + Import { + name: String, + from: String, + }, + Function { + name: String, + params: Vec, + return_type: Option, + extends: Option, + body: Block, + }, + Variable { + kind: VarKind, + name: String, + type_annotation: Option, + value: Expr, + }, + Defer { + condition: Option, + binding: Option, + body: Block, + }, + Watch { + target: String, + body: Block, + }, + Return(Option), + If(Expr, Box, Option>), + For { + var: String, + index: Option, + iterable: Expr, + body: Box, + }, + While(Expr, Box), + Expression(Expr), + Block(Block), +} + +#[derive(Debug, Clone)] +pub enum VarKind { + Const, + Var, + Live, +} + +#[derive(Debug, Clone)] +pub struct Param { + name: String, + type_annotation: Option, +} + +#[derive(Debug, Clone)] +pub enum Type { + Primitive(String), + Array(Box), + Optional(Box), + ErrorUnion(Option, Box), + Named(String), +} + +pub struct SolacePrattParser { + pratt: PrattParser, +} + +impl SolacePrattParser { + pub fn new() -> Self { + let pratt = PrattParser::new() + .op(Op::infix(Rule::assign, Assoc::Right)) + .op(Op::infix(Rule::question, Assoc::Right) | Op::infix(Rule::colon, Assoc::Right)) + .op(Op::infix(Rule::or, Assoc::Left)) + .op(Op::infix(Rule::and, Assoc::Left)) + .op(Op::infix(Rule::eq, Assoc::Left) + | Op::infix(Rule::ne, Assoc::Left) + | Op::infix(Rule::is_kw, Assoc::Left)) + .op(Op::infix(Rule::lt, Assoc::Left) + | Op::infix(Rule::gt, Assoc::Left) + | Op::infix(Rule::le, Assoc::Left) + | Op::infix(Rule::ge, Assoc::Left)) + .op(Op::infix(Rule::range_inclusive, Assoc::Left) + | Op::infix(Rule::range_exclusive, Assoc::Left)) + .op(Op::infix(Rule::plus, Assoc::Left) | Op::infix(Rule::minus, Assoc::Left)) + .op(Op::infix(Rule::multiply, Assoc::Left) + | Op::infix(Rule::divide, Assoc::Left) + | Op::infix(Rule::modulo, Assoc::Left)) + .op(Op::prefix(Rule::not) + | Op::prefix(Rule::minus) + | Op::prefix(Rule::increment) + | Op::prefix(Rule::decrement)) + .op(Op::postfix(Rule::increment) | Op::postfix(Rule::decrement)); + + SolacePrattParser { pratt } + } + + pub fn parse_expr(&self, pairs: Pairs) -> Result { + if pairs.clone().count() == 0 { + return Ok(Expr::Empty); + } + + self.pratt + .map_primary(|primary| self.parse_primary(primary)) + .map_infix(|lhs, op, rhs| self.parse_infix(lhs, op, rhs)) + .map_prefix(|op, rhs| self.parse_prefix(op, rhs)) + .map_postfix(|lhs, op| self.parse_postfix(lhs, op)) + .parse(pairs) + } + + fn parse_primary(&self, pair: Pair) -> Result { + match pair.as_rule() { + Rule::number_literal => { + let num = pair + .as_str() + .parse::() + .map_err(|_| ParseError::InvalidNumber(pair.as_rule()))?; + Ok(Expr::Number(num)) + } + Rule::string_literal => { + let s = pair.as_str(); + Ok(Expr::String(s[1..s.len() - 1].to_string())) + } + Rule::boolean_literal => Ok(Expr::Boolean(pair.as_str() == "true")), + Rule::none_kw => Ok(Expr::None), + Rule::undefined_kw => Ok(Expr::Undefined), + Rule::underscore => Ok(Expr::Underscore), + Rule::identifier => Ok(Expr::Identifier(pair.as_str().to_string())), + Rule::array_literal => { + let mut elements = vec![]; + for inner in pair.into_inner() { + if inner.as_rule() == Rule::expression { + elements.push(self.parse_expr(inner.into_inner())?); + } + } + Ok(Expr::Array(elements)) + } + Rule::if_expr => self.parse_if_expr(pair), + Rule::match_expr => self.parse_match_expr(pair), + _ => Err(ParseError::UnknownPrimary(pair.as_rule())), + } + } + + fn parse_infix( + &self, + lhs: Result, + op: Pair, + rhs: Result, + ) -> Result { + let lhs = lhs?; + let rhs = rhs?; + + match op.as_rule() { + Rule::plus => Ok(Expr::Binary(BinaryOp::Add, Box::new(lhs), Box::new(rhs))), + Rule::minus => Ok(Expr::Binary(BinaryOp::Sub, Box::new(lhs), Box::new(rhs))), + Rule::multiply => Ok(Expr::Binary(BinaryOp::Mul, Box::new(lhs), Box::new(rhs))), + Rule::divide => Ok(Expr::Binary(BinaryOp::Div, Box::new(lhs), Box::new(rhs))), + Rule::modulo => Ok(Expr::Binary(BinaryOp::Mod, Box::new(lhs), Box::new(rhs))), + Rule::eq => Ok(Expr::Binary(BinaryOp::Eq, Box::new(lhs), Box::new(rhs))), + Rule::ne => Ok(Expr::Binary(BinaryOp::Ne, Box::new(lhs), Box::new(rhs))), + Rule::lt => Ok(Expr::Binary(BinaryOp::Lt, Box::new(lhs), Box::new(rhs))), + Rule::gt => Ok(Expr::Binary(BinaryOp::Gt, Box::new(lhs), Box::new(rhs))), + Rule::le => Ok(Expr::Binary(BinaryOp::Le, Box::new(lhs), Box::new(rhs))), + Rule::ge => Ok(Expr::Binary(BinaryOp::Ge, Box::new(lhs), Box::new(rhs))), + Rule::and => Ok(Expr::Binary(BinaryOp::And, Box::new(lhs), Box::new(rhs))), + Rule::or => Ok(Expr::Binary(BinaryOp::Or, Box::new(lhs), Box::new(rhs))), + Rule::is_kw => Ok(Expr::Binary(BinaryOp::Is, Box::new(lhs), Box::new(rhs))), + Rule::range_inclusive => Ok(Expr::Binary( + BinaryOp::RangeInclusive, + Box::new(lhs), + Box::new(rhs), + )), + Rule::range_exclusive => Ok(Expr::Binary( + BinaryOp::RangeExclusive, + Box::new(lhs), + Box::new(rhs), + )), + Rule::assign => { + if let Expr::Identifier(name) = lhs { + Ok(Expr::Assignment(name, Box::new(rhs))) + } else { + Err(ParseError::InvalidLeftHand(lhs)) + } + } + Rule::question => { + // Handle ternary - need to parse the rest + // This is simplified - in practice you'd need more complex handling + Ok(Expr::Ternary( + Box::new(lhs), + Box::new(rhs), + Box::new(Expr::None), + )) + } + _ => Err(ParseError::UnknownInfixOperator(op.as_rule())), + } + } + + fn parse_prefix( + &self, + op: Pair, + rhs: Result, + ) -> Result { + let rhs = rhs?; + + match op.as_rule() { + Rule::not => Ok(Expr::Unary(UnaryOp::Not, Box::new(rhs))), + Rule::minus => Ok(Expr::Unary(UnaryOp::Neg, Box::new(rhs))), + Rule::increment => Ok(Expr::Unary(UnaryOp::PreIncrement, Box::new(rhs))), + Rule::decrement => Ok(Expr::Unary(UnaryOp::PreDecrement, Box::new(rhs))), + _ => Err(ParseError::UnknownPrefixOperator(op.as_rule())), + } + } + + fn parse_postfix( + &self, + lhs: Result, + op: Pair, + ) -> Result { + let lhs = lhs?; + + match op.as_rule() { + Rule::increment => Ok(Expr::PostIncrement(Box::new(lhs))), + Rule::decrement => Ok(Expr::PostDecrement(Box::new(lhs))), + _ => Err(ParseError::UnknownPostfixOperator(op.as_rule())), + } + } + + fn parse_if_expr(&self, pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let condition = self.parse_expr(inner.next().unwrap().into_inner())?; + let then_block = self.parse_block(inner.next().unwrap())?; + let else_block = inner.next().map(|p| self.parse_block(p)).transpose()?; + + Ok(Expr::If( + Box::new(condition), + Box::new(then_block), + else_block.map(Box::new), + )) + } + + fn parse_match_expr(&self, pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let target = if let Some(p) = inner.peek() { + if p.as_rule() == Rule::expression { + Some(Box::new( + self.parse_expr(inner.next().unwrap().into_inner())?, + )) + } else { + None + } + } else { + None + }; + + let mut arms = vec![]; + for arm_pair in inner { + if arm_pair.as_rule() == Rule::match_arm { + arms.push(self.parse_match_arm(arm_pair)?); + } + } + + Ok(Expr::Match(target, arms)) + } + + fn parse_match_arm(&self, pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let pattern = self.parse_match_pattern(inner.next().unwrap())?; + let body = self.parse_expr(inner.next().unwrap().into_inner())?; + + Ok(MatchArm { pattern, body }) + } + + fn parse_match_pattern(&self, pair: Pair) -> Result { + // Simplified pattern parsing + match pair.as_rule() { + Rule::underscore => Ok(MatchPattern::Wildcard), + _ => Ok(MatchPattern::Expression( + self.parse_expr(pair.into_inner())?, + )), + } + } + + fn parse_block(&self, pair: Pair) -> Result { + let mut statements = vec![]; + for stmt in pair.into_inner() { + statements.push(self.parse_statement(stmt)?); + } + Ok(Block { statements }) + } + + fn parse_statement(&self, pair: Pair) -> Result { + match pair.as_rule() { + Rule::expression_stmt => { + let expr = self.parse_expr(pair.into_inner())?; + Ok(Statement::Expression(expr)) + } + Rule::return_stmt => { + let mut inner = pair.into_inner(); + let expr = inner + .next() + .map(|p| self.parse_expr(p.into_inner())) + .transpose()?; + Ok(Statement::Return(expr)) + } + Rule::function_decl => { + let decl = self.parse_function_decl(pair.into_inner())?; + Ok(decl) + } + // Add other statement parsing here + _ => Err(ParseError::UnknownStatement(pair.as_rule())), + } + } + + fn parse_type(&self, pair: Pair) -> Result { + match pair.as_rule() { + Rule::type_annotation => { + // Type annotation starts with colon, skip it + let mut inner = pair.into_inner(); + inner.next(); // skip colon + self.parse_type_expr(inner.next().unwrap()) + } + Rule::type_expr => self.parse_type_expr(pair), + _ => Err(ParseError::ShouldBeType(pair.as_rule())), + } + } + + fn parse_type_expr(&self, pair: Pair) -> Result { + let inner = pair.into_inner(); + let mut current_type = None; + let mut is_optional = false; + let mut error_type = None; + let mut array_depth = 0; + + for part in inner { + match part.as_rule() { + Rule::optional_prefix => { + is_optional = true; + } + Rule::error_union_prefix => { + // Could be just "!", which defaults to Error!, or "ErrorType!" + let prefix_inner = part.into_inner(); + if let Some(error_name) = prefix_inner.peek() { + error_type = Some(error_name.as_str().to_string()); + } else { + error_type = Some("Error".to_string()); + } + } + Rule::base_type => { + current_type = Some(self.parse_base_type(part)?); + } + Rule::array_suffix => { + array_depth += 1; + } + _ => {} + } + } + + let mut result_type = current_type.ok_or(ParseError::MissingBaseType())?; + + // Apply array suffixes + for _ in 0..array_depth { + result_type = Type::Array(Box::new(result_type)); + } + + // Apply error union + if let Some(err_type) = error_type { + result_type = Type::ErrorUnion(Some(err_type), Box::new(result_type)); + } + + // Apply optional + if is_optional { + result_type = Type::Optional(Box::new(result_type)); + } + + Ok(result_type) + } + + fn parse_base_type(&self, pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let type_part = inner.next().unwrap(); + + match type_part.as_rule() { + Rule::primitive_type => Ok(Type::Primitive(type_part.as_str().to_string())), + Rule::identifier => Ok(Type::Named(type_part.as_str().to_string())), + _ => Err(ParseError::UnknownTypePart(type_part.as_rule())), + } + } + + fn parse_param_list(&self, pair: Pair) -> Result, ParseError> { + let mut params = vec![]; + + for inner in pair.into_inner() { + if inner.as_rule() == Rule::param { + params.push(self.parse_param(inner)?); + } + } + + Ok(params) + } + + fn parse_param(&self, pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + + let type_annotation = if let Some(type_pair) = inner.next() { + Some(self.parse_type(type_pair)?) + } else { + None + }; + + Ok(Param { + name, + type_annotation, + }) + } + + fn parse_return_type(&self, pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let wrapped = inner.next().unwrap(); + + // Skip intermediate rules + let first = match wrapped.as_rule() { + Rule::return_type_simple => wrapped.into_inner().next().unwrap(), + Rule::return_type_named => wrapped.into_inner().next().unwrap(), + _ => wrapped, + }; + + match first.as_rule() { + // Named return variable: fn foo() => (name: Type = default?) {/*...*/} + Rule::arrow_fat => { + let name = inner.next().unwrap().as_str().to_string(); + let type_annotation = self.parse_type(inner.next().unwrap())?; + let default_value = if let Some(expr_pair) = inner.next() { + Some(self.parse_expr(expr_pair.into_inner())?) + } else { + None + }; + + Ok(ReturnType::Named { + name, + type_annotation, + default_value, + }) + } + // Simple return type: fn foo(): Type {/*...*/} + Rule::type_annotation => { + let type_annotation = self.parse_type(first)?; + Ok(ReturnType::Simple(type_annotation)) + } + // Brother ewww... whats that? + _ => Err(ParseError::UnknownReturnType( + first.as_str().to_owned(), + first.as_rule(), + )), + } + } + + fn parse_function_decl(&self, mut pairs: Pairs) -> Result { + // Skip 'fn' keyword + pairs.next(); + + let name = pairs.next().unwrap().as_str(); + let params = self.parse_param_list(pairs.next().unwrap())?; + + let mut extends = None; + let mut return_type = None; + let mut body = None; + + while let Some(remaining) = pairs.next() { + match remaining.as_rule() { + Rule::extends_kw => { + extends = Some(pairs.next().unwrap().as_str().to_string()); + } + Rule::return_type => { + return_type = Some(self.parse_return_type(remaining)?); + } + Rule::block => { + body = Some(self.parse_block(remaining)?); + } + _ => {} + } + } + + Ok(Statement::Function { + name: name.to_owned(), + params, + return_type, + extends, + body: body.ok_or(ParseError::MissingFunctionBody(name.to_owned()))?, + }) + } +} + +#[derive(Debug, Clone)] +pub struct Program { + pub statements: Vec, +} + +impl Program { + pub fn from_pairs(pairs: Pairs) -> Result { + let parser = SolacePrattParser::new(); + let mut statements = Vec::new(); + + for pair in pairs { + if pair.as_rule() == Rule::program { + for stmt_pair in pair.into_inner() { + if stmt_pair.as_rule() != Rule::EOI { + let stmt = parser.parse_statement(stmt_pair)?; + statements.push(stmt); + } + } + } + } + + Ok(Program { statements }) + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..5f60813 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,76 @@ +pub mod ast; + +use pest::Parser; +use pest::iterators::Pair; +use pest_derive::Parser; +use thiserror::Error; + +#[derive(Parser)] +#[grammar = "solace.pest"] +pub struct SolaceParser; + +#[derive(Error, Debug)] +pub enum ParseError { + #[error("Parse error: {0}")] + PestError(#[from] pest::error::Error), + + #[error("Unexpected rule: {0:?}")] + UnexpectedRule(Rule), + + #[error("Statement not implemented: {0:?}")] + UnknownStatement(Rule), + + #[error("Unknown keyword, literal or expression: {0:?}")] + UnknownPrimary(Rule), + + #[error("Left side of assignment must be an identifier: {0:?}")] + InvalidLeftHand(ast::Expr), + + #[error("Unexpected infix operator: {0:?}")] + UnknownInfixOperator(Rule), + + #[error("Unexpected prefix operator: {0:?}")] + UnknownPrefixOperator(Rule), + + #[error("Unexpected postfix operator: {0:?}")] + UnknownPostfixOperator(Rule), + + #[error("Expected type annotation or type expression, got: {0:?}")] + ShouldBeType(Rule), + + #[error("Unexpected return type: \"{0}\" ({1:?})")] + UnknownReturnType(String, Rule), + + #[error("Unexpected base type: {0:?}")] + UnknownTypePart(Rule), + + #[error("Invalid Number: {0:?}")] + InvalidNumber(Rule), + + #[error("No base type found")] + MissingBaseType(), + + #[error("Function body required: {0}")] + MissingFunctionBody(String), +} + +pub fn parse(input: &str, debug: bool) -> Result { + if debug { + let mut debug_pairs = SolaceParser::parse(Rule::program, input)?; + print_parse_tree(debug_pairs.next().unwrap(), 0); + } + + let pairs = SolaceParser::parse(Rule::program, input)?; + let program = ast::Program::from_pairs(pairs)?; + + Ok(program) +} + +fn print_parse_tree(pair: Pair, indent: usize) { + let indent_str = " ".repeat(indent); + println!("{}{:?}: \"{}\"", indent_str, pair.as_rule(), pair.as_str()); + + for inner_pair in pair.into_inner() { + print_parse_tree(inner_pair, indent + 1) + } +} diff --git a/src/solace.pest b/src/solace.pest index c094ca7..e9c473c 100644 --- a/src/solace.pest +++ b/src/solace.pest @@ -86,20 +86,12 @@ comparison_op = { le | ge | eq | ne | lt | gt } increment = @{ "++" } decrement = @{ "--" } -// Delimiters -lparen = { "(" } -rparen = { ")" } -lbrace = { "{" } -rbrace = { "}" } -lbracket = { "[" } -rbracket = { "]" } - // Types - Fixed to avoid left recursion primitive_type = { "string" | "number" | "boolean" | "undefined" } base_type = { primitive_type | identifier } // Type suffixes -array_suffix = { lbracket ~ rbracket } +array_suffix = { "[" ~ "]" } optional_prefix = { question } error_union_prefix = { identifier? ~ not } @@ -115,11 +107,11 @@ type_annotation = { colon ~ type_expr } // Parameters optional_suffix = { question } param = { identifier ~ optional_suffix? ~ type_annotation? } -param_list = { lparen ~ (param ~ (comma ~ param)*)? ~ rparen } +param_list = { "(" ~ (param ~ (comma ~ param)*)? ~ ")" } // Return type with named return variable return_type_simple = { type_annotation } -return_type_named = { arrow_fat ~ lparen ~ identifier ~ type_annotation ~ (assign ~ expression)? ~ rparen } +return_type_named = { arrow_fat ~ "(" ~ identifier ~ type_annotation ~ (assign ~ expression)? ~ ")" } return_type = { return_type_simple | return_type_named } // Expressions @@ -133,23 +125,25 @@ primary_expr = { undefined_kw | function_call_expr | identifier | - lparen ~ expression ~ rparen | + grouped_expression | array_literal | if_expr | match_expr | continue_expr } -// Function calls including ok() and err() -function_call_expr = { (ok_kw | err_kw | failure_kw | success_kw) ~ lparen ~ expression_list? ~ rparen } +grouped_expression = { "(" ~ expression ~ ")" } -array_literal = { lbracket ~ expression_list? ~ rbracket } +// Function calls including ok() and err() +function_call_expr = { (ok_kw | err_kw | failure_kw | success_kw) ~ "(" ~ expression_list? ~ ")" } + +array_literal = { "[" ~ expression_list? ~ "]" } expression_list = { expression ~ (comma ~ expression)* } // Member access and indexing member_access = { dot ~ identifier } -index_access = { lbracket ~ expression ~ rbracket } -call_suffix = { lparen ~ expression_list? ~ rparen } +index_access = { "[" ~ expression ~ "]" } +call_suffix = { "(" ~ expression_list? ~ ")" } postfix_expr = { primary_expr ~ (member_access | index_access | call_suffix | increment | decrement)* } @@ -164,16 +158,16 @@ logical_and_expr = { equality_expr ~ (and ~ equality_expr)* } logical_or_expr = { logical_and_expr ~ (or ~ logical_and_expr)* } ternary_expr = { logical_or_expr ~ (question ~ expression ~ colon ~ expression)? } assignment_expr = { lvalue ~ assign ~ expression } -lvalue = { identifier ~ (dot ~ identifier | lbracket ~ expression ~ rbracket)* } +lvalue = { identifier ~ (dot ~ identifier | "[" ~ expression ~ "]")* } expression = { assignment_expr | ternary_expr } // If expression and statement -if_expr = { if_kw ~ expression ~ lbrace ~ statement* ~ rbrace ~ (else_kw ~ lbrace ~ statement* ~ rbrace)? } +if_expr = { if_kw ~ expression ~ "{" ~ statement* ~ "}" ~ (else_kw ~ "{" ~ statement* ~ "}")? } if_expr_short = { if_kw ~ expression ~ colon ~ expression ~ (else_kw ~ colon ~ expression)? } // Match expression -match_expr = { match_kw ~ expression? ~ lbrace ~ match_arm* ~ rbrace } +match_expr = { match_kw ~ expression? ~ "{" ~ match_arm* ~ "}" } match_arm = { match_pattern ~ arrow ~ (expression | block) } match_pattern = { expression } @@ -181,7 +175,7 @@ match_pattern = { expression } continue_expr = { continue_kw } // Statements -statement = { +statement = _{ import_stmt | function_decl | variable_decl | @@ -230,12 +224,12 @@ while_stmt = { continue_stmt = { continue_kw } -match_stmt = { match_kw ~ expression? ~ lbrace ~ match_arm* ~ rbrace } +match_stmt = { match_kw ~ expression? ~ "{" ~ match_arm* ~ "}" } expression_stmt = { expression } // Blocks -block = { lbrace ~ statement* ~ rbrace } +block = { "{" ~ statement* ~ "}" } // Program program = { SOI ~ statement* ~ EOI } diff --git a/src/transformer/js.rs b/src/transformer/js.rs new file mode 100644 index 0000000..0d84bd5 --- /dev/null +++ b/src/transformer/js.rs @@ -0,0 +1,14 @@ +use crate::parser::ast; +use swc_ecma_ast as swc_ast; +use swc_common::DUMMY_SP; + +pub struct JsTransformer; + +impl JsTransformer { + pub fn new() -> Self { + Self + } + pub fn transform(&self, program: ast::Program) -> swc_ast::Module { + todo!("Implement Solace AST to SWC AST transformer") + } +} diff --git a/src/transformer/mod.rs b/src/transformer/mod.rs new file mode 100644 index 0000000..ba02ba9 --- /dev/null +++ b/src/transformer/mod.rs @@ -0,0 +1,4 @@ +pub mod js; + +// maybe one day: +// pub mod wasm; diff --git a/test.short.solace b/test.short.solace new file mode 100644 index 0000000..faaa53d --- /dev/null +++ b/test.short.solace @@ -0,0 +1,3 @@ +fn timesTwo(x: number): number { + return x*2 +}