Compare commits

...

31 Commits

Author SHA1 Message Date
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
e71d618d10 Add luajit v,p,dump command support 2025-06-25 14:27:43 +10:00
3ac7df004d Allow Runtime to deref to inner State 2025-06-25 14:27:05 +10:00
7e8a655186 Allow opening jitlib in luajit-sys 2025-06-25 14:26:54 +10:00
3ba568dc90 Enable tokio_unstable for task tracing 2025-06-25 14:26:22 +10:00
a295107bac Create channel and task libs 2025-06-25 10:43:00 +10:00
e0898c22a0 Add basic fs lib 2025-06-25 10:40:36 +10:00
69ac13ea47 Rename some ipaddr methods 2025-06-25 01:36:52 +10:00
2352cb0225 Implement async support in metatype 2025-06-25 01:36:43 +10:00
cbf786206d Document all dependencies 2025-06-24 22:49:56 +10:00
9ba9762185 Add derive_more 2025-06-24 22:49:47 +10:00
ee64b22510 Convert lua return types to stubs as well 2025-06-24 22:49:18 +10:00
8c47987a45 Implement basic net module 2025-06-24 22:49:02 +10:00
122ef04b16 Allow documenting of extern "Lua" functions 2025-06-24 22:23:26 +10:00
1aa9b4aa02 Implement IntoFfi for Result 2025-06-24 22:22:58 +10:00
9d5bcc5ef2 Rename Panic to Panics 2025-06-24 21:51:40 +10:00
3dd375b071 Refactor luaffi proc-macro 2025-06-24 19:30:39 +10:00
f8e7b8ae62 Implement IntoFfi for Strings 2025-06-24 13:03:40 +10:00
45db380466 Rename ToFfi to IntoFfi 2025-06-24 11:42:11 +10:00
cadf0a8551 Update luaffi 2025-06-24 10:37:28 +10:00
7548c68c7c Add more documentation on why to use out-param 2025-06-24 10:36:42 +10:00
91e1f33b6c Use double underscores for consistency 2025-06-24 10:36:26 +10:00
a9a067f5a9 Allow returning tuple in expression form 2025-06-24 10:36:11 +10:00
37 changed files with 3155 additions and 794 deletions

2
.cargo/config.toml Normal file
View File

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

631
Cargo.lock generated
View File

