Compare commits

...

26 Commits

Author SHA1 Message Date
2078dd0d8e Delete test.lua 2025-06-26 13:57:45 +10:00
dfd57e0ad0 Add macro to luaify blocks instead of exprs 2025-06-26 13:57:10 +10:00
2a6015c19a Use pretty assertions in luaify tests 2025-06-26 13:48:54 +10:00
88ab48cddb Modules should be registered in luby not lb 2025-06-26 13:46:39 +10:00
bcb734846d Implement lb_tcpsocket 2025-06-26 13:07:24 +10:00
2fe9515702 Add newlines in generated cdef for readability 2025-06-26 13:06:39 +10:00
f456a544e1 Disable all lualsp diagnostics in lib.lua 2025-06-26 13:05:50 +10:00
ba969b9e56 Rename lb_libx to lb_xlib 2025-06-26 00:40:26 +10:00
da5acd6bc8 Add stab tag for metamethods 2025-06-26 00:39:22 +10:00
cbb47840e4 Alias stub types to rust types where possible 2025-06-25 23:16:42 +10:00
2967513cbb Allowing dumping generated cdef 2025-06-25 22:48:46 +10:00
014687068f Add file write operations 2025-06-25 22:26:43 +10:00
85a0110e1a Disable warnings in luaffi lib.lua 2025-06-25 22:19:17 +10:00
91302db725 Print nicer error messages 2025-06-25 21:45:29 +10:00
c249549b3c Fix unknown feature warning 2025-06-25 21:25:02 +10:00
30596d9331 Impl generic From/IntoFfi for Option 2025-06-25 21:24:24 +10:00
530a1530ba Call it library not module 2025-06-25 20:13:09 +10:00
a81271c0a8 No need to specify versions for local dependencies 2025-06-25 20:09:01 +10:00
7ec60f0e6e Add version command 2025-06-25 20:04:33 +10:00
82726ebb5d Set author and homepage for all packages 2025-06-25 19:57:58 +10:00
86bfc7ad34 Add read and read_sync methods 2025-06-25 18:42:40 +10:00
6cdf186b61 Add exports header to module docs 2025-06-25 18:42:29 +10:00
98100d02fa Implement string parameter specialisation 2025-06-25 18:42:09 +10:00
681dd332ab Avoid unnecessarily coping result inner value if it doesn't require ownership 2025-06-25 16:39:47 +10:00
af3dbe60cb Add Lua and FFI tags to documentation 2025-06-25 15:46:17 +10:00
a07f62ecc5 Add tracing to lb 2025-06-25 15:02:53 +10:00
37 changed files with 1544 additions and 436 deletions

View File

@@ -1,3 +1,2 @@
[build] [build]
rustflags = ["--cfg", "tokio_unstable"] rustflags = ["--cfg", "tokio_unstable"]
rustdocflags = ["--cfg", "tokio_unstable"]

597
Cargo.lock generated
View File

@@ -255,6 +255,32 @@ name = "camino"
version = "1.1.10" version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "cc" name = "cc"
@@ -262,6 +288,8 @@ version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [ dependencies = [
"jobserver",
"libc",
"shlex", "shlex",
] ]
@@ -444,6 +472,46 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "deranged"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.0.1" version = "2.0.1"
@@ -466,6 +534,23 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
@@ -510,6 +595,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.31"
@@ -557,7 +651,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.1+wasi-snapshot-preview1",
]
[[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 0.14.2+wasi-0.2.4",
] ]
[[package]] [[package]]
@@ -566,6 +672,19 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "git2"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.2" version = "0.3.2"
@@ -729,12 +848,119 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@@ -785,6 +1011,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom 0.3.3",
"libc",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -793,15 +1029,16 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "lb" name = "lb"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"camino", "camino",
"derive_more", "derive_more",
"luaffi", "luaffi",
"luaify",
"luajit", "luajit",
"sysexits", "sysexits",
"thiserror",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@@ -810,6 +1047,18 @@ version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libgit2-sys"
version = "0.18.2+1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.8.8" version = "0.8.8"
@@ -830,12 +1079,30 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "libz-sys"
version = "1.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.9.4" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.13" version = "0.4.13"
@@ -854,7 +1121,7 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "luaffi" name = "luaffi"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"bstr", "bstr",
"luaffi_impl", "luaffi_impl",
@@ -865,7 +1132,7 @@ dependencies = [
[[package]] [[package]]
name = "luaffi_impl" name = "luaffi_impl"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
@@ -875,8 +1142,9 @@ dependencies = [
[[package]] [[package]]
name = "luaify" name = "luaify"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@@ -884,7 +1152,7 @@ dependencies = [
[[package]] [[package]]
name = "luajit" name = "luajit"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bstr", "bstr",
@@ -895,7 +1163,7 @@ dependencies = [
[[package]] [[package]]
name = "luajit-sys" name = "luajit-sys"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cc", "cc",
@@ -904,7 +1172,7 @@ dependencies = [
[[package]] [[package]]
name = "luby" name = "luby"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"clap", "clap",
"console-subscriber", "console-subscriber",
@@ -916,6 +1184,7 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"vergen-git2",
] ]
[[package]] [[package]]
@@ -976,7 +1245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -1000,6 +1269,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1009,6 +1284,15 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.7" version = "0.36.7"
@@ -1038,9 +1322,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "owo-colors" name = "owo-colors"
version = "4.2.1" version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
@@ -1103,6 +1387,27 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@@ -1112,6 +1417,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.35" version = "0.2.35"
@@ -1172,6 +1487,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@@ -1199,7 +1520,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.16",
] ]
[[package]] [[package]]
@@ -1267,6 +1588,15 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.7"
@@ -1298,6 +1628,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.219"
@@ -1382,6 +1721,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@@ -1405,6 +1750,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "sysexits" name = "sysexits"
version = "0.9.0" version = "0.9.0"
@@ -1440,6 +1796,49 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.45.1" version = "1.45.1"
@@ -1655,6 +2054,23 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.2"
@@ -1667,6 +2083,53 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "9.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b2bf58be11fc9414104c6d3a2e464163db5ef74b12296bda593cac37b6e4777"
dependencies = [
"anyhow",
"cargo_metadata",
"derive_builder",
"regex",
"rustc_version",
"rustversion",
"vergen-lib",
]
[[package]]
name = "vergen-git2"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f6ee511ec45098eabade8a0750e76eec671e7fb2d9360c563911336bea9cac1"
dependencies = [
"anyhow",
"derive_builder",
"git2",
"rustversion",
"time",
"vergen",
"vergen-lib",
]
[[package]]
name = "vergen-lib"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166"
dependencies = [
"anyhow",
"derive_builder",
"rustversion",
]
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@@ -1682,6 +2145,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[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]] [[package]]
name = "which" name = "which"
version = "8.0.0" version = "8.0.0"
@@ -1876,6 +2348,51 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" 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",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.26" version = "0.8.26"
@@ -1895,3 +2412,57 @@ dependencies = [
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -1,4 +1,5 @@
[workspace] [workspace]
resolver = "3"
members = [ members = [
"crates/lb", "crates/lb",
"crates/luaffi", "crates/luaffi",
@@ -8,23 +9,38 @@ members = [
"crates/luajit-sys", "crates/luajit-sys",
] ]
[workspace.package]
version = "0.0.1"
edition = "2024"
license = "MIT"
authors = ["luaneko <lumi@lua.re>"]
homepage = "https://git.lua.re/luaneko/luby/"
repository = "https://git.lua.re/luaneko/luby/"
[package]
name = "luby"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[profile] [profile]
dev.panic = "abort" dev.panic = "abort"
release.panic = "abort" release.panic = "abort"
[package]
name = "luby"
version = "0.1.0"
edition = "2024"
[dependencies] [dependencies]
clap = { version = "4.5.40", features = ["derive"] } clap = { version = "4.5.40", features = ["derive", "env"] }
console-subscriber = "0.4.1" console-subscriber = "0.4.1"
lb = { version = "0.1.0", path = "crates/lb" } lb = { path = "crates/lb" }
luajit = { version = "0.1.0", path = "crates/luajit", features = ["runtime"] } luajit = { path = "crates/luajit", features = ["runtime"] }
mimalloc = "0.1.47" mimalloc = "0.1.47"
owo-colors = "4.2.1" owo-colors = "4.2.1"
sysexits = "0.9.0" sysexits = "0.9.0"
tokio = { version = "1.45.1", features = ["full", "tracing"] } tokio = { version = "1.45.1", features = ["full", "tracing"] }
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.19" tracing-subscriber = "0.3.19"
[build-dependencies]
vergen-git2 = { version = "1.0.7", features = ["cargo", "rustc"] }

13
build.rs Normal file
View File

@@ -0,0 +1,13 @@
use vergen_git2::{CargoBuilder, Emitter, Git2Builder, RustcBuilder};
fn main() {
Emitter::default()
.add_instructions(&CargoBuilder::all_cargo().unwrap())
.unwrap()
.add_instructions(&Git2Builder::all_git().unwrap())
.unwrap()
.add_instructions(&RustcBuilder::all_rustc().unwrap())
.unwrap()
.emit()
.unwrap();
}

View File

@@ -1,16 +1,18 @@
[package] [package]
name = "lb" name = "lb"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies] [dependencies]
camino = "1.1.10" camino = "1.1.10"
derive_more = { version = "2.0.1", features = ["full"] } derive_more = { version = "2.0.1", features = ["full"] }
luaffi = { version = "0.1.0", path = "../luaffi" } luaffi = { path = "../luaffi" }
luajit = { version = "0.1.0", path = "../luajit" } luajit = { path = "../luajit" }
sysexits = "0.9.0" sysexits = "0.9.0"
tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal"] } thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal", "tracing"] }
[dev-dependencies] tracing = "0.1.41"
luaify = { path = "../luaify" }
tokio = { version = "1.45.1", features = ["full"] }

