commit 7ae25ab36801b18ed2eb6be18c7996ae2d4c9d40 Author: luaneko Date: Fri Jun 6 05:29:52 2025 +1000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9ea694 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +*.generated.lua diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..42e5766 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "luajit"] + path = luajit + url = https://luajit.org/git/luajit.git diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..309c483 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime.version": "LuaJIT", + "diagnostics.disable": ["redefined-local"] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..aa1f036 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,757 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cbindgen" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +dependencies = [ + "shlex", +] + +[[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.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "luabi" +version = "0.1.0" +dependencies = [ + "blake2", + "bstr", + "cbindgen", + "cc", + "clap", + "digest", + "glob", + "libmimalloc-sys", + "lz4_flex", + "md-5", + "mimalloc", + "owo-colors", + "sha2", + "thiserror", + "which", +] + +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mimalloc" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "owo-colors" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8fbac78 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +members = ["luabi"] +resolver = "3" + +[profile.release] +panic = "abort" +lto = "fat" +strip = "debuginfo" diff --git a/luabi/Cargo.toml b/luabi/Cargo.toml new file mode 100644 index 0000000..5dccaf7 --- /dev/null +++ b/luabi/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "luabi" +version = "0.1.0" +edition = "2024" + +[dependencies] +blake2 = "0.10.6" +bstr = "1.12.0" +clap = { version = "4.5.39", features = ["derive"] } +digest = "0.10.7" +libmimalloc-sys = "0.1.42" +lz4_flex = "0.11.3" +md-5 = "0.10.6" +mimalloc = "0.1.46" +owo-colors = "4.2.1" +sha2 = "0.10.9" +thiserror = "2.0.12" + +[build-dependencies] +cbindgen = "0.29.0" +cc = "1.2.25" +glob = "0.3.2" +lz4_flex = "0.11.3" +which = "7.0.3" diff --git a/luabi/build.rs b/luabi/build.rs new file mode 100644 index 0000000..133dd09 --- /dev/null +++ b/luabi/build.rs @@ -0,0 +1,189 @@ +use cbindgen::{EnumConfig, Language, Style}; +use glob::glob; +use lz4_flex::compress_prepend_size; +use std::{ + env::{var, var_os}, + fs::{File, copy, create_dir_all, read_dir, write}, + io::{self, Write}, + path::{Path, PathBuf}, + process::Command, + thread::available_parallelism, +}; +use which::which; + +fn main() { + let target = var("TARGET").unwrap(); + let host = var("HOST").unwrap(); + let src_path: PathBuf = var("CARGO_MANIFEST_DIR").unwrap().into(); + let out_path: PathBuf = var("OUT_DIR").unwrap().into(); + let luajit_path = out_path.join("luajit/src"); + let lib_rt_path = out_path.join("runtime.lua.lz4"); + + copy_recursive(src_path.join("../luajit"), luajit_path.parent().unwrap()) + .expect("failed to copy luajit source"); + + build_luajit(&target, &host, &luajit_path); + build_lua(&src_path, &lib_rt_path); + + println!("cargo::rerun-if-changed={}/src", src_path.display()); + println!("cargo::rustc-link-arg=-rdynamic"); +} + +fn build_luajit(target: &str, host: &str, src_path: &Path) { + let mut make = match host { + "x86_64-unknown-dragonfly" => Command::new("gmake"), + "x86_64-unknown-freebsd" => Command::new("gmake"), + _ => Command::new("make"), + }; + + make.current_dir(&src_path) + .arg("-e") + .arg(format!( + "-j{}", + available_parallelism().map_or(1, |n| n.into()) + )) + .env("BUILDMODE", "static"); + + let mut xcflags = vec![ + "-fPIC", + "-DLUAJIT_ENABLE_LUA52COMPAT", // enable lua 5.2 compatibility + "-DLUAJIT_USE_SYSMALLOC", // disable the built-in allocator (we provide our own) + ]; + + if cfg!(debug_assertions) { + xcflags.extend_from_slice(&[ + "-DLUA_USE_ASSERT", + "-DLUA_USE_APICHECK", + "-DLUAJIT_USE_GDBJIT", // gdb support + ]); + } + + if target.ends_with("-apple-darwin") && var_os("MACOSX_DEPLOYMENT_TARGET").is_none() { + make.env( + "MACOSX_DEPLOYMENT_TARGET", + match target.split_once("-").unwrap() { + ("aarch64", _) | ("arm64e", _) => "11.0", + ("x86_64", _) | ("x86_64h", _) => "10.11", + (arch, _) => panic!("unknown macos target architecture '{arch}'"), + }, + ); + } else if target.contains("-windows-") || target.ends_with("-windows") { + make.env("TARGET_SYS", "Windows"); + } else if target.contains("-linux-") || target.ends_with("-linux") { + make.env("TARGET_SYS", "Linux"); + } else { + make.env("TARGET_SYS", "Other"); + } + + let host_cc = cc::Build::new().target(host).get_compiler(); + let host_cc_path = which(host_cc.path()).expect("failed to find host cc"); + + if var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap() == "32" { + make.env("HOST_CC", format!("{} -m32", host_cc_path.display())); // cross-compile to 32-bit on 64-bit host + } else { + make.env("HOST_CC", &host_cc_path); + } + + let target_cc = cc::Build::new().target(target).get_compiler(); + let target_cc_path = which(target_cc.path()).expect("failed to find target cc"); + let target_bin_path = target_cc_path.parent().unwrap(); + + if let Some(prefix) = target_cc_path.to_str().unwrap().strip_suffix("gcc") { + make.env("DEFAULT_CC", "gcc").env("CROSS", prefix); + } else if let Some(prefix) = target_cc_path.to_str().unwrap().strip_suffix("clang") { + make.env("DEFAULT_CC", "clang").env("CROSS", prefix); + } else { + make.env("DEFAULT_CC", target_cc_path.file_name().unwrap()) + .env("CROSS", format!("{}/", target_bin_path.display())); + } + + make.env("XCFLAGS", xcflags.join(" ")); + println!("running luajit make: {make:?}"); + + let status = make.status().expect("failed to execute make"); + if !status.success() { + panic!("failed to compile luajit: {status}: {make:?}"); + } + + println!("cargo::rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET"); + println!("cargo::rustc-link-search=native={}", src_path.display()); + println!("cargo::rustc-link-lib=static=luajit"); +} + +fn build_lua(src_path: &Path, out_path: &Path) { + let mut buf = Vec::new(); + writeln!(&mut buf, "require(\"ffi\").cdef [[").unwrap(); + cbindgen::generate_with_config( + src_path, + cbindgen::Config { + language: Language::C, + no_includes: true, + documentation: false, + line_length: 1000, + style: Style::Type, + enumeration: EnumConfig { + prefix_with_name: true, + ..Default::default() + }, + ..Default::default() + }, + ) + .expect("failed to generate bindings") + .write(&mut buf); + writeln!(&mut buf, "]]").unwrap(); + + let mut paths = glob(&format!("{}/src/**/*.lua", src_path.display())) + .unwrap() + .collect::, _>>() + .unwrap(); + paths.sort(); + + for path in paths { + let name = path + .strip_prefix(src_path) + .unwrap() + .strip_prefix("src") + .unwrap() + .with_extension("") + .to_string_lossy() + .replace("/", "."); + + writeln!(&mut buf, "package.preload[\"lb.{name}\"] = function(...)").unwrap(); + std::io::copy( + &mut File::open(path).expect("failed to open file"), + &mut buf, + ) + .unwrap(); + writeln!(&mut buf, "end").unwrap(); + } + + // debugging: + write( + PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap()).join("runtime.generated.lua"), + &buf, + ) + .unwrap(); + + write(out_path, compress_prepend_size(&buf)).unwrap(); +} + +fn copy_recursive(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + create_dir_all(&dst)?; + + for entry in read_dir(src)? { + let entry = entry?; + let ftype = entry.file_type()?; + if entry.file_name().to_str() == Some(".git") { + continue; + } + + let path = dst.as_ref().join(entry.file_name()); + if ftype.is_dir() { + copy_recursive(entry.path(), path)?; + } else { + copy(entry.path(), path)?; + } + } + + Ok(()) +} diff --git a/luabi/src/core.lua b/luabi/src/core.lua new file mode 100644 index 0000000..dfa6e28 --- /dev/null +++ b/luabi/src/core.lua @@ -0,0 +1,112 @@ +local ffi = require("ffi") +local C = ffi.C + +local mm_names = { + -- known luajit metamethods (see lj_obj.h) + __index = true, + __newindex = true, + __gc = true, + __eq = true, + __len = true, + __lt = true, + __le = true, + __concat = true, + __call = true, + __add = true, + __sub = true, + __mul = true, + __div = true, + __mod = true, + __pow = true, + __unm = true, + __metatable = true, + __tostring = true, + __pairs = true, + __ipairs = true, + + -- luabi metamethods + __init = true, +} + +local function Type(name, def) + local obj_mt = { + __tostring = function(self) + return string.format("%s %p", name, self) + end, + } + + local type = {} + local type_mt = { + __tostring = function(self) + return string.format("lb.core::Type<%s> %p", name, self) + end, + + __call = function(_, ...) + local self = setmetatable({}, obj_mt) + local __init = obj_mt.__init + if __init ~= nil then __init(self, ...) end + return self + end, + } + + for key, value in pairs(def) do + if mm_names[key] == true then + obj_mt[key] = value + else + type[key] = value + end + end + + type_mt.__index = obj_mt.__index + obj_mt.__index = type + return setmetatable(type, type_mt) +end + +local Module = Type("lb.core::Module", { + __init = function(self, name, def) + self.__name = name + for key, value in pairs(def) do + self[key] = value + end + end, + + __tostring = function(self) + return string.format("lb.core::Module<%s> %p", self.__name, self) + end, + + __call = function(self, ...) + local call = self.__call + if call ~= nil then return call(...) end + local default = self.__default + if default ~= nil then return default end + error(string.format("module '%s' has no default export", self.__name)) + end, +}) + +local function unwrap_opt(res) + -- read value out and forget option cdata + if res.has == true then return res.value end + return nil +end + +local function unwrap_res(res) + if res.err ~= nil then + -- read message out, drop error cdata and throw + local buf = C.lb_error_msg(res.err) + local msg = ffi.string(buf.ptr, buf.len) + C.lb_error_drop(buf) + error(msg) + else + -- error is nil so value must have been initialised + -- read value out and forget result cdata + return res.value + end +end + +return Module("lb.core", { + Module = Module, + Type = Type, + + unwrap_opt = unwrap_opt, + unwrap_res = unwrap_res, +}) diff --git a/luabi/src/core.rs b/luabi/src/core.rs new file mode 100644 index 0000000..6ff3fe5 --- /dev/null +++ b/luabi/src/core.rs @@ -0,0 +1,275 @@ +use crate::{ + hash::LbHashError, + str::{LbStr, LbStrError}, +}; +use std::{ + collections::HashMap, + mem::{ManuallyDrop, MaybeUninit}, + ops::{Deref, DerefMut}, + ptr::{self, NonNull}, + slice, +}; +use thiserror::Error; + +#[repr(C)] +pub struct LbOpt { + value: MaybeUninit, + has: bool, +} + +impl LbOpt { + pub fn some(value: T) -> Self { + Self { + value: MaybeUninit::new(value), + has: true, + } + } + + pub fn none() -> Self { + Self { + value: MaybeUninit::uninit(), + has: false, + } + } +} + +impl From> for LbOpt { + fn from(value: Option) -> Self { + match value { + Some(value) => Self::some(value), + None => Self::none(), + } + } +} + +impl Drop for LbOpt { + fn drop(&mut self) { + if self.has { + unsafe { + // SAFETY: since option has a value, value must have been initialised + self.value.assume_init_drop(); + } + } + } +} + +#[repr(C)] +pub struct LbRes { + value: MaybeUninit, + err: Option>, +} + +impl LbRes { + pub fn ok(value: T) -> Self { + Self { + value: MaybeUninit::new(value), + err: None, + } + } + + pub fn err(error: LbError) -> Self { + Self { + value: MaybeUninit::uninit(), + err: Some(Box::new(error)), + } + } +} + +impl From> for LbRes { + fn from(value: Result) -> Self { + match value { + Ok(value) => Self::ok(value), + Err(error) => Self::err(error), + } + } +} + +impl Drop for LbRes { + fn drop(&mut self) { + if self.err.is_none() { + unsafe { + // SAFETY: since result is not an error, value must have been initialised + self.value.assume_init_drop(); + } + } + } +} + +#[derive(Debug, Error)] +pub enum LbError { + #[error("{0}")] + String(#[from] LbStrError), + #[error("{0}")] + Hash(#[from] LbHashError), +} + +impl LbError { + pub fn message(&self) -> String { + format!("{self}") + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_error_drop(&mut self) { + unsafe { ptr::drop_in_place(self) } + } + + #[unsafe(no_mangle)] + pub extern "C" fn lb_error_msg(&self) -> LbStr { + self.message().into() + } +} + +#[repr(C)] +pub struct LbVec { + ptr: NonNull, + len: usize, + cap: usize, +} + +impl LbVec { + pub fn new() -> Self { + Vec::new().into() + } +} + +impl Deref for LbVec { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) } + } +} + +impl DerefMut for LbVec { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) } + } +} + +impl From> for LbVec { + fn from(value: Vec) -> Self { + let len = value.len(); + let cap = value.capacity(); + let ptr = ManuallyDrop::new(value).as_mut_ptr(); + Self { + ptr: unsafe { NonNull::new_unchecked(ptr) }.into(), + len, + cap, + } + } +} + +impl From> for Vec { + fn from(value: LbVec) -> Self { + let len = value.len; + let cap = value.cap; + let ptr = ManuallyDrop::new(value).ptr.as_ptr(); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + } +} + +#[repr(C)] +pub struct LbMap { + inner: Box>, +} + +#[derive(Debug)] +struct LbMapImpl(HashMap); + +impl LbMap { + pub fn new() -> Self { + Self { + inner: Box::new(LbMapImpl(HashMap::new())), + } + } +} + +impl Deref for LbMap { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.inner.0 + } +} + +impl DerefMut for LbMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner.0 + } +} + +impl From> for LbMap { + fn from(value: HashMap) -> Self { + Self { + inner: Box::new(LbMapImpl(value)), + } + } +} + +impl From> for HashMap { + fn from(value: LbMap) -> Self { + value.inner.0 + } +} + +pub type LbBuf = LbVec; + +impl LbBuf { + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_buf_new(ptr: *const u8, len: usize) -> LbBuf { + unsafe { slice::from_raw_parts(ptr, len).to_vec().into() } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_buf_drop(&mut self) { + unsafe { ptr::drop_in_place(self) } + } +} + +pub type LbParams = LbMap; + +impl LbParams { + #[unsafe(no_mangle)] + pub extern "C" fn lb_params_new() -> Self { + Self::new() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_params_drop(&mut self) { + unsafe { ptr::drop_in_place(self) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_params_get(&self, key: *const u8, key_len: usize) -> LbOpt { + std::str::from_utf8(unsafe { slice::from_raw_parts(key, key_len) }) + .ok() + .and_then(|key| self.get(key).map(|s| s.to_owned().into())) + .into() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_params_set( + &mut self, + key: *const u8, + key_len: usize, + value: *const u8, + value_len: usize, + ) -> LbRes { + std::str::from_utf8(unsafe { slice::from_raw_parts(key, key_len) }) + .and_then(|key| { + std::str::from_utf8(unsafe { slice::from_raw_parts(value, value_len) }) + .map(|value| (key, value)) + }) + .map_err(|err| LbStrError::InvalidUtf8(err).into()) + .map(|(key, value)| self.insert(key.into(), value.into())) + .map(|_| true) + .into() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_params_del(&mut self, key: *const u8, key_len: usize) -> bool { + std::str::from_utf8(unsafe { slice::from_raw_parts(key, key_len) }) + .map(|key| self.remove(key).is_some()) + .unwrap_or(false) + } +} diff --git a/luabi/src/fs.rs b/luabi/src/fs.rs new file mode 100644 index 0000000..746142e --- /dev/null +++ b/luabi/src/fs.rs @@ -0,0 +1,14 @@ +// use std::{ +// ffi::{CStr, OsStr, OsString, c_char, c_int}, +// slice, +// }; + +// #[unsafe(no_mangle)] +// pub unsafe extern "C" fn lb_fs_touch(path: *const u8, path_len: usize) -> c_int { +// let path = match std::str::from_utf8(unsafe { slice::from_raw_parts(path, path_len) }) { +// Ok(path) => path, +// Err(_) => return -1, // invalid utf8 string +// }; + +// 0 +// } diff --git a/luabi/src/hash.lua b/luabi/src/hash.lua new file mode 100644 index 0000000..ac30371 --- /dev/null +++ b/luabi/src/hash.lua @@ -0,0 +1,56 @@ +local core = require("lb.core") +local ffi = require("ffi") +local C = ffi.C + +local algs = { + md5 = C.LbHasherAlgorithm_Md5, + sha224 = C.LbHasherAlgorithm_Sha224, + sha256 = C.LbHasherAlgorithm_Sha256, + sha384 = C.LbHasherAlgorithm_Sha384, + sha512 = C.LbHasherAlgorithm_Sha512, + blake2b = C.LbHasherAlgorithm_Blake2b, + blake2s = C.LbHasherAlgorithm_Blake2s, +} + +local alg_names = {} +for name, _ in pairs(algs) do + alg_names[#alg_names + 1] = name +end +table.sort(alg_names) + +local Hasher = core.Type("lb.hash::Hasher", { + __init = function(self, name, params) + if name == nil then + error("algorithm must be specified: one of " .. table.concat(alg_names, ", ")) + end + + local name = tostring(name) + local alg = algs[name] + if alg == nil then error(string.format("unknown hash algorithm '%s'", name)) end + + self.__name = name + self.__ptr = ffi.gc(core.unwrap_res(C.lb_hasher_new(alg, nil)), C.lb_hasher_drop) + end, + + name = function(self) + return self.__name + end, + + update = function(self, data) + local data = tostring(data) + C.lb_hasher_update(self.__ptr, data, data:len()) + return self + end, + + done = function(self, format) + local buf = C.lb_hasher_digest(self.__ptr) + local digest = ffi.string(buf.ptr, buf.len) + C.lb_buf_drop(buf) + return digest + end, +}) + +return core.Module("lb.hash", { + __call = Hasher, + Hasher = Hasher, +}) diff --git a/luabi/src/hash.rs b/luabi/src/hash.rs new file mode 100644 index 0000000..e0cd97e --- /dev/null +++ b/luabi/src/hash.rs @@ -0,0 +1,157 @@ +use crate::core::{LbBuf, LbError, LbParams, LbRes}; +use blake2::{Blake2b512, Blake2bVar, Blake2s256, Blake2sVar}; +use digest::InvalidOutputSize; +use md5::Md5; +use sha2::{Sha224, Sha256, Sha384, Sha512, Sha512_224, Sha512_256}; +use std::{mem, num::ParseIntError, ptr, slice}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum LbHashError { + #[error("invalid parameter '{0}': {1}")] + InvalidIntParam(&'static str, ParseIntError), + #[error("invalid parameter '{PARAM_OUTPUT_SIZE}': {0}")] + InvalidOutputSize(#[from] InvalidOutputSize), +} + +#[repr(C)] +#[derive(Debug)] +pub struct LbHasher { + alg: LbHasherAlgorithm, + hasher: Box, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum LbHasherAlgorithm { + Md5, + Sha224, + Sha256, + Sha384, + Sha512, + Blake2b, + Blake2s, +} + +#[derive(Debug)] +enum LbHasherImpl { + Md5(Md5), + Sha224(Sha224), + Sha256(Sha256), + Sha384(Sha384), + Sha512(Sha512), + Sha512_224(Sha512_224), + Sha512_256(Sha512_256), + Blake2b(Blake2bVar), + Blake2b512(Blake2b512), + Blake2s(Blake2sVar), + Blake2s256(Blake2s256), +} + +const PARAM_OUTPUT_SIZE: &'static str = "output_size"; + +fn param_output_size(params: Option<&LbParams>) -> Result, LbHashError> { + params + .and_then(|p| p.get(PARAM_OUTPUT_SIZE)) + .map(|s| s.parse()) + .transpose() + .map_err(|err| LbHashError::InvalidIntParam(PARAM_OUTPUT_SIZE, err)) +} + +impl LbHasher { + pub fn new(alg: LbHasherAlgorithm, params: Option<&LbParams>) -> Result { + use digest::{Digest, VariableOutput}; + let hasher = Box::new(match alg { + LbHasherAlgorithm::Md5 => LbHasherImpl::Md5(Digest::new()), + LbHasherAlgorithm::Sha224 => LbHasherImpl::Sha224(Digest::new()), + LbHasherAlgorithm::Sha256 => LbHasherImpl::Sha256(Digest::new()), + LbHasherAlgorithm::Sha384 => LbHasherImpl::Sha384(Digest::new()), + LbHasherAlgorithm::Sha512 => param_output_size(params).and_then(|size| match size { + Some(224) => Ok(LbHasherImpl::Sha512_224(Digest::new())), + Some(256) => Ok(LbHasherImpl::Sha512_256(Digest::new())), + Some(512) | None => Ok(LbHasherImpl::Sha512(Digest::new())), + _ => Err(LbHashError::InvalidOutputSize(InvalidOutputSize)), + })?, + LbHasherAlgorithm::Blake2b => { + param_output_size(params).and_then(|size| match size { + Some(512) | None => Ok(LbHasherImpl::Blake2b512(Blake2b512::new())), + size => VariableOutput::new(size.unwrap()) + .map(LbHasherImpl::Blake2b) + .map_err(LbHashError::InvalidOutputSize), + })? + } + LbHasherAlgorithm::Blake2s => { + param_output_size(params).and_then(|size| match size { + Some(256) | None => Ok(LbHasherImpl::Blake2s256(Blake2s256::new())), + size => VariableOutput::new(size.unwrap()) + .map(LbHasherImpl::Blake2s) + .map_err(LbHashError::InvalidOutputSize), + })? + } + }); + + Ok(Self { alg, hasher }) + } + + pub fn update(&mut self, data: impl AsRef<[u8]>) { + use digest::Update; + match *self.hasher { + LbHasherImpl::Md5(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Sha224(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Sha256(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Sha384(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Sha512(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Sha512_224(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Sha512_256(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Blake2b(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Blake2b512(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Blake2s(ref mut s) => s.update(data.as_ref()), + LbHasherImpl::Blake2s256(ref mut s) => s.update(data.as_ref()), + } + } + + pub fn digest(&mut self) -> Vec { + use digest::{DynDigest, VariableOutput}; + match *self.hasher { + LbHasherImpl::Md5(ref mut s) => s.finalize_reset(), + LbHasherImpl::Sha224(ref mut s) => s.finalize_reset(), + LbHasherImpl::Sha256(ref mut s) => s.finalize_reset(), + LbHasherImpl::Sha384(ref mut s) => s.finalize_reset(), + LbHasherImpl::Sha512(ref mut s) => s.finalize_reset(), + LbHasherImpl::Sha512_224(ref mut s) => s.finalize_reset(), + LbHasherImpl::Sha512_256(ref mut s) => s.finalize_reset(), + LbHasherImpl::Blake2b(ref mut s) => { + mem::replace(s, Blake2bVar::new(s.output_size()).unwrap()).finalize_boxed() + } + LbHasherImpl::Blake2b512(ref mut s) => s.finalize_reset(), + LbHasherImpl::Blake2s(ref mut s) => { + mem::replace(s, Blake2sVar::new(s.output_size()).unwrap()).finalize_boxed() + } + LbHasherImpl::Blake2s256(ref mut s) => s.finalize_reset(), + } + .to_vec() + } + + #[unsafe(no_mangle)] + pub extern "C" fn lb_hasher_new( + alg: LbHasherAlgorithm, + params: Option<&LbParams>, + ) -> LbRes { + Self::new(alg, params).map_err(LbError::Hash).into() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_hasher_drop(&mut self) { + unsafe { ptr::drop_in_place(self) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_hasher_update(&mut self, data: *const u8, data_len: usize) { + self.update(unsafe { slice::from_raw_parts(data, data_len) }) + } + + #[unsafe(no_mangle)] + pub extern "C" fn lb_hasher_digest(&mut self) -> LbBuf { + self.digest().into() + } +} diff --git a/luabi/src/lib.rs b/luabi/src/lib.rs new file mode 100644 index 0000000..44241ea --- /dev/null +++ b/luabi/src/lib.rs @@ -0,0 +1,4 @@ +pub mod core; +pub mod fs; +pub mod hash; +pub mod str; diff --git a/luabi/src/lua.rs b/luabi/src/lua.rs new file mode 100644 index 0000000..cb2201b --- /dev/null +++ b/luabi/src/lua.rs @@ -0,0 +1,924 @@ +#![allow(non_camel_case_types, non_snake_case, unused)] +use bstr::{BStr, BString, ByteSlice}; +use libmimalloc_sys::{mi_free, mi_realloc}; +use std::{ + backtrace::Backtrace, + ffi::{CStr, CString, NulError}, + fmt::{self, Display}, + marker::{PhantomData, PhantomPinned}, + os::raw::{c_char, c_double, c_int, c_void}, + ptr::{self, NonNull}, + rc::Rc, + slice, + thread::{self, Thread}, +}; +use thiserror::Error; + +/***** lua.h *****/ +/* mark for precompiled code (`Lua') */ +pub const LUA_SIGNATURE: &[u8] = b"\x1bLJ"; + +/* option for multiple returns in `lua_pcall' and `lua_call' */ +pub const LUA_MULTRET: c_int = -1; + +/* +** pseudo-indices +*/ +pub const LUA_REGISTRYINDEX: c_int = -10000; +pub const LUA_ENVIRONINDEX: c_int = -10001; +pub const LUA_GLOBALSINDEX: c_int = -10002; + +pub const fn lua_upvalueindex(i: c_int) -> c_int { + LUA_GLOBALSINDEX - i +} + +/* thread status */ +pub const LUA_OK: c_int = 0; +pub const LUA_YIELD: c_int = 1; +pub const LUA_ERRRUN: c_int = 2; +pub const LUA_ERRSYNTAX: c_int = 3; +pub const LUA_ERRMEM: c_int = 4; +pub const LUA_ERRERR: c_int = 5; + +#[repr(C)] +pub struct lua_State { + _data: [u8; 0], + _marker: PhantomData<(*mut u8, PhantomPinned)>, +} + +pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int; + +/* +** functions that read/write blocks when loading/dumping Lua chunks +*/ +pub type lua_Reader = unsafe extern "C-unwind" fn( + L: *mut lua_State, + ud: *mut c_void, + sz: *mut usize, +) -> *const c_char; + +/* +** prototype for memory-allocation functions +*/ +pub type lua_Writer = unsafe extern "C-unwind" fn( + L: *mut lua_State, + p: *const c_void, + sz: usize, + ud: *mut c_void, +) -> c_int; + +/* +** prototype for memory-allocation functions +*/ +pub type lua_Alloc = unsafe extern "C" fn( + ud: *mut c_void, + ptr: *mut c_void, + osize: usize, + nsize: usize, +) -> *mut c_void; + +/* +** basic types +*/ +pub const LUA_TNONE: c_int = -1; +pub const LUA_TNIL: c_int = 0; +pub const LUA_TBOOLEAN: c_int = 1; +pub const LUA_TLIGHTUSERDATA: c_int = 2; +pub const LUA_TNUMBER: c_int = 3; +pub const LUA_TSTRING: c_int = 4; +pub const LUA_TTABLE: c_int = 5; +pub const LUA_TFUNCTION: c_int = 6; +pub const LUA_TUSERDATA: c_int = 7; +pub const LUA_TTHREAD: c_int = 8; +pub const LUA_TCDATA: c_int = 10; + +/* minimum Lua stack available to a C function */ +pub const LUA_MINSTACK: c_int = 20; + +/* type of numbers in Lua */ +pub type lua_Number = c_double; + +/* type for integer functions */ +#[cfg(target_pointer_width = "32")] +pub type lua_Integer = i32; +#[cfg(target_pointer_width = "64")] +pub type lua_Integer = i64; + +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + /* + ** state manipulation + */ + pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State; + pub fn lua_close(L: *mut lua_State); + pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State; + pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction; + + /* + ** basic stack manipulation + */ + pub fn lua_gettop(L: *mut lua_State) -> c_int; + pub fn lua_settop(L: *mut lua_State, idx: c_int); + pub fn lua_pushvalue(L: *mut lua_State, idx: c_int); + pub fn lua_remove(L: *mut lua_State, idx: c_int); + pub fn lua_insert(L: *mut lua_State, idx: c_int); + pub fn lua_replace(L: *mut lua_State, idx: c_int); + pub fn lua_checkstack(L: *mut lua_State, sz: c_int) -> c_int; + pub fn lua_xmove(from: *mut lua_State, to: *mut lua_State, n: c_int); + + pub fn lua_isnumber(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_isstring(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_iscfunction(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_isuserdata(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_type(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_typename(L: *mut lua_State, tp: c_int) -> *const c_char; + + pub fn lua_equal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; + pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; + pub fn lua_lessthan(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int; + + pub fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number; + pub fn lua_tointeger(L: *mut lua_State, idx: c_int) -> lua_Integer; + pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; + pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize; + pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option; + pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; + pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State; + pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void; + + /* + ** push functions (C -> stack) + */ + pub fn lua_pushnil(L: *mut lua_State); + pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number); + pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer); + pub fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize); + pub fn lua_pushstring(L: *mut lua_State, s: *const c_char); + pub fn lua_pushfstring(L: *mut lua_State, fmt: *const c_char, ...) -> *const c_char; + pub fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, n: c_int); + pub fn lua_pushboolean(L: *mut lua_State, b: c_int); + pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void); + pub fn lua_pushthread(L: *mut lua_State) -> c_int; + + /* + ** get functions (Lua -> stack) + */ + pub fn lua_gettable(L: *mut lua_State, idx: c_int); + pub fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char); + pub fn lua_rawget(L: *mut lua_State, idx: c_int); + pub fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: c_int); + pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int); + pub fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void; + pub fn lua_getmetatable(L: *mut lua_State, objindex: c_int) -> c_int; + pub fn lua_getfenv(L: *mut lua_State, idx: c_int); + + /* + ** set functions (stack -> Lua) + */ + pub fn lua_settable(L: *mut lua_State, idx: c_int); + pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char); + pub fn lua_rawset(L: *mut lua_State, idx: c_int); + pub fn lua_rawseti(L: *mut lua_State, idx: c_int, n: c_int); + pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int; + pub fn lua_setfenv(L: *mut lua_State, idx: c_int) -> c_int; + + /* + ** `load' and `call' functions (load and run Lua code) + */ + pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int); + pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int; + pub fn lua_cpcall(L: *mut lua_State, func: lua_CFunction, ud: *mut c_void) -> c_int; + pub fn lua_load( + L: *mut lua_State, + reader: lua_Reader, + dt: *mut c_void, + chunkname: *const c_char, + ) -> c_int; + pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void) -> c_int; + + /* + ** coroutine functions + */ + pub fn lua_yield(L: *mut lua_State, nresults: c_int) -> c_int; + pub fn lua_resume(L: *mut lua_State, narg: c_int) -> c_int; + pub fn lua_status(L: *mut lua_State) -> c_int; +} + +/* +** garbage-collection function and options +*/ +pub const LUA_GCSTOP: c_int = 0; +pub const LUA_GCRESTART: c_int = 1; +pub const LUA_GCCOLLECT: c_int = 2; +pub const LUA_GCCOUNT: c_int = 3; +pub const LUA_GCCOUNTB: c_int = 4; +pub const LUA_GCSTEP: c_int = 5; +pub const LUA_GCSETPAUSE: c_int = 6; +pub const LUA_GCSETSTEPMUL: c_int = 7; + +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int; + + /* + ** miscellaneous functions + */ + pub fn lua_error(L: *mut lua_State) -> !; + pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; + pub fn lua_concat(L: *mut lua_State, n: c_int); + pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; + pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void); +} + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ +pub unsafe fn lua_pop(L: *mut lua_State, n: c_int) { + unsafe { lua_settop(L, -n - 1) } +} + +pub unsafe fn lua_newtable(L: *mut lua_State) { + unsafe { lua_createtable(L, 0, 0) } +} + +pub unsafe fn lua_register(L: *mut lua_State, n: *const c_char, f: lua_CFunction) { + unsafe { (lua_pushcfunction(L, f), lua_setglobal(L, n)) }; +} + +pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { + unsafe { lua_pushcclosure(L, f, 0) } +} + +pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize { + unsafe { lua_objlen(L, i) } +} + +pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TFUNCTION) as c_int } +} + +pub unsafe fn lua_istable(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TTABLE) as c_int } +} + +pub unsafe fn lua_islightuserdata(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TLIGHTUSERDATA) as c_int } +} + +pub unsafe fn lua_isnil(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TNIL) as c_int } +} + +pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TBOOLEAN) as c_int } +} + +pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TTHREAD) as c_int } +} + +pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TNONE) as c_int } +} + +pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) <= LUA_TNIL) as c_int } +} + +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: impl AsRef<[u8]>) { + unsafe { luab_pushstring(L, s) } +} + +pub unsafe fn lua_setglobal(L: *mut lua_State, s: *const c_char) { + unsafe { lua_setfield(L, LUA_GLOBALSINDEX, s) } +} + +pub unsafe fn lua_getglobal(L: *mut lua_State, s: *const c_char) { + unsafe { lua_getfield(L, LUA_GLOBALSINDEX, s) } +} + +pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { + unsafe { lua_tolstring(L, i, ptr::null_mut()) } +} + +/* +** compatibility macros and functions +*/ +pub unsafe fn lua_getregistry(L: *mut lua_State) { + unsafe { lua_pushvalue(L, LUA_REGISTRYINDEX) } +} + +pub unsafe fn lua_getgccount(L: *mut lua_State) -> c_int { + unsafe { lua_gc(L, LUA_GCCOUNT, 0) } +} + +pub type lua_Chunkreader = lua_Reader; +pub type lua_Chunkwriter = lua_Writer; + +/* hack */ +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn lua_setlevel(from: *mut lua_State, to: *mut lua_State); +} + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + +/* +** Event codes +*/ +pub const LUA_HOOKCALL: c_int = 0; +pub const LUA_HOOKRET: c_int = 1; +pub const LUA_HOOKLINE: c_int = 2; +pub const LUA_HOOKCOUNT: c_int = 3; +pub const LUA_HOOKTAILCALL: c_int = 4; + +/* +** Event masks +*/ +pub const LUA_MASKCALL: c_int = 1 << (LUA_HOOKCALL as usize); +pub const LUA_MASKRET: c_int = 1 << (LUA_HOOKRET as usize); +pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize); +pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); + +/* Functions to be called by the debuger in specific events */ +pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug); + +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int; + pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int; + pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; + pub fn lua_setlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char; + pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; + pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char; + pub fn lua_sethook( + L: *mut lua_State, + func: Option, + mask: c_int, + count: c_int, + ) -> c_int; + pub fn lua_gethook(L: *mut lua_State) -> Option; + pub fn lua_gethookmask(L: *mut lua_State) -> c_int; + pub fn lua_gethookcount(L: *mut lua_State) -> c_int; +} + +/* From Lua 5.2. */ +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn lua_upvalueid(L: *mut lua_State, idx: c_int, n: c_int) -> *mut c_void; + pub fn lua_upvaluejoin(L: *mut lua_State, idx1: c_int, n1: c_int, idx2: c_int, n2: c_int); + pub fn lua_loadx( + L: *mut lua_State, + reader: lua_Reader, + dt: *mut c_void, + chunkname: *const c_char, + mode: *const c_char, + ) -> c_int; + pub fn lua_version(L: *mut lua_State) -> *const lua_Number; + pub fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int); + pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number; + pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer; +} + +/* From Lua 5.3. */ +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn lua_isyieldable(L: *mut lua_State) -> c_int; +} + +/* activation record */ +#[repr(C)] +pub struct lua_Debug { + pub event: c_int, + pub name: *const c_char, /* (n) */ + pub namewhat: *const c_char, /* (n) `global', `local', `field', `method' */ + pub what: *const c_char, /* (S) `Lua', `C', `main', `tail' */ + pub source: *const c_char, /* (S) */ + pub currentline: c_int, /* (l) */ + pub nups: c_int, /* (u) number of upvalues */ + pub linedefined: c_int, /* (S) */ + pub lastlinedefined: c_int, /* (S) */ + pub short_src: [c_char; LUA_IDSIZE as usize], /* (S) */ + /* private part */ + i_ci: c_int, +} + +pub const LUA_IDSIZE: c_int = 60; /* Size of lua_Debug.short_src. */ + +/***** lualib.h *****/ +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn luaopen_base(L: *mut lua_State) -> c_int; + pub fn luaopen_math(L: *mut lua_State) -> c_int; + pub fn luaopen_string(L: *mut lua_State) -> c_int; + pub fn luaopen_table(L: *mut lua_State) -> c_int; + pub fn luaopen_io(L: *mut lua_State) -> c_int; + pub fn luaopen_os(L: *mut lua_State) -> c_int; + pub fn luaopen_package(L: *mut lua_State) -> c_int; + pub fn luaopen_debug(L: *mut lua_State) -> c_int; + pub fn luaopen_bit(L: *mut lua_State) -> c_int; + pub fn luaopen_jit(L: *mut lua_State) -> c_int; + pub fn luaopen_ffi(L: *mut lua_State) -> c_int; + pub fn luaopen_string_buffer(L: *mut lua_State) -> c_int; +} + +/***** lauxlib.h *****/ +#[link(name = "luajit", kind = "static")] +unsafe extern "C-unwind" { + pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int); +} + +#[derive(Debug, Clone, Copy)] +pub enum Library { + Base, + Math, + String, + Table, + Io, + Os, + Package, + Debug, + Bit, + Jit, + Ffi, + StringBuffer, +} + +impl Library { + pub const ALL: &[Library] = &[ + Library::Base, + Library::Math, + Library::String, + Library::Table, + Library::Io, + Library::Os, + Library::Package, + Library::Debug, + Library::Bit, + Library::Jit, + Library::Ffi, + Library::StringBuffer, + ]; + + pub fn name(&self) -> &'static str { + match self { + Self::Base => "base", + Self::Math => "math", + Self::String => "string", + Self::Table => "table", + Self::Io => "io", + Self::Os => "os", + Self::Package => "package", + Self::Debug => "debug", + Self::Bit => "bit", + Self::Jit => "jit", + Self::Ffi => "ffi", + Self::StringBuffer => "string_buffer", + } + } + + fn get_open_fn(&self) -> lua_CFunction { + match self { + Self::Base => luaopen_base, + Self::Math => luaopen_math, + Self::String => luaopen_string, + Self::Table => luaopen_table, + Self::Io => luaopen_io, + Self::Os => luaopen_os, + Self::Package => luaopen_package, + Self::Debug => luaopen_debug, + Self::Bit => luaopen_bit, + Self::Jit => luaopen_jit, + Self::Ffi => luaopen_ffi, + Self::StringBuffer => luaopen_string_buffer, + } + } +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("out of memory")] + OutOfMemory, + #[error("syntax error in chunk '{chunk}': {msg}")] + Syntax { chunk: BString, msg: BString }, + #[error("bad chunkname: {0}")] + BadChunkName(NulError), + #[error("{msg}")] + Call { msg: BString }, + #[error("{msg}")] + Resume { msg: BString, trace: BString }, +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum LoadMode { + #[default] + Auto, + Text, + Binary, +} + +impl LoadMode { + fn mode_str(&self) -> &'static CStr { + match self { + Self::Auto => c"bt", + Self::Text => c"t", + Self::Binary => c"b", + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum ResumeStatus { + #[default] + Ok, + Suspended, +} + +unsafe fn luab_pushstring(L: *mut lua_State, s: impl AsRef<[u8]>) { + unsafe { lua_pushlstring(L, s.as_ref().as_ptr().cast(), s.as_ref().len()) } +} + +// SAFETY: returned reference is valid only as long as it is present in the stack +unsafe fn luab_tostring(L: *mut lua_State, idx: c_int) -> Option<&'static BStr> { + unsafe { + let mut len = 0; + let ptr = lua_tolstring(L, idx, &mut len); + (!ptr.is_null()).then(|| slice::from_raw_parts(ptr.cast(), len).into()) + } +} + +unsafe fn luab_load( + L: *mut lua_State, + name: Option<&CStr>, + chunk: impl AsRef<[u8]>, + mode: LoadMode, +) -> Result<(), Error> { + unsafe { + type ReaderState<'s> = Option<&'s [u8]>; + let name = name.unwrap_or(c"?"); + let mut state: ReaderState = Some(chunk.as_ref()); + + unsafe extern "C-unwind" fn read_cb( + L: *mut lua_State, + state: *mut c_void, + size: *mut usize, + ) -> *const c_char { + unsafe { + if let Some(chunk) = (*(state as *mut ReaderState)).take() { + *size = chunk.len(); + chunk.as_ptr() as *const c_char + } else { + *size = 0; + ptr::null() + } + } + } + + lua_checkstack(L, 1); + + match lua_loadx( + L, + read_cb, + &raw mut state as *mut c_void, + name.as_ptr(), + mode.mode_str().as_ptr(), + ) { + LUA_OK => Ok(()), + LUA_ERRMEM => { + lua_pop(L, 1); + Err(Error::OutOfMemory) + } + LUA_ERRSYNTAX => { + let chunk = name.to_bytes().as_bstr().to_owned(); + let msg = luab_tostring(L, -1) + .filter(|s| !s.is_empty()) + .unwrap_or(b"unknown error".into()) + .to_owned(); + lua_pop(L, 1); + Err(Error::Syntax { chunk, msg }) + } + _ => unreachable!(), + } + } +} + +unsafe fn luab_call(L: *mut lua_State, narg: c_int, nret: c_int) -> Result { + unsafe { + assert!( + matches!(lua_status(L), LUA_OK | LUA_ERRERR), // see lua_pcall in lj_api.c + "thread {L:?} called in wrong state" + ); + + let base = lua_gettop(L) - (narg + 1); + match lua_pcall(L, narg, nret, 0) { + LUA_OK => { + let n = lua_gettop(L) - base; + assert!(n == nret || nret == LUA_MULTRET); + Ok(n) + } + LUA_ERRMEM => { + lua_pop(L, 1); + Err(Error::OutOfMemory) + } + LUA_ERRRUN | LUA_ERRERR => { + let msg = luab_tostring(L, -1) + .filter(|s| !s.is_empty()) + .unwrap_or(b"unknown error".into()) + .to_owned(); + lua_pop(L, 1); + Err(Error::Call { msg }) + } + _ => unreachable!(), + } + } +} + +unsafe fn luab_resume(L: *mut lua_State, narg: c_int) -> Result { + unsafe { + assert!( + matches!(lua_status(L), LUA_OK | LUA_YIELD), // see lua_resume in lj_api.c + "cannot resume dead thread {L:?}" + ); + + match lua_resume(L, narg) { + LUA_OK => Ok(ResumeStatus::Ok), + LUA_YIELD => Ok(ResumeStatus::Suspended), + LUA_ERRMEM => { + lua_pop(L, 1); + Err(Error::OutOfMemory) + } + LUA_ERRRUN | LUA_ERRERR => { + luaL_traceback(L, L, ptr::null(), 0); + + let msg = luab_tostring(L, -2) + .filter(|s| !s.is_empty()) + .unwrap_or(b"unknown error".into()) + .to_owned(); + + let trace = luab_tostring(L, -1) + .map(|s| s.strip_prefix(b"stack traceback:\n").unwrap_or(s).as_bstr()) + .filter(|s| !s.is_empty()) + .unwrap_or(b"".into()) + .to_owned(); + + lua_pop(L, 2); + Err(Error::Resume { msg, trace }) + } + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +struct StateInner(NonNull); + +impl Drop for StateInner { + fn drop(&mut self) { + unsafe { lua_close(self.0.as_ptr()) } + } +} + +#[derive(Debug)] +pub struct State { + inner: Rc, + ptr: NonNull, +} + +impl State { + pub fn new() -> Result { + unsafe { + let ptr = NonNull::new(lua_newstate(Self::alloc_cb, ptr::null_mut())) + .ok_or(Error::OutOfMemory)?; + + let state = Self { + inner: Rc::new(StateInner(ptr)), + ptr, + }; + + lua_atpanic(state.as_ptr(), Self::panic_cb); + + // lua_pushcfunction(state.as_ptr(), lua_handle_panic); + // lua_replace(state.as_ptr(), lua_upvalueindex(1)); + + Ok(state) + } + } + + unsafe extern "C" fn alloc_cb( + _ud: *mut c_void, + ptr: *mut c_void, + _osize: usize, + nsize: usize, + ) -> *mut c_void { + unsafe { + // NOTE: same as the reference implementation used by luaL_newstate + // https://www.lua.org/manual/5.1/manual.html#lua_Alloc + if nsize == 0 { + mi_free(ptr); + ptr::null_mut() + } else { + mi_realloc(ptr, nsize) + } + } + } + + unsafe extern "C-unwind" fn panic_cb(L: *mut lua_State) -> c_int { + unsafe { + let msg = luab_tostring(L, -1) + .filter(|s| !s.is_empty()) + .unwrap_or(b"unknown error".into()); + + panic!("{msg}") + } + } + + pub fn open_lib(&self, lib: Library) -> Result<(), Error> { + unsafe { + lua_checkstack(self.as_ptr(), 2); + lua_pushcfunction(self.as_ptr(), lib.get_open_fn()); + lua_pushliteral(self.as_ptr(), lib.name()); + luab_call(self.as_ptr(), 1, 0).map(|_| ()) + } + } + + pub fn open_libs<'a>(&self, libs: impl IntoIterator) -> Result<(), Error> { + for lib in libs { + self.open_lib(*lib)?; + } + + Ok(()) + } + + pub fn as_ptr(&self) -> *mut lua_State { + self.ptr.as_ptr() + } + + pub fn top(&self) -> c_int { + unsafe { lua_gettop(self.as_ptr()) } + } + + pub fn absindex(&self, idx: c_int) -> c_int { + // SAFETY: refer to index2adr in lj_api.c + unsafe { + if idx > 0 { + let top = lua_gettop(self.as_ptr()); + assert!(idx <= top, "invalid stack index {idx} (size={top})"); + idx + } else if idx > LUA_REGISTRYINDEX { + let top = lua_gettop(self.as_ptr()); + let i = top + idx + 1; + assert!(0 < i && i <= top, "invalid stack index {idx} (size={top})"); + i + } else { + idx + } + } + } + + pub fn push(&self, idx: c_int) { + unsafe { + lua_checkstack(self.as_ptr(), 1); + lua_pushvalue(self.as_ptr(), self.absindex(idx)); + } + } + + pub fn push_string(&self, s: impl AsRef<[u8]>) { + unsafe { + lua_checkstack(self.as_ptr(), 1); + luab_pushstring(self.as_ptr(), s); + } + } + + pub fn push_pointer(&self, p: *const T) { + unsafe { + lua_checkstack(self.as_ptr(), 1); + lua_pushlightuserdata(self.as_ptr(), p as *mut c_void); + } + } + + pub fn push_require(&self, module: impl AsRef<[u8]>) -> Result<(), Error> { + unsafe { + lua_checkstack(self.as_ptr(), 2); + luab_pushstring(self.as_ptr(), "require"); + lua_rawget(self.as_ptr(), LUA_GLOBALSINDEX); + luab_pushstring(self.as_ptr(), module); + Ok(()) + } + } + + pub fn to_string(&self, idx: c_int) -> Option { + // SAFETY: we copy the lua-owned string immediately so no reference to it is kept + unsafe { luab_tostring(self.as_ptr(), self.absindex(idx)).map(|s| s.to_owned()) } + } + + pub fn pop(&self, n: c_int) { + unsafe { + let top = lua_gettop(self.as_ptr()); + assert!( + 0 < n && n <= top, + "cannot pop {n} value(s) from the stack (size={top})" + ); + lua_pop(self.as_ptr(), n); + } + } + + pub fn get_table(&self, idx: c_int) { + unsafe { + let top = lua_gettop(self.as_ptr()); + assert!(top >= 1, "expected 1 value on the stack (size={top})"); + lua_rawget(self.as_ptr(), self.absindex(idx)); + } + } + + pub fn set_table(&self, idx: c_int) { + unsafe { + let top = lua_gettop(self.as_ptr()); + assert!(top >= 2, "expected 2 values on the stack (size={top})"); + lua_rawset(self.as_ptr(), self.absindex(idx)); + } + } + + pub fn load( + &self, + name: Option>, + chunk: impl AsRef<[u8]>, + mode: LoadMode, + ) -> Result<(), Error> { + unsafe { + luab_load( + self.as_ptr(), + name.map(|s| CString::new(s.as_ref())) + .transpose() + .map_err(Error::BadChunkName)? + .as_deref(), + chunk, + mode, + ) + } + } + + pub fn call(&self, narg: c_int, nret: c_int) -> Result { + unsafe { + let top = lua_gettop(self.as_ptr()); + let need = narg + 1; // need function on the stack too + + assert!( + 0 <= need && need <= top, + "expected {need} value(s) on the stack (size={top})" + ); + + assert!(0 <= nret || nret == LUA_MULTRET); + luab_call(self.as_ptr(), narg, nret) + } + } + + pub fn new_thread(&self) -> State { + unsafe { + lua_checkstack(self.as_ptr(), 2); + + // SAFETY: lua_newthread never returns null, but may throw on oom + let state = State { + ptr: NonNull::new_unchecked(lua_newthread(self.as_ptr())), + inner: Rc::clone(&self.inner), + }; + + lua_pushlightuserdata(self.as_ptr(), state.as_ptr() as *mut c_void); + lua_insert(self.as_ptr(), -2); // map thread pointer to thread + lua_rawset(self.as_ptr(), LUA_REGISTRYINDEX); + + state + } + } + + pub fn resume(&self, narg: c_int) -> Result { + unsafe { + let top = lua_gettop(self.as_ptr()); + let need = if lua_status(self.as_ptr()) == LUA_OK { + narg + 1 // need function on the stack too if not already suspended + } else { + narg + }; + + assert!( + 0 <= need && need <= top, + "expected {need} value(s) on the stack (size={top})" + ); + + luab_resume(self.as_ptr(), narg) + } + } +} + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let version = unsafe { *lua_version(self.as_ptr()) }; + write!(f, "lua {version} {:p}", self.as_ptr()) + } +} diff --git a/luabi/src/main.rs b/luabi/src/main.rs new file mode 100644 index 0000000..cbd165d --- /dev/null +++ b/luabi/src/main.rs @@ -0,0 +1,100 @@ +use clap::Parser; +use lz4_flex::decompress_size_prepended; +use mimalloc::MiMalloc; +use owo_colors::OwoColorize; +use std::{ + backtrace::Backtrace, + fmt, fs, panic, + thread::{self, Thread}, +}; + +pub mod core; +pub mod hash; +pub mod lua; +pub mod str; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; +static LIB_RT: &'static [u8] = include_bytes!(concat!(env!("OUT_DIR"), "/runtime.lua.lz4")); + +#[derive(Debug, Parser)] +struct Args { + /// Path to the main script. + #[clap(value_name = "SCRIPT")] + path: Option, + + /// Execute string 'chunk'. + #[clap(long, short = 'e', value_name = "CHUNK")] + evals: Vec, + + /// Require library 'name'. + #[clap(long, short = 'l', value_name = "NAME")] + libs: Vec, +} + +fn panic_cb(panic: &panic::PanicHookInfo) { + struct Message<'a> { + thread: Thread, + panic: &'a panic::PanicHookInfo<'a>, + } + + impl<'s> fmt::Display for Message<'s> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let thread = self.thread.name().unwrap_or(""); + let location = self.panic.location().unwrap(); + let payload = self.panic.payload(); + let msg = if let Some(s) = payload.downcast_ref::<&'static str>() { + s + } else if let Some(s) = payload.downcast_ref::() { + s.as_str() + } else { + "unknown error" + }; + + write!(f, "thread '{thread}' panicked at {location}:\n{msg}") + } + } + + let trace = Backtrace::force_capture(); + let thread = thread::current(); + print!("{}\n{trace}", Message { thread, panic }.red()); +} + +fn main() { + panic::set_hook(Box::new(panic_cb)); + + let args = Args::parse(); + let vm = new_vm().unwrap_or_else(|err| panic!("failed to initialise runtime: {err}")); + let main = vm.new_thread(); + + if let Some(ref path) = args.path { + match main + .load( + Some(format!("@{path}")), + fs::read(path).unwrap_or_else(|err| panic!("{err}")), + lua::LoadMode::Auto, + ) + .and_then(|()| main.resume(0)) + { + Ok(status) => println!("ok: {status:?}"), + Err(err) => match err { + lua::Error::Resume { msg, trace } => println!("{}\n{trace}", msg.red()), + err => println!("{}", err.red()), + }, + } + } +} + +fn new_vm() -> Result { + let vm = lua::State::new()?; + vm.open_libs(lua::Library::ALL)?; + + vm.load( + Some("@[luabi]"), + decompress_size_prepended(LIB_RT).unwrap(), + lua::LoadMode::Text, + )?; + + vm.call(0, 0)?; + Ok(vm) +} diff --git a/luabi/src/str.rs b/luabi/src/str.rs new file mode 100644 index 0000000..d834195 --- /dev/null +++ b/luabi/src/str.rs @@ -0,0 +1,70 @@ +use crate::core::{LbBuf, LbRes}; +use std::{ + ops::{Deref, DerefMut}, + ptr, slice, + str::Utf8Error, +}; +use thiserror::Error; + +#[repr(C)] +#[derive(Debug, Error)] +pub enum LbStrError { + #[error("{0}")] + InvalidUtf8(Utf8Error), +} + +#[repr(C)] +pub struct LbStr { + buf: LbBuf, +} + +impl LbStr { + pub fn new() -> Self { + String::new().into() + } +} + +impl Deref for LbStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + unsafe { std::str::from_utf8_unchecked(&self.buf) } + } +} + +impl DerefMut for LbStr { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::str::from_utf8_unchecked_mut(&mut self.buf) } + } +} + +impl From for LbStr { + fn from(value: String) -> Self { + Self { + buf: value.into_bytes().into(), + } + } +} + +impl From for String { + fn from(value: LbStr) -> Self { + unsafe { String::from_utf8_unchecked(value.buf.into()) } + } +} + +impl LbStr { + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_str_new(ptr: *const u8, len: usize) -> LbRes { + std::str::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }) + .map_err(|err| LbStrError::InvalidUtf8(err).into()) + .map(|str| LbStr { + buf: str.as_bytes().to_vec().into(), + }) + .into() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lb_str_drop(&mut self) { + unsafe { ptr::drop_in_place(self) } + } +} diff --git a/luajit b/luajit new file mode 160000 index 0000000..f9140a6 --- /dev/null +++ b/luajit @@ -0,0 +1 @@ +Subproject commit f9140a622a0c44a99efb391cc1c2358bc8098ab7 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..758d417 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 100 diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..d04e235 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,7 @@ +syntax = "LuaJIT" +indent_type = "Spaces" +indent_width = 2 +column_width = 100 +quote_style = "ForceDouble" +call_parentheses = "NoSingleTable" +collapse_simple_statement = "ConditionalOnly"