aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStanisław Zagórowski <hasohasacz@gmail.com>2024-01-11 02:20:44 +0100
committerStanisław Zagórowski <hasohasacz@gmail.com>2024-01-11 02:20:44 +0100
commit343bcfdfaaba32b7a46e1a945a4538e72d166ea8 (patch)
treeed27311160054515b6375a9f92e092772300ee65
huh whuh
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock863
-rw-r--r--Cargo.toml16
-rw-r--r--example/index.lua29
-rw-r--r--example/reluax.lua20
-rw-r--r--example/test.lua25
-rw-r--r--example/test.luax70
-rw-r--r--src/error.rs69
-rw-r--r--src/luax/lexer.rs426
-rw-r--r--src/luax/mod.rs101
-rw-r--r--src/luax/preprocessor.rs793
-rw-r--r--src/luax/tokens.rs147
-rw-r--r--src/main.rs102
-rw-r--r--src/server.rs107
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)
+ }
+}