View File

@@ -2,10 +2,10 @@
use luaffi::{cdef, metatype}; use luaffi::{cdef, metatype};
#[cdef] #[cdef]
pub struct lb_libchannel; pub struct lb_chanlib;
#[metatype] #[metatype]
impl lb_libchannel { impl lb_chanlib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self

View File

@@ -1,32 +1,43 @@
//! The `lb:fs` module provides utilities for interacting with the file system asynchronously. //! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
//! file system.
//! //!
//! See [`lb_libfs`] for items exported by this module. //! # Exports
//!
//! See [`lb_fslib`] for items exported by this library.
use luaffi::{cdef, metatype}; use luaffi::{cdef, metatype};
use std::io; use std::io;
use tokio::fs; use tokio::fs;
/// Items exported by the `lb:fs` module. /// Items exported by the `lb:fs` library.
/// ///
/// This module can be obtained by calling `require` in Lua. /// This library can be obtained by calling `require` in Lua.
/// ///
/// ```lua /// ```lua
/// local fs = require("lb:fs"); /// local fs = require("lb:fs");
/// ``` /// ```
#[cdef] #[cdef]
pub struct lb_libfs; pub struct lb_fslib;
#[metatype] #[metatype]
impl lb_libfs { impl lb_fslib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self
} }
pub extern "Lua" fn read(&self, path: string) -> string { pub async extern "Lua-C" fn read(&self, path: &str) -> io::Result<Vec<u8>> {
self.__read(path)
}
async extern "Lua-C" fn __read(&self, path: &str) -> io::Result<Vec<u8>> {
fs::read(path).await fs::read(path).await
} }
pub extern "Lua-C" fn read_sync(&self, path: &str) -> io::Result<Vec<u8>> {
std::fs::read(path)
}
pub async extern "Lua-C" fn write(&self, path: &str, contents: &[u8]) -> io::Result<()> {
fs::write(path, contents).await
}
pub extern "Lua-C" fn write_sync(&self, path: &str, contents: &[u8]) -> io::Result<()> {
std::fs::write(path, contents)
}
} }

View File

@@ -1,4 +1,4 @@
pub mod channel; pub mod chan;
pub mod fs; pub mod fs;
pub mod net; pub mod net;
pub mod runtime; pub mod runtime;

View File

@@ -1,27 +1,47 @@
//! The `lb:net` module provides an asynchronous network API for creating TCP or UDP servers and //! The `lb:net` library provides an asynchronous network API for creating TCP or UDP servers and
//! clients. //! clients.
//! //!
//! See [`lb_libnet`] for items exported by this module. //! # Exports
//!
//! See [`lb_netlib`] for items exported by this library.
use derive_more::{From, FromStr}; use derive_more::{From, FromStr};
use luaffi::{cdef, metatype}; use luaffi::{cdef, metatype};
use std::{ use std::{
io,
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
time::Duration,
}; };
use thiserror::Error;
use tokio::net::{TcpListener, TcpSocket, TcpStream}; use tokio::net::{TcpListener, TcpSocket, TcpStream};
/// Items exported by the `lb:net` module. /// Errors that can be thrown by this library.
/// ///
/// This module can be obtained by calling `require` in Lua. /// Functions which return this error will **throw** in Lua. The error message can be caught by
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)]
pub enum Error {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
InvalidAddr(#[from] AddrParseError),
#[error("socket was already converted")]
SocketConsumed,
}
type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:net` library.
///
/// This library can be obtained by calling
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
/// ///
/// ```lua /// ```lua
/// local net = require("lb:net"); /// local net = require("lb:net");
/// ``` /// ```
#[cdef] #[cdef]
pub struct lb_libnet; pub struct lb_netlib;
#[metatype] #[metatype]
impl lb_libnet { impl lb_netlib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self
@@ -57,11 +77,7 @@ impl lb_libnet {
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an /// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses /// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported. /// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
/// pub extern "Lua" fn ipaddr(&self, s: any) -> Result<lb_ipaddr> {
/// # Errors
///
/// Throws if `s` cannot be parsed as an IP address.
pub extern "Lua" fn ipaddr(&self, s: any) -> lb_ipaddr {
if __istype(__ct.lb_ipaddr, s) { if __istype(__ct.lb_ipaddr, s) {
__new(__ct.lb_ipaddr, s) // copy constructor __new(__ct.lb_ipaddr, s) // copy constructor
} else if __istype(__ct.lb_socketaddr, s) { } else if __istype(__ct.lb_socketaddr, s) {
@@ -71,8 +87,8 @@ impl lb_libnet {
} }
} }
extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr, AddrParseError> { extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr> {
s.parse() Ok(s.parse()?)
} }
/// Creates an [`lb_socketaddr`] from the given input. /// Creates an [`lb_socketaddr`] from the given input.
@@ -84,60 +100,67 @@ impl lb_libnet {
/// socket address string. Both IPv4 and IPv6 addresses are supported. /// socket address string. Both IPv4 and IPv6 addresses are supported.
/// ///
/// If `port` is not specified, `0` is used as the default. /// If `port` is not specified, `0` is used as the default.
/// pub extern "Lua" fn socketaddr(&self, s: any, port: any) -> Result<lb_socketaddr> {
/// # Errors
///
/// Throws if `s` cannot be parsed as an IP or socket address.
pub extern "Lua" fn socketaddr(&self, s: any, port: any) -> lb_socketaddr {
if port != () { if port != () {
self.__new_socketaddr(self.ipaddr(s), port) self.__new_skaddr(self.ipaddr(s), port)
} else { } else {
if __istype(__ct.lb_socketaddr, s) { if __istype(__ct.lb_socketaddr, s) {
__new(__ct.lb_socketaddr, s) // copy constructor __new(__ct.lb_socketaddr, s) // copy constructor
} else if __istype(__ct.lb_ipaddr, s) { } else if __istype(__ct.lb_ipaddr, s) {
self.__new_socketaddr(s, 0) // default port 0 self.__new_skaddr(s, 0) // default port 0
} else { } else {
self.__parse_socketaddr(s) self.__parse_skaddr(s)
} }
} }
} }
extern "Lua-C" fn __new_socketaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr { extern "Lua-C" fn __new_skaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
SocketAddr::new(ip.0, port).into() SocketAddr::new(ip.0, port).into()
} }
extern "Lua-C" fn __parse_socketaddr(&self, s: &str) -> Result<lb_socketaddr, AddrParseError> { extern "Lua-C" fn __parse_skaddr(&self, s: &str) -> Result<lb_socketaddr> {
s.parse() Ok(s.parse()?)
} }
/// Creates a new TCP socket configured for IPv4. /// Creates a new TCP socket configured for IPv4.
/// ///
/// See [`TcpSocket::new_v4`]. /// See [`TcpSocket::new_v4`].
/// pub extern "Lua-C" fn tcp(&self) -> Result<lb_tcpsocket> {
/// # Errors Ok(Some(TcpSocket::new_v4()?).into())
///
/// Throws if an error was encountered during the socket creation.
pub extern "Lua" fn tcp_v4(&self) -> lb_tcpsocket {
self.__new_tcp_v4()
} }
/// Creates a new TCP socket configured for IPv6. /// Creates a new TCP socket configured for IPv6.
/// ///
/// See [`TcpSocket::new_v6`]. /// See [`TcpSocket::new_v6`].
/// pub extern "Lua-C" fn tcp6(&self) -> Result<lb_tcpsocket> {
/// # Errors Ok(Some(TcpSocket::new_v6()?).into())
///
/// Throws if an error was encountered during the socket creation.
pub extern "Lua" fn tcp_v6(&self) -> lb_tcpsocket {
self.__new_tcp_v6()
} }
extern "Lua-C" fn __new_tcp_v4(&self) -> io::Result<lb_tcpsocket> { pub extern "Lua" fn bind_tcp(&self, addr: any, port: any) -> Result<lb_tcpsocket> {
TcpSocket::new_v4().map(lb_tcpsocket) let addr = self.socketaddr(addr, port);
let socket;
if addr.ip().is_v6() {
socket = self.tcp6();
} else {
socket = self.tcp();
}
socket.bind(addr);
socket
} }
extern "Lua-C" fn __new_tcp_v6(&self) -> io::Result<lb_tcpsocket> { pub extern "Lua" fn connect_tcp(&self, addr: any, port: any) -> Result<lb_tcpstream> {
TcpSocket::new_v6().map(lb_tcpsocket) let addr = self.socketaddr(addr, port);
let socket;
if addr.ip().is_v6() {
socket = self.tcp6();
} else {
socket = self.tcp();
}
socket.connect(addr)
}
pub extern "Lua" fn listen_tcp(&self, addr: any, port: any) -> Result<lb_tcplistener> {
self.bind_tcp(addr, port).listen(1024)
} }
} }
@@ -280,7 +303,7 @@ impl lb_socketaddr {
/// Sets the IP part of this address. /// Sets the IP part of this address.
/// ///
/// This function accepts the same arguments as [`ipaddr`](lb_libnet::ipaddr). /// This function accepts the same arguments as [`ipaddr`](lb_netlib::ipaddr).
pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self { pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self {
if __istype(__ct.lb_ipaddr, s) { if __istype(__ct.lb_ipaddr, s) {
self.__set_ip(s); self.__set_ip(s);
@@ -296,8 +319,8 @@ impl lb_socketaddr {
self.0.set_ip(ip.0); self.0.set_ip(ip.0);
} }
extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<(), AddrParseError> { extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<()> {
s.parse().map(|ip| self.0.set_ip(ip)) Ok(self.0.set_ip(s.parse()?))
} }
/// Returns the port part of this address. /// Returns the port part of this address.
@@ -306,7 +329,7 @@ impl lb_socketaddr {
} }
/// Sets the port part of this address. /// Sets the port part of this address.
pub extern "Lua" fn set_port(&mut self, port: number) -> &mut Self { pub extern "Lua" fn set_port(&mut self, port: integer) -> &mut Self {
self.__set_port(port); self.__set_port(port);
self self
} }
@@ -322,13 +345,125 @@ impl lb_socketaddr {
} }
} }
/// A TCP socket which has not yet been converted to a [`lb_tcpstream`] or [`lb_tcplistener`]. /// A TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
#[derive(Debug, From)] #[derive(Debug, From)]
#[cdef] #[cdef]
pub struct lb_tcpsocket(#[opaque] TcpSocket); pub struct lb_tcpsocket(#[opaque] Option<TcpSocket>);
#[metatype] #[metatype]
impl lb_tcpsocket {} impl lb_tcpsocket {
fn socket(&self) -> Result<&TcpSocket> {
self.0.as_ref().ok_or(Error::SocketConsumed)
}
/// See [`TcpSocket::keepalive`].
pub extern "Lua-C" fn keepalive(&self) -> Result<bool> {
Ok(self.socket()?.keepalive()?)
}
/// See [`TcpSocket::set_keepalive`].
pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_keepalive(enabled)?)
}
/// See [`TcpSocket::reuseaddr`].
pub extern "Lua-C" fn reuseaddr(&self) -> Result<bool> {
Ok(self.socket()?.reuseaddr()?)
}
/// See [`TcpSocket::set_reuseaddr`].
pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_reuseaddr(enabled)?)
}
/// See [`TcpSocket::reuseport`].
pub extern "Lua-C" fn reuseport(&self) -> Result<bool> {
Ok(self.socket()?.reuseport()?)
}
/// See [`TcpSocket::set_reuseport`].
pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_reuseport(enabled)?)
}
/// See [`TcpSocket::send_buffer_size`].
pub extern "Lua-C" fn sendbuf(&self) -> Result<u32> {
Ok(self.socket()?.send_buffer_size()?)
}
/// See [`TcpSocket::set_send_buffer_size`].
pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> {
Ok(self.socket()?.set_send_buffer_size(size)?)
}
/// See [`TcpSocket::recv_buffer_size`].
pub extern "Lua-C" fn recvbuf(&self) -> Result<u32> {
Ok(self.socket()?.recv_buffer_size()?)
}
/// See [`TcpSocket::set_recv_buffer_size`].
pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> {
Ok(self.socket()?.set_recv_buffer_size(size)?)
}
/// See [`TcpSocket::linger`].
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
Ok(self
.socket()?
.linger()?
.map(|n| n.as_secs_f64())
.unwrap_or(0.))
}
/// See [`TcpSocket::set_linger`].
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
Ok(self
.socket()?
.set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?)
}
/// See [`TcpSocket::nodelay`].
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
Ok(self.socket()?.nodelay()?)
}
/// See [`TcpSocket::set_nodelay`].
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_nodelay(enabled)?)
}
/// See [`TcpSocket::tos`].
pub extern "Lua-C" fn tos(&self) -> Result<u32> {
Ok(self.socket()?.tos()?)
}
/// See [`TcpSocket::set_tos`].
pub extern "Lua-C" fn set_tos(&self, tos: u32) -> Result<()> {
Ok(self.socket()?.set_tos(tos)?)
}
/// See [`TcpSocket::local_addr`].
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
Ok(self.socket()?.local_addr()?.into())
}
/// See [`TcpSocket::bind`].
pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> {
Ok(self.socket()?.bind(addr.0)?)
}
/// See [`TcpSocket::connect`].
pub async extern "Lua-C" fn connect(&mut self, addr: &lb_socketaddr) -> Result<lb_tcpstream> {
let socket = self.0.take().ok_or(Error::SocketConsumed)?;
Ok(socket.connect(addr.0).await?.into())
}
/// See [`TcpSocket::listen`].
pub extern "Lua-C" fn listen(&mut self, backlog: u32) -> Result<lb_tcplistener> {
let socket = self.0.take().ok_or(Error::SocketConsumed)?;
Ok(socket.listen(backlog)?.into())
}
}
#[derive(Debug, From)] #[derive(Debug, From)]
#[cdef] #[cdef]

