diff options
| author | Stanisław Zagórowski <hasohasacz@gmail.com> | 2024-01-11 02:20:44 +0100 |
|---|---|---|
| committer | Stanisław Zagórowski <hasohasacz@gmail.com> | 2024-01-11 02:20:44 +0100 |
| commit | 343bcfdfaaba32b7a46e1a945a4538e72d166ea8 (patch) | |
| tree | ed27311160054515b6375a9f92e092772300ee65 | |
huh whuh
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 863 | ||||
| -rw-r--r-- | Cargo.toml | 16 | ||||
| -rw-r--r-- | example/index.lua | 29 | ||||
| -rw-r--r-- | example/reluax.lua | 20 | ||||
| -rw-r--r-- | example/test.lua | 25 | ||||
| -rw-r--r-- | example/test.luax | 70 | ||||
| -rw-r--r-- | src/error.rs | 69 | ||||
| -rw-r--r-- | src/luax/lexer.rs | 426 | ||||
| -rw-r--r-- | src/luax/mod.rs | 101 | ||||
| -rw-r--r-- | src/luax/preprocessor.rs | 793 | ||||
| -rw-r--r-- | src/luax/tokens.rs | 147 | ||||
| -rw-r--r-- | src/main.rs | 102 | ||||
| -rw-r--r-- | src/server.rs | 107 |
14 files changed, 2769 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cc7f64a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,863 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "eyre" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991910e35c615d8cab86b5ab04be67e6ad24d2bf5f4f11fdbbed26da999bbeab" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reluax" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "http-body-util", + "hyper", + "hyper-util", + "rlua", + "tokio", + "utf8-read", +] + +[[package]] +name = "rlua" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d33e5ba15c3d43178f283ed5863d4531e292fc0e56fb773f3bea45f18e3a42a" +dependencies = [ + "bitflags", + "bstr", + "libc", + "num-traits", + "rlua-lua54-sys", +] + +[[package]] +name = "rlua-lua54-sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aafabafe1895cb4a2be81a56d7ff3d46bf4b5d2f9cfdbea2ed404cdabe96474" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8-read" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb566ac06e11c3f13872ebcb0e72ea5f97f0f65a16f45048d0adea21edc28018" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..46ee34d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "reluax" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.4.14", features = ["derive"] } +color-eyre = "0.6.2" +http-body-util = "0.1.0" +hyper = { version = "1.1.0", features = ["http1", "http2", "server"] } +hyper-util = { version = "0.1.2", features = ["http1", "http2", "server", "tokio"] } +rlua = "0.19.7" +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } +utf8-read = "0.4.0" diff --git a/example/index.lua b/example/index.lua new file mode 100644 index 0000000..76e1c0b --- /dev/null +++ b/example/index.lua @@ -0,0 +1,29 @@ +return function() + return { + tag = "html", + children = { + { + tag = "head", + children = { + { + tag = "title", + children = { + "Hello World" + } + } + } + }, + { + tag = "body", + children = { + { + tag = "h1", + children = { + "Hello World" + } + } + } + } + } + } +end diff --git a/example/reluax.lua b/example/reluax.lua new file mode 100644 index 0000000..0a5f2c7 --- /dev/null +++ b/example/reluax.lua @@ -0,0 +1,20 @@ +local index = require("index") + +math.randomseed(os.time()) + +local function route(path) + if path == '/' then + return index() + elseif path == '/goodbye' then + return "goodbye" + elseif path == '/random' then + return 'no more random' + end + + return nil +end + +return { + name = "hello-world", + route = route +} diff --git a/example/test.lua b/example/test.lua new file mode 100644 index 0000000..04d5bd0 --- /dev/null +++ b/example/test.lua @@ -0,0 +1,25 @@ + local install_path = vim . fn . stdpath 'data' .. '/site/pack/packer/start/packer.nvim' local is_bootstrap = false if vim . fn . empty ( vim . fn . glob ( install_path ) ) > 0 then is_bootstrap = true vim . fn . execute ( '!git clone https://github.com/wbthomason/packer.nvim ' .. install_path ) vim . cmd [[packadd packer.nvim]] end vim . opt . number = true vim . opt . autoindent = true vim . opt . tabstop = 4 vim . opt . shiftwidth = 4 vim . opt . smarttab = true vim . opt . softtabstop = 4 vim . opt . mouse = "a" vim . opt . scl = "yes" vim . opt . guifont = "Hack:h10" vim . opt . expandtab = true vim . opt . list = true vim . opt . listchars = "tab:➜ ,lead:·,trail:·" vim . opt . termguicolors = true vim . o . background = "dark" vim . opt . clipboard = "unnamedplus" vim . opt . foldmethod = "expr" vim . opt . foldexpr = "nvim_treesitter#foldexpr()" vim . opt . foldlevel = 99 a = { 'dollar' } require ( "plugins" ) if is_bootstrap then print ( "Bootstrapping complete. Restart when plugins are installed." ) require ( "packer" ) . sync ( ) return end require ( "mappings" ) require ( "lsp" ) require ( "plugin-config" ) vim . cmd ( "colorscheme fenchurch" ) vim . cmd ( [[ + autocmd! BufNewFile,BufRead *.vs,*.fs set ft=glsl + autocmd! BufNewFile,BufRead *.gd setlocal noexpandtab + autocmd! BufNewFile,BufRead *.ts,*.tsx setlocal tabstop=2 shiftwidth=2 softtabstop=2 + autocmd! BufNewFile,BufRead *.js,*.jsx setlocal tabstop=2 shiftwidth=2 softtabstop=2 + autocmd! BufNewFile,BufRead *.html,*.css,*.scss,*.json,*.yml,*.yaml,*.md,*.markdown setlocal tabstop=2 shiftwidth=2 softtabstop=2 +]] ) vim . cmd ( [[ + function! ZenMode() + if !exists('g:zen_mode') + let g:zen_mode = 0 + endif + if g:zen_mode == 0 + setlocal nolist + setlocal nonumber + lua require('lualine').hide() + g:zen_mode = 1 + else + setlocal list + setlocal number + lua require('lualine').hide({unhide=true}) + g:zen_mode = 0 + endif + endfunction + command! ZenMode call ZenMode() +]] )
\ No newline at end of file diff --git a/example/test.luax b/example/test.luax new file mode 100644 index 0000000..dfc9a3c --- /dev/null +++ b/example/test.luax @@ -0,0 +1,70 @@ +local install_path = vim.fn.stdpath 'data' .. '/site/pack/packer/start/packer.nvim' +local is_bootstrap = false +if vim.fn.empty(vim.fn.glob(install_path)) > 0 then + is_bootstrap = true + vim.fn.execute('!git clone https://github.com/wbthomason/packer.nvim ' .. install_path) + vim.cmd [[packadd packer.nvim]] +end + +vim.opt.number = true +vim.opt.autoindent = true +vim.opt.tabstop = 4 +vim.opt.shiftwidth = 4 +vim.opt.smarttab = true +vim.opt.softtabstop = 4 +vim.opt.mouse = "a" +vim.opt.scl = "yes" +vim.opt.guifont = "Hack:h10" +vim.opt.expandtab = true +vim.opt.list = true +vim.opt.listchars = "tab:➜ ,lead:·,trail:·" +vim.opt.termguicolors = true +vim.o.background = "dark" +vim.opt.clipboard = "unnamedplus" +vim.opt.foldmethod = "expr" +vim.opt.foldexpr = "nvim_treesitter#foldexpr()" +vim.opt.foldlevel = 99 + +require("plugins") + +if is_bootstrap then + print("Bootstrapping complete. Restart when plugins are installed.") + require("packer").sync() + + return +end + +require("mappings") +require("lsp") +require("plugin-config") + +vim.cmd("colorscheme fenchurch") + +vim.cmd([[ + autocmd! BufNewFile,BufRead *.vs,*.fs set ft=glsl + autocmd! BufNewFile,BufRead *.gd setlocal noexpandtab + autocmd! BufNewFile,BufRead *.ts,*.tsx setlocal tabstop=2 shiftwidth=2 softtabstop=2 + autocmd! BufNewFile,BufRead *.js,*.jsx setlocal tabstop=2 shiftwidth=2 softtabstop=2 + autocmd! BufNewFile,BufRead *.html,*.css,*.scss,*.json,*.yml,*.yaml,*.md,*.markdown setlocal tabstop=2 shiftwidth=2 softtabstop=2 +]]) + +-- zen mode +vim.cmd([[ + function! ZenMode() + if !exists('g:zen_mode') + let g:zen_mode = 0 + endif + if g:zen_mode == 0 + setlocal nolist + setlocal nonumber + lua require('lualine').hide() + g:zen_mode = 1 + else + setlocal list + setlocal number + lua require('lualine').hide({unhide=true}) + g:zen_mode = 0 + endif + endfunction + command! ZenMode call ZenMode() +]]) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..39c8cce --- /dev/null +++ b/src/error.rs @@ -0,0 +1,69 @@ +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum LuaXError { + InvalidStart, // used internally + MissingField(String), + WrongFieldType(String), + NonTableChildren, + UnexpectedCharacter(char), + UnexpectedToken(String), + NeededToken(String), + ExpectedVar, + ExpectedExpression, + UnterminatedStringLiteral, + InvalidEscapeSequence(char), + InvalidAssignmentTarget, +} + +impl std::error::Error for LuaXError {} + +impl Display for LuaXError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + LuaXError::InvalidStart => write!(f, "Invalid start"), + LuaXError::MissingField(field) => write!(f, "Missing required field: {}", field), + LuaXError::WrongFieldType(field) => write!(f, "Wrong field type: field {}", field), + LuaXError::NonTableChildren => write!(f, "Children must be tables"), + LuaXError::UnexpectedCharacter(c) => write!(f, "Unexpected character: '{}'", c), + LuaXError::UnexpectedToken(token) => write!(f, "Unexpected token: {}", token), + LuaXError::NeededToken(token) => write!(f, "Needed token: {}", token), + LuaXError::ExpectedVar => write!(f, "Expected variable"), + LuaXError::ExpectedExpression => write!(f, "Expected expression"), + LuaXError::UnterminatedStringLiteral => write!(f, "Unterminated string literal"), + LuaXError::InvalidEscapeSequence(c) => write!(f, "Invalid escape sequence: {}", c), + LuaXError::InvalidAssignmentTarget => write!(f, "Invalid assignment target"), + } + } +} + +#[derive(Debug)] +pub enum ReluaxError { + LuaX(LuaXError), + Lua(rlua::Error), + Server(String), +} + +impl std::error::Error for ReluaxError {} + +impl Display for ReluaxError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ReluaxError::LuaX(err) => write!(f, "{}", err), + ReluaxError::Lua(err) => write!(f, "{}", err), + ReluaxError::Server(err) => write!(f, "{}", err), + } + } +} + +impl From<LuaXError> for ReluaxError { + fn from(err: LuaXError) -> Self { + ReluaxError::LuaX(err) + } +} + +impl From<rlua::Error> for ReluaxError { + fn from(err: rlua::Error) -> Self { + ReluaxError::Lua(err) + } +} diff --git a/src/luax/lexer.rs b/src/luax/lexer.rs new file mode 100644 index 0000000..f1021ec --- /dev/null +++ b/src/luax/lexer.rs @@ -0,0 +1,426 @@ +use crate::error::LuaXError; +use color_eyre::Result; + +use super::tokens::{StringType, Token}; + +// Macro for trying to match with multiple functions +// If a function returns a token, the token is returned +// If a function returns an error, the error is returned +// If a function returns None, the next function is tried +macro_rules! try_all_paths { + () => { + TokenizeResult::None + }; + ($e:expr) => { + $e + }; + ($head:expr $(,$tail:expr)* $(,)?) => { + match $head { + TokenizeResult::Some(t) => TokenizeResult::Some(t), + TokenizeResult::Error(e) => TokenizeResult::Error(e), + TokenizeResult::None => try_all_paths!($($tail,)*), + } + }; +} + +pub trait TokenProducer { + fn next(&mut self) -> Option<Token>; +} + +#[derive(Debug)] +pub struct Lexer<'s> { + src: &'s str, + chars: std::str::Chars<'s>, + // Current character + current: Option<char>, + // Positioning + current_pos_in_bytes: usize, + // EOF + emitted_eof: bool, +} + +#[derive(Debug)] +enum TokenizeResult<'s> { + // A token was produced + Some(Token<'s>), + // No valid token found + None, + // An error occurred + Error(LuaXError), +} + +impl<'s> Lexer<'s> { + pub fn new(src: &'s str) -> Self { + let mut chars = src.chars(); + let current = chars.next(); + Self { + src, + chars, + current, + current_pos_in_bytes: 0, + emitted_eof: false, + } + } + + pub fn next_token(&mut self) -> Result<Option<Token<'s>>> { + let token = self.lex(); + + match token { + TokenizeResult::Some(token) => Ok(Some(token)), + TokenizeResult::Error(error) => Err(error.into()), + TokenizeResult::None => match self.current { + Some(c) => Err(LuaXError::UnexpectedCharacter(c).into()), + None => { + if self.emitted_eof { + Ok(None) + } else { + self.emitted_eof = true; + Ok(Some(Token::Eof)) + } + } + }, + } + } + + fn lex(&mut self) -> TokenizeResult<'s> { + self.skip_whitespace(); + + try_all_paths!( + self.single_char_token(), + self.double_char_token(), + self.triple_char_token(), + self.string(), + self.number(), + self.comment(), + self.identifier_or_keyword(), + ) + } + + fn skip_whitespace(&mut self) { + while let Some(c) = self.current { + match c { + ' ' | '\t' | '\n' => { + self.advance(); + } + _ => break, + } + } + } + + fn comment(&mut self) -> TokenizeResult<'s> { + if self.match_char('-') { + if self.match_char('-') { + while let Some(c) = self.current { + if c == '\n' { + break; + } + self.advance(); + } + self.lex() + } else { + TokenizeResult::Some(Token::Minus) + } + } else { + TokenizeResult::None + } + } + + fn single_char_token_case(&mut self, c: char, kind: Token<'s>) -> TokenizeResult<'s> { + if self.current == Some(c) { + self.advance(); + TokenizeResult::Some(kind) + } else { + TokenizeResult::None + } + } + + fn single_char_token(&mut self) -> TokenizeResult<'s> { + try_all_paths!( + self.single_char_token_case('+', Token::Plus), + self.single_char_token_case('*', Token::Star), + self.single_char_token_case('/', Token::Slash), + self.single_char_token_case('^', Token::Hat), + self.single_char_token_case('%', Token::Percent), + self.single_char_token_case('&', Token::Amp), + self.single_char_token_case('|', Token::Pipe), + self.single_char_token_case('#', Token::Hash), + self.single_char_token_case('(', Token::OpenParen), + self.single_char_token_case(')', Token::CloseParen), + self.single_char_token_case('{', Token::OpenBrace), + self.single_char_token_case('}', Token::CloseBrace), + self.single_char_token_case(']', Token::CloseBracket), + self.single_char_token_case(';', Token::Semicolon), + self.single_char_token_case(',', Token::Comma), + self.single_char_token_case('$', Token::Dollar), + ) + } + + fn double_char_token_case( + &mut self, + first: char, + second: char, + short: Option<Token<'s>>, + long: Token<'s>, + ) -> TokenizeResult<'s> { + if self.match_char(first) { + if self.match_char(second) { + TokenizeResult::Some(long) + } else if let Some(short) = short { + TokenizeResult::Some(short) + } else { + TokenizeResult::None + } + } else { + TokenizeResult::None + } + } + + fn double_char_token_case_with_alts<const N: usize>( + &mut self, + first: char, + second: [char; N], + short: Option<Token<'s>>, + long: [Token<'s>; N], + ) -> TokenizeResult<'s> { + if self.match_char(first) { + for i in 0..N { + if self.match_char(second[i]) { + return TokenizeResult::Some(long[i]); + } + } + if let Some(short) = short { + TokenizeResult::Some(short) + } else { + TokenizeResult::None + } + } else { + TokenizeResult::None + } + } + + fn double_char_token(&mut self) -> TokenizeResult<'s> { + try_all_paths!( + self.double_char_token_case_with_alts( + '<', + ['=', '<'], + Some(Token::Lt), + [Token::Le, Token::LtLt] + ), + self.double_char_token_case_with_alts( + '>', + ['=', '>'], + Some(Token::Gt), + [Token::Ge, Token::GtGt] + ), + self.double_char_token_case('=', '=', Some(Token::Eq), Token::EqEq), + self.double_char_token_case('~', '=', Some(Token::Tilde), Token::TildeEq), + self.double_char_token_case('/', '/', Some(Token::Slash), Token::SlashSlash), + self.double_char_token_case(':', ':', Some(Token::Colon), Token::ColonColon), + ) + } + + fn triple_char_token_case( + &mut self, + first: char, + second: char, + third: char, + short: Token<'s>, + mid: Token<'s>, + long: Token<'s>, + ) -> TokenizeResult<'s> { + if self.match_char(first) { + if self.match_char(second) { + if self.match_char(third) { + TokenizeResult::Some(long) + } else { + TokenizeResult::Some(mid) + } + } else { + TokenizeResult::Some(short) + } + } else { + TokenizeResult::None + } + } + + fn triple_char_token(&mut self) -> TokenizeResult<'s> { + try_all_paths!(self.triple_char_token_case( + '.', + '.', + '.', + Token::Dot, + Token::DotDot, + Token::DotDotDot + ),) + } + + fn number(&mut self) -> TokenizeResult<'s> { + if !self.current.map_or(false, |c| c.is_numeric()) { + return TokenizeResult::None; + } + + let start = self.current_pos_in_bytes; + + while self.current.map_or(false, |c| c.is_numeric()) { + self.advance(); + } + + if self.match_char('.') { + while self.current.map_or(false, |c| c.is_numeric()) { + self.advance(); + } + } + + if self.match_char('e') || self.match_char('E') { + if self.match_char('-') || self.match_char('+') { + self.advance(); + } + while self.current.map_or(false, |c| c.is_numeric()) { + self.advance(); + } + } + + let end = self.current_pos_in_bytes; + + TokenizeResult::Some(Token::Number(self.src[start..end].parse().unwrap())) + } + + fn string(&mut self) -> TokenizeResult<'s> { + let ty = if self.match_char('"') { + StringType::Double + } else if self.match_char('\'') { + StringType::Single + } else if self.match_char('[') { + if self.match_char('[') { + StringType::DoubleBracket + } else { + return TokenizeResult::Some(Token::OpenBracket); + } + } else { + return TokenizeResult::None; + }; + + let start = self.current_pos_in_bytes; + + match ty { + StringType::Single => { + let mut escaped = false; + while self.current.is_some() { + if self.match_char('\\') { + escaped = true; + } else if self.match_char('\'') && !escaped { + break; + } else { + escaped = false; + } + self.advance(); + } + } + StringType::Double => { + let mut escaped = false; + while self.current.is_some() { + if self.match_char('\\') { + escaped = true; + } else if self.match_char('"') && !escaped { + break; + } else { + escaped = false; + } + self.advance(); + } + } + StringType::DoubleBracket => { + let mut almost_close = false; + while self.current.is_some() { + if self.current == Some(']') { + if almost_close { + break; + } else { + almost_close = true; + } + } else { + almost_close = false; + } + self.advance(); + } + } + } + + if self.current.is_none() { + TokenizeResult::Error(LuaXError::UnterminatedStringLiteral) + } else { + let end = self.current_pos_in_bytes - 1; + if ty == StringType::DoubleBracket { + self.advance(); + } + + TokenizeResult::Some(Token::String(&self.src[start..end], ty)) + } + } + + fn identifier_or_keyword(&mut self) -> TokenizeResult<'s> { + if self.current.is_none() || !Self::is_valid_identifier_start(self.current.unwrap()) { + return TokenizeResult::None; + } + + let start = self.current_pos_in_bytes; + self.advance(); + + while self.current.is_some() && Self::is_valid_in_identifier(self.current.unwrap()) { + self.advance(); + } + + let end = self.current_pos_in_bytes; + + TokenizeResult::Some(match &self.src[start..end] { + "and" => Token::And, + "break" => Token::Break, + "do" => Token::Do, + "else" => Token::Else, + "elseif" => Token::ElseIf, + "end" => Token::End, + "false" => Token::False, + "for" => Token::For, + "function" => Token::Function, + "goto" => Token::Goto, + "if" => Token::If, + "in" => Token::In, + "local" => Token::Local, + "nil" => Token::Nil, + "not" => Token::Not, + "or" => Token::Or, + "repeat" => Token::Repeat, + "return" => Token::Return, + "then" => Token::Then, + "true" => Token::True, + "until" => Token::Until, + "while" => Token::While, + _ => Token::Identifier(&self.src[start..end]), + }) + } + + fn advance(&mut self) -> Option<char> { + self.current = self.chars.next(); + self.current_pos_in_bytes += self.current.map_or(0, |c| c.len_utf8()); + + self.current + } + + fn match_char(&mut self, c: char) -> bool { + if self.current == Some(c) { + self.advance(); + true + } else { + false + } + } + + fn is_valid_in_identifier(c: char) -> bool { + Self::is_valid_identifier_start(c) || c.is_numeric() + } + + fn is_valid_identifier_start(c: char) -> bool { + c.is_alphabetic() || c == '_' // TODO: Support some more unicode characters + // like emojis (or even emoji modifier sequences?) + } +} diff --git a/src/luax/mod.rs b/src/luax/mod.rs new file mode 100644 index 0000000..a7eb38f --- /dev/null +++ b/src/luax/mod.rs @@ -0,0 +1,101 @@ +use crate::Result; + +mod lexer; +mod preprocessor; +mod tokens; + +#[derive(Debug)] +pub enum Node<'s> { + Code(&'s str), + Text(&'s str), + Element(Element<'s>), +} + +impl<'s> Node<'s> { + pub fn to_table<W: std::io::Write>(&self, f: &mut W) -> Result<()> { + match &self { + Node::Code(s) => write!(f, "{}", s)?, + Node::Text(s) => write!(f, "\"{}\"", s)?, + Node::Element(element) => { + write!(f, "{{")?; + write!(f, "tag=\"{}\", ", element.tag)?; + for attribute in &element.attributes { + match &attribute.value { + Value::Literal(s) => write!(f, "{}=\"{}\", ", attribute.key, s)?, + Value::Expression(s) => write!(f, "{}={}, ", attribute.key, s)?, + } + } + write!(f, "children={{")?; + for child in &element.children { + child.to_table(f)?; + } + write!(f, "}}")?; + write!(f, "}}")?; + } + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Element<'s> { + pub tag: &'s str, + pub attributes: Vec<Attribute<'s>>, + pub children: Vec<Node<'s>>, +} + +#[derive(Debug)] +pub struct Attribute<'s> { + pub key: &'s str, + pub value: Value<'s>, +} + +#[derive(Debug)] +pub enum Value<'s> { + Literal(&'s str), + Expression(&'s str), +} + +#[derive(Debug)] +pub struct Template<'s> { + pub root: Node<'s>, +} + +impl<'s> Template<'s> { + pub fn to_table<W: std::io::Write>(&self, f: &mut W) -> Result<()> { + self.root.to_table(f) + } +} + +pub fn preprocess(s: &str) -> Result<String> { + let mut buf = Vec::new(); + let preprocessor = preprocessor::Preprocessor::new(s, &mut buf)?; + + preprocessor.preprocess()?; + + let s = String::from_utf8(buf).unwrap(); + + Ok(s) +} + +pub fn preprocess_dir(path: &std::path::Path) -> Result<()> { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + preprocess_dir(&path)?; + } else { + if path.extension().unwrap_or_default() != "luax" { + continue; + } + let s = std::fs::read_to_string(&path)?; + let s = preprocess(&s)?; + + let out_path = path.with_extension("lua"); + + std::fs::write(out_path, s)?; + } + } + + Ok(()) +} diff --git a/src/luax/preprocessor.rs b/src/luax/preprocessor.rs new file mode 100644 index 0000000..4934fe4 --- /dev/null +++ b/src/luax/preprocessor.rs @@ -0,0 +1,793 @@ +use std::io::Write; + +use crate::error::LuaXError; + +use crate::luax::lexer::Lexer; +use crate::luax::tokens::Token; + +use color_eyre::Result; + +/// Macro for trying to get a match from a list of functions +/// If a function returns a value, the token is returned +/// If a function returns an error, the error is returned +/// If a function returns the `InvalidStart` error, the next function is tried +macro_rules! alternatives { + () => { + Err(LuaXError::InvalidStart) + }; + ($e:expr) => { + $e + }; + ($head:expr $(,$tail:expr)* $(,)?) => { + match $head { + Ok(t) => Ok(t), + Err(e) => { + match e.downcast_ref::<LuaXError>() { + Some(LuaXError::InvalidStart) => alternatives!($($tail),*), + _ => Err(e), + } + } + } + }; +} + +macro_rules! repeat_until_not { + ($e:expr) => { + loop { + match $e { + Ok(()) => {} + Err(e) => match e.downcast_ref::<LuaXError>() { + Some(LuaXError::InvalidStart) => break, + _ => return Err(e), + }, + } + } + }; +} + +macro_rules! optionally { + ($e:expr) => { + match $e { + Ok(t) => Some(t), + Err(e) => match e.downcast_ref::<LuaXError>() { + Some(LuaXError::InvalidStart) => None, + _ => return Err(e), + }, + } + }; +} + +macro_rules! require { + ($e:expr, $err:expr $(,)?) => { + match $e { + Ok(t) => t, + Err(e) => match e.downcast_ref::<LuaXError>() { + Some(LuaXError::InvalidStart) => return Err($err.into()), + _ => return Err(e), + }, + } + }; +} + +pub struct Preprocessor<'s, W: Write> { + lexer: Lexer<'s>, + current: Token<'s>, + out_stream: W, +} + +impl<'s, W: Write> Preprocessor<'s, W> { + pub fn new(template: &'s str, mut out_stream: W) -> Result<Self> { + let mut lexer = Lexer::new(template); + let current = lexer.next_token()?.unwrap(); + Ok(Preprocessor { + lexer, + current, + out_stream, + }) + } + + fn next_token(&mut self) -> Result<()> { + if self.current != Token::Eof { + write!(self.out_stream, " {}", self.current)?; + } + println!("Current: {}", self.current); + match self.lexer.next_token()? { + Some(token) => { + self.current = token; + } + None => return Err(LuaXError::InvalidStart.into()), + } + Ok(()) + } + + fn match_token(&mut self, token: Token<'s>) -> Result<bool> { + if self.current == token { + self.next_token()?; + Ok(true) + } else { + Ok(false) + } + } + + fn consume_token(&mut self, token: Token<'s>, if_not: LuaXError) -> Result<()> { + if self.current == token { + self.next_token()?; + Ok(()) + } else { + Err(if_not.into()) + } + } + + fn next_token_silent(&mut self) -> Result<()> { + match self.lexer.next_token()? { + Some(token) => { + self.current = token; + println!("Current: {}", token); + } + None => return Err(LuaXError::InvalidStart.into()), + } + Ok(()) + } + + fn match_token_silent(&mut self, token: Token<'s>) -> Result<bool> { + if self.current == token { + self.next_token_silent()?; + Ok(true) + } else { + Ok(false) + } + } + + fn consume_token_silent(&mut self, token: Token<'s>, if_not: LuaXError) -> Result<()> { + if self.current == token { + self.next_token_silent()?; + Ok(()) + } else { + Err(if_not.into()) + } + } + + pub fn preprocess(mut self) -> Result<()> { + self.chunk() + } + + fn chunk(&mut self) -> Result<()> { + self.block() + } + + fn block(&mut self) -> Result<()> { + repeat_until_not!(self.statement()); + + optionally!(self.return_statement()); + + Ok(()) + } + + fn statement(&mut self) -> Result<()> { + if self.match_token(Token::Semicolon)? { + return Ok(()); + } + alternatives!( + self.label(), + self.break_statement(), + self.goto_statement(), + self.do_statement(), + self.while_statement(), + self.repeat_statement(), + self.if_statement(), + self.for_statement(), + self.function_statement(), + self.local_starting_statement(), + self.call_or_assignment(), + ) + } + + fn break_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Break)? { + return Err(LuaXError::InvalidStart.into()); + } + + Ok(()) + } + + fn goto_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Goto)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + Ok(()) + } + + fn do_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Do)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!(self.block(), LuaXError::ExpectedExpression); + + self.consume_token(Token::End, LuaXError::NeededToken(Token::End.to_string())) + } + + fn while_statement(&mut self) -> Result<()> { + if !self.match_token(Token::While)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!(self.expression(), LuaXError::ExpectedExpression); + + self.consume_token(Token::Do, LuaXError::NeededToken(Token::Do.to_string()))?; + + require!(self.block(), LuaXError::ExpectedExpression); + + self.consume_token(Token::End, LuaXError::NeededToken(Token::End.to_string())) + } + + fn repeat_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Repeat)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!(self.block(), LuaXError::ExpectedExpression); + + self.consume_token( + Token::Until, + LuaXError::NeededToken(Token::Until.to_string()), + )?; + + require!(self.expression(), LuaXError::ExpectedExpression); + + Ok(()) + } + + fn if_statement(&mut self) -> Result<()> { + if !self.match_token(Token::If)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!(self.expression(), LuaXError::ExpectedExpression); + + self.consume_token(Token::Then, LuaXError::NeededToken(Token::Then.to_string()))?; + + require!(self.block(), LuaXError::ExpectedExpression); + + loop { + if !self.match_token(Token::ElseIf)? { + break; + } + + require!(self.expression(), LuaXError::ExpectedExpression); + + self.consume_token(Token::Then, LuaXError::NeededToken(Token::Then.to_string()))?; + + require!(self.block(), LuaXError::ExpectedExpression); + } + + if self.match_token(Token::Else)? { + require!(self.block(), LuaXError::ExpectedExpression); + } + + self.consume_token(Token::End, LuaXError::NeededToken(Token::End.to_string())) + } + + fn for_statement(&mut self) -> Result<()> { + if !self.match_token(Token::For)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + if self.match_token(Token::Eq)? { + require!(self.expression(), LuaXError::ExpectedExpression); + + self.consume_token( + Token::Comma, + LuaXError::NeededToken(Token::Comma.to_string()), + )?; + + require!(self.expression(), LuaXError::ExpectedExpression); + + if self.match_token(Token::Comma)? { + require!(self.expression(), LuaXError::ExpectedExpression); + } + + require!(self.expression(), LuaXError::ExpectedExpression); + + if self.match_token(Token::Comma)? { + require!(self.expression(), LuaXError::ExpectedExpression); + } + } else { + loop { + if !self.match_token(Token::Comma)? { + break; + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + } + + self.consume_token(Token::In, LuaXError::NeededToken(Token::In.to_string()))?; + + require!(self.explist(), LuaXError::ExpectedExpression); + } + + self.consume_token(Token::Do, LuaXError::NeededToken(Token::Do.to_string()))?; + + require!(self.block(), LuaXError::ExpectedExpression); + + self.consume_token(Token::End, LuaXError::NeededToken(Token::End.to_string())) + } + + fn function_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Function)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!( + self.funcname(), + LuaXError::NeededToken("identifier".to_string()) + ); + + require!(self.funcbody(), LuaXError::ExpectedExpression); + + Ok(()) + } + + fn local_starting_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Local)? { + return Err(LuaXError::InvalidStart.into()); + } + + if self.match_token(Token::Function)? { + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + require!(self.funcbody(), LuaXError::ExpectedExpression); + + return Ok(()); + } + + require!( + self.attribute_name_list(), + LuaXError::NeededToken("identifier".to_string()) + ); + + if self.match_token(Token::Eq)? { + require!(self.explist(), LuaXError::ExpectedExpression); + } + + Ok(()) + } + + fn return_statement(&mut self) -> Result<()> { + if !self.match_token(Token::Return)? { + return Err(LuaXError::InvalidStart.into()); + } + + optionally!(self.expression()); + + loop { + if !self.match_token(Token::Comma)? { + break; + } + + require!(self.expression(), LuaXError::ExpectedExpression); + } + + self.match_token(Token::Semicolon)?; + + Ok(()) + } + + fn attribute(&mut self) -> Result<()> { + if !self.match_token(Token::Lt)? { + return Ok(()); + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + self.consume_token(Token::Gt, LuaXError::NeededToken(Token::Gt.to_string())) + } + + fn attribute_name_list(&mut self) -> Result<()> { + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + self.attribute()?; + + loop { + if !self.match_token(Token::Comma)? { + break; + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + self.attribute()?; + } + + Ok(()) + } + + fn label(&mut self) -> Result<()> { + if !self.match_token(Token::ColonColon)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + self.consume_token( + Token::ColonColon, + LuaXError::NeededToken(Token::ColonColon.to_string()), + ) + } + + fn funcname(&mut self) -> Result<()> { + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + + loop { + if !self.match_token(Token::Dot)? { + break; + } + + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + } + + if self.match_token(Token::Colon)? { + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + } + + Ok(()) + } + + fn explist(&mut self) -> Result<()> { + require!(self.expression(), LuaXError::ExpectedVar,); + + loop { + if !self.match_token(Token::Comma)? { + break; + } + + require!(self.expression(), LuaXError::ExpectedVar,); + } + + Ok(()) + } + + fn expression(&mut self) -> Result<()> { + alternatives!( + self.html_template(), + self.literal(), + self.function_def(), + self.access_or_call(), + self.table_constructor(), + self.unary_expression(), + )?; + + optionally!(self.binary_expression_followup()); + + Ok(()) + } + + fn literal(&mut self) -> Result<()> { + alternatives!( + self.number(), + self.string(), + self.boolean(), + self.nil_literal(), + ) + } + + fn call_or_assignment(&mut self) -> Result<()> { + if optionally!(self.access_or_call()).is_none() { + return Err(LuaXError::InvalidStart.into()); + } + + if self.match_token(Token::Eq)? { + require!(self.explist(), LuaXError::ExpectedExpression,); + } else if self.match_token(Token::Comma)? { + require!(self.access_or_call(), LuaXError::ExpectedExpression,); + while self.match_token(Token::Comma)? { + require!(self.access_or_call(), LuaXError::ExpectedExpression,); + } + if self.match_token(Token::Eq)? { + require!(self.explist(), LuaXError::ExpectedExpression,); + } + } + + Ok(()) + } + + fn access_or_call(&mut self) -> Result<()> { + if self.match_token(Token::OpenParen)? { + require!(self.expression(), LuaXError::ExpectedVar,); + self.consume_token( + Token::CloseParen, + LuaXError::NeededToken(Token::CloseParen.to_string()), + )?; + } else if optionally!(self.identifier()).is_some() { + } else { + return Err(LuaXError::InvalidStart.into()); + } + + // } else if let Some(()) = optionally!(self.html_template()) { + // return Ok(()); + + loop { + if self.match_token(Token::OpenBracket)? { + require!(self.expression(), LuaXError::ExpectedVar,); + self.consume_token( + Token::CloseBracket, + LuaXError::NeededToken(Token::CloseBracket.to_string()), + )?; + } else if self.match_token(Token::Dot)? { + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + } else if optionally!(self.args()).is_some() { + } else if self.match_token(Token::Colon)? { + require!( + self.identifier(), + LuaXError::NeededToken("identifier".to_string()) + ); + require!(self.args(), LuaXError::ExpectedExpression,); + } else { + break; + } + } + + Ok(()) + } + + fn args(&mut self) -> Result<()> { + if self.match_token(Token::OpenParen)? { + if !self.match_token(Token::CloseParen)? { + require!(self.explist(), LuaXError::ExpectedExpression,); + self.consume_token( + Token::CloseParen, + LuaXError::NeededToken(Token::CloseParen.to_string()), + ) + } else { + Ok(()) + } + } else if optionally!(self.table_constructor()).is_some() + || optionally!(self.string()).is_some() + { + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn function_def(&mut self) -> Result<()> { + if !self.match_token(Token::Function)? { + return Err(LuaXError::InvalidStart.into()); + } + + require!(self.funcbody(), LuaXError::ExpectedExpression); + + Ok(()) + } + + fn funcbody(&mut self) -> Result<()> { + self.consume_token( + Token::OpenParen, + LuaXError::NeededToken(Token::OpenParen.to_string()), + )?; + + optionally!(self.parlist()); + + self.consume_token( + Token::CloseParen, + LuaXError::NeededToken(Token::CloseParen.to_string()), + )?; + + require!(self.block(), LuaXError::ExpectedExpression); + + self.consume_token(Token::End, LuaXError::NeededToken(Token::End.to_string())) + } + + fn parlist(&mut self) -> Result<()> { + if self.match_token(Token::DotDotDot)? { + return Ok(()); + } + + if optionally!(self.identifier()).is_some() { + while self.match_token(Token::Comma)? { + if self.match_token(Token::DotDotDot)? { + return Ok(()); + } + + if optionally!(self.identifier()).is_some() { + continue; + } else { + return Err(LuaXError::ExpectedExpression.into()); + } + } + + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn table_constructor(&mut self) -> Result<()> { + if !self.match_token(Token::OpenBrace)? { + return Err(LuaXError::InvalidStart.into()); + } + + optionally!(self.fieldlist()); + + self.consume_token( + Token::CloseBrace, + LuaXError::NeededToken(Token::CloseBrace.to_string()), + ) + } + + fn fieldlist(&mut self) -> Result<()> { + require!(self.field(), LuaXError::ExpectedExpression); + + loop { + if !self.match_token(Token::Comma)? && !self.match_token(Token::Semicolon)? { + break; + } + + if self.match_token(Token::CloseBrace)? { + break; + } + + require!(self.field(), LuaXError::ExpectedExpression); + } + + Ok(()) + } + + fn field(&mut self) -> Result<()> { + if optionally!(self.identifier()).is_some() { + self.consume_token(Token::Eq, LuaXError::NeededToken(Token::Eq.to_string()))?; + require!(self.expression(), LuaXError::ExpectedExpression); + } else if self.match_token(Token::OpenBracket)? { + require!(self.expression(), LuaXError::ExpectedExpression); + self.consume_token( + Token::CloseBracket, + LuaXError::NeededToken(Token::CloseBracket.to_string()), + )?; + self.consume_token(Token::Eq, LuaXError::NeededToken(Token::Eq.to_string()))?; + require!(self.expression(), LuaXError::ExpectedExpression); + } else { + require!(self.expression(), LuaXError::ExpectedExpression); + } + + Ok(()) + } + + fn binary_expression_followup(&mut self) -> Result<()> { + loop { + if !self.match_token(Token::Plus)? + && !self.match_token(Token::Minus)? + && !self.match_token(Token::Star)? + && !self.match_token(Token::Slash)? + && !self.match_token(Token::SlashSlash)? + && !self.match_token(Token::Hat)? + && !self.match_token(Token::Percent)? + && !self.match_token(Token::Amp)? + && !self.match_token(Token::Tilde)? + && !self.match_token(Token::Pipe)? + && !self.match_token(Token::LtLt)? + && !self.match_token(Token::GtGt)? + && !self.match_token(Token::DotDot)? + && !self.match_token(Token::Lt)? + && !self.match_token(Token::Le)? + && !self.match_token(Token::Gt)? + && !self.match_token(Token::Ge)? + && !self.match_token(Token::EqEq)? + && !self.match_token(Token::TildeEq)? + && !self.match_token(Token::And)? + && !self.match_token(Token::Or)? + { + break; + } + + require!(self.expression(), LuaXError::ExpectedExpression); + } + + Ok(()) + } + + fn unary_expression(&mut self) -> Result<()> { + if !self.match_token(Token::Not)? + && !self.match_token(Token::Hash)? + && !self.match_token(Token::Minus)? + && !self.match_token(Token::Tilde)? + { + return Err(LuaXError::InvalidStart.into()); + } + + require!(self.expression(), LuaXError::ExpectedExpression); + + Ok(()) + } + + fn boolean(&mut self) -> Result<()> { + if self.match_token(Token::True)? || self.match_token(Token::False)? { + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn nil_literal(&mut self) -> Result<()> { + if self.match_token(Token::Nil)? { + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn number(&mut self) -> Result<()> { + if let Token::Number(_) = self.current { + self.next_token()?; + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn string(&mut self) -> Result<()> { + if let Token::String(..) = self.current { + self.next_token()?; + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn identifier(&mut self) -> Result<String> { + if let Token::Identifier(s) = self.current { + self.next_token()?; + Ok(s.to_string()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } + + fn html_template(&mut self) -> Result<()> { + if self.match_token_silent(Token::Dollar)? { + write!(self.out_stream, " {{ 'dollar' }}")?; + Ok(()) + } else { + Err(LuaXError::InvalidStart.into()) + } + } +} diff --git a/src/luax/tokens.rs b/src/luax/tokens.rs new file mode 100644 index 0000000..9f1058a --- /dev/null +++ b/src/luax/tokens.rs @@ -0,0 +1,147 @@ +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum StringType { + Single, + Double, + DoubleBracket, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Token<'s> { + /// Keywords + Break, + Goto, + Do, + End, + While, + Repeat, + Until, + If, + Then, + ElseIf, + Else, + For, + In, + Function, + Local, + Return, + True, + False, + Nil, + /// Symbols + Semicolon, + Eq, + Comma, + ColonColon, + Dot, + Colon, + OpenBracket, + CloseBracket, + DotDotDot, + OpenParen, + CloseParen, + OpenBrace, + CloseBrace, + /// Operators + Plus, + Minus, + Star, + Slash, + SlashSlash, + Hat, + Percent, + Amp, + Tilde, + Pipe, + LtLt, + GtGt, + DotDot, + Lt, + Le, + Gt, + Ge, + EqEq, + TildeEq, + And, + Or, + Hash, + Not, + /// Containers + Number(f64), + String(&'s str, StringType), + Identifier(&'s str), + + Dollar, + Eof, +} + +impl std::fmt::Display for Token<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Token::Break => write!(f, "break"), + Token::Goto => write!(f, "goto"), + Token::Do => write!(f, "do"), + Token::End => write!(f, "end"), + Token::While => write!(f, "while"), + Token::Repeat => write!(f, "repeat"), + Token::Until => write!(f, "until"), + Token::If => write!(f, "if"), + Token::Then => write!(f, "then"), + Token::ElseIf => write!(f, "elseif"), + Token::Else => write!(f, "else"), + Token::For => write!(f, "for"), + Token::In => write!(f, "in"), + Token::Function => write!(f, "function"), + Token::Local => write!(f, "local"), + Token::Return => write!(f, "return"), + Token::True => write!(f, "true"), + Token::False => write!(f, "false"), + Token::Nil => write!(f, "nil"), + Token::Semicolon => write!(f, ";"), + Token::Eq => write!(f, "="), + Token::Comma => write!(f, ","), + Token::ColonColon => write!(f, "::"), + Token::Dot => write!(f, "."), + Token::Colon => write!(f, ":"), + Token::OpenBracket => write!(f, "["), + Token::CloseBracket => write!(f, "]"), + Token::DotDotDot => write!(f, "..."), + Token::OpenParen => write!(f, "("), + Token::CloseParen => write!(f, ")"), + Token::OpenBrace => write!(f, "{{"), + Token::CloseBrace => write!(f, "}}"), + Token::Plus => write!(f, "+"), + Token::Minus => write!(f, "-"), + Token::Star => write!(f, "*"), + Token::Slash => write!(f, "/"), + Token::SlashSlash => write!(f, "//"), + Token::Hat => write!(f, "^"), + Token::Percent => write!(f, "%"), + Token::Amp => write!(f, "&"), + Token::Tilde => write!(f, "~"), + Token::Pipe => write!(f, "|"), + Token::LtLt => write!(f, "<<"), + Token::GtGt => write!(f, ">>"), + Token::DotDot => write!(f, ".."), + Token::Lt => write!(f, "<"), + Token::Le => write!(f, "<="), + Token::Gt => write!(f, ">"), + Token::Ge => write!(f, ">="), + Token::EqEq => write!(f, "=="), + Token::TildeEq => write!(f, "~="), + Token::And => write!(f, "and"), + Token::Or => write!(f, "or"), + Token::Hash => write!(f, "#"), + Token::Not => write!(f, "not"), + Token::Number(n) => write!(f, "{}", n), + Token::String(s, st) => match st { + StringType::Single => write!(f, "'{}'", s), + StringType::Double => write!(f, "\"{}\"", s), + StringType::DoubleBracket => write!(f, "[[{}]]", s), + }, + Token::Identifier(s) => write!(f, "{}", s), + Token::Dollar => write!(f, "$"), + + Token::Eof => write!(f, "EOF"), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f0683ae --- /dev/null +++ b/src/main.rs @@ -0,0 +1,102 @@ +use std::path::PathBuf; + +use clap::Parser; +use color_eyre::Result; +use error::{LuaXError, ReluaxError}; + +mod error; +mod luax; +mod server; + +fn table_to_html<W: std::io::Write>(table: rlua::Table, f: &mut W) -> Result<()> { + let type_name: Option<String> = table.get("tag").unwrap(); + + if type_name.is_none() { + return Err(ReluaxError::LuaX(LuaXError::MissingField("tag".to_string())).into()); + } + + let type_name = type_name.unwrap(); + + write!(f, "<{}", type_name)?; + let mut children = None; + for pair in table.pairs::<String, rlua::Value>() { + let (key, value) = pair?; + if key == "tag" { + continue; + } + if key == "children" { + children = Some(value); + continue; + } + match value { + rlua::Value::Nil => write!(f, " {}=\"\"", key)?, + rlua::Value::Boolean(b) => write!(f, " {}=\"{}\"", key, b)?, + rlua::Value::Integer(i) => write!(f, " {}=\"{}\"", key, i)?, + rlua::Value::Number(n) => write!(f, " {}=\"{}\"", key, n)?, + rlua::Value::String(s) => write!(f, " {}=\"{}\"", key, s.to_str()?)?, + _ => return Err(ReluaxError::LuaX(LuaXError::WrongFieldType(key)).into()), + } + } + write!(f, ">")?; + + if let Some(children) = children { + if let rlua::Value::Table(children) = children { + for child in children.sequence_values::<rlua::Value>() { + match child? { + rlua::Value::Table(child) => table_to_html(child, f)?, + rlua::Value::String(s) => write!(f, "{}", s.to_str()?)?, + _ => return Err(ReluaxError::LuaX(LuaXError::NonTableChildren).into()), + } + } + } else { + return Err(ReluaxError::LuaX(LuaXError::NonTableChildren).into()); + } + } + + write!(f, "</{}>", type_name)?; + + Ok(()) +} + +#[derive(Debug, Clone, clap::Parser)] +struct Args { + #[clap(short = 'C', long = "current-dir", default_value = ".")] + current_dir: std::path::PathBuf, + #[clap(short = 'p', long = "port", default_value = "4310")] + port: u16, +} + +#[tokio::main] +async fn main() -> color_eyre::Result<()> { + color_eyre::install().unwrap(); + let args = Args::parse(); + + let current_dir = args.current_dir; + + if !current_dir.is_dir() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{} is not a directory", current_dir.display()), + ) + .into()); + } + + std::env::set_current_dir(current_dir)?; + + luax::preprocess_dir(std::env::current_dir()?.as_path())?; + + println!("⛱️ Reluax files preprocessed!"); + + let entry = PathBuf::from("reluax.lua"); + + if !entry.is_file() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{} is not a file", entry.display()), + ).into()); + } + + server::Server::serve(args.port).await?; + + Ok(()) +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..4e3812f --- /dev/null +++ b/src/server.rs @@ -0,0 +1,107 @@ +use std::future::Future; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; + +use http_body_util::Full; +use hyper::body::{Bytes, Incoming}; +use hyper::server::conn::http1; +use hyper::service::Service; +use hyper::{Request, Response}; +use hyper_util::rt::TokioIo; +use tokio::net::TcpListener; + +use crate::error::ReluaxError; +use crate::{table_to_html, Result}; +use rlua::Lua; + +pub struct Server { + lua: Arc<Mutex<Lua>>, +} + +struct State { + lua: Arc<Mutex<Lua>>, +} + +static PORT: std::sync::OnceLock<u16> = std::sync::OnceLock::new(); + +impl Server { + pub async fn serve(port: u16) -> Result<()> { + PORT.set(port).unwrap(); + + let server = Self { + lua: Arc::new(Mutex::new(Lua::new())), + }; + server.start().await + } + + async fn start(self) -> Result<()> { + let addr = SocketAddr::from(([127, 0, 0, 1], *PORT.get().unwrap())); + let listener = TcpListener::bind(addr).await?; + + loop { + let (stream, _) = listener.accept().await?; + let io = TokioIo::new(stream); + let state = State { + lua: self.lua.clone(), + }; + tokio::task::spawn(async move { + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, state).await { + println!("Failed to serve connection: {:?}", err); + } + }); + }); + } + } +} + +fn mk_response(s: String) -> Result<Response<Full<Bytes>>> { + Ok(Response::builder().body(Full::new(Bytes::from(s))).unwrap()) +} + +impl Service<Request<Incoming>> for State { + type Response = Response<Full<Bytes>>; + type Error = color_eyre::Report; + type Future = + Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>; + + fn call(&self, req: Request<Incoming>) -> Self::Future { + let path = req.uri().path(); + let res = self.serve(path).map(mk_response).unwrap_or_else(|err| { + println!("Error: {}", err); + Err(err) + }); + + Box::pin(async { res }) + } +} + +impl State { + fn serve(&self, path: &str) -> Result<String> { + let lua = self.lua.lock().unwrap(); + + let res = lua.context(|ctx| -> Result<String> { + let manifest: rlua::Table = ctx.load("require('reluax')").eval()?; + + let route: rlua::Function = manifest.get("route")?; + + let res: rlua::Value = route.call((path,))?; + + match res { + rlua::Value::String(s) => Ok(s.to_str()?.to_string()), + rlua::Value::Table(t) => { + let mut buf = Vec::new(); + table_to_html(t, &mut buf)?; + let s = String::from_utf8(buf).unwrap(); + Ok(s) + } + rlua::Value::Nil => Err(ReluaxError::Server("No route found".to_string()).into()), + rlua::Value::Error(e) => Err(ReluaxError::Lua(e).into()), + _ => Err(ReluaxError::Server("Route returned invalid type".to_string()).into()), + } + })?; + + Ok(res) + } +} |