@@ -250,12 +250,46 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "camino"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "cc"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"jobserver",
"libc",
"shlex",
]
@@ -370,6 +404,15 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "convert_case"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
@@ -429,6 +472,79 @@ dependencies = [
"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]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.15.0"
@@ -473,6 +589,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "futures-channel"
version = "0.3.31"
@@ -520,7 +645,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"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]]
@@ -529,6 +666,19 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "glob"
version = "0.3.2"
@@ -692,12 +842,119 @@ dependencies = [
"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]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "indexmap"
version = "1.9.3"
@@ -748,6 +1005,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "lazy_static"
version = "1.5.0"
@@ -756,12 +1023,16 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lb"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"camino",
"derive_more",
"luaffi",
"luaify",
"luajit",
"sysexits",
"tokio",
"tracing",
]
[[package]]
@@ -770,6 +1041,18 @@ version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "libloading"
version = "0.8.8"
@@ -790,12 +1073,30 @@ dependencies = [
"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]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "lock_api"
version = "0.4.13"
@@ -814,7 +1115,7 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "luaffi"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bstr",
"luaffi_impl",
@@ -825,7 +1126,7 @@ dependencies = [
[[package]]
name = "luaffi_impl"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"darling",
"proc-macro2",
@@ -835,7 +1136,7 @@ dependencies = [
[[package]]
name = "luaify"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"proc-macro2",
"quote",
@@ -844,7 +1145,7 @@ dependencies = [
[[package]]
name = "luajit"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bitflags",
"bstr",
@@ -855,7 +1156,7 @@ dependencies = [
[[package]]
name = "luajit-sys"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bindgen",
"cc",
@@ -864,7 +1165,7 @@ dependencies = [
[[package]]
name = "luby"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"clap",
"console-subscriber",
@@ -872,9 +1173,11 @@ dependencies = [
"luajit",
"mimalloc",
"owo-colors",
"sysexits",
"tokio",
"tracing",
"tracing-subscriber",
"vergen-git2",
]
[[package]]
@@ -935,7 +1238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"wasi",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
@@ -959,6 +1262,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -968,6 +1277,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.36.7"
@@ -997,9 +1315,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "4.2.1"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec"
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
[[package]]
name = "parking_lot"
@@ -1062,6 +1380,27 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ppv-lite86"
version = "0.2.21"
@@ -1131,6 +1470,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.5"
@@ -1158,7 +1503,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.16",
]
[[package]]
@@ -1226,6 +1571,15 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "rustix"
version = "1.0.7"
@@ -1257,6 +1611,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.219"
@@ -1341,6 +1704,12 @@ dependencies = [
"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]]
name = "strsim"
version = "0.11.1"
@@ -1364,6 +1733,23 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "sysexits"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198f60d1f7f003f168507691e42d082df109ef0f05c6fd006e22528371a5f1b4"
[[package]]
name = "thiserror"
version = "2.0.12"
@@ -1393,6 +1779,49 @@ dependencies = [
"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]]
name = "tokio"
version = "1.45.1"
@@ -1596,6 +2025,35 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "utf8parse"
version = "0.2.2"
@@ -1608,6 +2066,53 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "want"
version = "0.3.1"
@@ -1623,6 +2128,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "which"
version = "8.0.0"
@@ -1817,6 +2331,45 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.26"
@@ -1836,3 +2389,57 @@ dependencies = [
"quote",
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -1,4 +1,5 @@
[workspace]
resolver = "3"
members = [
"crates/lb",
"crates/luaffi",
@@ -8,22 +9,38 @@ members = [
"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]
dev.panic = "abort"
release.panic = "abort"
[package]
name = "luby"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }
clap = { version = "4.5.40", features = ["derive", "env"] }
console-subscriber = "0.4.1"
lb = { version = "0.1.0", path = "crates/lb" }
luajit = { version = "0.1.0", path = "crates/luajit", features = ["runtime"] }
lb = { path = "crates/lb" }
luajit = { path = "crates/luajit", features = ["runtime"] }
mimalloc = "0.1.47"
owo-colors = "4.2.1"
sysexits = "0.9.0"
tokio = { version = "1.45.1", features = ["full", "tracing"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
[build-dependencies]
vergen-git2 = { version = "1.0.7", features = ["cargo", "rustc"] }

View File

@@ -1,10 +1,10 @@
[jobs.test]
command = ["cargo", "test", "--all"]
command = ["cargo", "test", "--workspace"]
need_stdout = true
[jobs.doc]
command = ["cargo", "doc", "--all", "--no-deps"]
command = ["cargo", "doc", "--workspace"]
[jobs.doc-open]
command = ["cargo", "doc", "--all", "--no-deps", "--open"]
command = ["cargo", "doc", "--workspace", "--open"]
on_success = "back"

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,12 +1,20 @@
[package]
name = "lb"
version = "0.1.0"
edition = "2024"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
luaffi = { version = "0.1.0", path = "../luaffi" }
luajit = { version = "0.1.0", path = "../luajit" }
tokio = { version = "1.45.1", features = ["rt", "time", "fs"] }
camino = "1.1.10"
derive_more = { version = "2.0.1", features = ["full"] }
luaffi = { path = "../luaffi" }
luajit = { path = "../luajit" }
sysexits = "0.9.0"
tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal", "tracing"] }
tracing = "0.1.41"
[dev-dependencies]
luaify = { path = "../luaify" }

64
crates/lb/src/channel.rs Normal file
View File

@@ -0,0 +1,64 @@
// use flume::{Receiver, Sender};
use luaffi::{cdef, metatype};
#[cdef]
pub struct lb_libchannel;
#[metatype]
impl lb_libchannel {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
extern "Lua" fn unbounded(self) {
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
self.__unbounded(send, recv);
(send, recv)
}
extern "Lua" fn bounded(self, cap: number) {
assert(cap >= 0, "channel capacity must be nonnegative");
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
self.__bounded(cap, send, recv);
(send, recv)
}
// extern "Lua-C" fn __unbounded(&self, s: *mut lb_sender, r: *mut lb_receiver) {
// let (send, recv) = flume::unbounded();
// unsafe {
// ptr::write(s, lb_sender { send });
// ptr::write(r, lb_receiver { recv });
// }
// }
// extern "Lua-C" fn __bounded(&self, cap: usize, s: *mut lb_sender, r: *mut lb_receiver) {
// let (send, recv) = flume::bounded(cap);
// unsafe {
// ptr::write(s, lb_sender { send });
// ptr::write(r, lb_receiver { recv });
// }
// }
}
// #[cdef]
// pub struct lb_sender {
// #[opaque]
// send: Sender<c_int>,
// }
// #[metatype]
// impl lb_sender {
// extern "Lua" fn send(self, value: _) {
// let key = __ref(value);
// }
// }
// #[cdef]
// pub struct lb_receiver {
// #[opaque]
// recv: Receiver<c_int>,
// }
// #[metatype]
// impl lb_receiver {}

34
crates/lb/src/fs.rs Normal file
View File

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

View File

@@ -1,2 +1,5 @@
pub mod rt;
pub mod channel;
pub mod fs;
pub mod net;
pub mod runtime;
pub mod task;

339
crates/lb/src/net.rs Normal file
View File

@@ -0,0 +1,339 @@
//! The `lb:net` module provides an asynchronous network API for creating TCP or UDP servers and
//! clients.
//!
//! # Exports
//!
//! See [`lb_libnet`] for items exported by this module.
use derive_more::{From, FromStr};
use luaffi::{cdef, metatype};
use std::{
io,
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
use tokio::net::{TcpListener, TcpSocket, TcpStream};
/// Items exported by the `lb:net` module.
///
/// This module can be obtained by calling `require` in Lua.
///
/// ```lua
/// local net = require("lb:net");
/// ```
#[cdef]
pub struct lb_libnet;
#[metatype]
impl lb_libnet {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
/// See [`Ipv4Addr::LOCALHOST`].
pub extern "Lua-C" fn localhost_v4(&self) -> lb_ipaddr {
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
}
/// See [`Ipv6Addr::LOCALHOST`].
pub extern "Lua-C" fn localhost_v6(&self) -> lb_ipaddr {
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
}
/// See [`Ipv4Addr::UNSPECIFIED`].
pub extern "Lua-C" fn unspecified_v4(&self) -> lb_ipaddr {
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
}
/// See [`Ipv6Addr::UNSPECIFIED`].
pub extern "Lua-C" fn unspecified_v6(&self) -> lb_ipaddr {
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
}
/// See [`Ipv4Addr::BROADCAST`].
pub extern "Lua-C" fn broadcast_v4(&self) -> lb_ipaddr {
lb_ipaddr(Ipv4Addr::BROADCAST.into())
}
/// Creates an [`lb_ipaddr`] from the given input.
///
/// 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
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
///
/// # 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) {
__new(__ct.lb_ipaddr, s) // copy constructor
} else if __istype(__ct.lb_socketaddr, s) {
s.ip()
} else {
self.__parse_ipaddr(s)
}
}
extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr, AddrParseError> {
s.parse()
}
/// Creates an [`lb_socketaddr`] from the given input.
///
/// A socket address is an IP address with a port number.
///
/// If `s` is an [`lb_socketaddr`], a copy of that value is returned. If `s` is an
/// [`lb_ipaddr`], a socket address with that IP address is returned. Otherwise, parses `s` as a
/// socket address string. Both IPv4 and IPv6 addresses are supported.
///
/// If `port` is not specified, `0` is used as the default.
///
/// # 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 != () {
self.__new_socketaddr(self.ipaddr(s), port)
} else {
if __istype(__ct.lb_socketaddr, s) {
__new(__ct.lb_socketaddr, s) // copy constructor
} else if __istype(__ct.lb_ipaddr, s) {
self.__new_socketaddr(s, 0) // default port 0
} else {
self.__parse_socketaddr(s)
}
}
}
extern "Lua-C" fn __new_socketaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
SocketAddr::new(ip.0, port).into()
}
extern "Lua-C" fn __parse_socketaddr(&self, s: &str) -> Result<lb_socketaddr, AddrParseError> {
s.parse()
}
/// Creates a new TCP socket configured for IPv4.
///
/// See [`TcpSocket::new_v4`].
///
/// # Errors
///
/// Throws if an error was encountered during the socket creation.
pub extern "Lua-C" fn tcp_v4(&self) -> io::Result<lb_tcpsocket> {
TcpSocket::new_v4().map(lb_tcpsocket)
}
/// Creates a new TCP socket configured for IPv6.
///
/// See [`TcpSocket::new_v6`].
///
/// # Errors
///
/// Throws if an error was encountered during the socket creation.
pub extern "Lua-C" fn tcp_v6(&self) -> io::Result<lb_tcpsocket> {
TcpSocket::new_v6().map(lb_tcpsocket)
}
}
/// An IP address, either IPv4 or IPv6.
///
/// # Example
///
/// This example creates an [`lb_ipaddr`] by parsing an IP address string.
///
/// ```lua
/// local net = require("lb:net");
/// local addr = net:ipaddr("127.0.0.1"); -- ipv4 loopback address
///
/// assert(addr:is_v4());
/// assert(addr:is_loopback());
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
#[cdef]
pub struct lb_ipaddr(#[opaque] IpAddr);
#[metatype]
impl lb_ipaddr {
/// See [`IpAddr::is_unspecified`].
pub extern "Lua-C" fn is_unspecified(&self) -> bool {
self.0.is_unspecified()
}
/// See [`IpAddr::is_loopback`].
pub extern "Lua-C" fn is_loopback(&self) -> bool {
self.0.is_loopback()
}
/// See [`IpAddr::is_multicast`].
pub extern "Lua-C" fn is_multicast(&self) -> bool {
self.0.is_multicast()
}
/// Returns the string `"v4"` if this is an IPv4 address or `"v6"` if this is an IPv6 address.
pub extern "Lua" fn family(&self) -> string {
if self.is_v6() { "v6" } else { "v4" }
}
/// Returns `true` if this is an IPv4 address.
pub extern "Lua-C" fn is_v4(&self) -> bool {
self.0.is_ipv4()
}
/// See [`Ipv4Addr::is_private`].
pub extern "Lua-C" fn is_v4_private(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_private(),
IpAddr::V6(_) => false,
}
}
/// See [`Ipv4Addr::is_link_local`].
pub extern "Lua-C" fn is_v4_link_local(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_link_local(),
IpAddr::V6(_) => false,
}
}
/// See [`Ipv4Addr::is_broadcast`].
pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_broadcast(),
IpAddr::V6(_) => false,
}
}
/// See [`Ipv4Addr::is_documentation`].
pub extern "Lua-C" fn is_v4_documentation(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_documentation(),
IpAddr::V6(_) => false,
}
}
/// Returns `true` if this is an IPv6 address.
pub extern "Lua-C" fn is_v6(&self) -> bool {
self.0.is_ipv6()
}
/// See [`Ipv6Addr::is_unique_local`].
pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool {
match self.0 {
IpAddr::V4(_) => false,
IpAddr::V6(v6) => v6.is_unique_local(),
}
}
/// See [`Ipv6Addr::is_unicast_link_local`].
pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool {
match self.0 {
IpAddr::V4(_) => false,
IpAddr::V6(v6) => v6.is_unicast_link_local(),
}
}
/// See [`Ipv4Addr::to_ipv6_compatible`].
pub extern "Lua-C" fn to_v6_compat(&self) -> Self {
match self.0 {
IpAddr::V4(v4) => Self(v4.to_ipv6_compatible().into()),
IpAddr::V6(_) => *self,
}
}
/// See [`Ipv4Addr::to_ipv6_mapped`].
pub extern "Lua-C" fn to_v6_mapped(&self) -> Self {
match self.0 {
IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()),
IpAddr::V6(_) => *self,
}
}
/// See [`IpAddr::to_canonical`].
pub extern "Lua-C" fn canonical(&self) -> Self {
self.0.to_canonical().into()
}
/// Returns the string representation of this address.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
self.0.to_string()
}
}
/// A socket address, which is an IP address with a port number.
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
#[cdef]
pub struct lb_socketaddr(#[opaque] SocketAddr);
#[metatype]
impl lb_socketaddr {
/// Returns the IP part of this address.
pub extern "Lua-C" fn ip(&self) -> lb_ipaddr {
self.0.ip().into()
}
/// Sets the IP part of this address.
///
/// This function accepts the same arguments as [`ipaddr`](lb_libnet::ipaddr).
pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self {
if __istype(__ct.lb_ipaddr, s) {
self.__set_ip(s);
} else if __istype(__ct.lb_socketaddr, s) {
self.__set_ip(s.ip());
} else {
self.__set_ip_parse(s);
}
self
}
extern "Lua-C" fn __set_ip(&mut self, ip: &lb_ipaddr) {
self.0.set_ip(ip.0);
}
extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<(), AddrParseError> {
s.parse().map(|ip| self.0.set_ip(ip))
}
/// Returns the port part of this address.
pub extern "Lua-C" fn port(&self) -> u16 {
self.0.port()
}
/// Sets the port part of this address.
pub extern "Lua" fn set_port(&mut self, port: number) -> &mut Self {
self.__set_port(port);
self
}
extern "Lua-C" fn __set_port(&mut self, port: u16) {
self.0.set_port(port)
}
/// Returns the string representation of this address.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
self.0.to_string()
}
}
/// A TCP socket which has not yet been converted to a [`lb_tcpstream`] or [`lb_tcplistener`].
#[derive(Debug, From)]
#[cdef]
pub struct lb_tcpsocket(#[opaque] TcpSocket);
#[metatype]
impl lb_tcpsocket {}
#[derive(Debug, From)]
#[cdef]
pub struct lb_tcpstream(#[opaque] TcpStream);
#[metatype]
impl lb_tcpstream {}
#[derive(Debug, From)]
#[cdef]
pub struct lb_tcplistener(#[opaque] TcpListener);
#[metatype]
impl lb_tcplistener {}

View File

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

85
crates/lb/src/runtime.rs Normal file
View File

@@ -0,0 +1,85 @@
use crate::{channel::lb_libchannel, fs::lb_libfs, net::lb_libnet, task::lb_libtask};
use derive_more::{Deref, DerefMut};
use luaffi::{Registry, Type};
use luajit::{Chunk, State};
use std::fmt::Display;
use tokio::{
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
task_local,
};
#[derive(Debug, Default)]
pub struct Builder {
registry: Registry,
}
impl Builder {
pub fn new() -> Self {
let mut registry = Registry::new();
registry
.preload::<lb_libtask>("lb:task")
.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 {
self.registry.preload::<T>(name);
self
}
pub fn registry(&self) -> &Registry {
&self.registry
}
pub fn build(&self) -> luajit::Result<Runtime> {
Ok(Runtime {
state: {
let mut s = State::new()?;
let mut chunk = Chunk::new(self.registry.done());
chunk.extend(include_bytes!("./runtime.lua"));
s.eval(chunk.path("[luby]"), 0, 0)?;
s
},
tasks: LocalSet::new(),
})
}
}
#[derive(Debug, Deref, DerefMut)]
pub struct Runtime {
#[deref]
#[deref_mut]
state: State,
tasks: LocalSet,
}
task_local! {
static STATE: State;
}
impl Runtime {
pub fn spawn<T: 'static>(
&self,
f: impl AsyncFnOnce(&mut State) -> T + 'static,
) -> JoinHandle<T> {
self.tasks
.spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
}
}
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut State) -> T + 'static) -> JoinHandle<T> {
spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
}
impl IntoFuture for Runtime {
type Output = ();
type IntoFuture = TaskLocalFuture<State, LocalSet>;
fn into_future(self) -> Self::IntoFuture {
STATE.scope(self.state, self.tasks)
}
}

46
crates/lb/src/task.rs Normal file
View File

@@ -0,0 +1,46 @@
use crate::runtime::spawn;
use luaffi::{cdef, metatype};
use std::{ffi::c_int, process};
use tokio::task::JoinHandle;
#[cdef]
pub struct lb_libtask;
#[metatype]
impl lb_libtask {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
pub extern "Lua" fn spawn(self, f: function, ...) {
// pack the function and its arguments into a table and pass its ref to rust
self.__spawn(__ref(__tpack(f, variadic!())))
}
extern "Lua-C" fn __spawn(&self, key: c_int) -> lb_task {
let handle = spawn(async move |s| {
// SAFETY: key is always unique, created by __ref above
let arg = unsafe { s.new_ref_unchecked(key) };
s.resize(0);
s.push(arg);
let narg = s.unpack(1, 1, None) - 1;
println!("{s:?}");
if let Err(_err) = s.call_async(narg, 0).await {
process::exit(1)
}
println!("{s:?}");
});
lb_task { handle }
}
}
#[cdef]
pub struct lb_task {
#[opaque]
handle: JoinHandle<()>,
}
#[metatype]
impl lb_task {}

32
crates/lb/tests/net.rs Normal file
View File

@@ -0,0 +1,32 @@
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
}

35
crates/lb/tests/task.rs Normal file
View File

@@ -0,0 +1,35 @@
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,15 @@
[package]
name = "luaffi"
version = "0.1.0"
edition = "2024"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
bstr = "1.12.0"
luaffi_impl = { version = "0.1.0", path = "../luaffi_impl" }
luaify = { version = "0.1.0", path = "../luaify" }
luaffi_impl = { path = "../luaffi_impl" }
luaify = { path = "../luaify" }
rustc-hash = "2.1.1"
simdutf8 = "0.1.5"

View File

@@ -1,7 +1,7 @@
use crate::{
__internal::{display, type_id},
Cdef, CdefBuilder, FfiReturnConvention, Metatype, MetatypeBuilder, ToFfi, Type, TypeBuilder,
UnsafeExternCFn,
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
TypeType, UnsafeExternCFn,
};
use luaify::luaify;
use std::{
@@ -21,7 +21,7 @@ const SIGNATURE: Signature = Signature::from_ne_bytes(*b"\x00lb_poll");
#[repr(C)]
#[allow(non_camel_case_types)]
pub struct lua_future<F: Future<Output: ToFfi>> {
pub struct lua_future<F: Future<Output: IntoFfi>> {
//
// SAFETY: LuaJIT guarantees that cdata payloads, which are GC-managed, are never relocated
// (i.e. pinned). We can safely assume that we are pinned and poll the future inside this
@@ -43,7 +43,7 @@ pub struct lua_future<F: Future<Output: ToFfi>> {
sig: Signature,
poll: fn(Pin<&mut Self>, cx: &mut Context) -> Poll<()>,
state: State<F>,
take: unsafe extern "C" fn(&mut Self) -> <F::Output as ToFfi>::To,
take: unsafe extern "C" fn(&mut Self) -> <F::Output as IntoFfi>::Into,
drop: unsafe extern "C" fn(&mut Self),
}
@@ -70,7 +70,7 @@ enum State<F: Future> {
Complete,
}
impl<F: Future<Output: ToFfi>> lua_future<F> {
impl<F: Future<Output: IntoFfi>> lua_future<F> {
pub fn new(fut: F) -> Self {
Self {
sig: SIGNATURE,
@@ -94,7 +94,7 @@ impl<F: Future<Output: ToFfi>> lua_future<F> {
}
}
unsafe extern "C" fn take(&mut self) -> <F::Output as ToFfi>::To {
unsafe extern "C" fn take(&mut self) -> <F::Output as IntoFfi>::Into {
// `fut:__take()` returns the fulfilled value by-value (not by out-param) because if we
// preallocate a cdata for the out-param and the thread for some reason gets dropped and
// never resumed, the GC could call the destructor on an uninitialised cdata.
@@ -131,13 +131,17 @@ impl Future for lua_pollable {
}
}
unsafe impl<F: Future<Output: ToFfi> + 'static> Type for lua_future<F> {
unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
fn name() -> impl Display {
display!("future__{:x}", type_id::<F>())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("struct future__{:x} {name}", type_id::<F>())
display!("struct {} {name}", Self::name())
}
fn build(s: &mut TypeBuilder) {
@@ -145,15 +149,15 @@ unsafe impl<F: Future<Output: ToFfi> + 'static> Type for lua_future<F> {
}
}
unsafe impl<F: Future<Output: ToFfi> + 'static> Cdef for lua_future<F> {
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
fn build(s: &mut CdefBuilder) {
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as ToFfi>::To>>("__take")
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
}
}
unsafe impl<F: Future<Output: ToFfi> + 'static> Metatype for lua_future<F> {
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
type Target = Self;
fn build(s: &mut MetatypeBuilder) {
@@ -161,30 +165,45 @@ unsafe impl<F: Future<Output: ToFfi> + 'static> Metatype for lua_future<F> {
}
}
unsafe impl<F: Future<Output: ToFfi> + 'static> ToFfi for lua_future<F> {
type To = lua_future<F>;
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
type Into = lua_future<F>;
fn convert(self) -> Self::To {
fn convention() -> FfiReturnConvention {
// futures are always returned by-value due to rust type inference limitations
FfiReturnConvention::ByValue
}
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
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 {
// 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
// gets resumed. Lua user code should never to worry about awaiting futures.
//
// Once the current thread gets resumed and we take the future's fulfilled value, we clear
// the finaliser on the future and forget it (there is nothing to call drop on).
// the finaliser on the future and forget it (there is nothing to drop once the value is
// taken).
//
// `coroutine.yield` is cached as `yield` and `ffi.gc` as `gc` in locals (see lib.rs)
// `coroutine.yield` is cached as `__yield` and `ffi.gc` as `__gc` in locals (see lib.rs)
display!(
"yield({ret}); {ret} = gc({ret}, nil):__take(); {}",
<F::Output as ToFfi>::postlude(ret, FfiReturnConvention::ByValue)
"__yield({ret}); {ret} = __gc({ret}, nil):__take(); {}",
<F::Output as IntoFfi>::postlude(ret)
)
}
}
impl<F: IntoFuture<Output: ToFfi>> From<F> for lua_future<F::IntoFuture> {
impl<F: IntoFuture<Output: IntoFfi>> From<F> for lua_future<F::IntoFuture> {
fn from(value: F) -> Self {
Self::new(value.into_future())
}

View File

@@ -6,6 +6,23 @@ use std::{
hash::{Hash, Hasher},
};
#[allow(non_camel_case_types)]
pub mod stub_types {
pub struct any;
pub struct nil;
pub struct boolean;
pub struct lightuserdata;
pub struct number;
pub struct integer;
pub struct string;
pub struct table;
pub struct function;
pub struct userdata;
pub struct thread;
pub struct cdata;
pub struct variadic;
}
pub fn type_id<T: 'static>() -> u64 {
let mut hash = FxHasher::default();
TypeId::of::<T>().hash(&mut hash);

View File

@@ -1,38 +1,34 @@
use crate::__internal::{disp, display, export, write_sep};
use crate::{
__internal::{disp, display, export, write_sep},
string::{DROP_BUFFER_FN, IS_UTF8_FN, lua_buffer},
};
pub use luaffi_impl::*;
use std::{
collections::HashSet,
ffi::{c_double, c_float, c_void},
fmt::{self, Display, Formatter, Write},
marker::PhantomData,
mem, slice,
mem,
};
pub mod future;
// pub mod option;
pub mod string;
#[doc(hidden)]
#[path = "./internal.rs"]
pub mod __internal;
const KEEP_FN: &str = "luaffi_keep";
const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub mod result;
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be
// garbage-collected until the end of the function. This shall exist until LuaJIT one day implements
// something like `ffi.keep(obj)`.
// garbage-collected until the end of the function (used in string.rs when string marshalling is
// going through the slow-path). This shall exist until LuaJIT one day implements something like
// `ffi.keep(obj)`.
//
// https://github.com/LuaJIT/LuaJIT/issues/1167
pub(crate) const KEEP_FN: &str = "luaffi_keep";
#[unsafe(export_name = "luaffi_keep")]
extern "C" fn __keep(_ptr: *const c_void) {}
#[unsafe(export_name = "luaffi_is_utf8")]
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
}
export![__keep, __is_utf8];
export![__keep];
const CACHE_LIBS: &[(&str, &str)] = &[
("table", "table"),
@@ -82,20 +78,20 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
("__tunpack", "table.unpack"),
// string
("__slen", "string.len"),
("__sformat", "string.format"),
("__sprintf", "string.format"),
("__ssub", "string.sub"),
("__sgsub", "string.gsub"),
("__sgmatch", "string.gmatch"),
("__sdump", "string.dump"),
// math
// math (used in luaify! macro)
("__fmod", "math.fmod"),
// coroutine
// coroutine (used in future.rs)
("__yield", "coroutine.yield"),
// package
("__preload", "package.preload"),
// debug
("__traceback", "debug.traceback"),
("__registry", "debug.getregistry()"),
("__registry", "debug.getregistry()"), // (used in lib.lua)
// ffi
("__C", "ffi.C"),
("__ct", "{}"),
@@ -108,8 +104,8 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
("__gc", "ffi.gc"),
("__sizeof", "ffi.sizeof"),
("__alignof", "ffi.alignof"),
("__intern", "ffi.string"),
// bit
("__intern", "ffi.string"), // (used in string.rs)
// bit (used in luaify! macro)
("__bnot", "bit.bnot"),
("__band", "bit.band"),
("__bor", "bit.bor"),
@@ -143,6 +139,7 @@ impl Registry {
let mut s = Self::default();
s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
s.declare::<UnsafeExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
s
}
@@ -154,6 +151,7 @@ impl Registry {
}
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void type");
self.include::<T>()
.funcs
.insert(name.to_string())
@@ -162,11 +160,12 @@ impl Registry {
}
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void type");
self.include::<T>();
let ct = T::name();
writeln!(
self.lua,
r#"__preload["{name}"] = function(...) return __ct.{}(...); end;"#,
T::name()
r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
)
.unwrap();
self
@@ -192,6 +191,8 @@ impl Display for Registry {
pub unsafe trait Type {
fn name() -> impl Display;
fn ty() -> TypeType;
fn cdecl(name: impl Display) -> impl Display;
fn extern_cdecl(name: impl Display) -> impl Display {
Self::cdecl(name)
@@ -200,6 +201,13 @@ pub unsafe trait Type {
fn build(b: &mut TypeBuilder);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeType {
Void,
Primitive,
Aggregate,
}
#[derive(Debug)]
pub struct TypeBuilder<'r> {
registry: &'r mut Registry,
@@ -256,6 +264,7 @@ impl<'r> CdefBuilder<'r> {
}
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void field");
self.registry.include::<T>();
self.field_raw(T::cdecl(name))
}
@@ -309,7 +318,7 @@ pub unsafe trait Metatype {
#[derive(Debug)]
pub struct MetatypeBuilder<'r> {
registry: &'r mut Registry,
name: String,
ct: String,
cdef: String,
lua: String,
}
@@ -318,7 +327,7 @@ impl<'r> MetatypeBuilder<'r> {
fn new<T: Metatype>(registry: &'r mut Registry) -> Self {
Self {
registry,
name: T::Target::name().to_string(),
ct: T::Target::name().to_string(),
cdef: String::new(),
lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(),
}
@@ -366,7 +375,7 @@ impl<'r> Drop for MetatypeBuilder<'r> {
fn drop(&mut self) {
let Self {
registry,
name,
ct,
cdef,
lua,
..
@@ -374,13 +383,18 @@ impl<'r> Drop for MetatypeBuilder<'r> {
registry.cdef.push_str(cdef);
registry.lua.push_str(lua);
writeln!(registry.lua, r#"__metatype(__ct.{name}, __mt); end;"#).unwrap();
writeln!(registry.lua, r#"__metatype(__ct.{ct}, __mt); end;"#).unwrap();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FfiReturnConvention {
ByValue,
ByOutParam,
}
pub unsafe trait FromFfi: Sized {
type From: Type + Sized;
type FromArg: Type + Sized;
fn require_keepalive() -> bool {
false
@@ -391,32 +405,35 @@ pub unsafe trait FromFfi: Sized {
}
fn convert(from: Self::From) -> Self;
fn convert_arg(from: Self::FromArg) -> Self;
}
pub unsafe trait ToFfi: Sized {
type To: Type + Sized;
pub unsafe trait IntoFfi: Sized {
type Into: Type + Sized;
fn postlude(_ret: &str, _conv: FfiReturnConvention) -> impl Display {
fn convention() -> FfiReturnConvention {
match Self::Into::ty() {
TypeType::Void | TypeType::Primitive => FfiReturnConvention::ByValue,
TypeType::Aggregate => FfiReturnConvention::ByOutParam,
}
}
fn require_owned() -> bool {
true
}
fn postlude(_ret: &str) -> impl Display {
""
}
fn convert(self) -> Self::To;
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FfiReturnConvention {
Void,
#[default]
ByValue,
ByOutParam,
fn convert(self) -> Self::Into;
}
#[derive(Debug)]
pub struct MetatypeMethodBuilder<'r, 'm> {
metatype: &'m mut MetatypeBuilder<'r>,
params: String, // parameters to the lua function
args: String, // arguments to the C call
lparams: String, // parameters to the lua function
cparams: String, // parameters to the lua function
cargs: String, // arguments to the C call
prelude: String, // function body prelude
postlude: String, // function body postlude
}
@@ -425,100 +442,196 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
Self {
metatype,
params: String::new(),
args: String::new(),
lparams: String::new(),
cparams: String::new(),
cargs: String::new(),
prelude: String::new(),
postlude: String::new(),
}
}
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
(!self.params.is_empty()).then(|| self.params.push_str(", "));
(!self.args.is_empty()).then(|| self.args.push_str(", "));
write!(self.params, "{name}").unwrap();
write!(self.args, "{name}").unwrap();
assert!(
T::From::ty() != TypeType::Void,
"cannot declare void parameter"
);
let Self {
metatype: MetatypeBuilder { registry, .. },
lparams,
cparams,
cargs,
prelude,
postlude,
..
} = self;
registry.include::<T::From>();
(!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap();
write!(cparams, "{}", T::From::cdecl(&name)).unwrap();
write!(cargs, "{name}").unwrap();
if T::require_keepalive() {
write!(self.prelude, "local __keep_{name} = {name}; ").unwrap();
write!(self.postlude, "__C.{KEEP_FN}(__keep_{name}); ").unwrap();
write!(prelude, "local __keep_{name} = {name}; ").unwrap();
write!(postlude, "__C.{KEEP_FN}(__keep_{name}); ").unwrap();
}
let name = name.to_string();
write!(self.prelude, "{}", T::prelude(&name)).unwrap();
write!(prelude, "{}", T::prelude(&name.to_string())).unwrap();
self
}
pub fn param_str(&mut self, name: impl Display) -> &mut Self {
// fast-path for &str and &[u8]-like parameters
pub fn param_str(
&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`
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
// temporary cdata to pass the string and its length in one argument
(!self.params.is_empty()).then(|| self.params.push_str(", "));
(!self.args.is_empty()).then(|| self.args.push_str(", "));
write!(self.params, "{name}").unwrap();
write!(self.args, "{name}, __{name}_len").unwrap();
write!(self.prelude, "local __{name}_len = 0; ").unwrap();
write!(
self.prelude,
r#"if {name} ~= nil then assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); __{name}_len = #{name}; end; "#
)
.unwrap();
// temporary cdata to pass the string and its length in one argument.
let Self {
lparams,
cparams,
cargs,
prelude,
..
} = self;
let param_ptr = <*const u8>::cdecl(&name);
let param_len = usize::cdecl(format!("{name}_len"));
(!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap();
write!(cparams, "{param_ptr}, {param_len}").unwrap();
write!(cargs, "{name}, __{name}_len").unwrap();
write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").unwrap();
write!(prelude, r#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
if check_utf8 {
write!(prelude, r#"assert(__C.{IS_UTF8_FN}({name}, __{name}_len), "argument '{name}' must be a valid utf-8 string"); "#).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
}
pub fn param_ignored(&mut self) -> &mut Self {
(!self.params.is_empty()).then(|| self.params.push_str(", "));
write!(self.params, "_").unwrap();
(!self.lparams.is_empty()).then(|| self.lparams.push_str(", "));
write!(self.lparams, "_").unwrap();
self
}
pub fn call<T: ToFfi>(&mut self, func: impl Display, ret: FfiReturnConvention) {
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
let Self {
metatype,
params,
args,
metatype:
MetatypeBuilder {
registry,
cdef,
lua,
..
},
lparams,
cparams,
cargs,
prelude,
postlude,
..
} = self;
let lua = &mut metatype.lua;
write!(lua, "function({params}) {prelude}").unwrap();
registry.include::<T::Into>();
write!(lua, "function({lparams}) {prelude}").unwrap();
match ret {
FfiReturnConvention::Void => {
write!(lua, "__C.{func}({args}); {postlude}end").unwrap();
}
match T::convention() {
FfiReturnConvention::ByValue => {
let check = T::postlude("__res", ret);
write!(
lua,
"local __res = __C.{func}({args}); {check}{postlude}return __res; end"
)
.unwrap();
if T::Into::ty() == TypeType::Void {
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
} else {
let check = T::postlude("__ret");
write!(lua, "local __ret = __C.{func}({cargs}); ").unwrap();
write!(lua, "{check}{postlude}return __ret; end").unwrap();
}
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
}
FfiReturnConvention::ByOutParam => {
let ct = T::To::name();
let check = T::postlude("__res", ret);
write!(lua, "local __res = __new(__ct.{ct}); __C.{func}(__res").unwrap();
if !args.is_empty() {
write!(lua, ", {args}").unwrap();
let ct = T::Into::name();
let check = T::postlude("__out");
write!(lua, "local __out = __new(__ct.{ct}); __C.{func}(__out").unwrap();
if !cargs.is_empty() {
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();
if !cparams.is_empty() {
write!(cdef, ", {cparams}").unwrap();
}
writeln!(cdef, ");").unwrap();
}
}
}
macro_rules! impl_primitive {
($rtype:ty, $ctype:expr) => {
unsafe impl Type for $rtype {
pub fn call_inferred<T: IntoFfi>(&mut self, func: impl Display, _infer: impl FnOnce() -> T) {
self.call::<T>(func)
}
}
//
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
// equivalent to passing a unit type as an argument in C.
//
macro_rules! impl_void {
($rty:ty) => {
unsafe impl Type for $rty {
fn name() -> impl Display {
$ctype
"void"
}
fn ty() -> TypeType {
TypeType::Void
}
fn cdecl(name: impl Display) -> impl Display {
display!("{} {name}", $ctype)
display!("void {name}")
}
fn build(_b: &mut TypeBuilder) {}
}
unsafe impl IntoFfi for $rty {
type Into = ();
fn convert(self) -> Self::Into {}
}
};
}
impl_void!(());
impl_void!(c_void);
macro_rules! impl_primitive {
($rty:ty, $cty:expr) => {
unsafe impl Type for $rty {
fn name() -> impl Display {
$cty
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
display!("{} {name}", $cty)
}
fn build(_b: &mut TypeBuilder) {}
@@ -526,87 +639,122 @@ macro_rules! impl_primitive {
};
}
impl_primitive!((), "void");
impl_primitive!(c_void, "void");
impl_primitive!(bool, "bool");
impl_primitive!(u8, "uint8_t");
impl_primitive!(u16, "uint16_t");
impl_primitive!(u32, "uint32_t");
impl_primitive!(u64, "uint64_t");
impl_primitive!(usize, "uintptr_t");
impl_primitive!(i8, "int8_t");
impl_primitive!(i16, "int16_t");
impl_primitive!(i32, "int32_t");
impl_primitive!(i64, "int64_t");
impl_primitive!(isize, "intptr_t");
impl_primitive!(c_float, "float");
impl_primitive!(c_double, "double");
unsafe impl ToFfi for () {
//
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
// equivalent to passing a unit type as an argument in C. `c_void` cannot be returned from rust
// so it should return the unit type instead.
//
type To = ();
fn convert(self) -> Self::To {}
fn postlude(_ret: &str, conv: FfiReturnConvention) -> impl Display {
assert!(
conv == FfiReturnConvention::Void,
"void type cannot be instantiated"
);
""
}
}
macro_rules! impl_primitive_abi {
($rtype:ty, $ctype:expr, $ltype:expr $(, $unwrap:expr)?) => {
impl_primitive!($rtype, $ctype);
//
// SAFETY: Primitive types are always copyable so we can pass and return them by value.
//
unsafe impl FromFfi for $rtype {
type From = Self;
type FromArg = Self;
unsafe impl FromFfi for bool {
type From = bool;
fn prelude(arg: &str) -> impl Display {
display!(r#"assert(type({arg}) == "{0}", "{0} expected in argument '{arg}', got " .. type({arg})); "#, $ltype)
display!(
r#"assert(type({arg}) == "boolean", "boolean expected in argument '{arg}', got " .. type({arg})); "#
)
}
fn convert(from: Self::From) -> Self {
from
}
fn convert_arg(from: Self::FromArg) -> Self {
from
}
}
unsafe impl ToFfi for $rtype {
type To = Self;
unsafe impl IntoFfi for bool {
type Into = bool;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
#[allow(unused)]
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
disp(move |f| Ok({
match conv {
FfiReturnConvention::Void => unreachable!(),
FfiReturnConvention::ByValue => {},
// if a primitive type for some reason gets returned by out-param, unwrap
// the cdata containing the value to convert it to the equivalent lua value
FfiReturnConvention::ByOutParam => { $(write!(f, "{ret} = {}; ", $unwrap(ret))?;)? },
}
}))
macro_rules! impl_number_fromabi {
($rty:ty) => {
unsafe impl FromFfi for $rty {
type From = Self;
fn prelude(arg: &str) -> impl Display {
display!(r#"do local __{arg} = {arg}; {arg} = tonumber({arg}); assert(type({arg}) == "number", "number expected in argument '{arg}', got " .. type(__{arg})); end; "#)
}
fn convert(from: Self::From) -> Self {
from
}
}
};
}
impl_primitive_abi!(bool, "bool", "boolean", |n| display!("{n} ~= 0"));
impl_primitive_abi!(u8, "uint8_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(u16, "uint16_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(u32, "uint32_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(u64, "uint64_t", "number");
impl_primitive_abi!(usize, "uintptr_t", "number");
impl_primitive_abi!(i8, "int8_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(i16, "int16_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(i32, "int32_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(i64, "int64_t", "number");
impl_primitive_abi!(isize, "intptr_t", "number");
impl_primitive_abi!(c_float, "float", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(c_double, "double", "number", |n| display!("tonumber({n})"));
impl_number_fromabi!(u8);
impl_number_fromabi!(u16);
impl_number_fromabi!(u32);
impl_number_fromabi!(u64);
impl_number_fromabi!(usize);
impl_number_fromabi!(i8);
impl_number_fromabi!(i16);
impl_number_fromabi!(i32);
impl_number_fromabi!(i64);
impl_number_fromabi!(isize);
impl_number_fromabi!(f32);
impl_number_fromabi!(f64);
macro_rules! impl_number_intoabi {
($rty:ty) => {
unsafe impl IntoFfi for $rty {
type Into = Self;
fn convert(self) -> Self::Into {
self
}
}
};
}
impl_number_intoabi!(u8);
impl_number_intoabi!(u16);
impl_number_intoabi!(u32);
impl_number_intoabi!(i8);
impl_number_intoabi!(i16);
impl_number_intoabi!(i32);
#[cfg(target_pointer_width = "32")]
impl_number_intoabi!(usize);
#[cfg(target_pointer_width = "32")]
impl_number_intoabi!(isize);
impl_number_intoabi!(c_float);
impl_number_intoabi!(c_double);
macro_rules! impl_bigint_intoabi {
($rty:ty) => {
unsafe impl IntoFfi for $rty {
type Into = Self;
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str) -> impl Display {
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
// precision here and that 53 bits of precision for big integers are enough. (the
// vain of Lua 5.3 integer subtype ;D )
display!("{ret} = tonumber({ret}); ")
}
}
};
}
impl_bigint_intoabi!(u64);
impl_bigint_intoabi!(i64);
#[cfg(target_pointer_width = "64")]
impl_bigint_intoabi!(usize);
#[cfg(target_pointer_width = "64")]
impl_bigint_intoabi!(isize);
macro_rules! impl_const_ptr {
($ty:ty) => {
@@ -615,6 +763,10 @@ macro_rules! impl_const_ptr {
display!("const_{}_ptr", T::name())
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("const *{name}"))
}
@@ -637,6 +789,10 @@ macro_rules! impl_mut_ptr {
display!("{}_ptr", T::name())
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("*{name}"))
}
@@ -665,15 +821,10 @@ macro_rules! impl_ptr_fromabi {
($ty:ty) => {
unsafe impl<T: Type> FromFfi for $ty {
type From = Self;
type FromArg = Self;
fn convert(from: Self::From) -> Self {
from
}
fn convert_arg(from: Self::FromArg) -> Self {
from
}
}
};
}
@@ -683,99 +834,97 @@ impl_ptr_fromabi!(*mut T);
impl_ptr_fromabi!(Option<&T>);
impl_ptr_fromabi!(Option<&mut T>);
unsafe impl<'s, T: Type> FromFfi for &'s T {
type From = Option<&'s T>;
type FromArg = Option<&'s T>;
fn prelude(arg: &str) -> impl Display {
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
}
fn convert(from: Self::From) -> Self {
debug_assert!(
from.is_some(),
"<&T>::convert() called on a null reference when it was checked to be non-null"
);
unsafe { from.unwrap_unchecked() }
}
fn convert_arg(from: Self::FromArg) -> Self {
FromFfi::convert(from)
}
}
unsafe impl<'s, T: Type> FromFfi for &'s mut T {
//
// SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust
// code called via FFI can be running at the same time on the same OS thread (no Lua
// reentrancy).
//
// i.e. The call stack will always look something like this:
//
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI): This is SAFE and the only use case
// we support. All references (mutable or not) to Rust user objects will be dropped before
// returning to Lua.
//
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI) -> Lua (via callback): This is
// UNSAFE because we cannot prevent the Lua callback from calling back into Rust code via
// FFI which could violate exclusive borrow semantics. This is prevented by not implementing
// `FromFfi` for function pointers (see below).
//
// The runtime does not keep any references to Rust user objects boxed in cdata (futures are
// the only exception; their ownership is transferred to the runtime via yield).
//
type From = Option<&'s mut T>;
type FromArg = Option<&'s mut T>;
fn prelude(arg: &str) -> impl Display {
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
}
fn convert(from: Self::From) -> Self {
debug_assert!(
from.is_some(),
"<&mut T>::convert() called on a null reference when it was checked to be non-null"
);
unsafe { from.unwrap_unchecked() }
}
fn convert_arg(from: Self::FromArg) -> Self {
FromFfi::convert(from)
}
}
//
// SAFETY: Return by value for pointers, which maps to a `cdata` return in lua containing the
// pointer (`T *`). We also map null pointers to `nil` for convenience (otherwise it's still a cdata
// value containing a null pointer)
//
macro_rules! impl_ptr_toabi {
macro_rules! impl_ptr_intoabi {
($ty:ty) => {
unsafe impl<T: Type> ToFfi for $ty {
type To = Self;
unsafe impl<T: Type> IntoFfi for $ty {
type Into = Self;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
fn postlude(ret: &str) -> impl Display {
display!("if {ret} == nil then {ret} = nil; end; ")
}
}
};
}
impl_ptr_toabi!(*const T);
impl_ptr_toabi!(*mut T);
impl_ptr_toabi!(&'static T);
impl_ptr_toabi!(&'static mut T);
impl_ptr_toabi!(Option<&'static T>);
impl_ptr_toabi!(Option<&'static mut T>);
impl_ptr_intoabi!(*const T);
impl_ptr_intoabi!(*mut T);
impl_ptr_intoabi!(Option<&'static T>);
impl_ptr_intoabi!(Option<&'static mut T>);
//
// SAFETY: No `FromFfi` and `ToFfi` for arrays because passing or returning them by value is not a
// SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust code
// called via FFI can be running at the same time on the same OS thread (no Lua reentrancy).
//
// i.e. The call stack will always look something like this:
//
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI): This is SAFE and the only use case we
// support. All references (mutable or not) to Rust user objects will be dropped before
// returning to Lua.
//
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI) -> Lua (via callback): This is UNSAFE
// because we cannot prevent the Lua callback from calling back into Rust code via FFI which
// could violate exclusive borrow semantics. This is prevented by not implementing `FromFfi` for
// function pointers (see below).
//
// The runtime does not keep any references to Rust user objects boxed in cdata (futures are the
// only exception; their ownership is transferred to the runtime via yield).
//
macro_rules! impl_ref_fromabi {
($ty:ty) => {
unsafe impl<'s, T: Type> FromFfi for $ty {
type From = Option<$ty>;
fn prelude(arg: &str) -> impl Display {
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
}
fn convert(from: Self::From) -> Self {
// SAFETY: we already checked that the reference is nonnull from the lua side
debug_assert!(
from.is_some(),
"<{}>::convert() called on a null reference when it was checked to be nonnull",
stringify!($ty),
);
unsafe { from.unwrap_unchecked() }
}
}
};
}
impl_ref_fromabi!(&'s T);
impl_ref_fromabi!(&'s mut T);
//
// SAFETY: `IntoFfi` only for 'static references because we cannot guarantee that the pointer will
// not outlive the pointee otherwise.
//
macro_rules! impl_ref_intoabi {
($ty:ty) => {
unsafe impl<T: Type> IntoFfi for $ty {
type Into = Self;
fn convert(self) -> Self::Into {
self
}
}
};
}
impl_ref_intoabi!(&'static T);
impl_ref_intoabi!(&'static mut T);
//
// SAFETY: No `FromFfi` and `IntoFfi` for arrays because passing or returning them by value is not a
// thing in C (they are just pointers).
//
// TODO: we could automatically convert them to tables and vice-versa
@@ -785,6 +934,10 @@ unsafe impl<T: Type> Type for [T] {
display!("{}_arr", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("{name}[]")
}
@@ -799,6 +952,10 @@ unsafe impl<T: Type, const N: usize> Type for [T; N] {
display!("{}_arr{N}", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("{name}[{N}]")
}
@@ -820,17 +977,21 @@ macro_rules! impl_function {
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
// in `&mut T`).
//
// We also can't implement `ToFfi` because we can't call `FromFfi` and `ToFfi` for the
// We also can't implement `IntoFfi` because we can't call `FromFfi` and `IntoFfi` for the
// function's respective argument and return values.
//
unsafe impl<$($arg: Type,)* $ret: Type> Type for $ty<($($arg,)*), $ret> {
fn name() -> impl Display {
disp(|f| Ok({
write!(f, "fn_{}", $ret::name())?;
$(write!(f, "_{}", $arg::name())?;)*
$(write!(f, "__{}", $arg::name())?;)*
}))
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
$ret::cdecl(disp(move |f| Ok({
let mut _n = 0;

View File

@@ -1,78 +0,0 @@
use crate::{Cdef, CdefBuilder, FfiReturnConvention, FromFfi, ToFfi, Type, TypeBuilder, display};
use std::{ffi::c_int, fmt::Display, ptr};
#[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 cdecl(name: impl Display) -> impl Display {
display!("struct option__{} {name}", T::name())
}
fn build(b: &mut TypeBuilder) {
b.include::<T>().cdef::<Self>();
}
}
unsafe impl<T: Type> Cdef for lua_option<T> {
fn build(b: &mut CdefBuilder) {
b.field::<c_int>("__tag").field::<T>("__value");
}
}
unsafe impl<T: FromFfi> FromFfi for Option<T> {
type From = lua_option<T::From>;
type FromArg = *mut Self::From; // pass by-ref
fn require_keepalive() -> bool {
T::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
let ct = Self::From::name();
display!(
"if {arg} == nil then {arg} = __new(__ct.{ct}); else {}{arg} = __new(__ct.{ct}, 1, {arg}); end; ",
T::prelude(arg)
)
}
fn convert(from: Self::From) -> Self {
match from {
lua_option::Some(value) => Some(T::convert(value)),
lua_option::None => None,
}
}
fn convert_arg(from: Self::FromArg) -> Self {
debug_assert!(!from.is_null());
Self::convert(unsafe { ptr::replace(from, lua_option::None) })
}
}
unsafe impl<T: ToFfi> ToFfi for Option<T> {
type To = lua_option<T::To>;
fn convert(self) -> Self::To {
match self {
Some(value) => lua_option::Some(value.convert()),
None => lua_option::None,
}
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
// if we don't have a value, return nil. otherwise copy out the inner value immediately,
// forget the option cdata, then call postlude on the inner value.
display!(
"if {ret}.__tag == 0 then {ret} = nil; else {ret} = {ret}.__value; {}end; ",
T::postlude(ret, FfiReturnConvention::ByValue)
)
}
}

View File

@@ -0,0 +1,94 @@
use crate::{
__internal::{disp, display},
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType,
string::{DROP_BUFFER_FN, lua_buffer},
};
use std::{ffi::c_int, fmt::Display};
#[repr(C)]
#[allow(non_camel_case_types)]
pub enum lua_result<T> {
Err(lua_buffer), // __tag = 0
Ok(T), // __tag = 1
}
unsafe impl<T: Type> Type for lua_result<T> {
fn name() -> impl Display {
display!("result__{}", 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_result<T> {
fn build(b: &mut CdefBuilder) {
b.field::<c_int>("__tag").inner_union(|b| {
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
b.field::<lua_buffer>("__err");
});
}
}
unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
type Into = lua_result<T::Into>;
fn convert(self) -> Self::Into {
match self {
Ok(value) => lua_result::Ok(T::convert(value)),
Err(err) => lua_result::Err(lua_buffer::new(err.to_string())),
}
}
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 {
disp(move |f| {
write!(f, "if {ret}.__tag ~= 0 then ")?;
match T::Into::ty() {
TypeType::Void => write!(f, "{ret} = nil; ")?, // for void results, 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
// result.
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
write!(f, "{}", T::postlude(ret))?;
} else {
// inner value is a "temporary" 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} = {ret}; {ret} = {ret}.__value; ")?;
write!(f, "do {}end; ", T::postlude(ret))?;
write!(f, "__C.{KEEP_FN}(__{ret}); ")?; // keep original result alive
}
}
}
write!(
f,
"else \
local {ret}_err = __intern({ret}.__err.__ptr, {ret}.__err.__len); \
__C.{DROP_BUFFER_FN}({ret}.__err); \
return error({ret}_err); \
end; "
)
})
}
}

View File

@@ -1,6 +1,28 @@
use crate::{__internal::disp, FromFfi, IS_UTF8_FN, Type};
use crate::{
__internal::{disp, display, export},
FromFfi, IntoFfi,
};
use bstr::{BStr, BString};
use luaffi_impl::{cdef, metatype};
use std::{fmt, ptr, slice};
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice};
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
#[unsafe(export_name = "luaffi_is_utf8")]
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
debug_assert!(!ptr.is_null());
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
}
#[unsafe(export_name = "luaffi_drop_buffer")]
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
debug_assert!(!buf.is_null());
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
drop(unsafe { Vec::from_raw_parts((*buf).__ptr, (*buf).__len, (*buf).__cap) })
}
export![__is_utf8, __drop_buffer];
#[derive(Debug, Clone, Copy)]
#[cdef]
@@ -10,52 +32,94 @@ pub struct lua_buf {
}
#[metatype]
impl lua_buf {}
impl lua_buf {
// this takes a slice and decomposes it into its raw parts. caller should ensure the result is
// used only as long as the original buffer is still alive.
pub(crate) fn new(s: &[u8]) -> Self {
Self {
__ptr: s.as_ptr(),
__len: s.len(),
}
}
unsafe impl FromFfi for *const [u8] {
type From = lua_buf;
type FromArg = *const Self::From;
pub(crate) fn null() -> Self {
Self {
__ptr: ptr::null(),
__len: 0,
}
}
}
#[derive(Debug, Clone, Copy)]
#[cdef]
pub struct lua_buffer {
__ptr: *mut u8,
__len: usize,
__cap: usize,
}
#[metatype]
impl lua_buffer {
// this takes ownership of the Vec and decomposes it into its raw parts. the result must be
// dropped by `__drop_buffer` (see [`DROP_BUFFER_FN`]).
pub(crate) fn new(s: impl Into<Vec<u8>>) -> Self {
let s = s.into();
Self {
__cap: s.capacity(),
__len: s.len(),
__ptr: ManuallyDrop::new(s).as_mut_ptr(),
}
}
pub(crate) fn null() -> Self {
Self {
__ptr: ptr::null_mut(),
__len: 0,
__cap: 0,
}
}
}
unsafe impl<'s> FromFfi for &'s [u8] {
type From = Option<&'s lua_buf>;
fn require_keepalive() -> bool {
true
}
fn prelude(arg: &str) -> impl fmt::Display {
fn prelude(arg: &str) -> impl Display {
// this converts string arguments to a `lua_buf` with a pointer to the string and its length
disp(move |f| {
let ct = Self::From::name();
write!(
f,
r#"if {arg} ~= nil then assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
)?;
write!(f, "{arg} = __new(__ct.{ct}, {arg}, #{arg}); end; ")
// SAFETY: the lua_buf is only valid for as long as the string is alive. we've ensured
// that it is alive for at least the duration of the ffi call via `require_keepalive()`.
write!(f, "{arg} = __new(__ct.lua_buf, {arg}, #{arg}); end; ")
})
}
fn convert(from: Self::From) -> Self {
ptr::slice_from_raw_parts(from.__ptr, from.__len)
}
fn convert_arg(from: Self::FromArg) -> Self {
if from.is_null() {
ptr::slice_from_raw_parts(ptr::null(), 0)
} else {
Self::convert(unsafe { *from })
}
// SAFETY: we already checked that the string is nonnull from the lua side
debug_assert!(from.is_some());
let from = unsafe { from.unwrap_unchecked() };
debug_assert!(!from.__ptr.is_null());
unsafe { slice::from_raw_parts(from.__ptr, from.__len) }
}
}
unsafe impl FromFfi for &str {
type From = lua_buf;
type FromArg = *const Self::From;
unsafe impl<'s> FromFfi for &'s str {
type From = Option<&'s lua_buf>;
fn require_keepalive() -> bool {
true
}
fn prelude(arg: &str) -> impl fmt::Display {
fn prelude(arg: &str) -> impl Display {
// this converts string arguments to a `lua_buf` with a pointer to the string and its length
// and ensures that the string is valid utf8
disp(move |f| {
let ct = Self::From::name();
write!(
f,
r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
@@ -64,25 +128,156 @@ unsafe impl FromFfi for &str {
f,
r#"assert(__C.{IS_UTF8_FN}({arg}, #{arg}), "argument '{arg}' must be a valid utf-8 string"); "#
)?;
write!(f, "{arg} = __new(__ct.{ct}, {arg}, #{arg}); ")
write!(f, "{arg} = __new(__ct.lua_buf, {arg}, #{arg}); ")
})
}
fn convert(from: Self::From) -> Self {
// SAFETY: we already checked that the string is nonnull and valid utf8 from the lua side
debug_assert!(from.is_some());
let from = unsafe { from.unwrap_unchecked() };
debug_assert!(!from.__ptr.is_null());
let s = unsafe { slice::from_raw_parts(from.__ptr, from.__len) };
let from = unsafe { slice::from_raw_parts(from.__ptr, from.__len) };
debug_assert!(
std::str::from_utf8(s).is_ok(),
std::str::from_utf8(from).is_ok(),
"<&str>::convert() called on an invalid utf8 string when it was checked to be valid"
);
unsafe { std::str::from_utf8_unchecked(s) }
unsafe { std::str::from_utf8_unchecked(from) }
}
}
fn convert_arg(from: Self::FromArg) -> Self {
debug_assert!(!from.is_null());
unsafe { Self::convert(*from) }
unsafe impl IntoFfi for &'static [u8] {
type Into = lua_buf;
fn convert(self) -> Self::Into {
// SAFETY: the slice is 'static so the resulting lua_buf is always valid
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 {
display!("{ret} = __intern({ret}.__ptr, {ret}.__len); ")
}
}
unsafe impl IntoFfi for Vec<u8> {
type Into = lua_buffer;
fn convert(self) -> Self::Into {
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 {
display!(
"local {ret}_buf = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}({ret}_buf); "
)
}
}
macro_rules! impl_from_via {
($ty:ty, $via:ty) => {
unsafe impl<'s> FromFfi for $ty {
type From = <$via as FromFfi>::From;
fn require_keepalive() -> bool {
<$via as FromFfi>::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
<$via as FromFfi>::prelude(arg)
}
fn convert(from: Self::From) -> Self {
<$via as FromFfi>::convert(from).into()
}
}
};
}
impl_from_via!(&'s BStr, &'s [u8]);
macro_rules! impl_into_via {
($ty:ty, $via:ty) => {
unsafe impl IntoFfi for $ty {
type Into = <$via as IntoFfi>::Into;
fn convert(self) -> Self::Into {
<$via as IntoFfi>::convert(self.into())
}
fn postlude(ret: &str) -> impl Display {
<$via as IntoFfi>::postlude(ret)
}
}
};
}
impl_into_via!(&'static BStr, &'static [u8]);
impl_into_via!(&'static str, &'static BStr);
impl_into_via!(BString, Vec<u8>);
impl_into_via!(String, BString);
macro_rules! impl_optional_from {
($ty:ty) => {
unsafe impl<'s> FromFfi for Option<$ty> {
type From = <$ty as FromFfi>::From;
fn require_keepalive() -> bool {
<$ty as FromFfi>::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
// just pass a null pointer if argument is nil
display!(
"if {arg} ~= nil then {}end; ",
<$ty as FromFfi>::prelude(arg)
)
}
fn convert(from: Self::From) -> Self {
from.map(|s| <$ty as FromFfi>::convert(Some(s)))
}
}
};
}
impl_optional_from!(&'s [u8]);
impl_optional_from!(&'s BStr);
impl_optional_from!(&'s str);
macro_rules! impl_optional_into {
($ty:ty, $null:expr) => {
unsafe impl IntoFfi for Option<$ty> {
type Into = <$ty as IntoFfi>::Into;
fn convert(self) -> Self::Into {
self.map_or($null, <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret)
)
}
}
};
}
impl_optional_into!(&'static [u8], lua_buf::null());
impl_optional_into!(&'static BStr, lua_buf::null());
impl_optional_into!(&'static str, lua_buf::null());
impl_optional_into!(Vec<u8>, lua_buffer::null());
impl_optional_into!(BString, lua_buffer::null());
impl_optional_into!(String, lua_buffer::null());

View File

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

View File

@@ -1,8 +1,8 @@
use crate::utils::{ffi_crate, syn_assert, syn_error};
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{ext::IdentExt, *};
use quote::{format_ident, quote, quote_spanned};
use syn::{ext::IdentExt, spanned::Spanned, *};
#[derive(Debug, FromMeta)]
pub struct Args {}
@@ -24,35 +24,39 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
let mod_name = format_ident!("__{name}_cdef");
Ok(quote! {
Ok(quote_spanned!(name.span() =>
#[repr(C)]
#[allow(non_camel_case_types)]
#item
#[doc(hidden)]
#[allow(unused, non_snake_case)]
/// Automatically generated by luaffi.
mod #mod_name {
use super::*;
#impl_type
#impl_cdef
}
})
))
}
fn generate_type(ty: &Ident) -> Result<TokenStream> {
let ffi = ffi_crate();
let fmt = quote!(::std::format!);
let name = LitStr::new(&format!("{}", ty.unraw()), ty.span());
let cdecl_fmt = LitStr::new(&format!("struct {} {{}}", ty.unraw()), ty.span());
let span = ty.span();
let name = LitStr::new(&ty.unraw().to_string(), span);
Ok(quote! {
Ok(quote_spanned!(span =>
unsafe impl #ffi::Type for #ty {
fn name() -> impl ::std::fmt::Display {
#name
}
fn ty() -> #ffi::TypeType {
#ffi::TypeType::Aggregate
}
fn cdecl(name: impl ::std::fmt::Display) -> impl ::std::fmt::Display {
#fmt(#cdecl_fmt, name)
::std::format!("struct {} {name}", #name)
}
fn build(b: &mut #ffi::TypeBuilder) {
@@ -60,11 +64,12 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
}
}
unsafe impl #ffi::ToFfi for #ty {
type To = Self;
fn convert(self) -> Self::To { self }
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
unsafe impl #ffi::IntoFfi for #ty {
type Into = Self;
fn convert(self) -> Self::Into { self }
}
})
))
}
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
@@ -76,13 +81,14 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
let ffi = ffi_crate();
let ty = &str.ident;
let build = generate_build_cdef(&to_cfields(&mut str.fields)?)?;
let span = ty.span();
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
Ok(quote! {
Ok(quote_spanned!(span =>
unsafe impl #ffi::Cdef for #ty {
fn build(b: &mut #ffi::CdefBuilder) { #build }
}
})
))
}
fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
@@ -94,22 +100,24 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
let ffi = ffi_crate();
let ty = &enu.ident;
let span = ty.span();
let build = enu
.variants
.iter_mut()
.map(|variant| {
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
Ok(quote! { b.inner_struct(|b| { #build }); })
let span = variant.span();
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
Ok(quote_spanned!(span => b.inner_struct(|b| { #build })))
})
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
Ok(quote_spanned!(span =>
unsafe impl #ffi::Cdef for #ty {
fn build(b: &mut #ffi::CdefBuilder) {
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build)* });
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build;)* });
}
}
})
))
}
struct CField {
@@ -123,7 +131,7 @@ struct CFieldAttrs {
opaque: bool,
}
fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
match fields {
Fields::Named(fields) => fields.named.iter_mut(),
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
@@ -133,17 +141,17 @@ fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
.map(|(i, field)| {
Ok(CField {
name: match field.ident {
Some(ref name) => format!("{}", name.unraw()),
Some(ref name) => name.unraw().to_string(),
None => format!("__{i}"),
},
ty: field.ty.clone(),
attrs: parse_attrs(&mut field.attrs)?,
attrs: parse_cfield_attrs(&mut field.attrs)?,
})
})
.collect()
}
fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
fn parse_cfield_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
let mut parsed = CFieldAttrs::default();
let mut i = 0;
while let Some(attr) = attrs.get(i) {
@@ -160,11 +168,11 @@ fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
Ok(parsed)
}
fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
let mut body = vec![quote! {
fn generate_cdef_build(fields: &[CField]) -> Result<TokenStream> {
let mut body = vec![quote!(
let mut offset = 0;
let mut align = 1;
}];
)];
fn offset(i: usize) -> Ident {
format_ident!("offset{i}")
@@ -173,40 +181,41 @@ fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
let max = quote!(::std::cmp::Ord::max);
let size_of = quote!(::std::mem::size_of);
let align_of = quote!(::std::mem::align_of);
for (i, field) in fields.iter().enumerate() {
let ty = &field.ty;
let offset = offset(i);
body.push(quote! {
body.push(quote_spanned!(ty.span() =>
// round up current offset to the alignment of field type for field offset
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
align = #max(align, #align_of::<#ty>());
let #offset = offset;
offset += #size_of::<#ty>();
});
));
}
body.push(quote! {
body.push(quote!(
// round up final offset to the total alignment of struct for struct size
let size = (offset + align - 1) & !(align - 1);
});
));
let len = fields.len();
for (i, field) in fields.iter().enumerate() {
let name = &field.name;
let ty = &field.ty;
if field.attrs.opaque {
body.push(if i == len - 1 {
body.push(if field.attrs.opaque {
if i == len - 1 {
let a = offset(i);
quote! { b.field_opaque(size - #a); } // last field
quote_spanned!(ty.span() => b.field_opaque(size - #a);) // last field
} else {
let a = offset(i);
let b = offset(i + 1);
quote! { b.field_opaque(#b - #a); }
});
} else {
body.push(quote! { b.field::<#ty>(#name); });
quote_spanned!(ty.span() => b.field_opaque(#b - #a);)
}
} else {
quote_spanned!(ty.span() => b.field::<#ty>(#name);)
});
}
Ok(quote! { #(#body)* })
Ok(quote!(#(#body)*))
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ use syn::{spanned::Spanned, *};
macro_rules! syn_error {
($src:expr, $($fmt:expr),+) => {{
use syn::spanned::*;
return Err(syn::Error::new($src.span(), format!($($fmt),*)));
}};
}
@@ -56,15 +55,15 @@ pub fn is_unit(ty: &Type) -> bool {
}
}
pub fn is_primitive(ty: &Type) -> bool {
pub fn is_primitivelike(ty: &Type) -> bool {
match ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => true, // unit type
Type::Reference(_) | Type::Ptr(_) => true,
Type::Paren(paren) => is_primitive(&paren.elem),
Type::Tuple(tuple) if tuple.elems.is_empty() => return true, // unit type
Type::Reference(_) | Type::Ptr(_) => return true,
Type::Paren(paren) => return is_primitivelike(&paren.elem),
Type::Path(path) => {
if let Some(name) = path.path.get_ident() {
matches!(
format!("{name}").as_str(),
return matches!(
name.to_string().as_str(),
"bool"
| "u8"
| "u16"
@@ -95,11 +94,66 @@ pub fn is_primitive(ty: &Type) -> bool {
| "c_size_t"
| "c_ssize_t"
| "c_ptrdiff_t"
)
} else {
);
}
}
_ => {}
}
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]
name = "luaify"
version = "0.1.0"
edition = "2024"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[lib]
proc-macro = true

View File

@@ -523,7 +523,7 @@ fn generate_expr_tuple(f: &mut Formatter, tuple: &ExprTuple, cx: Context) -> Res
f.write("nil");
Ok(())
}
_ if cx.is_multi_expr() => generate_punctuated_expr(f, &tuple.elems),
_ if cx.is_ret() || cx.is_multi_expr() => generate_punctuated_expr(f, &tuple.elems),
_ => syn_error!(tuple, "expected single-valued expression"),
}
}

View File

@@ -158,7 +158,7 @@ impl Visitor {
}
};
let tmp = format_ident!("_{ident}");
let tmp = format_ident!("__{ident}");
let span = cast.span();
*expr = match ty {
LuaType::Any => parse_quote_spanned!(span => {}),

View File

@@ -81,7 +81,7 @@ fn local_fn() {
fn check(self: string, arg: number) {}
inner
}),
r#"function()local function check(self,arg)do if type(self)=="number"then self=tostring(self);else assert(type(self)=="string","string expected in \'self\', got "..type(self));end;end;do local _arg=arg;arg=tonumber(arg);assert(arg~=nil,"number expected in \'arg\', got "..type(_arg));end;end;return inner;end"#
r#"function()local function check(self,arg)do if type(self)=="number"then self=tostring(self);else assert(type(self)=="string","string expected in \'self\', got "..type(self));end;end;do local __arg=arg;arg=tonumber(arg);assert(arg~=nil,"number expected in \'arg\', got "..type(__arg));end;end;return inner;end"#
);
}
@@ -220,7 +220,7 @@ fn type_checks() {
);
assert_eq!(
luaify!(|s| { s as number }),
r#"function(s)do local _s=s;s=tonumber(s);assert(s~=nil,"number expected in \'s\', got "..type(_s));end;end"#
r#"function(s)do local __s=s;s=tonumber(s);assert(s~=nil,"number expected in \'s\', got "..type(__s));end;end"#
);
assert_eq!(
luaify!(|s| { s as nil }),

View File

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

View File

@@ -214,6 +214,11 @@ fn build_runtime(src_path: &Path) {
let status = panic_err!(make.status(), "failed to execute make");
(!status.success()).then(|| panic!("failed to compile luajit: {status}: {make:?}"));
println!(
"cargo::rustc-env=LUAJIT_SYS_JITLIB={}",
src_path.join("jit").display(),
);
if feature!("runtime") {
println!("cargo::rustc-link-search=native={}", src_path.display());
println!("cargo::rustc-link-lib=static=luajit");

View File

@@ -1,14 +1,53 @@
#![allow(nonstandard_style)]
use std::{ffi::*, ptr};
include!(env!("LUAJIT_SYS_BINDGEN"));
// #[cfg(all(panic = "abort", feature = "unwind"))]
// compile_error!(r#"feature "unwind" cannot be enabled if panic = "abort""#);
// #[cfg(all(panic = "unwind", not(feature = "unwind")))]
// compile_error!(r#"feature "unwind" must be enabled if panic = "unwind""#);
include!(env!("LUAJIT_SYS_BINDGEN"));
pub unsafe extern "C" fn luaJIT_openlibs(L: *mut lua_State) {
unsafe {
lua_getglobal(L, c"package".as_ptr());
lua_getfield(L, -1, c"preload".as_ptr());
lua_replace(L, -2);
macro_rules! load {
($n:literal, $f:literal) => {{
let n: &'static CStr = $n;
let f = include_bytes!(concat!(env!("LUAJIT_SYS_JITLIB"), "/", $f));
if luaL_loadbuffer(L, f.as_ptr().cast(), f.len(), n.as_ptr()) == 0 {
lua_setfield(L, -2, n.as_ptr());
} else {
lua_error(L);
}
}};
}
load!(c"jit.vmdef", "vmdef.lua");
load!(c"jit.dis_x86", "dis_x86.lua");
load!(c"jit.dis_x64", "dis_x64.lua");
load!(c"jit.dis_arm", "dis_arm.lua");
load!(c"jit.dis_arm64", "dis_arm64.lua");
load!(c"jit.dis_arm64be", "dis_arm64be.lua");
load!(c"jit.dis_ppc", "dis_ppc.lua");
load!(c"jit.dis_mips", "dis_mips.lua");
load!(c"jit.dis_mipsel", "dis_mipsel.lua");
load!(c"jit.dis_mips64", "dis_mips64.lua");
load!(c"jit.dis_mips64el", "dis_mips64el.lua");
load!(c"jit.dis_mips64r6", "dis_mips64r6.lua");
load!(c"jit.dis_mips64r6el", "dis_mips64r6el.lua");
load!(c"jit.bc", "bc.lua");
load!(c"jit.bcsave", "bcsave.lua");
load!(c"jit.v", "v.lua");
load!(c"jit.p", "p.lua");
load!(c"jit.dump", "dump.lua");
load!(c"jit.zone", "zone.lua");
lua_pop(L, 1);
}
}
// constants not exposed by lua.h
pub const LUA_TPROTO: c_int = LUA_TTHREAD + 1;
pub const LUA_TCDATA: c_int = LUA_TTHREAD + 2;

View File

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

View File

@@ -25,6 +25,16 @@ pub fn version() -> &'static str {
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.
#[derive(Debug, Error)]
#[non_exhaustive]
@@ -505,10 +515,12 @@ impl State {
}
unsafe extern "C" fn open_cb(L: *mut lua_State) -> c_int {
// SAFETY: we wrap luaL_openlibs so we can potentially catch any library open errors
unsafe { luaL_openlibs(L) }
unsafe {
luaL_openlibs(L);
luaJIT_openlibs(L); // luajit-sys extension to open jitlibs
0
}
}
/// Creates a new empty thread (coroutine) associated with this state.
pub fn new_thread(&self) -> Self {
@@ -613,7 +625,7 @@ impl Stack {
///
/// Equivalent to [`lua_settop`].
///
/// # Panic
/// # Panics
///
/// Panics if `n` is negative.
pub fn resize(&mut self, n: c_int) {
@@ -632,7 +644,7 @@ impl Stack {
///
/// Equivalent to [`lua_checkstack`].
///
/// # Panic
/// # Panics
///
/// Panics if `n` is negative or reallocation fails.
pub fn ensure(&self, n: c_int) {
@@ -645,7 +657,7 @@ impl Stack {
///
/// Equivalent to [`lua_pop`].
///
/// # Panic
/// # Panics
///
/// Panics if there are less than `n` values on the stack.
pub fn pop(&mut self, n: c_int) {
@@ -660,7 +672,7 @@ impl Stack {
///
/// Equivalent to [`lua_insert`].
///
/// # Panic
/// # Panics
///
/// Panics if the stack is empty or the index `idx` is invalid.
pub fn pop_insert(&mut self, idx: c_int) {
@@ -677,7 +689,7 @@ impl Stack {
///
/// Equivalent to [`lua_replace`].
///
/// # Panic
/// # Panics
///
/// Panics if the stack is empty or the index `idx` is invalid.
pub fn pop_replace(&mut self, idx: c_int) {
@@ -720,7 +732,7 @@ impl Stack {
/// Handle for the value at index `idx`.
///
/// # Panic
/// # Panics
///
/// Panics if the index `idx` is invalid.
pub fn slot<'s>(&'s self, idx: c_int) -> Slot<'s> {
@@ -758,7 +770,7 @@ impl Stack {
///
/// Equivalent to [`lua_pushcclosure`].
///
/// # Panic
/// # Panics
///
/// Panics if the given function pointer is null.
pub fn push_function_raw(&mut self, f: lua_CFunction, upvals: c_int) {
@@ -774,7 +786,7 @@ impl Stack {
///
/// Equivalent to [`lua_rawget`].
///
/// # Panic
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn get(&mut self, idx: c_int) {
@@ -794,7 +806,7 @@ impl Stack {
///
/// Equivalent to [`lua_rawgeti`].
///
/// # Panic
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn geti(&mut self, idx: c_int, n: c_int) {
@@ -816,7 +828,7 @@ impl Stack {
///
/// Equivalent to [`lua_rawset`].
///
/// # Panic
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn set(&mut self, idx: c_int) {
@@ -838,7 +850,7 @@ impl Stack {
///
/// Equivalent to [`lua_rawseti`].
///
/// # Panic
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn seti(&mut self, idx: c_int, n: c_int) {
@@ -863,7 +875,7 @@ impl Stack {
///
/// Equivalent to `table.pack(...)`.
///
/// # Panic
/// # Panics
///
/// Panics if `n` is negative, there are not enough values on the stack, or the value at index
/// `idx` is not a table.
@@ -899,7 +911,7 @@ impl Stack {
///
/// Equivalent to `table.unpack(list, i, j)`.
///
/// # Panic
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn unpack(&mut self, idx: c_int, i: c_int, j: Option<c_int>) -> c_int {
@@ -930,6 +942,21 @@ impl Stack {
n
}
/// Pushes the result of a Lua `require(...)` call onto the stack.
///
/// Any return values from the library `name` are pushed. Lua libraries are allowed to return
/// multiple values. If `nret` is not [`LUA_MULTRET`], then the number of return values pushed
/// will be exactly `nret`, filling with nils if necessary. The number values pushed to the
/// stack is returned.
///
/// Equivalent to `require(name)`.
pub fn require(&mut self, name: impl AsRef<[u8]>, nret: c_int) -> Result<c_int> {
self.push("require");
self.get(LUA_GLOBALSINDEX);
self.push(name.as_ref());
self.call(1, nret)
}
/// Pushes the given chunk as a function at the top of the stack.
///
/// Equivalent to [`lua_loadx`].
@@ -990,7 +1017,7 @@ impl Stack {
///
/// Equivalent to `string.dump(f, mode)`.
///
/// # Panic
/// # Panics
///
/// Panics if the value at index `idx` is not a function.
pub fn dump(&self, idx: c_int, mode: DumpMode) -> Result<BString> {
@@ -1020,7 +1047,7 @@ impl Stack {
/// Equivalent to calling [`load`](Self::load) on the chunk and then [`call`](Self::call) on the
/// loaded function.
///
/// # Panic
/// # Panics
///
/// Panics if there are not enough values on the stack or thread status is invalid.
pub fn eval(&mut self, chunk: &Chunk, narg: c_int, nret: c_int) -> Result<c_int> {
@@ -1039,14 +1066,14 @@ impl Stack {
/// the index `top - narg` (i.e. the function is pushed first and then `narg` values as
/// arguments). All arguments and the function are popped from the stack and then any return
/// values are pushed. If `nret` is not [`LUA_MULTRET`], then the number of return values pushed
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of returned
/// values pushed to the stack is returned.
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of values pushed
/// to the stack is returned.
///
/// The current thread status must not be suspended or dead.
///
/// Equivalent to [`lua_pcall`].
///
/// # Panic
/// # Panics
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
@@ -1096,8 +1123,8 @@ impl Stack {
/// the index `top - narg` (i.e. the function is pushed first and then `narg` values as
/// arguments). All arguments and the function are popped from the stack and then any return
/// values are pushed. If `nret` is not [`LUA_MULTRET`], then the number of return values pushed
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of returned
/// values pushed to the stack is returned.
/// will be exactly `nret`, filling with nils if necessary. Finally, the number values pushed to
/// the stack is returned.
///
/// If the thread yields a Rust [`Future`] value, then it will be polled to completion before
/// the thread is resumed with the output of the [`Future`] as the argument. If the thread
@@ -1108,7 +1135,7 @@ impl Stack {
/// Equivalent to multiple calls to [`lua_resume`] until the thread completes with a normal
/// result.
///
/// # Panic
/// # Panics
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
@@ -1172,7 +1199,7 @@ impl Stack {
///
/// Equivalent to [`lua_resume`].
///
/// # Panic
/// # Panics
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
@@ -1316,8 +1343,7 @@ impl<'s> DerefMut for StackGuard<'s> {
impl<'s> Drop for StackGuard<'s> {
fn drop(&mut self) {
#[cfg(debug_assertions)]
if self.check_overpop {
if cfg!(debug_assertions) && self.check_overpop {
let new_size = self.stack.size();
assert!(
self.size <= new_size,

2
src/lib.rs Normal file
View File

@@ -0,0 +1,2 @@
pub use lb::fs;
pub use lb::net;

View File

@@ -1,9 +1,8 @@
use clap::Parser;
use lb_core::{GlobalState, PrettyError};
use mimalloc::MiMalloc;
use owo_colors::OwoColorize;
use std::{backtrace::Backtrace, net::SocketAddr, num::NonZero, panic, thread};
use tokio::{runtime, task::LocalSet};
use std::{backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, thread};
use sysexits::ExitCode;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
@@ -21,13 +20,21 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
};
eprint!(
"{}",
PrettyError::new(msg)
.with_trace(trace)
.prepend(format_args!(
"thread '{}' panicked at {location}",
"{}:\n{trace}",
format_args!(
"thread '{}' panicked at {location}: {msg}",
thread::current().name().unwrap_or("<unnamed>")
))
)
.red()
);
eprintln!(
"{}",
format_args!(
"This is a bug in luby. Please kindly report this at {}.",
env!("CARGO_PKG_REPOSITORY")
)
.yellow()
);
}
@@ -35,35 +42,60 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
struct Args {
/// Paths to scripts to execute.
#[clap(value_name = "SCRIPTS")]
paths: Vec<String>,
path: Vec<String>,
/// Strings to execute.
#[clap(long, short = 'e', value_name = "CHUNK")]
evals: Vec<String>,
eval: Vec<String>,
/// Libraries to require on startup.
/// Libraries to require.
#[clap(long, short = 'l', value_name = "NAME")]
libs: Vec<String>,
lib: Vec<String>,
/// Console log level.
#[clap(long, value_name = "LEVEL", default_value = "debug")]
log_level: tracing::Level,
log: tracing::Level,
/// Number of runtime worker threads.
#[clap(long, value_name = "THREADS", default_value_t = Self::threads())]
/// LuaJIT control commands.
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
jit: Vec<String>,
/// Number of worker threads.
#[clap(
long,
short = 'T',
help_heading = "Runtime",
value_name = "THREADS",
default_value_t = Self::threads()
)]
threads: NonZero<usize>,
/// Number of runtime blocking threads.
#[clap(long, value_name = "THREADS", default_value_t = Self::blocking_threads())]
/// Number of blocking threads.
#[clap(
long,
help_heading = "Runtime",
value_name = "THREADS",
default_value_t = Self::blocking_threads()
)]
blocking_threads: NonZero<usize>,
/// Enable tokio-console integration.
#[clap(long)]
#[clap(long, help_heading = "Debugging")]
enable_console: bool,
/// tokio-console publish address.
#[clap(long, value_name = "ADDRESS", default_value = "127.0.0.1:6669")]
#[clap(
long,
help_heading = "Debugging",
value_name = "ADDRESS",
default_value = "127.0.0.1:6669",
requires = "enable_console"
)]
console_addr: SocketAddr,
/// Print version.
#[clap(long, short = 'V')]
version: bool,
}
impl Args {
@@ -76,28 +108,54 @@ impl Args {
}
}
fn main() {
fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
move |err| {
eprintln!("{}", err.red());
code.exit()
}
}
fn main() -> Result<(), ExitCode> {
panic::set_hook(Box::new(panic_cb));
let args = Args::parse();
init_logger(&args);
let runtime = init_runtime(&args);
GlobalState::set(init_vm(&args));
if args.version {
return Ok(print_version());
}
let main = LocalSet::new();
main.spawn_local(run(args));
runtime.block_on(main);
init_logger(&args);
let tokio = init_tokio(&args);
let lua = init_lua(&args);
let main = lua.spawn(async |s| main_async(args, s).await);
tokio.block_on(async {
lua.await;
main.await.unwrap()
})
}
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 init_logger(args: &Args) {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{Layer, util::*};
let console = tracing_subscriber::fmt()
let log = tracing_subscriber::fmt()
.compact()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(LevelFilter::from(args.log_level).into())
.with_default_directive(LevelFilter::from(args.log).into())
.from_env_lossy(),
)
.with_file(false)
@@ -110,59 +168,93 @@ fn init_logger(args: &Args) {
.with_default_env()
.server_addr(args.console_addr)
.spawn()
.with_subscriber(console)
.with_subscriber(log)
.init()
} else {
console.init()
log.init()
}
}
fn init_runtime(args: &Args) -> runtime::Runtime {
if args.threads.get() == 1 {
runtime::Builder::new_current_thread()
} else {
runtime::Builder::new_multi_thread()
fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
let mut rt = match args.threads.get() {
1 => tokio::runtime::Builder::new_current_thread(),
n => {
let mut rt = tokio::runtime::Builder::new_multi_thread();
rt.worker_threads(n - 1);
rt
}
.enable_all()
.thread_name("lb")
.worker_threads(args.threads.get() - 1)
.max_blocking_threads(args.blocking_threads.get())
.build()
.unwrap_or_else(|err| panic!("failed to initialise runtime: {err}"))
}
fn init_vm(_args: &Args) -> luajit::State {
let mut state =
luajit::State::new().unwrap_or_else(|err| panic!("failed to initialise runtime: {err}"));
let mut registry = luaffi::Registry::new();
registry.preload::<lb_core::lb_core>("lb:core");
println!("{registry}");
state
.load(&luajit::Chunk::new(registry.done()).name("@[luby]"))
.and_then(|()| state.call(0, 0))
.unwrap_or_else(|err| panic!("failed to load modules: {err}"));
state
}
async fn run(args: Args) {
let mut state = GlobalState::new_thread();
for ref path in args.paths {
let chunk = match std::fs::read(path) {
Ok(chunk) => chunk,
Err(err) => return eprintln!("{}", format!("{path}: {err}").red()),
};
if let Err(err) = state.load(&luajit::Chunk::new(chunk).path(path)) {
return eprintln!("{}", err.red());
rt.enable_all()
.thread_name("luby")
.max_blocking_threads(args.blocking_threads.get())
.build()
.unwrap_or_else(exit_err(ExitCode::OsErr))
}
match state.call_async(0, 0).await {
Ok(_) => {}
Err(err) => GlobalState::uncaught_error(err),
fn init_lua(args: &Args) -> lb::runtime::Runtime {
let rt = lb::runtime::Builder::new();
let mut rt = rt.build().unwrap_or_else(exit_err(ExitCode::Software));
for arg in args.jit.iter() {
let mut s = rt.guard();
if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
{
(s.push("start"), s.get(-2), s.push(flags));
s.call(1, 0)
} else {
s.require("jit", 1).unwrap();
match arg.as_str() {
cmd @ ("on" | "off" | "flush") => {
(s.push(cmd), s.get(-2));
s.call(0, 0)
}
arg => {
(s.push("opt"), s.get(-2));
(s.push("start"), s.get(-2), s.push(arg));
s.call(1, 0)
}
}
}
.unwrap_or_else(exit_err(ExitCode::Usage));
}
rt
}
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
match s {
"p" => Some(("p", "Flspv10")),
"v" => Some(("v", "-")),
"dump" => Some(("dump", "tirs")),
_ => s.split_once('='),
}
}
async fn main_async(args: Args, state: &mut luajit::State) -> Result<(), ExitCode> {
for ref path in args.path {
let mut s = state.guard();
let chunk = match std::fs::read(path) {
Ok(chunk) => chunk,
Err(err) => {
eprintln!("{}", format_args!("{path}: {err}").red());
ExitCode::NoInput.exit();
}
};
s.load(&luajit::Chunk::new(chunk).path(path))
.unwrap_or_else(exit_err(ExitCode::NoInput));
if let Err(err) = s.call_async(0, 0).await {
match err.trace() {
Some(trace) => eprintln!("{}\n{trace}", err.red()), // runtime error
None => eprintln!("{}", err.red()),
}
ExitCode::DataErr.exit();
}
}
Ok(())
}