View File

@@ -1,5 +0,0 @@
local task = require("lb:task")
function spawn(f, ...)
return task:spawn(f, ...)
end

View File

@@ -1,4 +1,3 @@
use crate::{channel::lb_libchannel, fs::lb_libfs, net::lb_libnet, task::lb_libtask};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use luaffi::{Registry, Type}; use luaffi::{Registry, Type};
use luajit::{Chunk, State}; use luajit::{Chunk, State};
@@ -15,15 +14,13 @@ pub struct Builder {
impl Builder { impl Builder {
pub fn new() -> Self { pub fn new() -> Self {
let mut registry = Registry::new(); Self {
registry: Registry::new(),
}
}
registry pub fn registry(&self) -> &Registry {
.preload::<lb_libtask>("lb:task") &self.registry
.preload::<lb_libchannel>("lb:channel")
.preload::<lb_libfs>("lb:fs")
.preload::<lb_libnet>("lb:net");
Self { registry }
} }
pub fn module<T: Type>(&mut self, name: impl Display) -> &mut Self { pub fn module<T: Type>(&mut self, name: impl Display) -> &mut Self {
@@ -31,17 +28,11 @@ impl Builder {
self self
} }
pub fn registry(&self) -> &Registry {
&self.registry
}
pub fn build(&self) -> luajit::Result<Runtime> { pub fn build(&self) -> luajit::Result<Runtime> {
Ok(Runtime { Ok(Runtime {
state: { state: {
let mut s = State::new()?; let mut s = State::new()?;
let mut chunk = Chunk::new(self.registry.done()); s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
chunk.extend(include_bytes!("./runtime.lua"));
s.eval(chunk.path("[luby]"), 0, 0)?;
s s
}, },
tasks: LocalSet::new(), tasks: LocalSet::new(),
@@ -72,6 +63,9 @@ impl Runtime {
} }
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut State) -> T + 'static) -> JoinHandle<T> { pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut State) -> T + 'static) -> JoinHandle<T> {
// SAFETY: `new_thread` must be called inside `spawn_local` because this free-standing spawn
// function may be called via ffi from lua, and it is not safe to access the lua state within
// ffi calls.
spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await }) spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
} }

View File

@@ -4,10 +4,10 @@ use std::{ffi::c_int, process};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
#[cdef] #[cdef]
pub struct lb_libtask; pub struct lb_tasklib;
#[metatype] #[metatype]
impl lb_libtask { impl lb_tasklib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self

View File

@@ -1,32 +0,0 @@
use lb::runtime;
use luaify::luaify;
use luajit::{Chunk, LoadMode};
use tokio::test;
async fn run_lua(s: &'static str) {
let rt = runtime::Builder::new().build().unwrap();
let task = rt.spawn(async move |state| {
println!("executing test chunk: {s}");
state
.load(Chunk::new(s).mode(LoadMode::TEXT))
.unwrap_or_else(|err| panic!("{err}"));
state
.call_async(0, 0)
.await
.unwrap_or_else(|err| panic!("{err}"));
});
rt.await;
task.await.unwrap_or_else(|err| panic!("{err}"));
}
#[test]
async fn ipaddr() {
run_lua(luaify!({
let net = require("lb:net");
print(net.ipaddr("127.0.0.1"));
}))
.await
}

View File

@@ -1,35 +0,0 @@
use lb::runtime;
use luaify::luaify;
use luajit::{Chunk, LoadMode};
use tokio::test;
async fn run_lua(s: &'static str) {
let rt = runtime::Builder::new().build().unwrap();
let task = rt.spawn(async move |state| {
println!("executing test chunk: {s}");
state
.load(Chunk::new(s).mode(LoadMode::TEXT))
.unwrap_or_else(|err| panic!("{err}"));
state
.call_async(0, 0)
.await
.unwrap_or_else(|err| panic!("{err}"));
});
rt.await;
task.await.unwrap_or_else(|err| panic!("{err}"));
}
#[test]
async fn task_test() {
run_lua(luaify!({
let thing = spawn(|| {
print("spawn callback!!!!!!!!!!!!!");
});
print("thing is", thing);
//
}))
.await
}

View File

@@ -1,11 +1,19 @@
[package] [package]
name = "luaffi" name = "luaffi"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[features]
option_ref_abi = []
option_string_abi = []
[dependencies] [dependencies]
bstr = "1.12.0" bstr = "1.12.0"
luaffi_impl = { version = "0.1.0", path = "../luaffi_impl" } luaffi_impl = { path = "../luaffi_impl" }
luaify = { version = "0.1.0", path = "../luaify" } luaify = { path = "../luaify" }
rustc-hash = "2.1.1" rustc-hash = "2.1.1"
simdutf8 = "0.1.5" simdutf8 = "0.1.5"

View File

@@ -177,6 +177,15 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
self self
} }
fn require_owned() -> bool {
// future always requires full ownership of itself even if it's a "temporary", because we
// must yield a full cdata to the runtime not a cdata containing a pointer to the future. if
// this is set to false, postlude might receive a reference lua_future cdata instead of a
// full lua_future cdata, and the runtime might incorrectly read the pointer value as
// lua_future itself (it does not dereference it).
true
}
fn postlude(ret: &str) -> impl Display { fn postlude(ret: &str) -> impl Display {
// When returning a future from Rust to Lua, yield it immediately to the runtime which will // When returning a future from Rust to Lua, yield it immediately to the runtime which will
// poll it to completion in the background, then take the fulfilled value once the thread // poll it to completion in the background, then take the fulfilled value once the thread

View File

@@ -10,11 +10,11 @@ use std::{
pub mod stub_types { pub mod stub_types {
pub struct any; pub struct any;
pub struct nil; pub struct nil;
pub struct boolean; pub type boolean = bool;
pub struct lightuserdata; pub struct lightuserdata;
pub struct number; pub struct number;
pub struct integer; pub struct integer;
pub struct string; pub type string = String;
pub struct table; pub struct table;
pub struct function; pub struct function;
pub struct userdata; pub struct userdata;

View File

@@ -1,3 +1,4 @@
---@diagnostic disable
local LUA_REFNIL = -1 -- lib_aux.c local LUA_REFNIL = -1 -- lib_aux.c
local FREELIST_REF = 0 local FREELIST_REF = 0

View File

@@ -11,13 +11,13 @@ use std::{
mem, mem,
}; };
pub mod future;
pub mod string;
#[doc(hidden)] #[doc(hidden)]
#[path = "./internal.rs"] #[path = "./internal.rs"]
pub mod __internal; pub mod __internal;
pub mod future;
pub mod option;
pub mod result; pub mod result;
pub mod string;
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be // Dummy function to ensure that strings passed to Rust via wrapper objects will not be
// garbage-collected until the end of the function (used in string.rs when string marshalling is // garbage-collected until the end of the function (used in string.rs when string marshalling is
@@ -171,7 +171,7 @@ impl Registry {
self self
} }
pub fn done(&self) -> String { pub fn build(&self) -> String {
self.to_string() self.to_string()
} }
} }
@@ -345,12 +345,12 @@ impl<'r> MetatypeBuilder<'r> {
) -> &mut Self { ) -> &mut Self {
write!(self.lua, "__idx.{name} = ").unwrap(); write!(self.lua, "__idx.{name} = ").unwrap();
f(&mut MetatypeMethodBuilder::new(self)); f(&mut MetatypeMethodBuilder::new(self));
write!(self.lua, "; ").unwrap(); writeln!(self.lua, ";").unwrap();
self self
} }
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self { pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
write!(self.lua, "__idx.{name} = {value}; ").unwrap(); writeln!(self.lua, "__idx.{name} = {value};").unwrap();
self self
} }
@@ -361,12 +361,12 @@ impl<'r> MetatypeBuilder<'r> {
) -> &mut Self { ) -> &mut Self {
write!(self.lua, "__mt.__{name} = ").unwrap(); write!(self.lua, "__mt.__{name} = ").unwrap();
f(&mut MetatypeMethodBuilder::new(self)); f(&mut MetatypeMethodBuilder::new(self));
write!(self.lua, "; ").unwrap(); writeln!(self.lua, ";").unwrap();
self self
} }
pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self { pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
write!(self.lua, "__mt.__{name} = {value}; ").unwrap(); writeln!(self.lua, "__mt.__{name} = {value};").unwrap();
self self
} }
} }
@@ -417,6 +417,10 @@ pub unsafe trait IntoFfi: Sized {
} }
} }
fn require_owned() -> bool {
true
}
fn postlude(_ret: &str) -> impl Display { fn postlude(_ret: &str) -> impl Display {
"" ""
} }
@@ -481,12 +485,17 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
self self
} }
pub fn param_str(&mut self, name: impl Display) -> &mut Self { pub fn param_str(
// fast-path for &str and &[u8]-like parameters &mut self,
name: impl Display,
allow_nil: bool,
check_utf8: bool,
) -> &mut Self {
// fast-path for &[u8] and &str-like parameters
// //
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len` // this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a // arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
// temporary cdata to pass the string and its length in one argument // temporary cdata to pass the string and its length in one argument.
let Self { let Self {
lparams, lparams,
cparams, cparams,
@@ -495,22 +504,26 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
.. ..
} = self; } = self;
let param_ptr = <*const u8>::cdecl("ptr"); let param_ptr = <*const u8>::cdecl(&name);
let param_len = usize::cdecl("len"); let param_len = usize::cdecl(format!("{name}_len"));
(!lparams.is_empty()).then(|| lparams.push_str(", ")); (!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", ")); (!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", ")); (!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap(); write!(lparams, "{name}").unwrap();
write!(cparams, "{param_ptr}, {param_len}",).unwrap(); write!(cparams, "{param_ptr}, {param_len}").unwrap();
write!(cargs, "{name}, __{name}_len").unwrap(); write!(cargs, "{name}, __{name}_len").unwrap();
write!(prelude, "local __{name}_len = 0; ").unwrap(); write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").unwrap();
write!( write!(prelude, r#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
prelude, write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
r#"if {name} ~= nil then assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); __{name}_len = #{name}; end; "# if check_utf8 {
) write!(prelude, r#"assert(__C.{IS_UTF8_FN}({name}, __{name}_len), "argument '{name}' must be a valid utf-8 string"); "#).unwrap();
.unwrap(); }
if !allow_nil {
write!(prelude, r#"else return error("string expected in argument '{name}', got " .. type({name})); "#).unwrap();
}
write!(prelude, r#"end; "#).unwrap();
self self
} }
@@ -545,21 +558,21 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
if T::Into::ty() == TypeType::Void { if T::Into::ty() == TypeType::Void {
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap(); write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
} else { } else {
let check = T::postlude("__res"); let check = T::postlude("__ret");
write!(lua, "local __res = __C.{func}({cargs}); ").unwrap(); write!(lua, "local __ret = __C.{func}({cargs}); ").unwrap();
write!(lua, "{check}{postlude}return __res; end").unwrap(); write!(lua, "{check}{postlude}return __ret; end").unwrap();
} }
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap(); writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
} }
FfiReturnConvention::ByOutParam => { FfiReturnConvention::ByOutParam => {
let ct = T::Into::name(); let ct = T::Into::name();
let check = T::postlude("__res"); let check = T::postlude("__out");
write!(lua, "local __res = __new(__ct.{ct}); __C.{func}(__res").unwrap(); write!(lua, "local __out = __new(__ct.{ct}); __C.{func}(__out").unwrap();
if !cargs.is_empty() { if !cargs.is_empty() {
write!(lua, ", {cargs}").unwrap(); write!(lua, ", {cargs}").unwrap();
} }
write!(lua, "); {check}{postlude}return __res; end").unwrap(); write!(lua, "); {check}{postlude}return __out; end").unwrap();
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("out")).unwrap(); write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("out")).unwrap();
if !cparams.is_empty() { if !cparams.is_empty() {
write!(cdef, ", {cparams}").unwrap(); write!(cdef, ", {cparams}").unwrap();
@@ -844,7 +857,9 @@ macro_rules! impl_ptr_intoabi {
impl_ptr_intoabi!(*const T); impl_ptr_intoabi!(*const T);
impl_ptr_intoabi!(*mut T); impl_ptr_intoabi!(*mut T);
#[cfg(feature = "option_ref_abi")] // disabled because it conflicts with the generic Option<T> impl
impl_ptr_intoabi!(Option<&'static T>); impl_ptr_intoabi!(Option<&'static T>);
#[cfg(feature = "option_ref_abi")]
impl_ptr_intoabi!(Option<&'static mut T>); impl_ptr_intoabi!(Option<&'static mut T>);
// //

View File

@@ -0,0 +1,84 @@
use crate::{
__internal::{disp, display},
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType,
};
use std::{ffi::c_int, fmt::Display};
#[repr(C)]
#[allow(non_camel_case_types)]
pub enum lua_option<T> {
None, // __tag = 0
Some(T), // __tag = 1
}
unsafe impl<T: Type> Type for lua_option<T> {
fn name() -> impl Display {
display!("option__{}", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("struct {} {name}", Self::name())
}
fn build(b: &mut TypeBuilder) {
b.cdef::<Self>();
}
}
unsafe impl<T: Type> Cdef for lua_option<T> {
fn build(b: &mut CdefBuilder) {
b.field::<c_int>("__tag");
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
}
}
unsafe impl<T: IntoFfi> IntoFfi for Option<T> {
type Into = lua_option<T::Into>;
fn convert(self) -> Self::Into {
match self {
Some(value) => lua_option::Some(T::convert(value)),
None => lua_option::None,
}
}
fn require_owned() -> bool {
// lua_option is only used to transmit information about whether we have a value or not and
// is forgotten immediately after use, so there is no need for an owned option
false
}
fn postlude(ret: &str) -> impl Display {
disp(move |f| {
write!(f, "if {ret}.__tag ~= 0 then ")?;
match T::Into::ty() {
TypeType::Void => write!(f, "{ret} = nil; ")?, // for void options, we don't have a __value
TypeType::Primitive => {
// can always copy primitives to stack
write!(f, "{ret} = {ret}.__value; {}", T::postlude(ret))?;
}
TypeType::Aggregate => {
let ct = T::Into::name();
if T::require_owned() {
// inner value requires ownership; copy it into its own cdata and forget
// option.
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
write!(f, "{}", T::postlude(ret))?;
} else {
// inner value is a "temporary" like an option itself and doesn't require
// full ownership of itself. we just need to keep the option object alive
// until its postlude completes.
write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
write!(f, "do {}end; ", T::postlude(ret))?;
write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original option alive
}
}
}
write!(f, "else {ret} = nil; end; ")
})
}
}

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
__internal::{disp, display}, __internal::{disp, display},
Cdef, CdefBuilder, IntoFfi, Type, TypeBuilder, TypeType, Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType,
string::{DROP_BUFFER_FN, lua_buffer}, string::{DROP_BUFFER_FN, lua_buffer},
}; };
use std::{ffi::c_int, fmt::Display}; use std::{ffi::c_int, fmt::Display};
@@ -49,22 +49,44 @@ unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
} }
} }
fn require_owned() -> bool {
// lua_result is only used to transmit information about whether an operation succeeded or
// not and is forgotten immediately after use, so there is no need for an owned result
false
}
fn postlude(ret: &str) -> impl Display { fn postlude(ret: &str) -> impl Display {
disp(move |f| { disp(move |f| {
let ct = T::Into::name();
write!(f, "if {ret}.__tag ~= 0 then ")?; write!(f, "if {ret}.__tag ~= 0 then ")?;
match T::Into::ty() { match T::Into::ty() {
TypeType::Void => write!(f, "{ret} = nil; "), // for void results, we don't have a __value TypeType::Void => write!(f, "{ret} = nil; ")?, // for void results, we don't have a __value
TypeType::Primitive => write!(f, "{ret} = {ret}.__value; "), TypeType::Primitive => {
TypeType::Aggregate => write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); "), // can always copy primitives to stack
}?; write!(f, "{ret} = {ret}.__value; {}", T::postlude(ret))?;
}
TypeType::Aggregate => {
let ct = T::Into::name();
if T::require_owned() {
// inner value requires ownership; copy it into its own cdata and forget
// result.
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
write!(f, "{}", T::postlude(ret))?; write!(f, "{}", T::postlude(ret))?;
} else {
// inner value is a "temporary" like a result itself and doesn't require
// full ownership of itself. we just need to keep the result object alive
// until its postlude completes.
write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
write!(f, "do {}end; ", T::postlude(ret))?;
write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original result alive
}
}
}
write!( write!(
f, f,
"else \ "else \
local __{ret}_msg = __intern({ret}.__err.__ptr, {ret}.__err.__len); \ local {ret}_err = __intern({ret}.__err.__ptr, {ret}.__err.__len); \
__C.{DROP_BUFFER_FN}({ret}.__err); \ __C.{DROP_BUFFER_FN}({ret}.__err); \
return error(__{ret}_msg); \ return error({ret}_err); \
end; " end; "
) )
}) })

View File

@@ -4,7 +4,7 @@ use crate::{
}; };
use bstr::{BStr, BString}; use bstr::{BStr, BString};
use luaffi_impl::{cdef, metatype}; use luaffi_impl::{cdef, metatype};
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice}; use std::{fmt::Display, mem::ManuallyDrop, slice};
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8"; pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer"; pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
@@ -42,6 +42,7 @@ impl lua_buf {
} }
} }
#[cfg(feature = "option_string_abi")]
pub(crate) fn null() -> Self { pub(crate) fn null() -> Self {
Self { Self {
__ptr: ptr::null(), __ptr: ptr::null(),
@@ -71,6 +72,7 @@ impl lua_buffer {
} }
} }
#[cfg(feature = "option_string_abi")]
pub(crate) fn null() -> Self { pub(crate) fn null() -> Self {
Self { Self {
__ptr: ptr::null_mut(), __ptr: ptr::null_mut(),
@@ -154,8 +156,14 @@ unsafe impl IntoFfi for &'static [u8] {
lua_buf::new(self) lua_buf::new(self)
} }
fn require_owned() -> bool {
// lua_buf is only used to have its contents interned then forgotten immediately; no need
// for ownership of it
false
}
fn postlude(ret: &str) -> impl Display { fn postlude(ret: &str) -> impl Display {
display!("{ret} = __intern({ret}.__ptr, {ret}.__len)") display!("{ret} = __intern({ret}.__ptr, {ret}.__len); ")
} }
} }
@@ -166,9 +174,15 @@ unsafe impl IntoFfi for Vec<u8> {
lua_buffer::new(self) lua_buffer::new(self)
} }
fn require_owned() -> bool {
// lua_buffer is only used to have its contents interned then forgotten immediately; no need
// for ownership of it
false
}
fn postlude(ret: &str) -> impl Display { fn postlude(ret: &str) -> impl Display {
display!( display!(
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; " "local {ret}_buf = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}({ret}_buf); "
) )
} }
} }
@@ -216,6 +230,13 @@ impl_into_via!(&'static str, &'static BStr);
impl_into_via!(BString, Vec<u8>); impl_into_via!(BString, Vec<u8>);
impl_into_via!(String, BString); impl_into_via!(String, BString);
// `Option<String>: From/IntoFfi` isn't implemented because it conflicts with the generic
// `Option<T>: From/IntoFfi` impl and rust doesn't have specialisation yet (and probably not anytime
// soon). this is fine for now because we have specialisation for string-like parameters implemented
// in the #[metatype] macro already, and string returns wrapped in `Option<T>` isn't much additional
// overhead.
#[cfg(feature = "option_string_abi")]
mod impl_option_string {
macro_rules! impl_optional_from { macro_rules! impl_optional_from {
($ty:ty) => { ($ty:ty) => {
unsafe impl<'s> FromFfi for Option<$ty> { unsafe impl<'s> FromFfi for Option<$ty> {
@@ -269,3 +290,4 @@ impl_optional_into!(&'static str, lua_buf::null());
impl_optional_into!(Vec<u8>, lua_buffer::null()); impl_optional_into!(Vec<u8>, lua_buffer::null());
impl_optional_into!(BString, lua_buffer::null()); impl_optional_into!(BString, lua_buffer::null());
impl_optional_into!(String, lua_buffer::null()); impl_optional_into!(String, lua_buffer::null());
}

View File

@@ -1,7 +1,11 @@
[package] [package]
name = "luaffi_impl" name = "luaffi_impl"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[lib] [lib]
proc-macro = true proc-macro = true

View File

@@ -24,7 +24,7 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
let mod_name = format_ident!("__{name}_cdef"); let mod_name = format_ident!("__{name}_cdef");
Ok(quote!( Ok(quote_spanned!(name.span() =>
#[repr(C)] #[repr(C)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#item #item

View File

@@ -1,5 +1,6 @@
use crate::utils::{ use crate::utils::{
ffi_crate, is_primitivelike, is_unit, pat_ident, syn_assert, syn_error, ty_name, StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
syn_assert, syn_error, ty_name,
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote, quote_spanned}; use quote::{ToTokens, format_ident, quote, quote_spanned};
@@ -16,7 +17,7 @@ pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
let impls = generate_impls(&mut imp)?; let impls = generate_impls(&mut imp)?;
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?); let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
Ok(quote!( Ok(quote_spanned!(imp.self_ty.span() =>
#imp #imp
#[doc(hidden)] #[doc(hidden)]
@@ -29,12 +30,27 @@ pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
)) ))
} }
struct Registry {
ty: Ident,
shims: Vec<ImplItemFn>,
build: Vec<TokenStream>,
}
impl Registry {
fn new(ty: Ident) -> Self {
Self {
ty,
shims: vec![],
build: vec![],
}
}
}
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> { fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ffi = ffi_crate(); let ffi = ffi_crate();
let ty = imp.self_ty.clone(); let ty = imp.self_ty.clone();
let ty_name = ty_name(&ty)?; let ty_name = ty_name(&ty)?;
let mut ffi_funcs = FfiRegistry::new(ty_name.clone()); let mut registry = Registry::new(ty_name.clone());
let mut lua_funcs = LuaRegistry::new(ty_name.clone());
let mut mms = HashSet::new(); let mut mms = HashSet::new();
let mut lua_drop = None; let mut lua_drop = None;
@@ -47,7 +63,7 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
); );
} }
add_ffi_function(&mut ffi_funcs, &func)?; add_ffi_function(&mut registry, &func)?;
} }
for func in get_lua_functions(imp)? { for func in get_lua_functions(imp)? {
@@ -59,38 +75,33 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
); );
} }
if func.attrs.metamethod == Some(Metamethod::Gc) { if let Some(Metamethod::Gc) = func.attrs.metamethod {
lua_drop = Some(func); lua_drop = Some(func);
} else { } else {
add_lua_function(&mut lua_funcs, &func)?; add_lua_function(&mut registry, &func)?;
} }
} }
if !mms.contains(&Metamethod::New) { if !mms.contains(&Metamethod::New) {
inject_fallback_new(&mut lua_funcs)?; inject_fallback_new(&mut registry)?;
} }
inject_merged_drop(&mut ffi_funcs, lua_drop.as_ref())?; inject_merged_drop(&mut registry, lua_drop.as_ref())?;
let ffi_shims = &ffi_funcs.shims; let shims = &registry.shims;
let ffi_build = &ffi_funcs.build; let build = &registry.build;
let lua_build = &lua_funcs.build; let exports = generate_ffi_exports(&registry)?;
let ffi_exports = generate_ffi_exports(&ffi_funcs)?;
Ok(quote! { Ok(quote_spanned!(ty.span() =>
impl #ty { #(#ffi_shims)* } impl #ty { #(#shims)* }
unsafe impl #ffi::Metatype for #ty { unsafe impl #ffi::Metatype for #ty {
type Target = Self; type Target = Self;
fn build(b: &mut #ffi::MetatypeBuilder) { #(#build)* }
fn build(b: &mut #ffi::MetatypeBuilder) {
#(#ffi_build)*
#(#lua_build)*
}
} }
#ffi_exports #exports
}) ))
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -201,9 +212,15 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
&& let Some(ref abi) = abi.name && let Some(ref abi) = abi.name
&& abi.value() == "Lua-C" && abi.value() == "Lua-C"
{ {
syn_assert!(
func.sig.generics.params.len() == 0,
func.sig.generics,
"cannot be generic"
);
func.sig.abi = None; func.sig.abi = None;
let params = func let params: Vec<_> = func
.sig .sig
.inputs .inputs
.iter() .iter()
@@ -221,9 +238,29 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
ReturnType::Type(_, ref ty) => (**ty).clone(), ReturnType::Type(_, ref ty) => (**ty).clone(),
}; };
for param in params.iter() {
// double underscores are reserved for generated glue code
syn_assert!(
!pat_ident(&param.pat)?.to_string().starts_with("__"),
param.pat,
"parameter names should not start with `__`"
);
// lifetime should be determined by the caller (lua)
if let Type::Reference(ref ty) = *param.ty {
syn_assert!(
ty.lifetime.is_none(),
ty.lifetime,
"lifetime should be determined by the caller"
);
}
}
let attrs = parse_ffi_function_attrs(&mut func.attrs)?; let attrs = parse_ffi_function_attrs(&mut func.attrs)?;
attrs.metamethod.map(|mm| document_metamethod(func, mm)); attrs.metamethod.map(|mm| document_metamethod(func, mm));
document_ffi_function(func);
funcs.push(FfiFunction { funcs.push(FfiFunction {
name: func.sig.ident.clone(), name: func.sig.ident.clone(),
is_async: func.sig.asyncness.is_some(), is_async: func.sig.asyncness.is_some(),
@@ -259,14 +296,26 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
Ok(parsed) Ok(parsed)
} }
#[derive(Debug, Clone, Copy)]
enum FfiParameterType { enum FfiParameterType {
Default, Default,
StringLike(StringLike),
OptionStringLike(StringLike),
} }
fn get_ffi_param_type(_ty: &Type) -> FfiParameterType { fn get_ffi_param_type(ty: &Type) -> FfiParameterType {
if let Some(str) = is_stringlike(ty) {
FfiParameterType::StringLike(str)
} else if let Some(arg) = is_optionlike(ty)
&& let Some(str) = is_stringlike(arg)
{
FfiParameterType::OptionStringLike(str)
} else {
FfiParameterType::Default FfiParameterType::Default
} }
}
#[derive(Debug, Clone, Copy)]
enum FfiReturnType { enum FfiReturnType {
Void, Void,
ByValue, ByValue,
@@ -296,23 +345,7 @@ fn get_ffi_ret_type(ty: &Type) -> FfiReturnType {
} }
} }
struct FfiRegistry { fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
ty: Ident,
shims: Vec<ImplItemFn>,
build: Vec<TokenStream>,
}
impl FfiRegistry {
fn new(ty: Ident) -> Self {
Self {
ty,
shims: vec![],
build: vec![],
}
}
}
fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<()> {
let ffi = ffi_crate(); let ffi = ffi_crate();
let ty = &registry.ty; let ty = &registry.ty;
let func_name = &func.name; let func_name = &func.name;
@@ -364,6 +397,41 @@ fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<()
b.param::<#func_param>(#name); b.param::<#func_param>(#name);
)); ));
} }
ty @ (FfiParameterType::StringLike(str) | FfiParameterType::OptionStringLike(str)) => {
let shim_param_len = format_ident!("arg{i}_len");
shim_params.push(quote_spanned!(func_param.span() =>
#shim_param: ::std::option::Option<&::std::primitive::u8>,
#shim_param_len: ::std::primitive::usize
));
let allow_nil = matches!(ty, FfiParameterType::OptionStringLike(_));
let check_utf8 = matches!(str, StringLike::Str);
let mut func_arg = quote_spanned!(func_param.span() =>
#shim_param.map(|s| ::std::slice::from_raw_parts(s, #shim_param_len))
);
func_arg = match str {
StringLike::SliceU8 => func_arg,
StringLike::BStr => {
quote_spanned!(func_param.span() => #func_arg.map(::bstr::BStr::new))
}
StringLike::Str => {
quote_spanned!(func_param.span() => #func_arg.map(|s| {
::std::debug_assert!(::std::str::from_utf8(s).is_ok());
::std::str::from_utf8_unchecked(s)
}))
}
};
if !allow_nil {
func_arg = quote_spanned!(func_param.span() => {
let arg = #func_arg;
::std::debug_assert!(arg.is_some());
arg.unwrap_unchecked()
});
}
func_args.push(func_arg);
build.push(quote_spanned!(param.pat.span() =>
b.param_str(#name, #allow_nil, #check_utf8);
));
}
} }
} }
@@ -392,8 +460,12 @@ fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<()
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByOutParam <#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByOutParam
)); ));
shim_params.insert(0, quote!(out: *mut #shim_ret)); shim_params.insert(0, quote_spanned!(func_ret.span() => out: *mut #shim_ret));
(shim_body, shim_ret) = (quote!(::std::ptr::write(out, #shim_body)), quote!(()));
(shim_body, shim_ret) = (
quote_spanned!(func_ret.span() => ::std::ptr::write(out, #shim_body)),
quote_spanned!(func_ret.span() => ()),
);
} }
}; };
} }
@@ -417,19 +489,23 @@ fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<()
)); ));
registry.build.push(match func.attrs.metamethod { registry.build.push(match func.attrs.metamethod {
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });), Some(ref mm) => quote_spanned!(func_name.span() =>
None => quote!(b.index(#lua_name, |b| { #(#build)* });), b.metatable(#mm, |b| { #(#build)* });
),
None => quote_spanned!(func_name.span() =>
b.index(#lua_name, |b| { #(#build)* });
),
}); });
registry.shims.push(parse_quote_spanned!(func_name.span() => registry.shims.push(parse_quote_spanned!(func_name.span() =>
#[unsafe(export_name = #c_name)] #[unsafe(export_name = #c_name)]
unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { #shim_body } unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { unsafe { #shim_body } }
)); ));
Ok(()) Ok(())
} }
fn generate_ffi_exports(registry: &FfiRegistry) -> Result<TokenStream> { fn generate_ffi_exports(registry: &Registry) -> Result<TokenStream> {
let ty = &registry.ty; let ty = &registry.ty;
let names = registry.shims.iter().map(|f| &f.sig.ident); let names = registry.shims.iter().map(|f| &f.sig.ident);
@@ -464,6 +540,12 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
&& let Some(ref abi) = abi.name && let Some(ref abi) = abi.name
&& abi.value() == "Lua" && abi.value() == "Lua"
{ {
syn_assert!(
func.sig.generics.params.len() == 0,
func.sig.generics,
"cannot be generic"
);
let mut params: Vec<_> = func let mut params: Vec<_> = func
.sig .sig
.inputs .inputs
@@ -485,6 +567,8 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
let attrs = parse_lua_function_attrs(&mut func.attrs)?; let attrs = parse_lua_function_attrs(&mut func.attrs)?;
attrs.metamethod.map(|mm| document_metamethod(func, mm)); attrs.metamethod.map(|mm| document_metamethod(func, mm));
document_lua_function(func);
funcs.push(LuaFunction { funcs.push(LuaFunction {
name: func.sig.ident.clone(), name: func.sig.ident.clone(),
params, params,
@@ -506,9 +590,10 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
// documentation generation // documentation generation
func.sig.abi = None; func.sig.abi = None;
func.attrs.push(parse_quote!(#[allow(unused)])); func.attrs.push(parse_quote!(#[allow(unused)]));
func.block = parse_quote!({ func.block.stmts.clear();
func.block.stmts.push(parse_quote!(
::std::unreachable!("cannot call lua function from rust"); ::std::unreachable!("cannot call lua function from rust");
}); ));
let inputs = &mut func.sig.inputs; let inputs = &mut func.sig.inputs;
let output = &mut func.sig.output; let output = &mut func.sig.output;
@@ -584,63 +669,27 @@ fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAtt
Ok(parsed) Ok(parsed)
} }
struct LuaRegistry { fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
ty: Ident,
build: Vec<TokenStream>,
}
impl LuaRegistry {
fn new(ty: Ident) -> Self {
Self { ty, build: vec![] }
}
}
fn add_lua_function(registry: &mut LuaRegistry, func: &LuaFunction) -> Result<()> {
let ffi = ffi_crate(); let ffi = ffi_crate();
let luaify = quote!(#ffi::__internal::luaify!); let luaify = quote!(#ffi::__internal::luaify!);
let name = func.name.unraw().to_string(); let func_name = &func.name;
let params = &func.params; let params = &func.params;
let body = &func.body; let body = &func.body;
let name = func_name.unraw().to_string();
registry.build.push(match func.attrs.metamethod { registry.build.push(match func.attrs.metamethod {
Some(ref mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));), Some(ref mm) => quote_spanned!(func_name.span() =>
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));), b.metatable_raw(#mm, #luaify(|#(#params),*| #body));
),
None => quote_spanned!(func_name.span() =>
b.index_raw(#name, #luaify(|#(#params),*| #body));
),
}); });
Ok(()) Ok(())
} }
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) { fn inject_fallback_new(registry: &mut Registry) -> Result<()> {
let s = match method {
Metamethod::Eq => "This is a metamethod which is called by the `==` operator.".into(),
Metamethod::Len => "This is a metamethod which is called by the `#` operator.".into(),
Metamethod::Lt => "This is a metamethod which is called by the `<` operator.".into(),
Metamethod::Le => "This is a metamethod which is called by the `<=` operator.".into(),
Metamethod::Concat => "This is a metamethod which is called by the `..` operator.".into(),
Metamethod::Add => "This is a metamethod which is called by the `+` operator.".into(),
Metamethod::Sub => "This is a metamethod which is called by the `-` operator.".into(),
Metamethod::Mul => "This is a metamethod which is called by the `*` operator.".into(),
Metamethod::Div => "This is a metamethod which is called by the `/` operator.".into(),
Metamethod::Mod => "This is a metamethod which is called by the `%` operator.".into(),
Metamethod::Pow => "This is a metamethod which is called by the `^` operator.".into(),
Metamethod::Unm => "This is a metamethod which is called by the `-` operator.".into(),
Metamethod::ToString => {
"This is a metamethod which can be called by the `tostring` built-in function.".into()
}
Metamethod::Pairs => {
"This is a metamethod which can be called by the `pairs` built-in function.".into()
}
Metamethod::Ipairs => {
"This is a metamethod which can be called by the `ipairs` built-in function.".into()
}
_ => format!("This is a metamethod and cannot be called directly."),
};
func.attrs.push(parse_quote!(#[doc = ""]));
func.attrs.push(parse_quote!(#[doc = #s]));
}
fn inject_fallback_new(registry: &mut LuaRegistry) -> Result<()> {
let ty = &registry.ty; let ty = &registry.ty;
let lua = format!( let lua = format!(
r#"function() error("type '{}' has no constructor"); end"#, r#"function() error("type '{}' has no constructor"); end"#,
@@ -654,7 +703,7 @@ fn inject_fallback_new(registry: &mut LuaRegistry) -> Result<()> {
Ok(()) Ok(())
} }
fn inject_merged_drop(registry: &mut FfiRegistry, lua: Option<&LuaFunction>) -> Result<()> { fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Result<()> {
let ffi = ffi_crate(); let ffi = ffi_crate();
let luaify = quote!(#ffi::__internal::luaify!); let luaify = quote!(#ffi::__internal::luaify!);
let ty = &registry.ty; let ty = &registry.ty;
@@ -711,3 +760,49 @@ fn inject_merged_drop(registry: &mut FfiRegistry, lua: Option<&LuaFunction>) ->
Ok(()) Ok(())
} }
fn document_ffi_function(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span class="stab" title="This function is implemented in Rust and called via FFI." style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">FFI</span>"#
]));
}
fn document_lua_function(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span class="stab" title="This function is implemented in Lua." style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Lua</span>"#
]));
}
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
let s = match method {
Metamethod::Eq => "This is a metamethod which is called by the `==` operator.".into(),
Metamethod::Len => "This is a metamethod which is called by the `#` operator.".into(),
Metamethod::Lt => "This is a metamethod which is called by the `<` operator.".into(),
Metamethod::Le => "This is a metamethod which is called by the `<=` operator.".into(),
Metamethod::Concat => "This is a metamethod which is called by the `..` operator.".into(),
Metamethod::Add => "This is a metamethod which is called by the `+` operator.".into(),
Metamethod::Sub => "This is a metamethod which is called by the `-` operator.".into(),
Metamethod::Mul => "This is a metamethod which is called by the `*` operator.".into(),
Metamethod::Div => "This is a metamethod which is called by the `/` operator.".into(),
Metamethod::Mod => "This is a metamethod which is called by the `%` operator.".into(),
Metamethod::Pow => "This is a metamethod which is called by the `^` operator.".into(),
Metamethod::Unm => "This is a metamethod which is called by the `-` operator.".into(),
Metamethod::ToString => {
"This is a metamethod which can be called by the `tostring` built-in function.".into()
}
Metamethod::Pairs => {
"This is a metamethod which can be called by the `pairs` built-in function.".into()
}
Metamethod::Ipairs => {
"This is a metamethod which can be called by the `ipairs` built-in function.".into()
}
_ => format!("This is a metamethod and cannot be called directly."),
};
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span class="stab" title="This function is a metamethod." style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Metamethod</span>"#
]));
func.attrs.push(parse_quote!(#[doc = ""]));
func.attrs.push(parse_quote!(#[doc = #s]));
}

View File

@@ -57,13 +57,13 @@ pub fn is_unit(ty: &Type) -> bool {
pub fn is_primitivelike(ty: &Type) -> bool { pub fn is_primitivelike(ty: &Type) -> bool {
match ty { match ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => true, // unit type Type::Tuple(tuple) if tuple.elems.is_empty() => return true, // unit type
Type::Reference(_) | Type::Ptr(_) => true, Type::Reference(_) | Type::Ptr(_) => return true,
Type::Paren(paren) => is_primitivelike(&paren.elem), Type::Paren(paren) => return is_primitivelike(&paren.elem),
Type::Path(path) => { Type::Path(path) => {
if let Some(name) = path.path.get_ident() { if let Some(name) = path.path.get_ident() {
matches!( return matches!(
format!("{name}").as_str(), name.to_string().as_str(),
"bool" "bool"
| "u8" | "u8"
| "u16" | "u16"
@@ -94,11 +94,66 @@ pub fn is_primitivelike(ty: &Type) -> bool {
| "c_size_t" | "c_size_t"
| "c_ssize_t" | "c_ssize_t"
| "c_ptrdiff_t" | "c_ptrdiff_t"
) );
} else { }
}
_ => {}
}
false false
} }
#[derive(Debug, Clone, Copy)]
pub enum StringLike {
SliceU8,
Str,
BStr,
} }
_ => false,
pub fn is_stringlike(ty: &Type) -> Option<StringLike> {
if let Type::Reference(ty) = ty
&& ty.mutability.is_none()
&& ty.lifetime.is_none()
{
match *ty.elem {
Type::Slice(ref slice) => {
// match &[u8]
if let Type::Path(ref path) = *slice.elem
&& let Some(name) = path.path.get_ident()
&& name == "u8"
{
return Some(StringLike::SliceU8);
}
}
Type::Path(ref path) => {
// match &str or &BStr
if let Some(name) = path.path.get_ident() {
match name.to_string().as_str() {
"str" => return Some(StringLike::Str),
"BStr" => return Some(StringLike::BStr),
_ => {}
}
}
}
_ => {}
}
}
None
}
pub fn is_optionlike(ty: &Type) -> Option<&Type> {
if let Type::Path(path) = ty
&& path.path.leading_colon.is_none()
&& path.path.segments.len() == 1
&& let Some(segment) = path.path.segments.get(0)
&& segment.ident == "Option"
&& let PathArguments::AngleBracketed(ref angle) = segment.arguments
&& angle.args.len() == 1
&& let Some(GenericArgument::Type(ty)) = angle.args.get(0)
{
Some(ty)
} else {
None
} }
} }

View File

@@ -1,7 +1,11 @@
[package] [package]
name = "luaify" name = "luaify"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[lib] [lib]
proc-macro = true proc-macro = true
@@ -10,3 +14,6 @@ proc-macro = true
proc-macro2 = "1.0.95" proc-macro2 = "1.0.95"
quote = "1.0.40" quote = "1.0.40"
syn = { version = "2.0.103", features = ["full", "visit-mut"] } syn = { version = "2.0.103", features = ["full", "visit-mut"] }
[dev-dependencies]
pretty_assertions = "1.4.1"

View File

@@ -14,6 +14,12 @@ pub fn generate(expr: &Expr) -> Result<TokenStream> {
Ok(f.done()) Ok(f.done())
} }
pub fn generate_chunk(block: &Block) -> Result<TokenStream> {
let mut f = Formatter::default();
generate_block(&mut f, &block, Context::stmt(false))?;
Ok(f.done())
}
#[derive(Default)] #[derive(Default)]
struct Formatter { struct Formatter {
buf: String, buf: String,

View File

@@ -1,4 +1,7 @@
use crate::{generate::generate, transform::transform}; use crate::{
generate::{generate, generate_chunk},
transform::{transform, transform_chunk},
};
use proc_macro::TokenStream as TokenStream1; use proc_macro::TokenStream as TokenStream1;
use quote::ToTokens; use quote::ToTokens;
use syn::parse_macro_input; use syn::parse_macro_input;
@@ -16,3 +19,13 @@ pub fn luaify(input: TokenStream1) -> TokenStream1 {
} }
.into() .into()
} }
#[proc_macro]
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
let mut block = parse_macro_input!(input);
match transform_chunk(&mut block).and_then(|()| generate_chunk(&block)) {
Ok(s) => s,
Err(err) => err.into_compile_error().into_token_stream(),
}
.into()
}

View File

@@ -9,6 +9,12 @@ pub fn transform(expr: &mut Expr) -> Result<()> {
visitor.result visitor.result
} }
pub fn transform_chunk(block: &mut Block) -> Result<()> {
let mut visitor = Visitor::new();
visitor.visit_block_mut(block);
visitor.result
}
#[derive(Debug)] #[derive(Debug)]
struct Visitor { struct Visitor {
result: Result<()>, result: Result<()>,

View File

@@ -1,4 +1,5 @@
use luaify::luaify; use luaify::{luaify, luaify_chunk};
use pretty_assertions::assert_eq;
#[test] #[test]
fn raw_ident() { fn raw_ident() {
@@ -402,3 +403,19 @@ fn length() {
r#"local a,b,c=#a,#b,#c;"# r#"local a,b,c=#a,#b,#c;"#
); );
} }
#[test]
fn chunk() {
assert_eq!(
luaify_chunk!({
if a == b {
c()
} else if b == c {
a()
} else {
d()
}
}),
"if a==b then c();elseif b==c then a();else d();end;"
);
}

View File

@@ -1,7 +1,11 @@
[package] [package]
name = "luajit-sys" name = "luajit-sys"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[lib] [lib]
path = "lib.rs" path = "lib.rs"

View File

@@ -1,7 +1,11 @@
[package] [package]
name = "luajit" name = "luajit"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[features] [features]
runtime = ["luajit-sys/runtime"] runtime = ["luajit-sys/runtime"]
@@ -10,6 +14,6 @@ unwind = ["luajit-sys/unwind"]
[dependencies] [dependencies]
bitflags = { version = "2.9.1", features = ["std"] } bitflags = { version = "2.9.1", features = ["std"] }
bstr = "1.12.0" bstr = "1.12.0"
luaffi = { version = "0.1.0", path = "../luaffi" } luaffi = { path = "../luaffi" }
luajit-sys = { version = "0.1.0", path = "../luajit-sys" } luajit-sys = { path = "../luajit-sys" }
thiserror = "2.0.12" thiserror = "2.0.12"

View File

@@ -25,6 +25,16 @@ pub fn version() -> &'static str {
LUAJIT_VERSION.to_str().unwrap() LUAJIT_VERSION.to_str().unwrap()
} }
/// LuaJIT copyright string.
pub fn copyright() -> &'static str {
LUAJIT_COPYRIGHT.to_str().unwrap()
}
/// LuaJIT URL string.
pub fn url() -> &'static str {
LUAJIT_URL.to_str().unwrap()
}
/// Lua error. /// Lua error.
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[non_exhaustive] #[non_exhaustive]
@@ -1333,8 +1343,7 @@ impl<'s> DerefMut for StackGuard<'s> {
impl<'s> Drop for StackGuard<'s> { impl<'s> Drop for StackGuard<'s> {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(debug_assertions)] if cfg!(debug_assertions) && self.check_overpop {
if self.check_overpop {
let new_size = self.stack.size(); let new_size = self.stack.size();
assert!( assert!(
self.size <= new_size, self.size <= new_size,

View File

@@ -1,2 +1,14 @@
pub use lb::chan;
pub use lb::fs; pub use lb::fs;
pub use lb::net; pub use lb::net;
pub use lb::task;
#[doc(hidden)]
pub fn load_modules(runtime: &mut lb::runtime::Builder) {
// core modules
runtime
.module::<task::lb_tasklib>("lb:task")
.module::<chan::lb_chanlib>("lb:channel")
.module::<fs::lb_fslib>("lb:fs")
.module::<net::lb_netlib>("lb:net");
}

View File

@@ -1,7 +1,9 @@
use clap::Parser; use clap::Parser;
use mimalloc::MiMalloc; use mimalloc::MiMalloc;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use std::{backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, thread}; use std::{
backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, process, thread,
};
use sysexits::ExitCode; use sysexits::ExitCode;
#[global_allocator] #[global_allocator]
@@ -20,18 +22,23 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
}; };
eprint!( eprint!(
"{}:\n{trace}", "{}\n{trace}",
format_args!( format_args!(
"thread '{}' panicked at {location}: {msg}", "thread '{}' panicked at {location}: {msg}",
thread::current().name().unwrap_or("<unnamed>") thread::current().name().unwrap_or("<unnamed>")
) )
.red() .red()
.bold()
); );
eprintln!( eprintln!(
"{}", "{}",
"This is a bug in luby. Please kindly report this at https://git.lua.re/luaneko/luby." format_args!(
"luby should never panic. Please kindly report this bug at {}.",
env!("CARGO_PKG_REPOSITORY")
)
.yellow() .yellow()
.bold()
); );
} }
@@ -45,7 +52,7 @@ struct Args {
#[clap(long, short = 'e', value_name = "CHUNK")] #[clap(long, short = 'e', value_name = "CHUNK")]
eval: Vec<String>, eval: Vec<String>,
/// Libraries to require on startup. /// Libraries to require.
#[clap(long, short = 'l', value_name = "NAME")] #[clap(long, short = 'l', value_name = "NAME")]
lib: Vec<String>, lib: Vec<String>,
@@ -54,29 +61,54 @@ struct Args {
log: tracing::Level, log: tracing::Level,
/// LuaJIT control commands. /// LuaJIT control commands.
#[clap(long, short = 'j', value_name = "CMD=FLAGS")] #[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
jit: Vec<String>, jit: Vec<String>,
/// Number of tokio worker threads. /// Number of worker threads.
#[clap(long, value_name = "THREADS", default_value_t = Self::threads())] #[clap(
long,
short = 'T',
help_heading = "Runtime",
value_name = "THREADS",
default_value_t = Self::threads()
)]
threads: NonZero<usize>, threads: NonZero<usize>,
/// Number of tokio blocking threads. /// Number of blocking threads.
#[clap(long, value_name = "THREADS", default_value_t = Self::blocking_threads())] #[clap(
long,
help_heading = "Runtime",
value_name = "THREADS",
default_value_t = Self::blocking_threads()
)]
blocking_threads: NonZero<usize>, blocking_threads: NonZero<usize>,
/// Enable tokio-console integration. /// Enable tokio-console integration.
#[clap(long)] #[clap(long, help_heading = "Debugging")]
enable_console: bool, enable_console: bool,
/// tokio-console publish address. /// tokio-console publish address.
#[clap( #[clap(
long, long,
help_heading = "Debugging",
value_name = "ADDRESS", value_name = "ADDRESS",
default_value = "127.0.0.1:6669", default_value = "127.0.0.1:6669",
requires = "enable_console" requires = "enable_console"
)] )]
console_addr: SocketAddr, console_addr: SocketAddr,
/// Dump internal data.
#[clap(
long,
help_heading = "Debugging",
value_name = "DATA",
value_parser = ["cdef"]
)]
dump: Vec<String>,
/// Print version.
#[clap(long, short = 'V')]
version: bool,
} }
impl Args { impl Args {
@@ -89,17 +121,14 @@ impl Args {
} }
} }
fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T { fn main() {
move |err| {
eprintln!("{}", err.red());
code.exit()
}
}
fn main() -> Result<(), ExitCode> {
panic::set_hook(Box::new(panic_cb)); panic::set_hook(Box::new(panic_cb));
let args = Args::parse(); let args = Args::parse();
if args.version {
return print_version();
}
init_logger(&args); init_logger(&args);
let tokio = init_tokio(&args); let tokio = init_tokio(&args);
@@ -112,6 +141,25 @@ fn main() -> Result<(), ExitCode> {
}) })
} }
fn print_version() {
println!("luby {}", env!("VERGEN_GIT_DESCRIBE"));
println!("{}\n", env!("CARGO_PKG_HOMEPAGE"));
println!("Compiled with {} -- {}", luajit::version(), luajit::url());
println!(
"Compiled with rustc {} on {} for {}",
env!("VERGEN_RUSTC_SEMVER"),
env!("VERGEN_RUSTC_HOST_TRIPLE"),
env!("VERGEN_CARGO_TARGET_TRIPLE"),
);
}
fn unwrap_exit<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
move |err| {
eprintln!("{}", err.red().bold());
code.exit()
}
}
fn init_logger(args: &Args) { fn init_logger(args: &Args) {
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::{Layer, util::*}; use tracing_subscriber::{Layer, util::*};
@@ -154,12 +202,18 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
.thread_name("luby") .thread_name("luby")
.max_blocking_threads(args.blocking_threads.get()) .max_blocking_threads(args.blocking_threads.get())
.build() .build()
.unwrap_or_else(exit_err(ExitCode::OsErr)) .unwrap_or_else(unwrap_exit(ExitCode::OsErr))
} }
fn init_lua(args: &Args) -> lb::runtime::Runtime { fn init_lua(args: &Args) -> lb::runtime::Runtime {
let rt = lb::runtime::Builder::new(); let mut rt = lb::runtime::Builder::new();
let mut rt = rt.build().unwrap_or_else(exit_err(ExitCode::Software)); luby::load_modules(&mut rt);
if args.dump.iter().find(|s| *s == "cdef").is_some() {
print!("{}", rt.registry());
}
let mut rt = rt.build().unwrap();
for arg in args.jit.iter() { for arg in args.jit.iter() {
let mut s = rt.guard(); let mut s = rt.guard();
@@ -182,7 +236,7 @@ fn init_lua(args: &Args) -> lb::runtime::Runtime {
} }
} }
} }
.unwrap_or_else(exit_err(ExitCode::Usage)); .unwrap_or_else(unwrap_exit(ExitCode::Usage));
} }
rt rt
@@ -190,36 +244,34 @@ fn init_lua(args: &Args) -> lb::runtime::Runtime {
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> { fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
match s { match s {
"p" => Some(("p", "Flspv10")), "p" => Some(("p", "Flspv10")), // default -jp flags
"v" => Some(("v", "-")), "v" => Some(("v", "-")), // default -jv flags
"dump" => Some(("dump", "tirs")), "dump" => Some(("dump", "tirs")), // default -jdump flags
_ => s.split_once('='), _ => s.split_once('='),
} }
} }
async fn main_async(args: Args, state: &mut luajit::State) -> Result<(), ExitCode> { async fn main_async(args: Args, state: &mut luajit::State) {
for ref path in args.path { for ref path in args.path {
let mut s = state.guard(); let mut s = state.guard();
let chunk = match std::fs::read(path) { let chunk = match std::fs::read(path) {
Ok(chunk) => chunk, Ok(chunk) => chunk,
Err(err) => { Err(err) => {
eprintln!("{}", format_args!("{path}: {err}").red()); eprintln!("{}", format_args!("{path}: {err}").red().bold());
ExitCode::NoInput.exit(); ExitCode::NoInput.exit();
} }
}; };
s.load(&luajit::Chunk::new(chunk).path(path)) s.load(&luajit::Chunk::new(chunk).path(path))
.unwrap_or_else(exit_err(ExitCode::NoInput)); .unwrap_or_else(unwrap_exit(ExitCode::NoInput));
if let Err(err) = s.call_async(0, 0).await { if let Err(err) = s.call_async(0, 0).await {
match err.trace() { match err.trace() {
Some(trace) => eprintln!("{}\n{trace}", err.red()), // runtime error Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
None => eprintln!("{}", err.red()), None => eprintln!("{}", err.red().bold()),
} }
ExitCode::DataErr.exit(); process::exit(1);
} }
} }
Ok(())
} }

View File

@@ -1,6 +0,0 @@
local ffi = require("ffi")
local lb = ffi.new("struct lb_core")
print(lb)
lb.spawn("")