Compare commits
58 Commits
v0.0.1
...
27c40c3244
| Author | SHA1 | Date | |
|---|---|---|---|
|
27c40c3244
|
|||
|
ccae0046fb
|
|||
|
e027623d40
|
|||
|
8a49321110
|
|||
|
c70b043281
|
|||
|
f6b91cde10
|
|||
|
e05e2f4cb3
|
|||
|
6a4c726965
|
|||
|
5f1f6dab7a
|
|||
|
a760beabc1
|
|||
|
5ea532f1c6
|
|||
|
c07ec4c3ad
|
|||
|
7c40fb4322
|
|||
|
eb7b05d07a
|
|||
|
3a7f2366e4
|
|||
|
8443c44671
|
|||
|
72b3afaeea
|
|||
|
503985269a
|
|||
|
0839e7ce9a
|
|||
|
cdfb2522ac
|
|||
|
0c4639c3e9
|
|||
|
4f548bf9e9
|
|||
|
40829cdfc6
|
|||
|
db4faea821
|
|||
|
2964ebbe1b
|
|||
|
b1572cc9f1
|
|||
|
862fcfe891
|
|||
|
6a194e98e8
|
|||
|
d2e06c9a70
|
|||
|
679ffed807
|
|||
|
549f96d4dc
|
|||
|
09d7e51345
|
|||
|
8a74ade6a6
|
|||
|
9338be7eb0
|
|||
|
240e0111bf
|
|||
|
01f459ffaf
|
|||
|
9b7dbcc141
|
|||
|
24c5e9edc2
|
|||
|
0cafac0948
|
|||
|
1c1753234d
|
|||
|
31b5ff5ab9
|
|||
|
2078dd0d8e
|
|||
|
dfd57e0ad0
|
|||
|
2a6015c19a
|
|||
|
88ab48cddb
|
|||
|
bcb734846d
|
|||
|
2fe9515702
|
|||
|
f456a544e1
|
|||
|
ba969b9e56
|
|||
|
da5acd6bc8
|
|||
|
cbb47840e4
|
|||
|
2967513cbb
|
|||
|
014687068f
|
|||
|
85a0110e1a
|
|||
|
91302db725
|
|||
|
c249549b3c
|
|||
|
30596d9331
|
|||
|
530a1530ba
|
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||||
"runtime.version": "LuaJIT",
|
"runtime.version": "LuaJIT",
|
||||||
"diagnostics.disable": ["redefined-local", "lowercase-global"]
|
"diagnostics.disable": [
|
||||||
|
"undefined-global",
|
||||||
|
"redefined-local",
|
||||||
|
"lowercase-global"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
143
Cargo.lock
generated
143
Cargo.lock
generated
@@ -534,6 +534,12 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -573,6 +579,12 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -685,6 +697,19 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "globset"
|
||||||
|
version = "0.4.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"bstr",
|
||||||
|
"log",
|
||||||
|
"regex-automata 0.4.9",
|
||||||
|
"regex-syntax 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.10"
|
version = "0.4.10"
|
||||||
@@ -1025,14 +1050,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
name = "lb"
|
name = "lb"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"camino",
|
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"globset",
|
||||||
"luaffi",
|
"luaffi",
|
||||||
"luaify",
|
|
||||||
"luajit",
|
"luajit",
|
||||||
"sysexits",
|
"sysexits",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1097,16 +1123,6 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
@@ -1138,6 +1154,7 @@ dependencies = [
|
|||||||
name = "luaify"
|
name = "luaify"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"pretty_assertions",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -1319,29 +1336,6 @@ version = "4.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
|
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.12.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.9.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@@ -1410,6 +1404,16 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||||
|
dependencies = [
|
||||||
|
"diff",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.35"
|
version = "0.2.35"
|
||||||
@@ -1506,15 +1510,6 @@ dependencies = [
|
|||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.5.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -1606,10 +1601,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "same-file"
|
||||||
version = "1.2.0"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
@@ -1667,15 +1665,6 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signal-hook-registry"
|
|
||||||
version = "1.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simdutf8"
|
name = "simdutf8"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -1750,6 +1739,19 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "198f60d1f7f003f168507691e42d082df109ef0f05c6fd006e22528371a5f1b4"
|
checksum = "198f60d1f7f003f168507691e42d082df109ef0f05c6fd006e22528371a5f1b4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
@@ -1832,9 +1834,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2113,6 +2113,16 @@ dependencies = [
|
|||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -2164,6 +2174,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -2346,6 +2365,12 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|||||||
19
Cargo.toml
19
Cargo.toml
@@ -30,17 +30,28 @@ repository.workspace = true
|
|||||||
dev.panic = "abort"
|
dev.panic = "abort"
|
||||||
release.panic = "abort"
|
release.panic = "abort"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "main"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["task", "fs", "net"]
|
||||||
|
task = ["lb/task"]
|
||||||
|
fs = ["lb/fs"]
|
||||||
|
net = ["lb/net"]
|
||||||
|
tokio-console = ["dep:console-subscriber"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.40", features = ["derive", "env"] }
|
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||||
console-subscriber = "0.4.1"
|
console-subscriber = { version = "0.4.1", optional = true }
|
||||||
lb = { path = "crates/lb" }
|
lb = { path = "crates/lb", features = ["runtime"] }
|
||||||
luajit = { path = "crates/luajit", features = ["runtime"] }
|
luajit = { path = "crates/luajit", features = ["runtime"] }
|
||||||
mimalloc = "0.1.47"
|
mimalloc = "0.1.47"
|
||||||
owo-colors = "4.2.1"
|
owo-colors = "4.2.1"
|
||||||
sysexits = "0.9.0"
|
sysexits = "0.9.0"
|
||||||
tokio = { version = "1.45.1", features = ["full", "tracing"] }
|
tokio = { version = "1.45.1", features = ["rt", "rt-multi-thread"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
vergen-git2 = { version = "1.0.7", features = ["cargo", "rustc"] }
|
vergen-git2 = { version = "1.0.7", features = ["cargo", "rustc"] }
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
[jobs.test]
|
|
||||||
command = ["cargo", "test", "--workspace"]
|
|
||||||
need_stdout = true
|
|
||||||
|
|
||||||
[jobs.doc]
|
[jobs.doc]
|
||||||
command = ["cargo", "doc", "--workspace"]
|
command = ["cargo", "doc", "--workspace"]
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,19 @@ authors.workspace = true
|
|||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
runtime = ["tokio/rt"]
|
||||||
|
task = ["tokio/rt", "tokio/time"]
|
||||||
|
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
||||||
|
net = ["tokio/net"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
camino = "1.1.10"
|
|
||||||
derive_more = { version = "2.0.1", features = ["full"] }
|
derive_more = { version = "2.0.1", features = ["full"] }
|
||||||
|
globset = { version = "0.4.16", optional = true }
|
||||||
luaffi = { path = "../luaffi" }
|
luaffi = { path = "../luaffi" }
|
||||||
luajit = { path = "../luajit" }
|
luajit = { path = "../luajit" }
|
||||||
sysexits = "0.9.0"
|
sysexits = "0.9.0"
|
||||||
tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal", "tracing"] }
|
tempfile = { version = "3.20.0", optional = true }
|
||||||
tracing = "0.1.41"
|
thiserror = "2.0.12"
|
||||||
|
tokio = { version = "1.45.1" }
|
||||||
[dev-dependencies]
|
walkdir = { version = "2.5.0", optional = true }
|
||||||
luaify = { path = "../luaify" }
|
|
||||||
tokio = { version = "1.45.1", features = ["full"] }
|
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
// use flume::{Receiver, Sender};
|
//! # Channel library
|
||||||
|
//!
|
||||||
|
//! The `lb:chan` library provides primitives for asynchronous communication between tasks via
|
||||||
|
//! message passing channels.
|
||||||
|
//!
|
||||||
|
//! ## Exports
|
||||||
|
//!
|
||||||
|
//! See [`lb_chanlib`] for items exported by this library.
|
||||||
use luaffi::{cdef, metatype};
|
use luaffi::{cdef, metatype};
|
||||||
|
|
||||||
#[cdef]
|
/// Items exported by the `lb:chan` library.
|
||||||
pub struct lb_libchannel;
|
///
|
||||||
|
/// This library can be acquired by calling
|
||||||
|
/// [`require("lb:chan")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local chan = require("lb:chan");
|
||||||
|
/// ```
|
||||||
|
#[cdef(module = "lb:chan")]
|
||||||
|
pub struct lb_chanlib;
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_libchannel {
|
impl lb_chanlib {
|
||||||
#[new]
|
#[new]
|
||||||
extern "Lua-C" fn new() -> Self {
|
extern "Lua-C" fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
@@ -17,7 +32,7 @@ impl lb_libchannel {
|
|||||||
(send, recv)
|
(send, recv)
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua" fn bounded(self, cap: number) {
|
extern "Lua" fn bounded(self, cap: u32) {
|
||||||
assert(cap >= 0, "channel capacity must be nonnegative");
|
assert(cap >= 0, "channel capacity must be nonnegative");
|
||||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
||||||
self.__bounded(cap, send, recv);
|
self.__bounded(cap, send, recv);
|
||||||
@@ -1,34 +1,429 @@
|
|||||||
//! The `lb:fs` module provides utilities for interacting with the file system asynchronously.
|
//! # Filesystem library
|
||||||
//!
|
//!
|
||||||
//! # Exports
|
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
|
||||||
|
//! filesystem.
|
||||||
//!
|
//!
|
||||||
//! See [`lb_libfs`] for items exported by this module.
|
//! ## Asynchronous by default
|
||||||
|
//!
|
||||||
|
//! Filesystem operations are blocking by nature; to provide asynchronicity, luby performs blocking
|
||||||
|
//! operations in a background thread pool by default. Synchronous complements to all asynchronous
|
||||||
|
//! functions are always provided.
|
||||||
|
//!
|
||||||
|
//! ## Exports
|
||||||
|
//!
|
||||||
|
//! See [`lb_fslib`] for items exported by this library.
|
||||||
|
use derive_more::From;
|
||||||
use luaffi::{cdef, metatype};
|
use luaffi::{cdef, metatype};
|
||||||
use std::io;
|
use std::{path::PathBuf, time::SystemTime};
|
||||||
use tokio::fs;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Items exported by the `lb:fs` module.
|
/// Errors that can be thrown by this library.
|
||||||
///
|
///
|
||||||
/// This module can be obtained by calling `require` in Lua.
|
/// Functions which return this error will **throw** in Lua. The error message can be caught by
|
||||||
|
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// I/O error.
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
/// Walk directory error.
|
||||||
|
#[error("{0}")]
|
||||||
|
Walk(#[from] walkdir::Error),
|
||||||
|
/// Glob pattern error.
|
||||||
|
#[error("{0}")]
|
||||||
|
Glob(#[from] globset::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Items exported by the `lb:fs` library.
|
||||||
|
///
|
||||||
|
/// This library can be acquired by calling
|
||||||
|
/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||||
///
|
///
|
||||||
/// ```lua
|
/// ```lua
|
||||||
/// local fs = require("lb:fs");
|
/// local fs = require("lb:fs");
|
||||||
/// ```
|
/// ```
|
||||||
#[cdef]
|
#[cdef(module = "lb:fs")]
|
||||||
pub struct lb_libfs;
|
pub struct lb_fslib;
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_libfs {
|
impl lb_fslib {
|
||||||
#[new]
|
#[new]
|
||||||
extern "Lua-C" fn new() -> Self {
|
extern "Lua-C" fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async extern "Lua-C" fn read(&self, path: &str) -> io::Result<Vec<u8>> {
|
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
||||||
fs::read(path).await
|
Ok(tokio::fs::read(path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Lua-C" fn read_sync(&self, path: &str) -> io::Result<Vec<u8>> {
|
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
||||||
std::fs::read(path)
|
Ok(std::fs::read(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
|
||||||
|
Ok(tokio::fs::write(path, contents).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
|
||||||
|
Ok(std::fs::write(path, contents)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async extern "Lua-C" fn read_dir(path: &str) -> Result<lb_read_dir> {
|
||||||
|
Ok(tokio::fs::read_dir(path).await?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result<lb_read_dir_sync> {
|
||||||
|
Ok(std::fs::read_dir(path)?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
|
||||||
|
walkdir::WalkDir::new(path).into_iter().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn glob(pattern: &str) -> Result<lb_glob_dir> {
|
||||||
|
Self::glob_dir(".", pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn glob_dir(path: &str, pattern: &str) -> Result<lb_glob_dir> {
|
||||||
|
let prefix = PathBuf::from(path);
|
||||||
|
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
|
||||||
|
let matcher = globset::GlobSet::builder()
|
||||||
|
.add(globset::Glob::new(pattern)?)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
Ok(lb_glob_dir {
|
||||||
|
iter,
|
||||||
|
matcher,
|
||||||
|
prefix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
|
||||||
|
Ok(tempfile::tempdir()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result<lb_temp_dir> {
|
||||||
|
Ok(tempfile::tempdir_in(path)?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over the entries in a directory.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_read_dir(#[opaque] tokio::fs::ReadDir);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_read_dir {
|
||||||
|
#[call]
|
||||||
|
pub async extern "Lua-C" fn next(&mut self) -> Result<Option<lb_dir_entry>> {
|
||||||
|
Ok(self.0.next_entry().await?.map(Into::into))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Synchronous version of [`lb_read_dir`].
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_read_dir_sync(#[opaque] std::fs::ReadDir);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_read_dir_sync {
|
||||||
|
#[call]
|
||||||
|
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_dir_entry_sync>> {
|
||||||
|
Ok(self.0.next().transpose()?.map(Into::into))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry inside of a directory on the filesystem.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_dir_entry(#[opaque] tokio::fs::DirEntry);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_dir_entry {
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
|
self.0.file_name().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||||
|
Ok(self.0.file_type().await?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
|
Ok(self.0.metadata().await?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
|
self.0.ino()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Synchronous version of [`lb_dir_entry`].
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_dir_entry_sync(#[opaque] std::fs::DirEntry);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_dir_entry_sync {
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
|
self.0.file_name().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||||
|
Ok(self.0.file_type()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
|
Ok(self.0.metadata()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
|
use std::os::unix::fs::DirEntryExt;
|
||||||
|
self.0.ino()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structure representing the type of a file with accessors for each file type.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_file_type(#[opaque] std::fs::FileType);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_file_type {
|
||||||
|
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||||
|
self.0.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||||
|
if self.0.is_file() {
|
||||||
|
"file"
|
||||||
|
} else if self.0.is_dir() {
|
||||||
|
"dir"
|
||||||
|
} else if self.0.is_symlink() {
|
||||||
|
"symlink"
|
||||||
|
} else {
|
||||||
|
"other"
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata information about a file.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_file_meta(#[opaque] std::fs::Metadata);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_file_meta {
|
||||||
|
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||||
|
self.0.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||||
|
self.0.file_type().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn size(&self) -> u64 {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
|
||||||
|
self.0.permissions().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn created(&self) -> Result<f64> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.created()?
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|dur| dur.as_secs_f64())
|
||||||
|
.unwrap_or(0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.modified()?
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|dur| dur.as_secs_f64())
|
||||||
|
.unwrap_or(0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.accessed()?
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|dur| dur.as_secs_f64())
|
||||||
|
.unwrap_or(0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||||
|
let ty = self.0.file_type();
|
||||||
|
if ty.is_file() {
|
||||||
|
format!("file {}", self.0.len())
|
||||||
|
} else if ty.is_dir() {
|
||||||
|
"dir".into()
|
||||||
|
} else if ty.is_symlink() {
|
||||||
|
"symlink".into()
|
||||||
|
} else {
|
||||||
|
"other".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Representation of the various permissions on a file.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_file_perms(#[opaque] std::fs::Permissions);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_file_perms {
|
||||||
|
pub extern "Lua-C" fn readonly(&self) -> bool {
|
||||||
|
self.0.readonly()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn set_readonly(&mut self, readonly: bool) {
|
||||||
|
self.0.set_readonly(readonly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator for recursively descending into a directory.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_walk_dir(#[opaque] walkdir::IntoIter);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_walk_dir {
|
||||||
|
#[call]
|
||||||
|
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_walk_dir_entry>> {
|
||||||
|
Ok(self.0.next().transpose()?.map(Into::into))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_walk_dir_entry(#[opaque] walkdir::DirEntry);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_walk_dir_entry {
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
|
self.0.file_name().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||||
|
self.0.file_type().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
|
Ok(self.0.metadata()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
|
self.0.path_is_symlink()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub extern "Lua-C" fn depth(&self) -> u32 {
|
||||||
|
self.0.depth() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
|
use walkdir::DirEntryExt;
|
||||||
|
self.0.ino()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator that yields paths from the filesystem that match a particular pattern.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_glob_dir {
|
||||||
|
#[opaque]
|
||||||
|
iter: walkdir::IntoIter,
|
||||||
|
#[opaque]
|
||||||
|
matcher: globset::GlobSet,
|
||||||
|
#[opaque]
|
||||||
|
prefix: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_glob_dir {
|
||||||
|
#[call]
|
||||||
|
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_walk_dir_entry>> {
|
||||||
|
while let Some(res) = self.iter.next() {
|
||||||
|
let entry = res?;
|
||||||
|
let path = entry.path().strip_prefix(&self.prefix).unwrap();
|
||||||
|
if self.matcher.is_match(path) {
|
||||||
|
return Ok(Some(entry.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Directory in the filesystem that is automatically deleted when it is garbage-collected.
|
||||||
|
#[derive(Debug, From)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_temp_dir(#[opaque] tempfile::TempDir);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_temp_dir {
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
pub mod channel;
|
//! luby standard library
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#[cfg(feature = "task")]
|
||||||
|
pub mod chan;
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
#[cfg(feature = "net")]
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
#[cfg(feature = "task")]
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|||||||
@@ -1,56 +1,79 @@
|
|||||||
//! The `lb:net` module provides an asynchronous network API for creating TCP or UDP servers and
|
//! # Networking library
|
||||||
|
//!
|
||||||
|
//! The `lb:net` library provides an asynchronous network API for creating TCP or UDP servers and
|
||||||
//! clients.
|
//! clients.
|
||||||
//!
|
//!
|
||||||
//! # Exports
|
//! ## Exports
|
||||||
//!
|
//!
|
||||||
//! See [`lb_libnet`] for items exported by this module.
|
//! See [`lb_netlib`] for items exported by this library.
|
||||||
use derive_more::{From, FromStr};
|
use derive_more::{From, FromStr};
|
||||||
use luaffi::{cdef, metatype};
|
use luaffi::{cdef, marker::OneOf, metatype};
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
|
||||||
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use thiserror::Error;
|
||||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||||
|
|
||||||
/// Items exported by the `lb:net` module.
|
/// Errors that can be thrown by this library.
|
||||||
///
|
///
|
||||||
/// This module can be obtained by calling `require` in Lua.
|
/// Functions which return this error will **throw** in Lua. The error message can be caught by
|
||||||
|
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// I/O error.
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
/// IP or socket address syntax error.
|
||||||
|
#[error("{0}")]
|
||||||
|
InvalidAddr(#[from] AddrParseError),
|
||||||
|
/// Socket was already converted and cannot be used anymore.
|
||||||
|
#[error("socket was already converted")]
|
||||||
|
SocketConsumed,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Items exported by the `lb:net` library.
|
||||||
|
///
|
||||||
|
/// This library can be acquired by calling
|
||||||
|
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||||
///
|
///
|
||||||
/// ```lua
|
/// ```lua
|
||||||
/// local net = require("lb:net");
|
/// local net = require("lb:net");
|
||||||
/// ```
|
/// ```
|
||||||
#[cdef]
|
#[cdef(module = "lb:net")]
|
||||||
pub struct lb_libnet;
|
pub struct lb_netlib;
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_libnet {
|
impl lb_netlib {
|
||||||
#[new]
|
#[new]
|
||||||
extern "Lua-C" fn new() -> Self {
|
extern "Lua-C" fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Ipv4Addr::LOCALHOST`].
|
/// An IPv4 address representing localhost: `127.0.0.1`
|
||||||
pub extern "Lua-C" fn localhost_v4(&self) -> lb_ipaddr {
|
pub extern "Lua-C" fn localhost_v4() -> lb_ipaddr {
|
||||||
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Ipv6Addr::LOCALHOST`].
|
/// An IPv6 address representing localhost: `::1`
|
||||||
pub extern "Lua-C" fn localhost_v6(&self) -> lb_ipaddr {
|
pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr {
|
||||||
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Ipv4Addr::UNSPECIFIED`].
|
/// An IPv4 address representing an unspecified address: `0.0.0.0`
|
||||||
pub extern "Lua-C" fn unspecified_v4(&self) -> lb_ipaddr {
|
pub extern "Lua-C" fn unspecified_v4() -> lb_ipaddr {
|
||||||
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Ipv6Addr::UNSPECIFIED`].
|
/// An IPv6 address representing an unspecified address: `::`
|
||||||
pub extern "Lua-C" fn unspecified_v6(&self) -> lb_ipaddr {
|
pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr {
|
||||||
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Ipv4Addr::BROADCAST`].
|
/// An IPv4 address representing the broadcast address: `255.255.255.255`
|
||||||
pub extern "Lua-C" fn broadcast_v4(&self) -> lb_ipaddr {
|
pub extern "Lua-C" fn broadcast_v4() -> lb_ipaddr {
|
||||||
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,22 +82,20 @@ impl lb_libnet {
|
|||||||
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
|
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
|
||||||
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
|
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
|
||||||
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
|
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
|
||||||
///
|
pub extern "Lua" fn ipaddr(
|
||||||
/// # Errors
|
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||||
///
|
) -> Result<lb_ipaddr> {
|
||||||
/// Throws if `s` cannot be parsed as an IP address.
|
if __istype(__ct.lb_ipaddr, addr) {
|
||||||
pub extern "Lua" fn ipaddr(&self, s: any) -> lb_ipaddr {
|
__new(__ct.lb_ipaddr, addr) // copy constructor
|
||||||
if __istype(__ct.lb_ipaddr, s) {
|
} else if __istype(__ct.lb_socketaddr, addr) {
|
||||||
__new(__ct.lb_ipaddr, s) // copy constructor
|
|
||||||
} else if __istype(__ct.lb_socketaddr, s) {
|
|
||||||
s.ip()
|
s.ip()
|
||||||
} else {
|
} else {
|
||||||
self.__parse_ipaddr(s)
|
Self::__parse_ipaddr(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr, AddrParseError> {
|
extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result<lb_ipaddr> {
|
||||||
s.parse()
|
Ok(addr.parse()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an [`lb_socketaddr`] from the given input.
|
/// Creates an [`lb_socketaddr`] from the given input.
|
||||||
@@ -86,56 +107,87 @@ impl lb_libnet {
|
|||||||
/// socket address string. Both IPv4 and IPv6 addresses are supported.
|
/// socket address string. Both IPv4 and IPv6 addresses are supported.
|
||||||
///
|
///
|
||||||
/// If `port` is not specified, `0` is used as the default.
|
/// If `port` is not specified, `0` is used as the default.
|
||||||
///
|
pub extern "Lua" fn socketaddr(
|
||||||
/// # Errors
|
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||||
///
|
port: Option<u16>,
|
||||||
/// Throws if `s` cannot be parsed as an IP or socket address.
|
) -> Result<lb_socketaddr> {
|
||||||
pub extern "Lua" fn socketaddr(&self, s: any, port: any) -> lb_socketaddr {
|
|
||||||
if port != () {
|
if port != () {
|
||||||
self.__new_socketaddr(self.ipaddr(s), port)
|
Self::__new_skaddr(Self::ipaddr(addr), port)
|
||||||
} else {
|
} else {
|
||||||
if __istype(__ct.lb_socketaddr, s) {
|
if __istype(__ct.lb_socketaddr, addr) {
|
||||||
__new(__ct.lb_socketaddr, s) // copy constructor
|
__new(__ct.lb_socketaddr, addr) // copy constructor
|
||||||
} else if __istype(__ct.lb_ipaddr, s) {
|
} else if __istype(__ct.lb_ipaddr, addr) {
|
||||||
self.__new_socketaddr(s, 0) // default port 0
|
Self::__new_skaddr(addr, 0) // default port 0
|
||||||
} else {
|
} else {
|
||||||
self.__parse_socketaddr(s)
|
Self::__parse_skaddr(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __new_socketaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
||||||
SocketAddr::new(ip.0, port).into()
|
SocketAddr::new(ip.0, port).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __parse_socketaddr(&self, s: &str) -> Result<lb_socketaddr, AddrParseError> {
|
extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result<lb_socketaddr> {
|
||||||
s.parse()
|
Ok(if let Ok(addr) = addr.parse() {
|
||||||
|
SocketAddr::new(addr, 0).into() // default port 0
|
||||||
|
} else {
|
||||||
|
addr.parse::<SocketAddr>()?.into()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new TCP socket configured for IPv4.
|
/// Creates a new TCP socket configured for IPv4.
|
||||||
///
|
///
|
||||||
/// See [`TcpSocket::new_v4`].
|
/// See [`TcpSocket::new_v4`].
|
||||||
///
|
pub extern "Lua-C" fn tcp() -> Result<lb_tcpsocket> {
|
||||||
/// # Errors
|
Ok(Some(TcpSocket::new_v4()?).into())
|
||||||
///
|
|
||||||
/// 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.
|
/// Creates a new TCP socket configured for IPv6.
|
||||||
///
|
///
|
||||||
/// See [`TcpSocket::new_v6`].
|
/// See [`TcpSocket::new_v6`].
|
||||||
///
|
pub extern "Lua-C" fn tcp6() -> Result<lb_tcpsocket> {
|
||||||
/// # Errors
|
Ok(Some(TcpSocket::new_v6()?).into())
|
||||||
///
|
}
|
||||||
/// Throws if an error was encountered during the socket creation.
|
|
||||||
pub extern "Lua-C" fn tcp_v6(&self) -> io::Result<lb_tcpsocket> {
|
pub async extern "Lua" fn bind_tcp(
|
||||||
TcpSocket::new_v6().map(lb_tcpsocket)
|
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_tcpsocket> {
|
||||||
|
let addr = Self::socketaddr(addr, port);
|
||||||
|
let socket;
|
||||||
|
if addr.ip().is_v6() {
|
||||||
|
socket = Self::tcp6();
|
||||||
|
} else {
|
||||||
|
socket = Self::tcp();
|
||||||
|
}
|
||||||
|
socket.bind(addr);
|
||||||
|
socket
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async extern "Lua" fn connect_tcp(
|
||||||
|
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_tcpstream> {
|
||||||
|
let addr = Self::socketaddr(addr, port);
|
||||||
|
let socket;
|
||||||
|
if addr.ip().is_v6() {
|
||||||
|
socket = Self::tcp6();
|
||||||
|
} else {
|
||||||
|
socket = Self::tcp();
|
||||||
|
}
|
||||||
|
socket.connect(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async extern "Lua" fn listen_tcp(
|
||||||
|
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_tcplistener> {
|
||||||
|
Self::bind_tcp(addr, port).listen(1024)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An IP address, either IPv4 or IPv6.
|
/// IP address, either IPv4 or IPv6.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -143,7 +195,7 @@ impl lb_libnet {
|
|||||||
///
|
///
|
||||||
/// ```lua
|
/// ```lua
|
||||||
/// local net = require("lb:net");
|
/// local net = require("lb:net");
|
||||||
/// local addr = net:ipaddr("127.0.0.1"); -- ipv4 loopback address
|
/// local addr = net.ipaddr("127.0.0.1"); -- ipv4 loopback address
|
||||||
///
|
///
|
||||||
/// assert(addr:is_v4());
|
/// assert(addr:is_v4());
|
||||||
/// assert(addr:is_loopback());
|
/// assert(addr:is_loopback());
|
||||||
@@ -170,7 +222,7 @@ impl lb_ipaddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the string `"v4"` if this is an IPv4 address or `"v6"` if this is an IPv6 address.
|
/// 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 {
|
pub extern "Lua" fn family(&self) -> String {
|
||||||
if self.is_v6() { "v6" } else { "v4" }
|
if self.is_v6() { "v6" } else { "v4" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +312,7 @@ impl lb_ipaddr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A socket address, which is an IP address with a port number.
|
/// Socket address, which is an IP address with a port number.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
||||||
@@ -274,14 +326,17 @@ impl lb_socketaddr {
|
|||||||
|
|
||||||
/// Sets the IP part of this address.
|
/// Sets the IP part of this address.
|
||||||
///
|
///
|
||||||
/// This function accepts the same arguments as [`ipaddr`](lb_libnet::ipaddr).
|
/// This function accepts the same arguments as [`ipaddr`](lb_netlib::ipaddr).
|
||||||
pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self {
|
pub extern "Lua" fn set_ip(
|
||||||
if __istype(__ct.lb_ipaddr, s) {
|
&mut self,
|
||||||
self.__set_ip(s);
|
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
||||||
} else if __istype(__ct.lb_socketaddr, s) {
|
) -> &mut Self {
|
||||||
self.__set_ip(s.ip());
|
if __istype(__ct.lb_ipaddr, addr) {
|
||||||
|
self.__set_ip(addr);
|
||||||
|
} else if __istype(__ct.lb_socketaddr, addr) {
|
||||||
|
self.__set_ip(addr.ip());
|
||||||
} else {
|
} else {
|
||||||
self.__set_ip_parse(s);
|
self.__set_ip_parse(addr);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -290,8 +345,8 @@ impl lb_socketaddr {
|
|||||||
self.0.set_ip(ip.0);
|
self.0.set_ip(ip.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<(), AddrParseError> {
|
extern "Lua-C" fn __set_ip_parse(&mut self, addr: &str) -> Result<()> {
|
||||||
s.parse().map(|ip| self.0.set_ip(ip))
|
Ok(self.0.set_ip(addr.parse()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the port part of this address.
|
/// Returns the port part of this address.
|
||||||
@@ -300,7 +355,7 @@ impl lb_socketaddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the port part of this address.
|
/// Sets the port part of this address.
|
||||||
pub extern "Lua" fn set_port(&mut self, port: number) -> &mut Self {
|
pub extern "Lua" fn set_port(&mut self, port: u16) -> &mut Self {
|
||||||
self.__set_port(port);
|
self.__set_port(port);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -316,14 +371,127 @@ impl lb_socketaddr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A TCP socket which has not yet been converted to a [`lb_tcpstream`] or [`lb_tcplistener`].
|
/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug, From)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_tcpsocket(#[opaque] TcpSocket);
|
pub struct lb_tcpsocket(#[opaque] Option<TcpSocket>);
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_tcpsocket {}
|
impl lb_tcpsocket {
|
||||||
|
fn socket(&self) -> Result<&TcpSocket> {
|
||||||
|
self.0.as_ref().ok_or(Error::SocketConsumed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::keepalive`].
|
||||||
|
pub extern "Lua-C" fn keepalive(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.keepalive()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_keepalive`].
|
||||||
|
pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_keepalive(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::reuseaddr`].
|
||||||
|
pub extern "Lua-C" fn reuseaddr(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.reuseaddr()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_reuseaddr`].
|
||||||
|
pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_reuseaddr(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::reuseport`].
|
||||||
|
pub extern "Lua-C" fn reuseport(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.reuseport()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_reuseport`].
|
||||||
|
pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_reuseport(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::send_buffer_size`].
|
||||||
|
pub extern "Lua-C" fn sendbuf(&self) -> Result<u32> {
|
||||||
|
Ok(self.socket()?.send_buffer_size()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_send_buffer_size`].
|
||||||
|
pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_send_buffer_size(size)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::recv_buffer_size`].
|
||||||
|
pub extern "Lua-C" fn recvbuf(&self) -> Result<u32> {
|
||||||
|
Ok(self.socket()?.recv_buffer_size()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_recv_buffer_size`].
|
||||||
|
pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_recv_buffer_size(size)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::linger`].
|
||||||
|
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
|
||||||
|
Ok(self
|
||||||
|
.socket()?
|
||||||
|
.linger()?
|
||||||
|
.map(|n| n.as_secs_f64())
|
||||||
|
.unwrap_or(0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_linger`].
|
||||||
|
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
|
||||||
|
Ok(self
|
||||||
|
.socket()?
|
||||||
|
.set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::nodelay`].
|
||||||
|
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.nodelay()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_nodelay`].
|
||||||
|
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_nodelay(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::tos`].
|
||||||
|
pub extern "Lua-C" fn tos(&self) -> Result<u32> {
|
||||||
|
Ok(self.socket()?.tos()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::set_tos`].
|
||||||
|
pub extern "Lua-C" fn set_tos(&self, tos: u32) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_tos(tos)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::local_addr`].
|
||||||
|
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.socket()?.local_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::bind`].
|
||||||
|
pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> {
|
||||||
|
Ok(self.socket()?.bind(addr.0)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::connect`].
|
||||||
|
pub async extern "Lua-C" fn connect(&mut self, addr: &lb_socketaddr) -> Result<lb_tcpstream> {
|
||||||
|
let socket = self.0.take().ok_or(Error::SocketConsumed)?;
|
||||||
|
Ok(socket.connect(addr.0).await?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`TcpSocket::listen`].
|
||||||
|
pub extern "Lua-C" fn listen(&mut self, backlog: u32) -> Result<lb_tcplistener> {
|
||||||
|
let socket = self.0.take().ok_or(Error::SocketConsumed)?;
|
||||||
|
Ok(socket.listen(backlog)?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TCP connection between a local and a remote socket.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug, From)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_tcpstream(#[opaque] TcpStream);
|
pub struct lb_tcpstream(#[opaque] TcpStream);
|
||||||
@@ -331,6 +499,7 @@ pub struct lb_tcpstream(#[opaque] TcpStream);
|
|||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_tcpstream {}
|
impl lb_tcpstream {}
|
||||||
|
|
||||||
|
/// TCP socket server, listening for connections.
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug, From)]
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_tcplistener(#[opaque] TcpListener);
|
pub struct lb_tcplistener(#[opaque] TcpListener);
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
local task = require("lb:task")
|
|
||||||
|
|
||||||
function spawn(f, ...)
|
|
||||||
return task:spawn(f, ...)
|
|
||||||
end
|
|
||||||
@@ -1,85 +1,115 @@
|
|||||||
use crate::{channel::lb_libchannel, fs::lb_libfs, net::lb_libnet, task::lb_libtask};
|
#![doc(hidden)]
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use luaffi::{Registry, Type};
|
use luaffi::{Module, Registry};
|
||||||
use luajit::{Chunk, State};
|
use luajit::{Chunk, State};
|
||||||
use std::fmt::Display;
|
use std::rc::Rc;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
||||||
task_local,
|
task_local,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
pub type ErrorFn = dyn Fn(&luajit::Error);
|
||||||
|
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
|
report_err: Rc<ErrorFn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut registry = Registry::new();
|
Self {
|
||||||
|
registry: Registry::new(),
|
||||||
registry
|
report_err: Rc::new(|err| match err.trace() {
|
||||||
.preload::<lb_libtask>("lb:task")
|
Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"),
|
||||||
.preload::<lb_libchannel>("lb:channel")
|
None => eprintln!("unhandled lua error: {err}"),
|
||||||
.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 {
|
pub fn registry(&self) -> &Registry {
|
||||||
&self.registry
|
&self.registry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unhandled_error(&mut self, handler: impl Fn(&luajit::Error) + 'static) -> &mut Self {
|
||||||
|
self.report_err = Rc::new(handler);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn module<T: Module>(&mut self) -> &mut Self {
|
||||||
|
self.registry.preload::<T>();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> luajit::Result<Runtime> {
|
pub fn build(&self) -> luajit::Result<Runtime> {
|
||||||
Ok(Runtime {
|
Ok(Runtime {
|
||||||
state: {
|
cx: Context {
|
||||||
let mut s = State::new()?;
|
state: {
|
||||||
let mut chunk = Chunk::new(self.registry.done());
|
let mut s = State::new()?;
|
||||||
chunk.extend(include_bytes!("./runtime.lua"));
|
s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
|
||||||
s.eval(chunk.path("[luby]"), 0, 0)?;
|
s
|
||||||
s
|
},
|
||||||
|
report_err: self.report_err.clone(),
|
||||||
},
|
},
|
||||||
tasks: LocalSet::new(),
|
tasks: LocalSet::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deref, DerefMut)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
#[deref]
|
#[deref]
|
||||||
#[deref_mut]
|
#[deref_mut]
|
||||||
state: State,
|
cx: Context,
|
||||||
tasks: LocalSet,
|
tasks: LocalSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
task_local! {
|
|
||||||
static STATE: State;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
pub fn spawn<T: 'static>(
|
pub fn spawn<T: 'static>(
|
||||||
&self,
|
&self,
|
||||||
f: impl AsyncFnOnce(&mut State) -> T + 'static,
|
f: impl AsyncFnOnce(&mut Context) -> T + 'static,
|
||||||
) -> JoinHandle<T> {
|
) -> JoinHandle<T> {
|
||||||
self.tasks
|
self.tasks
|
||||||
.spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
|
.spawn_local(async move { f(&mut CURRENT.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 {
|
impl IntoFuture for Runtime {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
type IntoFuture = TaskLocalFuture<State, LocalSet>;
|
type IntoFuture = TaskLocalFuture<Context, LocalSet>;
|
||||||
|
|
||||||
fn into_future(self) -> Self::IntoFuture {
|
fn into_future(self) -> Self::IntoFuture {
|
||||||
STATE.scope(self.state, self.tasks)
|
CURRENT.scope(self.cx, self.tasks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static CURRENT: Context;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
|
pub struct Context {
|
||||||
|
#[deref]
|
||||||
|
#[deref_mut]
|
||||||
|
state: State,
|
||||||
|
report_err: Rc<ErrorFn>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn new_thread(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
state: self.state.new_thread(),
|
||||||
|
report_err: self.report_err.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_error(&self, err: &luajit::Error) {
|
||||||
|
(self.report_err)(&err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut Context) -> T + 'static) -> JoinHandle<T> {
|
||||||
|
// SAFETY: `new_thread` must be called inside `spawn_local` because this free-standing spawn
|
||||||
|
// function may be called via ffi from lua, and it is not safe to access the lua state within
|
||||||
|
// ffi calls.
|
||||||
|
spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await })
|
||||||
|
}
|
||||||
|
|||||||
3
crates/lb/src/task.lua
Normal file
3
crates/lb/src/task.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
local task = require("lb:task")
|
||||||
|
sleep = task.sleep
|
||||||
|
spawn = task.spawn
|
||||||
@@ -1,46 +1,107 @@
|
|||||||
|
//! # Task library
|
||||||
|
//!
|
||||||
|
//! The `lb:task` library primitives for asynchronous communication between tasks via message
|
||||||
|
//! passing channels.
|
||||||
|
//!
|
||||||
|
//! ## Exports
|
||||||
|
//!
|
||||||
|
//! See [`lb_tasklib`] for items exported by this library.
|
||||||
use crate::runtime::spawn;
|
use crate::runtime::spawn;
|
||||||
use luaffi::{cdef, metatype};
|
use luaffi::{
|
||||||
use std::{ffi::c_int, process};
|
cdef,
|
||||||
use tokio::task::JoinHandle;
|
marker::{function, many},
|
||||||
|
metatype,
|
||||||
|
};
|
||||||
|
use luajit::{LUA_MULTRET, Type};
|
||||||
|
use std::{ffi::c_int, time::Duration};
|
||||||
|
use tokio::{task::JoinHandle, time::sleep};
|
||||||
|
|
||||||
#[cdef]
|
/// Items exported by the `lb:task` library.
|
||||||
pub struct lb_libtask;
|
///
|
||||||
|
/// This library can be acquired by calling
|
||||||
|
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local task = require("lb:task");
|
||||||
|
/// ```
|
||||||
|
#[cdef(module = "lb:task")]
|
||||||
|
pub struct lb_tasklib;
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_libtask {
|
#[include("task.lua")]
|
||||||
|
impl lb_tasklib {
|
||||||
#[new]
|
#[new]
|
||||||
extern "Lua-C" fn new() -> Self {
|
extern "Lua-C" fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Lua" fn spawn(self, f: function, ...) {
|
pub async extern "Lua-C" fn sleep(ms: f64) {
|
||||||
// pack the function and its arguments into a table and pass its ref to rust
|
sleep(Duration::from_secs_f64(ms / 1000.)).await;
|
||||||
self.__spawn(__ref(__tpack(f, variadic!())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __spawn(&self, key: c_int) -> lb_task {
|
pub extern "Lua" fn spawn(f: function, ...) -> lb_task {
|
||||||
let handle = spawn(async move |s| {
|
// pack the function and its arguments into a table and pass its ref to rust.
|
||||||
// SAFETY: key is always unique, created by __ref above
|
//
|
||||||
let arg = unsafe { s.new_ref_unchecked(key) };
|
// this table is used from rust-side to call the function with its args, and it's also
|
||||||
|
// reused to store its return values that the task handle can return when awaited. the ref
|
||||||
|
// is owned by the task handle and unref'ed when it's gc'ed.
|
||||||
|
Self::__spawn(__ref(__tpack(f, variadic!())))
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "Lua-C" fn __spawn(key: c_int) -> lb_task {
|
||||||
|
let handle = spawn(async move |cx| {
|
||||||
|
// SAFETY: key is always unique, created by __ref above.
|
||||||
|
let arg = unsafe { cx.new_ref_unchecked(key) };
|
||||||
|
let mut s = cx.guard();
|
||||||
s.resize(0);
|
s.resize(0);
|
||||||
s.push(arg);
|
s.push(&arg);
|
||||||
let narg = s.unpack(1, 1, None) - 1;
|
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the table
|
||||||
println!("{s:?}");
|
debug_assert!(s.slot(2).type_of() == Type::Function);
|
||||||
if let Err(_err) = s.call_async(narg, 0).await {
|
match s.call_async(narg, LUA_MULTRET).await {
|
||||||
process::exit(1)
|
Ok(nret) => {
|
||||||
|
s.pack(1, nret); // pack the return values back into the table
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
drop(s);
|
||||||
|
cx.report_error(&err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
println!("{s:?}");
|
let _ = arg.into_raw(); // the original ref is owned by the task handle and unref'ed there
|
||||||
});
|
});
|
||||||
|
|
||||||
lb_task { handle }
|
lb_task {
|
||||||
|
handle: Some(handle),
|
||||||
|
__ref: key,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle for an asynchronous task created by [`spawn`](lb_tasklib::spawn).
|
||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_task {
|
pub struct lb_task {
|
||||||
#[opaque]
|
#[opaque]
|
||||||
handle: JoinHandle<()>,
|
handle: Option<JoinHandle<()>>,
|
||||||
|
__ref: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_task {}
|
impl lb_task {
|
||||||
|
pub async extern "Lua" fn r#await(&self) -> many {
|
||||||
|
self.__await();
|
||||||
|
let ret = __registry[self.__ref];
|
||||||
|
__tunpack(ret, 1, ret.n)
|
||||||
|
}
|
||||||
|
|
||||||
|
async extern "Lua-C" fn __await(&mut self) {
|
||||||
|
if let Some(handle) = self.handle.take() {
|
||||||
|
handle
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| panic!("task handler panicked: {err}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gc]
|
||||||
|
extern "Lua" fn gc(&self) {
|
||||||
|
__unref(self.__ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
17
crates/lb/tests/fs.lua
Normal file
17
crates/lb/tests/fs.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
local ok, fs = pcall(require, "lb:fs")
|
||||||
|
if not ok then return end
|
||||||
|
|
||||||
|
describe("temp files", function()
|
||||||
|
test("temp_dir cleans itself", function()
|
||||||
|
local path
|
||||||
|
do
|
||||||
|
local dir = fs.temp_dir()
|
||||||
|
path = dir:path()
|
||||||
|
assert(path and path ~= "")
|
||||||
|
fs.write(path .. "/test.txt", "test file")
|
||||||
|
assert(fs.read(path .. "/test.txt") == "test file")
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
assert(not pcall(fs.read, path .. "/test.txt"))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
17
crates/lb/tests/net.lua
Normal file
17
crates/lb/tests/net.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
local ok, net = pcall(require, "lb:net")
|
||||||
|
if not ok then return end
|
||||||
|
|
||||||
|
describe("tcp", function()
|
||||||
|
describe("socket", function()
|
||||||
|
test("bind", function()
|
||||||
|
local socket = net.bind_tcp("127.0.0.1")
|
||||||
|
|
||||||
|
-- binds to the correct port
|
||||||
|
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
||||||
|
assert(socket:local_addr():port() ~= 0)
|
||||||
|
|
||||||
|
-- should not be able to rebind socket
|
||||||
|
assert(not pcall(socket.bind, socket, net.socketaddr("127.0.0.1")))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
use lb::runtime;
|
|
||||||
use luaify::luaify;
|
|
||||||
use luajit::{Chunk, LoadMode};
|
|
||||||
use tokio::test;
|
|
||||||
|
|
||||||
async fn run_lua(s: &'static str) {
|
|
||||||
let rt = runtime::Builder::new().build().unwrap();
|
|
||||||
let task = rt.spawn(async move |state| {
|
|
||||||
println!("executing test chunk: {s}");
|
|
||||||
|
|
||||||
state
|
|
||||||
.load(Chunk::new(s).mode(LoadMode::TEXT))
|
|
||||||
.unwrap_or_else(|err| panic!("{err}"));
|
|
||||||
|
|
||||||
state
|
|
||||||
.call_async(0, 0)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| panic!("{err}"));
|
|
||||||
});
|
|
||||||
|
|
||||||
rt.await;
|
|
||||||
task.await.unwrap_or_else(|err| panic!("{err}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
async fn ipaddr() {
|
|
||||||
run_lua(luaify!({
|
|
||||||
let net = require("lb:net");
|
|
||||||
print(net.ipaddr("127.0.0.1"));
|
|
||||||
}))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
103
crates/lb/tests/task.lua
Normal file
103
crates/lb/tests/task.lua
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
local ok, task = pcall(require, "lb:task")
|
||||||
|
if not ok then return end
|
||||||
|
|
||||||
|
describe("spawn", function()
|
||||||
|
test("callback receives args", function()
|
||||||
|
spawn(function(...)
|
||||||
|
assert(select("#", ...) == 0)
|
||||||
|
end):await()
|
||||||
|
|
||||||
|
spawn(function(...)
|
||||||
|
assert(select("#", ...) == 1)
|
||||||
|
assert((...) == nil)
|
||||||
|
end, nil):await()
|
||||||
|
|
||||||
|
spawn(function(...)
|
||||||
|
assert(select("#", ...) == 4)
|
||||||
|
local args = table.pack(...)
|
||||||
|
assert(args[1] == 1 and args[2] == 2 and args[3] == nil and args[4] == 3)
|
||||||
|
end, 1, 2, nil, 3):await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("await returns callback results", function()
|
||||||
|
local res = table.pack(spawn(function()
|
||||||
|
-- no returns
|
||||||
|
end):await())
|
||||||
|
assert(res.n == 0)
|
||||||
|
|
||||||
|
local res = table.pack(spawn(function()
|
||||||
|
return nil
|
||||||
|
end):await())
|
||||||
|
assert(res.n == 1 and res[1] == nil)
|
||||||
|
|
||||||
|
local res = table.pack(spawn(function()
|
||||||
|
return 1, 2, nil, 3
|
||||||
|
end):await())
|
||||||
|
assert(res.n == 4 and res[1] == 1 and res[2] == 2 and res[3] == nil and res[4] == 3)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("callback args and results", function()
|
||||||
|
local res = table.pack(spawn(function(...)
|
||||||
|
assert(select("#", ...) == 5)
|
||||||
|
local args = table.pack(...)
|
||||||
|
assert(args[1] == 5 and args[2] == 4 and args[3] == nil and args[4] == 3, args[5] == nil)
|
||||||
|
return 1, 3, nil
|
||||||
|
end, 5, 4, nil, 3, nil):await())
|
||||||
|
assert(res.n == 3 and res[1] == 1 and res[2] == 3 and res[3] == nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("order is consistent", function()
|
||||||
|
-- all tasks spawned in one batch should be resumed in the spawn order
|
||||||
|
local tasks, nums = {}, {}
|
||||||
|
for i = 1, 10 do
|
||||||
|
table.insert(
|
||||||
|
tasks,
|
||||||
|
spawn(function()
|
||||||
|
table.insert(nums, i)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
for i = 10, 1, -1 do
|
||||||
|
tasks[i]:await()
|
||||||
|
end
|
||||||
|
assert(#nums == 10)
|
||||||
|
for i = 1, 10 do
|
||||||
|
assert(nums[i] == i)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("sleep", function()
|
||||||
|
test("sleep is asynchronous", function()
|
||||||
|
local value
|
||||||
|
spawn(function()
|
||||||
|
value = "value"
|
||||||
|
end)
|
||||||
|
assert(value == nil)
|
||||||
|
task.sleep(100) -- implicit await: if it's synchronous, value wouldn't change
|
||||||
|
assert(value == "value")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("task", function()
|
||||||
|
test("properly unrefs arg and ret table", function()
|
||||||
|
local registry = debug.getregistry()
|
||||||
|
local ref, t
|
||||||
|
local cb = function()
|
||||||
|
return "my ret"
|
||||||
|
end
|
||||||
|
do
|
||||||
|
local task = spawn(cb, "my arg")
|
||||||
|
ref = task.__ref
|
||||||
|
t = registry[ref]
|
||||||
|
assert(type(t) == "table")
|
||||||
|
assert(t[1] == cb)
|
||||||
|
assert(t[2] == "my arg")
|
||||||
|
task:await()
|
||||||
|
assert(registry[task.__ref] == t)
|
||||||
|
assert(t[1] == "my ret")
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
assert(registry[ref] ~= t) -- if task unref'ed it, it should be either nil or the next freelist number
|
||||||
|
end)
|
||||||
|
end)
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
use lb::runtime;
|
|
||||||
use luaify::luaify;
|
|
||||||
use luajit::{Chunk, LoadMode};
|
|
||||||
use tokio::test;
|
|
||||||
|
|
||||||
async fn run_lua(s: &'static str) {
|
|
||||||
let rt = runtime::Builder::new().build().unwrap();
|
|
||||||
let task = rt.spawn(async move |state| {
|
|
||||||
println!("executing test chunk: {s}");
|
|
||||||
|
|
||||||
state
|
|
||||||
.load(Chunk::new(s).mode(LoadMode::TEXT))
|
|
||||||
.unwrap_or_else(|err| panic!("{err}"));
|
|
||||||
|
|
||||||
state
|
|
||||||
.call_async(0, 0)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|err| panic!("{err}"));
|
|
||||||
});
|
|
||||||
|
|
||||||
rt.await;
|
|
||||||
task.await.unwrap_or_else(|err| panic!("{err}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
async fn task_test() {
|
|
||||||
run_lua(luaify!({
|
|
||||||
let thing = spawn(|| {
|
|
||||||
print("spawn callback!!!!!!!!!!!!!");
|
|
||||||
});
|
|
||||||
print("thing is", thing);
|
|
||||||
//
|
|
||||||
}))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,10 @@ authors.workspace = true
|
|||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
option_ref_abi = []
|
||||||
|
option_string_abi = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bstr = "1.12.0"
|
bstr = "1.12.0"
|
||||||
luaffi_impl = { path = "../luaffi_impl" }
|
luaffi_impl = { path = "../luaffi_impl" }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
__internal::{display, type_id},
|
__internal::{display, type_id},
|
||||||
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
|
Cdef, CdefBuilder, ExternCFn, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type,
|
||||||
TypeType, UnsafeExternCFn,
|
TypeBuilder, TypeType,
|
||||||
};
|
};
|
||||||
use luaify::luaify;
|
use luaify::luaify;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -70,6 +70,45 @@ enum State<F: Future> {
|
|||||||
Complete,
|
Complete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {} {name}", Self::name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(s: &mut TypeBuilder) {
|
||||||
|
s.cdef::<Self>().metatype::<Self>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<ExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
|
||||||
|
.field::<ExternCFn<(&mut Self,), ()>>("__drop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
||||||
|
type Target = Self;
|
||||||
|
|
||||||
|
fn build(s: &mut MetatypeBuilder) {
|
||||||
|
s.metatable_raw(
|
||||||
|
"gc",
|
||||||
|
luaify!(|self| {
|
||||||
|
self.__drop();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<F: Future<Output: IntoFfi>> lua_future<F> {
|
impl<F: Future<Output: IntoFfi>> lua_future<F> {
|
||||||
pub fn new(fut: F) -> Self {
|
pub fn new(fut: F) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -131,40 +170,6 @@ impl Future for lua_pollable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {} {name}", Self::name())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(s: &mut TypeBuilder) {
|
|
||||||
s.cdef::<Self>().metatype::<Self>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 IntoFfi>::Into>>("__take")
|
|
||||||
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
|
||||||
type Target = Self;
|
|
||||||
|
|
||||||
fn build(s: &mut MetatypeBuilder) {
|
|
||||||
s.metatable_raw("gc", luaify!(|self| self.__drop()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
|
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
|
||||||
type Into = lua_future<F>;
|
type Into = lua_future<F>;
|
||||||
|
|
||||||
|
|||||||
@@ -6,23 +6,6 @@ use std::{
|
|||||||
hash::{Hash, Hasher},
|
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 {
|
pub fn type_id<T: 'static>() -> u64 {
|
||||||
let mut hash = FxHasher::default();
|
let mut hash = FxHasher::default();
|
||||||
TypeId::of::<T>().hash(&mut hash);
|
TypeId::of::<T>().hash(&mut hash);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
---@diagnostic disable
|
||||||
local LUA_REFNIL = -1 -- lib_aux.c
|
local LUA_REFNIL = -1 -- lib_aux.c
|
||||||
local FREELIST_REF = 0
|
local FREELIST_REF = 0
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ use std::{
|
|||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod future;
|
|
||||||
pub mod string;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[path = "./internal.rs"]
|
#[path = "./internal.rs"]
|
||||||
pub mod __internal;
|
pub mod __internal;
|
||||||
|
pub mod future;
|
||||||
|
pub mod marker;
|
||||||
|
pub mod option;
|
||||||
pub mod result;
|
pub mod result;
|
||||||
|
pub mod string;
|
||||||
|
|
||||||
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be
|
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be
|
||||||
// garbage-collected until the end of the function (used in string.rs when string marshalling is
|
// garbage-collected until the end of the function (used in string.rs when string marshalling is
|
||||||
@@ -38,7 +39,7 @@ const CACHE_LIBS: &[(&str, &str)] = &[
|
|||||||
("package", "package"),
|
("package", "package"),
|
||||||
("debug", "debug"),
|
("debug", "debug"),
|
||||||
("jit", "jit"),
|
("jit", "jit"),
|
||||||
// require
|
// requires
|
||||||
("bit", r#"require("bit")"#),
|
("bit", r#"require("bit")"#),
|
||||||
("ffi", r#"require("ffi")"#),
|
("ffi", r#"require("ffi")"#),
|
||||||
("__tnew", r#"require("table.new")"#),
|
("__tnew", r#"require("table.new")"#),
|
||||||
@@ -47,7 +48,7 @@ const CACHE_LIBS: &[(&str, &str)] = &[
|
|||||||
|
|
||||||
// https://www.lua.org/manual/5.1/manual.html#5.1
|
// https://www.lua.org/manual/5.1/manual.html#5.1
|
||||||
const CACHE_LOCALS: &[(&str, &str)] = &[
|
const CACHE_LOCALS: &[(&str, &str)] = &[
|
||||||
// base
|
// baselib
|
||||||
("assert", "assert"),
|
("assert", "assert"),
|
||||||
("error", "error"),
|
("error", "error"),
|
||||||
("type", "type"),
|
("type", "type"),
|
||||||
@@ -68,7 +69,8 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
|
|||||||
("tonumber", "tonumber"),
|
("tonumber", "tonumber"),
|
||||||
("tostring", "tostring"),
|
("tostring", "tostring"),
|
||||||
("require", "require"),
|
("require", "require"),
|
||||||
// table
|
("__yield", "coroutine.yield"), // (used in future.rs)
|
||||||
|
// tablib
|
||||||
("__tconcat", "table.concat"),
|
("__tconcat", "table.concat"),
|
||||||
("__tinsert", "table.insert"),
|
("__tinsert", "table.insert"),
|
||||||
("__tmaxn", "table.maxn"),
|
("__tmaxn", "table.maxn"),
|
||||||
@@ -76,23 +78,21 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
|
|||||||
("__tsort", "table.sort"),
|
("__tsort", "table.sort"),
|
||||||
("__tpack", "table.pack"),
|
("__tpack", "table.pack"),
|
||||||
("__tunpack", "table.unpack"),
|
("__tunpack", "table.unpack"),
|
||||||
// string
|
// strlib
|
||||||
("__slen", "string.len"),
|
("__slen", "string.len"),
|
||||||
("__sprintf", "string.format"),
|
("__sprintf", "string.format"),
|
||||||
("__ssub", "string.sub"),
|
("__ssub", "string.sub"),
|
||||||
("__sgsub", "string.gsub"),
|
("__sgsub", "string.gsub"),
|
||||||
("__sgmatch", "string.gmatch"),
|
("__sgmatch", "string.gmatch"),
|
||||||
("__sdump", "string.dump"),
|
("__sdump", "string.dump"),
|
||||||
// math (used in luaify! macro)
|
// mathlib (used in luaify! macro)
|
||||||
("__fmod", "math.fmod"),
|
("__fmod", "math.fmod"),
|
||||||
// coroutine (used in future.rs)
|
// loadlib
|
||||||
("__yield", "coroutine.yield"),
|
|
||||||
// package
|
|
||||||
("__preload", "package.preload"),
|
("__preload", "package.preload"),
|
||||||
// debug
|
// dblib
|
||||||
("__traceback", "debug.traceback"),
|
("__traceback", "debug.traceback"),
|
||||||
("__registry", "debug.getregistry()"), // (used in lib.lua)
|
("__registry", "debug.getregistry()"), // (used in lib.lua)
|
||||||
// ffi
|
// ffilib
|
||||||
("__C", "ffi.C"),
|
("__C", "ffi.C"),
|
||||||
("__ct", "{}"),
|
("__ct", "{}"),
|
||||||
("__cdef", "ffi.cdef"),
|
("__cdef", "ffi.cdef"),
|
||||||
@@ -105,7 +105,7 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
|
|||||||
("__sizeof", "ffi.sizeof"),
|
("__sizeof", "ffi.sizeof"),
|
||||||
("__alignof", "ffi.alignof"),
|
("__alignof", "ffi.alignof"),
|
||||||
("__intern", "ffi.string"), // (used in string.rs)
|
("__intern", "ffi.string"), // (used in string.rs)
|
||||||
// bit (used in luaify! macro)
|
// bitlib (used in luaify! macro)
|
||||||
("__bnot", "bit.bnot"),
|
("__bnot", "bit.bnot"),
|
||||||
("__band", "bit.band"),
|
("__band", "bit.band"),
|
||||||
("__bor", "bit.bor"),
|
("__bor", "bit.bor"),
|
||||||
@@ -129,7 +129,7 @@ fn cache_local(f: &mut Formatter, list: &[(&str, &str)]) -> fmt::Result {
|
|||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
types: HashSet<String>,
|
types: HashSet<String>,
|
||||||
funcs: HashSet<String>,
|
decls: HashSet<String>,
|
||||||
cdef: String,
|
cdef: String,
|
||||||
lua: String,
|
lua: String,
|
||||||
}
|
}
|
||||||
@@ -137,41 +137,41 @@ pub struct Registry {
|
|||||||
impl Registry {
|
impl Registry {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut s = Self::default();
|
let mut s = Self::default();
|
||||||
s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
|
s.declare::<ExternCFn<(*const c_void,), ()>>(KEEP_FN);
|
||||||
s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
|
s.declare::<ExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
|
||||||
s.declare::<UnsafeExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
|
s.declare::<ExternCFn<(*mut lua_buffer,), ()>>(DROP_BUFFER_FN);
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn include<T: Type>(&mut self) -> &mut Self {
|
pub fn include<T: Type>(&mut self) -> &mut Self {
|
||||||
self.types
|
self.types
|
||||||
.insert(T::name().to_string())
|
.insert(T::name().to_string())
|
||||||
.then(|| T::build(&mut TypeBuilder::new::<T>(self)));
|
.then(|| T::build(&mut TypeBuilder::new(self)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||||
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
||||||
self.include::<T>()
|
self.include::<T>()
|
||||||
.funcs
|
.decls
|
||||||
.insert(name.to_string())
|
.insert(T::extern_cdecl(&name).to_string())
|
||||||
.then(|| writeln!(self.cdef, "{};", T::extern_cdecl(name)).unwrap());
|
.then(|| writeln!(self.cdef, "{};", T::extern_cdecl(&name)).unwrap());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
pub fn preload<T: Module>(&mut self) -> &mut Self {
|
||||||
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
||||||
self.include::<T>();
|
let name = <T as Module>::name();
|
||||||
let ct = T::name();
|
let ct = <T as Type>::name();
|
||||||
writeln!(
|
writeln!(
|
||||||
self.lua,
|
self.lua,
|
||||||
r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
|
r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self
|
self.include::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn done(&self) -> String {
|
pub fn build(&self) -> String {
|
||||||
self.to_string()
|
self.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ impl Display for Registry {
|
|||||||
cache_local(f, CACHE_LIBS)?;
|
cache_local(f, CACHE_LIBS)?;
|
||||||
cache_local(f, CACHE_LOCALS)?;
|
cache_local(f, CACHE_LOCALS)?;
|
||||||
writeln!(f, "{}", include_str!("./lib.lua"))?;
|
writeln!(f, "{}", include_str!("./lib.lua"))?;
|
||||||
writeln!(f, "__cdef [[\n{}\n]];", self.cdef.trim())?;
|
writeln!(f, "__cdef [[\n{}\n]];", self.cdef.trim_end())?;
|
||||||
write!(f, "{}", self.lua)
|
write!(f, "{}", self.lua)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,53 +210,49 @@ pub enum TypeType {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TypeBuilder<'r> {
|
pub struct TypeBuilder<'r> {
|
||||||
registry: &'r mut Registry,
|
reg: &'r mut Registry,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> TypeBuilder<'r> {
|
impl<'r> TypeBuilder<'r> {
|
||||||
fn new<T: Type>(registry: &'r mut Registry) -> Self {
|
fn new(reg: &'r mut Registry) -> Self {
|
||||||
let ct = T::name();
|
Self { reg }
|
||||||
let cdecl = T::cdecl("");
|
|
||||||
writeln!(registry.lua, r#"__ct.{ct} = __typeof("{cdecl}");"#).unwrap();
|
|
||||||
Self { registry }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn include<T: Type>(&mut self) -> &mut Self {
|
|
||||||
self.registry.include::<T>();
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cdef<T: Cdef>(&mut self) -> &mut Self {
|
pub fn cdef<T: Cdef>(&mut self) -> &mut Self {
|
||||||
let mut b = CdefBuilder::new::<T>(self.registry);
|
let mut b = CdefBuilder::new::<T>(self.reg);
|
||||||
<T as Cdef>::build(&mut b);
|
<T as Cdef>::build(&mut b);
|
||||||
drop(b);
|
drop(b);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metatype<T: Metatype>(&mut self) -> &mut Self {
|
pub fn metatype<T: Metatype>(&mut self) -> &mut Self {
|
||||||
let mut b = MetatypeBuilder::new::<T>(self.registry);
|
let mut b = MetatypeBuilder::new::<T>(self.reg);
|
||||||
<T as Metatype>::build(&mut b);
|
<T as Metatype>::build(&mut b);
|
||||||
drop(b);
|
drop(b);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Module: Type {
|
||||||
|
fn name() -> impl Display;
|
||||||
|
}
|
||||||
|
|
||||||
pub unsafe trait Cdef: Type {
|
pub unsafe trait Cdef: Type {
|
||||||
fn build(b: &mut CdefBuilder);
|
fn build(b: &mut CdefBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CdefBuilder<'r> {
|
pub struct CdefBuilder<'r> {
|
||||||
registry: &'r mut Registry,
|
reg: &'r mut Registry,
|
||||||
cdef: String,
|
cdef: String,
|
||||||
align: usize,
|
align: usize,
|
||||||
opaque: usize,
|
opaque: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> CdefBuilder<'r> {
|
impl<'r> CdefBuilder<'r> {
|
||||||
fn new<T: Cdef>(registry: &'r mut Registry) -> Self {
|
fn new<T: Cdef>(reg: &'r mut Registry) -> Self {
|
||||||
Self {
|
Self {
|
||||||
registry,
|
reg,
|
||||||
cdef: format!("{} {{ ", T::cdecl("")),
|
cdef: format!("{} {{ ", T::cdecl("")),
|
||||||
align: mem::align_of::<T>(),
|
align: mem::align_of::<T>(),
|
||||||
opaque: 0,
|
opaque: 0,
|
||||||
@@ -265,7 +261,7 @@ impl<'r> CdefBuilder<'r> {
|
|||||||
|
|
||||||
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||||
assert!(T::ty() != TypeType::Void, "cannot declare void field");
|
assert!(T::ty() != TypeType::Void, "cannot declare void field");
|
||||||
self.registry.include::<T>();
|
self.reg.include::<T>();
|
||||||
self.field_raw(T::cdecl(name))
|
self.field_raw(T::cdecl(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,14 +295,11 @@ impl<'r> CdefBuilder<'r> {
|
|||||||
impl<'r> Drop for CdefBuilder<'r> {
|
impl<'r> Drop for CdefBuilder<'r> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let Self {
|
let Self {
|
||||||
registry,
|
reg, cdef, align, ..
|
||||||
cdef,
|
|
||||||
align,
|
|
||||||
..
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
registry.cdef.push_str(cdef);
|
reg.cdef.push_str(cdef);
|
||||||
writeln!(registry.cdef, "}} __attribute__((aligned({align})));").unwrap();
|
writeln!(reg.cdef, "}} __attribute__((aligned({align})));").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,56 +310,73 @@ pub unsafe trait Metatype {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MetatypeBuilder<'r> {
|
pub struct MetatypeBuilder<'r> {
|
||||||
registry: &'r mut Registry,
|
reg: &'r mut Registry,
|
||||||
ct: String,
|
ct: String,
|
||||||
cdef: String,
|
cdef: String,
|
||||||
lua: String,
|
lua: String,
|
||||||
|
lua_includes: Vec<&'static str>,
|
||||||
|
has_index: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> MetatypeBuilder<'r> {
|
impl<'r> MetatypeBuilder<'r> {
|
||||||
fn new<T: Metatype>(registry: &'r mut Registry) -> Self {
|
fn new<T: Metatype>(reg: &'r mut Registry) -> Self {
|
||||||
|
// NOTE: this needs to be written first, because recursively included dependency types might
|
||||||
|
// need it
|
||||||
|
let ct = T::Target::name();
|
||||||
|
let cdecl = T::Target::cdecl("");
|
||||||
|
writeln!(reg.lua, r#"__ct.{ct} = __typeof("{cdecl}");"#).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
registry,
|
reg,
|
||||||
ct: T::Target::name().to_string(),
|
ct: T::Target::name().to_string(),
|
||||||
cdef: String::new(),
|
cdef: String::new(),
|
||||||
lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(),
|
lua: String::new(),
|
||||||
|
lua_includes: vec![],
|
||||||
|
has_index: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||||
self.registry.declare::<T>(name);
|
self.reg.declare::<T>(name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn include_lua(&mut self, lua: &'static str) -> &mut Self {
|
||||||
|
self.lua_includes.push(lua);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index(
|
pub fn index(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Display,
|
name: impl Display,
|
||||||
f: impl FnOnce(&mut MetatypeMethodBuilder),
|
f: impl FnOnce(&mut MetatypeFunctionBuilder),
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
write!(self.lua, "__idx.{name} = ").unwrap();
|
write!(self.lua, "Self.{name} = ").unwrap();
|
||||||
f(&mut MetatypeMethodBuilder::new(self));
|
f(&mut MetatypeFunctionBuilder::new(self));
|
||||||
write!(self.lua, "; ").unwrap();
|
writeln!(self.lua, ";").unwrap();
|
||||||
|
self.has_index = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
||||||
write!(self.lua, "__idx.{name} = {value}; ").unwrap();
|
writeln!(self.lua, "Self.{name} = {value};").unwrap();
|
||||||
|
self.has_index = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metatable(
|
pub fn metatable(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Display,
|
name: impl Display,
|
||||||
f: impl FnOnce(&mut MetatypeMethodBuilder),
|
f: impl FnOnce(&mut MetatypeFunctionBuilder),
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
write!(self.lua, "__mt.__{name} = ").unwrap();
|
write!(self.lua, "__mt.__{name} = ").unwrap();
|
||||||
f(&mut MetatypeMethodBuilder::new(self));
|
f(&mut MetatypeFunctionBuilder::new(self));
|
||||||
write!(self.lua, "; ").unwrap();
|
writeln!(self.lua, ";").unwrap();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
|
||||||
write!(self.lua, "__mt.__{name} = {value}; ").unwrap();
|
writeln!(self.lua, "__mt.__{name} = {value};").unwrap();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,16 +384,28 @@ impl<'r> MetatypeBuilder<'r> {
|
|||||||
impl<'r> Drop for MetatypeBuilder<'r> {
|
impl<'r> Drop for MetatypeBuilder<'r> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let Self {
|
let Self {
|
||||||
registry,
|
reg,
|
||||||
ct,
|
ct,
|
||||||
cdef,
|
cdef,
|
||||||
lua,
|
lua,
|
||||||
..
|
lua_includes,
|
||||||
|
has_index,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
registry.cdef.push_str(cdef);
|
write!(reg.lua, r#"do local __mt = {{}}; "#).unwrap();
|
||||||
registry.lua.push_str(lua);
|
|
||||||
writeln!(registry.lua, r#"__metatype(__ct.{ct}, __mt); end;"#).unwrap();
|
if *has_index {
|
||||||
|
write!(reg.lua, r#"local Self = {{}}; __mt.__index = Self; "#).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
reg.cdef.push_str(cdef);
|
||||||
|
reg.lua.push_str(lua.trim_end());
|
||||||
|
|
||||||
|
writeln!(reg.lua, r#" __metatype(__ct.{ct}, __mt); end;"#).unwrap();
|
||||||
|
|
||||||
|
for lua in lua_includes {
|
||||||
|
writeln!(reg.lua, "do {}\nend;", lua.trim_end()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,16 +451,16 @@ pub unsafe trait IntoFfi: Sized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MetatypeMethodBuilder<'r, 'm> {
|
pub struct MetatypeFunctionBuilder<'r, 'm> {
|
||||||
metatype: &'m mut MetatypeBuilder<'r>,
|
metatype: &'m mut MetatypeBuilder<'r>,
|
||||||
lparams: String, // parameters to the lua function
|
lparams: String, // lua function parameters
|
||||||
cparams: String, // parameters to the lua function
|
cparams: String, // C function parameters
|
||||||
cargs: String, // arguments to the C call
|
cargs: String, // C call arguments
|
||||||
prelude: String, // function body prelude
|
prelude: String, // lua function body prelude
|
||||||
postlude: String, // function body postlude
|
postlude: String, // lua function body postlude
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||||
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
|
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
metatype,
|
metatype,
|
||||||
@@ -457,7 +479,7 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let Self {
|
let Self {
|
||||||
metatype: MetatypeBuilder { registry, .. },
|
metatype: MetatypeBuilder { reg, .. },
|
||||||
lparams,
|
lparams,
|
||||||
cparams,
|
cparams,
|
||||||
cargs,
|
cargs,
|
||||||
@@ -466,7 +488,7 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
registry.include::<T::From>();
|
reg.include::<T::From>();
|
||||||
|
|
||||||
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
||||||
(!cparams.is_empty()).then(|| cparams.push_str(", "));
|
(!cparams.is_empty()).then(|| cparams.push_str(", "));
|
||||||
@@ -517,12 +539,15 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
|||||||
write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").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#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
||||||
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
|
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
|
||||||
|
|
||||||
if check_utf8 {
|
if check_utf8 {
|
||||||
write!(prelude, r#"assert(__C.{IS_UTF8_FN}({name}, __{name}_len), "argument '{name}' must be a valid utf-8 string"); "#).unwrap();
|
write!(prelude, r#"assert(__C.{IS_UTF8_FN}({name}, __{name}_len), "argument '{name}' must be a valid utf-8 string"); "#).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allow_nil {
|
if !allow_nil {
|
||||||
write!(prelude, r#"else return error("string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
write!(prelude, r#"else return error("string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(prelude, r#"end; "#).unwrap();
|
write!(prelude, r#"end; "#).unwrap();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -535,13 +560,7 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
|||||||
|
|
||||||
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
|
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
|
||||||
let Self {
|
let Self {
|
||||||
metatype:
|
metatype: MetatypeBuilder { reg, cdef, lua, .. },
|
||||||
MetatypeBuilder {
|
|
||||||
registry,
|
|
||||||
cdef,
|
|
||||||
lua,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
lparams,
|
lparams,
|
||||||
cparams,
|
cparams,
|
||||||
cargs,
|
cargs,
|
||||||
@@ -550,7 +569,7 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
registry.include::<T::Into>();
|
reg.include::<T::Into>();
|
||||||
write!(lua, "function({lparams}) {prelude}").unwrap();
|
write!(lua, "function({lparams}) {prelude}").unwrap();
|
||||||
|
|
||||||
match T::convention() {
|
match T::convention() {
|
||||||
@@ -587,6 +606,10 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Annotation {
|
||||||
|
fn annotation() -> impl Display;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
|
// 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.
|
// equivalent to passing a unit type as an argument in C.
|
||||||
@@ -620,7 +643,7 @@ impl_void!(());
|
|||||||
impl_void!(c_void);
|
impl_void!(c_void);
|
||||||
|
|
||||||
macro_rules! impl_primitive {
|
macro_rules! impl_primitive {
|
||||||
($rty:ty, $cty:expr) => {
|
($rty:ty, $cty:expr, $lty:expr) => {
|
||||||
unsafe impl Type for $rty {
|
unsafe impl Type for $rty {
|
||||||
fn name() -> impl Display {
|
fn name() -> impl Display {
|
||||||
$cty
|
$cty
|
||||||
@@ -636,22 +659,28 @@ macro_rules! impl_primitive {
|
|||||||
|
|
||||||
fn build(_b: &mut TypeBuilder) {}
|
fn build(_b: &mut TypeBuilder) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Annotation for $rty {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
$lty
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_primitive!(bool, "bool");
|
impl_primitive!(bool, "bool", "boolean");
|
||||||
impl_primitive!(u8, "uint8_t");
|
impl_primitive!(u8, "uint8_t", "number");
|
||||||
impl_primitive!(u16, "uint16_t");
|
impl_primitive!(u16, "uint16_t", "number");
|
||||||
impl_primitive!(u32, "uint32_t");
|
impl_primitive!(u32, "uint32_t", "number");
|
||||||
impl_primitive!(u64, "uint64_t");
|
impl_primitive!(u64, "uint64_t", "number");
|
||||||
impl_primitive!(usize, "uintptr_t");
|
impl_primitive!(usize, "uintptr_t", "number");
|
||||||
impl_primitive!(i8, "int8_t");
|
impl_primitive!(i8, "int8_t", "number");
|
||||||
impl_primitive!(i16, "int16_t");
|
impl_primitive!(i16, "int16_t", "number");
|
||||||
impl_primitive!(i32, "int32_t");
|
impl_primitive!(i32, "int32_t", "number");
|
||||||
impl_primitive!(i64, "int64_t");
|
impl_primitive!(i64, "int64_t", "number");
|
||||||
impl_primitive!(isize, "intptr_t");
|
impl_primitive!(isize, "intptr_t", "number");
|
||||||
impl_primitive!(c_float, "float");
|
impl_primitive!(c_float, "float", "number");
|
||||||
impl_primitive!(c_double, "double");
|
impl_primitive!(c_double, "double", "number");
|
||||||
|
|
||||||
unsafe impl FromFfi for bool {
|
unsafe impl FromFfi for bool {
|
||||||
type From = bool;
|
type From = bool;
|
||||||
@@ -756,11 +785,19 @@ impl_bigint_intoabi!(usize);
|
|||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
impl_bigint_intoabi!(isize);
|
impl_bigint_intoabi!(isize);
|
||||||
|
|
||||||
macro_rules! impl_const_ptr {
|
macro_rules! impl_ptr {
|
||||||
($ty:ty) => {
|
($ty:ty, $mutable:expr) => {
|
||||||
unsafe impl<T: Type> Type for $ty {
|
unsafe impl<T> Type for $ty
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
fn name() -> impl Display {
|
fn name() -> impl Display {
|
||||||
display!("const_{}_ptr", T::name())
|
disp(|f| {
|
||||||
|
if !$mutable {
|
||||||
|
write!(f, "const_")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}_ptr", T::name())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ty() -> TypeType {
|
fn ty() -> TypeType {
|
||||||
@@ -768,45 +805,59 @@ macro_rules! impl_const_ptr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cdecl(name: impl Display) -> impl Display {
|
fn cdecl(name: impl Display) -> impl Display {
|
||||||
T::cdecl(display!("const *{name}"))
|
T::cdecl(disp(move |f| {
|
||||||
|
if !$mutable {
|
||||||
|
write!(f, "const ")?;
|
||||||
|
}
|
||||||
|
write!(f, "*{name}")
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(b: &mut TypeBuilder) {
|
fn build(b: &mut TypeBuilder) {
|
||||||
b.include::<T>();
|
b.reg.include::<T>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_const_ptr!(*const T);
|
impl_ptr!(&T, false);
|
||||||
impl_const_ptr!(&T);
|
impl_ptr!(&mut T, true);
|
||||||
impl_const_ptr!(Option<&T>);
|
impl_ptr!(*const T, false);
|
||||||
|
impl_ptr!(*mut T, true);
|
||||||
|
impl_ptr!(Option<&T>, false);
|
||||||
|
impl_ptr!(Option<&mut T>, true);
|
||||||
|
|
||||||
macro_rules! impl_mut_ptr {
|
macro_rules! impl_ptr_annotation {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
unsafe impl<T: Type> Type for $ty {
|
impl<T> Annotation for $ty
|
||||||
fn name() -> impl Display {
|
where
|
||||||
display!("{}_ptr", T::name())
|
T: Annotation,
|
||||||
}
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
fn ty() -> TypeType {
|
"lightuserdata"
|
||||||
TypeType::Primitive
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cdecl(name: impl Display) -> impl Display {
|
|
||||||
T::cdecl(display!("*{name}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(b: &mut TypeBuilder) {
|
|
||||||
b.include::<T>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_mut_ptr!(*mut T);
|
impl_ptr_annotation!(*const T);
|
||||||
impl_mut_ptr!(&mut T);
|
impl_ptr_annotation!(*mut T);
|
||||||
impl_mut_ptr!(Option<&mut T>);
|
|
||||||
|
macro_rules! impl_ref_annotation {
|
||||||
|
($ty:ty) => {
|
||||||
|
impl<T> Annotation for $ty
|
||||||
|
where
|
||||||
|
T: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("{}", T::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_ref_annotation!(&T);
|
||||||
|
impl_ref_annotation!(&mut T);
|
||||||
|
|
||||||
//
|
//
|
||||||
// SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either:
|
// SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either:
|
||||||
@@ -819,7 +870,10 @@ impl_mut_ptr!(Option<&mut T>);
|
|||||||
//
|
//
|
||||||
macro_rules! impl_ptr_fromabi {
|
macro_rules! impl_ptr_fromabi {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
unsafe impl<T: Type> FromFfi for $ty {
|
unsafe impl<T> FromFfi for $ty
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
type From = Self;
|
type From = Self;
|
||||||
|
|
||||||
fn convert(from: Self::From) -> Self {
|
fn convert(from: Self::From) -> Self {
|
||||||
@@ -841,7 +895,10 @@ impl_ptr_fromabi!(Option<&mut T>);
|
|||||||
//
|
//
|
||||||
macro_rules! impl_ptr_intoabi {
|
macro_rules! impl_ptr_intoabi {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
unsafe impl<T: Type> IntoFfi for $ty {
|
unsafe impl<T> IntoFfi for $ty
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
type Into = Self;
|
type Into = Self;
|
||||||
|
|
||||||
fn convert(self) -> Self::Into {
|
fn convert(self) -> Self::Into {
|
||||||
@@ -857,7 +914,9 @@ macro_rules! impl_ptr_intoabi {
|
|||||||
|
|
||||||
impl_ptr_intoabi!(*const T);
|
impl_ptr_intoabi!(*const T);
|
||||||
impl_ptr_intoabi!(*mut T);
|
impl_ptr_intoabi!(*mut T);
|
||||||
|
#[cfg(feature = "option_ref_abi")] // disabled because it conflicts with the generic Option<T> impl
|
||||||
impl_ptr_intoabi!(Option<&'static T>);
|
impl_ptr_intoabi!(Option<&'static T>);
|
||||||
|
#[cfg(feature = "option_ref_abi")]
|
||||||
impl_ptr_intoabi!(Option<&'static mut T>);
|
impl_ptr_intoabi!(Option<&'static mut T>);
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -880,7 +939,10 @@ impl_ptr_intoabi!(Option<&'static mut T>);
|
|||||||
//
|
//
|
||||||
macro_rules! impl_ref_fromabi {
|
macro_rules! impl_ref_fromabi {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
unsafe impl<'s, T: Type> FromFfi for $ty {
|
unsafe impl<'s, T> FromFfi for $ty
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
type From = Option<$ty>;
|
type From = Option<$ty>;
|
||||||
|
|
||||||
fn prelude(arg: &str) -> impl Display {
|
fn prelude(arg: &str) -> impl Display {
|
||||||
@@ -910,7 +972,10 @@ impl_ref_fromabi!(&'s mut T);
|
|||||||
//
|
//
|
||||||
macro_rules! impl_ref_intoabi {
|
macro_rules! impl_ref_intoabi {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
unsafe impl<T: Type> IntoFfi for $ty {
|
unsafe impl<T> IntoFfi for $ty
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
type Into = Self;
|
type Into = Self;
|
||||||
|
|
||||||
fn convert(self) -> Self::Into {
|
fn convert(self) -> Self::Into {
|
||||||
@@ -929,7 +994,10 @@ impl_ref_intoabi!(&'static mut T);
|
|||||||
//
|
//
|
||||||
// TODO: we could automatically convert them to tables and vice-versa
|
// TODO: we could automatically convert them to tables and vice-versa
|
||||||
//
|
//
|
||||||
unsafe impl<T: Type> Type for [T] {
|
unsafe impl<T> Type for [T]
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
fn name() -> impl Display {
|
fn name() -> impl Display {
|
||||||
display!("{}_arr", T::name())
|
display!("{}_arr", T::name())
|
||||||
}
|
}
|
||||||
@@ -943,11 +1011,23 @@ unsafe impl<T: Type> Type for [T] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(b: &mut TypeBuilder) {
|
fn build(b: &mut TypeBuilder) {
|
||||||
b.include::<T>();
|
b.reg.include::<T>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: Type, const N: usize> Type for [T; N] {
|
impl<T> Annotation for [T]
|
||||||
|
where
|
||||||
|
T: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("{}[]", T::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, const N: usize> Type for [T; N]
|
||||||
|
where
|
||||||
|
T: Type,
|
||||||
|
{
|
||||||
fn name() -> impl Display {
|
fn name() -> impl Display {
|
||||||
display!("{}_arr{N}", T::name())
|
display!("{}_arr{N}", T::name())
|
||||||
}
|
}
|
||||||
@@ -961,18 +1041,27 @@ unsafe impl<T: Type, const N: usize> Type for [T; N] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(b: &mut TypeBuilder) {
|
fn build(b: &mut TypeBuilder) {
|
||||||
b.include::<T>();
|
b.reg.include::<T>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UnsafeExternCFn<In, Out>(PhantomData<unsafe extern "C" fn(In) -> Out>);
|
impl<T, const N: usize> Annotation for [T; N]
|
||||||
|
where
|
||||||
|
T: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("{}[]", T::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_function {
|
pub struct ExternCFn<I, O>(PhantomData<extern "C" fn(I) -> O>);
|
||||||
(fn($($arg:tt),*) -> $ret:tt) => {
|
|
||||||
impl_function!(UnsafeExternCFn, fn($($arg),*) -> $ret);
|
macro_rules! impl_externcfn {
|
||||||
|
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||||
|
impl_externcfn!(ExternCFn, fn($($arg),*) -> $ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
($ty:tt, fn($($arg:tt),*) -> $ret:tt) => {
|
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
|
||||||
//
|
//
|
||||||
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
|
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
|
||||||
// in `&mut T`).
|
// in `&mut T`).
|
||||||
@@ -980,7 +1069,11 @@ macro_rules! impl_function {
|
|||||||
// We also can't implement `IntoFfi` because we can't call `FromFfi` and `IntoFfi` 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.
|
// function's respective argument and return values.
|
||||||
//
|
//
|
||||||
unsafe impl<$($arg: Type,)* $ret: Type> Type for $ty<($($arg,)*), $ret> {
|
unsafe impl<$($arg,)* $ret> Type for $ty<($($arg,)*), $ret>
|
||||||
|
where
|
||||||
|
$($arg: Type,)*
|
||||||
|
$ret: Type,
|
||||||
|
{
|
||||||
fn name() -> impl Display {
|
fn name() -> impl Display {
|
||||||
disp(|f| Ok({
|
disp(|f| Ok({
|
||||||
write!(f, "fn_{}", $ret::name())?;
|
write!(f, "fn_{}", $ret::name())?;
|
||||||
@@ -994,8 +1087,8 @@ macro_rules! impl_function {
|
|||||||
|
|
||||||
fn cdecl(name: impl Display) -> impl Display {
|
fn cdecl(name: impl Display) -> impl Display {
|
||||||
$ret::cdecl(disp(move |f| Ok({
|
$ret::cdecl(disp(move |f| Ok({
|
||||||
let mut _n = 0;
|
|
||||||
write!(f, "(*{name})(")?;
|
write!(f, "(*{name})(")?;
|
||||||
|
let mut _n = 0;
|
||||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
|
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
})))
|
})))
|
||||||
@@ -1004,26 +1097,40 @@ macro_rules! impl_function {
|
|||||||
fn extern_cdecl(name: impl Display) -> impl Display {
|
fn extern_cdecl(name: impl Display) -> impl Display {
|
||||||
$ret::cdecl(disp(move |f| Ok({
|
$ret::cdecl(disp(move |f| Ok({
|
||||||
// for top-level function declarations in cdef
|
// for top-level function declarations in cdef
|
||||||
let mut _n = 0;
|
|
||||||
write!(f, "{name}(")?;
|
write!(f, "{name}(")?;
|
||||||
|
let mut _n = 0;
|
||||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
|
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::cdecl(""))?; _n += 1;)*
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(b: &mut TypeBuilder) {
|
fn build(b: &mut TypeBuilder) {
|
||||||
$(b.include::<$arg>();)*
|
$(b.reg.include::<$arg>();)*
|
||||||
b.include::<$ret>();
|
b.reg.include::<$ret>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_function!(fn() -> Z);
|
impl_externcfn!(fn() -> A);
|
||||||
impl_function!(fn(A) -> Z);
|
impl_externcfn!(fn(A) -> B);
|
||||||
impl_function!(fn(A, B) -> Z);
|
impl_externcfn!(fn(A, B) -> C);
|
||||||
impl_function!(fn(A, B, C) -> Z);
|
impl_externcfn!(fn(A, B, C) -> D);
|
||||||
impl_function!(fn(A, B, C, D) -> Z);
|
impl_externcfn!(fn(A, B, C, D) -> E);
|
||||||
impl_function!(fn(A, B, C, D, E) -> Z);
|
impl_externcfn!(fn(A, B, C, D, E) -> F);
|
||||||
impl_function!(fn(A, B, C, D, E, F) -> Z);
|
impl_externcfn!(fn(A, B, C, D, E, F) -> G);
|
||||||
impl_function!(fn(A, B, C, D, E, F, G) -> Z);
|
impl_externcfn!(fn(A, B, C, D, E, F, G) -> H);
|
||||||
|
impl_externcfn!(fn(A, B, C, D, E, F, G, H) -> I);
|
||||||
|
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||||
|
|
||||||
|
impl<'s> Annotation for &'s str {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Annotation for String {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
193
crates/luaffi/src/marker.rs
Normal file
193
crates/luaffi/src/marker.rs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use crate::{
|
||||||
|
__internal::{disp, display},
|
||||||
|
Annotation,
|
||||||
|
};
|
||||||
|
use std::{fmt::Display, marker::PhantomData};
|
||||||
|
|
||||||
|
enum Marker {}
|
||||||
|
|
||||||
|
pub struct any(Marker);
|
||||||
|
|
||||||
|
impl Annotation for any {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct many(Marker);
|
||||||
|
|
||||||
|
impl Annotation for many {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct nil(Marker);
|
||||||
|
|
||||||
|
impl Annotation for nil {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"nil"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct lightuserdata(Marker);
|
||||||
|
|
||||||
|
impl Annotation for lightuserdata {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"lightuserdata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct table<K, V>(Marker, PhantomData<*mut [(K, V)]>);
|
||||||
|
|
||||||
|
impl<K, V> Annotation for table<K, V>
|
||||||
|
where
|
||||||
|
K: Annotation,
|
||||||
|
V: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("table<{}, {}>", K::annotation(), V::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct function(Marker);
|
||||||
|
|
||||||
|
impl Annotation for function {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"function"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct fun<I, O>(Marker, PhantomData<fn(I) -> O>);
|
||||||
|
|
||||||
|
macro_rules! impl_fun {
|
||||||
|
(fn($($arg:ident),*)) => {
|
||||||
|
impl<$($arg,)*> Annotation for fun<($($arg,)*), ()>
|
||||||
|
where
|
||||||
|
$($arg: Annotation,)*
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
disp(|f| {
|
||||||
|
write!(f, "fun(")?;
|
||||||
|
let mut _n = 0;
|
||||||
|
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::annotation())?; _n += 1;)*
|
||||||
|
write!(f, ")")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||||
|
impl<$($arg,)* $ret> Annotation for fun<($($arg,)*), $ret>
|
||||||
|
where
|
||||||
|
$($arg: Annotation,)*
|
||||||
|
$ret: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
disp(|f| {
|
||||||
|
write!(f, "fun(")?;
|
||||||
|
let mut _n = 0;
|
||||||
|
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::annotation())?; _n += 1;)*
|
||||||
|
write!(f, "): {}", $ret::annotation())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_fun!(fn($($arg),*));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_fun!(fn() -> A);
|
||||||
|
impl_fun!(fn(A) -> B);
|
||||||
|
impl_fun!(fn(A, B) -> C);
|
||||||
|
impl_fun!(fn(A, B, C) -> D);
|
||||||
|
impl_fun!(fn(A, B, C, D) -> E);
|
||||||
|
impl_fun!(fn(A, B, C, D, E) -> F);
|
||||||
|
impl_fun!(fn(A, B, C, D, E, F) -> G);
|
||||||
|
impl_fun!(fn(A, B, C, D, E, F, G) -> H);
|
||||||
|
impl_fun!(fn(A, B, C, D, E, F, G, H) -> I);
|
||||||
|
impl_fun!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||||
|
|
||||||
|
pub struct userdata(Marker);
|
||||||
|
|
||||||
|
impl Annotation for userdata {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"userdata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct thread(Marker);
|
||||||
|
|
||||||
|
impl Annotation for thread {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"thread"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct cdata(Marker);
|
||||||
|
|
||||||
|
impl Annotation for cdata {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"cdata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Either<T, U>(Marker, PhantomData<(T, U)>);
|
||||||
|
|
||||||
|
impl<T, U> Annotation for Either<T, U>
|
||||||
|
where
|
||||||
|
T: Annotation,
|
||||||
|
U: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("({} | {})", T::annotation(), U::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OneOf<X>(Marker, PhantomData<X>);
|
||||||
|
|
||||||
|
macro_rules! impl_oneof {
|
||||||
|
($($ty:ident),+) => {
|
||||||
|
impl<$($ty),+> Annotation for OneOf<($($ty,)+)>
|
||||||
|
where
|
||||||
|
$($ty: Annotation),+
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
disp(|f| {
|
||||||
|
write!(f, "(")?;
|
||||||
|
let mut _n = 0;
|
||||||
|
$(if _n != 0 { write!(f, " | ")?; } write!(f, "{}", $ty::annotation())?; _n += 1;)*
|
||||||
|
write!(f, ")")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_oneof!(A, B);
|
||||||
|
impl_oneof!(A, B, C);
|
||||||
|
impl_oneof!(A, B, C, D);
|
||||||
|
impl_oneof!(A, B, C, D, E);
|
||||||
|
impl_oneof!(A, B, C, D, E, F);
|
||||||
|
impl_oneof!(A, B, C, D, E, F, G);
|
||||||
|
impl_oneof!(A, B, C, D, E, F, G, H);
|
||||||
|
impl_oneof!(A, B, C, D, E, F, G, H, I);
|
||||||
|
|
||||||
|
impl<T> Annotation for Option<T>
|
||||||
|
where
|
||||||
|
T: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("{}?", T::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Annotation for Result<T, E>
|
||||||
|
where
|
||||||
|
T: Annotation,
|
||||||
|
{
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
display!("{}", T::annotation())
|
||||||
|
}
|
||||||
|
}
|
||||||
89
crates/luaffi/src/option.rs
Normal file
89
crates/luaffi/src/option.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use crate::{
|
||||||
|
__internal::{disp, display, type_id},
|
||||||
|
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||||
|
};
|
||||||
|
use std::{ffi::c_int, fmt::Display};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum lua_option<T> {
|
||||||
|
None, // __tag = 0
|
||||||
|
Some(T), // __tag = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Type + 'static> Type for lua_option<T> {
|
||||||
|
fn name() -> impl Display {
|
||||||
|
display!("__option_{:x}", type_id::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
|
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>().metatype::<Self>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Type + 'static> Cdef for lua_option<T> {
|
||||||
|
fn build(b: &mut CdefBuilder) {
|
||||||
|
b.field::<c_int>("__tag");
|
||||||
|
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Type + 'static> Metatype for lua_option<T> {
|
||||||
|
type Target = Self;
|
||||||
|
fn build(_b: &mut MetatypeBuilder) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: IntoFfi<Into: 'static>> IntoFfi for Option<T> {
|
||||||
|
type Into = lua_option<T::Into>;
|
||||||
|
|
||||||
|
fn convert(self) -> Self::Into {
|
||||||
|
match self {
|
||||||
|
Some(value) => lua_option::Some(T::convert(value)),
|
||||||
|
None => lua_option::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_owned() -> bool {
|
||||||
|
// lua_option is only used to transmit information about whether we have a value or not and
|
||||||
|
// is forgotten immediately after use, so there is no need for an owned option
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn postlude(ret: &str) -> impl Display {
|
||||||
|
disp(move |f| {
|
||||||
|
write!(f, "if {ret}.__tag ~= 0 then ")?;
|
||||||
|
match T::Into::ty() {
|
||||||
|
TypeType::Void => write!(f, "{ret} = nil; ")?, // for void options, we don't have a __value
|
||||||
|
TypeType::Primitive => {
|
||||||
|
// can always copy primitives to stack
|
||||||
|
write!(f, "{ret} = {ret}.__value; {}", T::postlude(ret))?;
|
||||||
|
}
|
||||||
|
TypeType::Aggregate => {
|
||||||
|
let ct = T::Into::name();
|
||||||
|
if T::require_owned() {
|
||||||
|
// inner value requires ownership; copy it into its own cdata and forget
|
||||||
|
// option.
|
||||||
|
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
|
||||||
|
write!(f, "{}", T::postlude(ret))?;
|
||||||
|
} else {
|
||||||
|
// inner value is a "temporary" like an option itself and doesn't require
|
||||||
|
// full ownership of itself. we just need to keep the option object alive
|
||||||
|
// until its postlude completes.
|
||||||
|
write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
|
||||||
|
write!(f, "do {}end; ", T::postlude(ret))?;
|
||||||
|
write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original option alive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "else {ret} = nil; end; ")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
__internal::{disp, display},
|
__internal::{disp, display, type_id},
|
||||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType,
|
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||||
string::{DROP_BUFFER_FN, lua_buffer},
|
string::{DROP_BUFFER_FN, lua_buffer},
|
||||||
};
|
};
|
||||||
use std::{ffi::c_int, fmt::Display};
|
use std::{ffi::c_int, fmt::Display};
|
||||||
@@ -12,9 +12,9 @@ pub enum lua_result<T> {
|
|||||||
Ok(T), // __tag = 1
|
Ok(T), // __tag = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: Type> Type for lua_result<T> {
|
unsafe impl<T: Type + 'static> Type for lua_result<T> {
|
||||||
fn name() -> impl Display {
|
fn name() -> impl Display {
|
||||||
display!("result__{}", T::name())
|
display!("__result_{:x}", type_id::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ty() -> TypeType {
|
fn ty() -> TypeType {
|
||||||
@@ -26,11 +26,11 @@ unsafe impl<T: Type> Type for lua_result<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(b: &mut TypeBuilder) {
|
fn build(b: &mut TypeBuilder) {
|
||||||
b.cdef::<Self>();
|
b.cdef::<Self>().metatype::<Self>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: Type> Cdef for lua_result<T> {
|
unsafe impl<T: Type + 'static> Cdef for lua_result<T> {
|
||||||
fn build(b: &mut CdefBuilder) {
|
fn build(b: &mut CdefBuilder) {
|
||||||
b.field::<c_int>("__tag").inner_union(|b| {
|
b.field::<c_int>("__tag").inner_union(|b| {
|
||||||
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||||
@@ -39,7 +39,12 @@ unsafe impl<T: Type> Cdef for lua_result<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
|
unsafe impl<T: Type + 'static> Metatype for lua_result<T> {
|
||||||
|
type Target = Self;
|
||||||
|
fn build(_b: &mut MetatypeBuilder) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: IntoFfi<Into: 'static>, E: Display> IntoFfi for Result<T, E> {
|
||||||
type Into = lua_result<T::Into>;
|
type Into = lua_result<T::Into>;
|
||||||
|
|
||||||
fn convert(self) -> Self::Into {
|
fn convert(self) -> Self::Into {
|
||||||
@@ -72,12 +77,12 @@ unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
|
|||||||
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
|
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
|
||||||
write!(f, "{}", T::postlude(ret))?;
|
write!(f, "{}", T::postlude(ret))?;
|
||||||
} else {
|
} else {
|
||||||
// inner value is a "temporary" itself and doesn't require full ownership of
|
// inner value is a "temporary" like a result itself and doesn't require
|
||||||
// itself. we just need to keep the result object alive until its postlude
|
// full ownership of itself. we just need to keep the result object alive
|
||||||
// completes.
|
// until its postlude completes.
|
||||||
write!(f, "local __{ret} = {ret}; {ret} = {ret}.__value; ")?;
|
write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
|
||||||
write!(f, "do {}end; ", T::postlude(ret))?;
|
write!(f, "do {}end; ", T::postlude(ret))?;
|
||||||
write!(f, "__C.{KEEP_FN}(__{ret}); ")?; // keep original result alive
|
write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original result alive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bstr::{BStr, BString};
|
use bstr::{BStr, BString};
|
||||||
use luaffi_impl::{cdef, metatype};
|
use luaffi_impl::{cdef, metatype};
|
||||||
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice};
|
use std::{fmt::Display, mem::ManuallyDrop, slice};
|
||||||
|
|
||||||
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
|
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
|
||||||
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
||||||
@@ -42,6 +42,7 @@ impl lua_buf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "option_string_abi")]
|
||||||
pub(crate) fn null() -> Self {
|
pub(crate) fn null() -> Self {
|
||||||
Self {
|
Self {
|
||||||
__ptr: ptr::null(),
|
__ptr: ptr::null(),
|
||||||
@@ -71,6 +72,7 @@ impl lua_buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "option_string_abi")]
|
||||||
pub(crate) fn null() -> Self {
|
pub(crate) fn null() -> Self {
|
||||||
Self {
|
Self {
|
||||||
__ptr: ptr::null_mut(),
|
__ptr: ptr::null_mut(),
|
||||||
@@ -228,56 +230,64 @@ impl_into_via!(&'static str, &'static BStr);
|
|||||||
impl_into_via!(BString, Vec<u8>);
|
impl_into_via!(BString, Vec<u8>);
|
||||||
impl_into_via!(String, BString);
|
impl_into_via!(String, BString);
|
||||||
|
|
||||||
macro_rules! impl_optional_from {
|
// `Option<String>: From/IntoFfi` isn't implemented because it conflicts with the generic
|
||||||
($ty:ty) => {
|
// `Option<T>: From/IntoFfi` impl and rust doesn't have specialisation yet (and probably not anytime
|
||||||
unsafe impl<'s> FromFfi for Option<$ty> {
|
// soon). this is fine for now because we have specialisation for string-like parameters implemented
|
||||||
type From = <$ty as FromFfi>::From;
|
// in the #[metatype] macro already, and string returns wrapped in `Option<T>` isn't much additional
|
||||||
|
// overhead.
|
||||||
|
#[cfg(feature = "option_string_abi")]
|
||||||
|
mod impl_option_string {
|
||||||
|
macro_rules! impl_optional_from {
|
||||||
|
($ty:ty) => {
|
||||||
|
unsafe impl<'s> FromFfi for Option<$ty> {
|
||||||
|
type From = <$ty as FromFfi>::From;
|
||||||
|
|
||||||
fn require_keepalive() -> bool {
|
fn require_keepalive() -> bool {
|
||||||
<$ty as FromFfi>::require_keepalive()
|
<$ty as FromFfi>::require_keepalive()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prelude(arg: &str) -> impl Display {
|
fn prelude(arg: &str) -> impl Display {
|
||||||
// just pass a null pointer if argument is nil
|
// just pass a null pointer if argument is nil
|
||||||
display!(
|
display!(
|
||||||
"if {arg} ~= nil then {}end; ",
|
"if {arg} ~= nil then {}end; ",
|
||||||
<$ty as FromFfi>::prelude(arg)
|
<$ty as FromFfi>::prelude(arg)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert(from: Self::From) -> Self {
|
fn convert(from: Self::From) -> Self {
|
||||||
from.map(|s| <$ty as FromFfi>::convert(Some(s)))
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
|
||||||
|
|||||||
@@ -5,18 +5,26 @@ use quote::{format_ident, quote, quote_spanned};
|
|||||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||||
|
|
||||||
#[derive(Debug, FromMeta)]
|
#[derive(Debug, FromMeta)]
|
||||||
pub struct Args {}
|
pub struct Args {
|
||||||
|
module: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||||
let (name, impl_type, impl_cdef) = match item {
|
let (name, impl_type, impl_module, impl_cdef) = match item {
|
||||||
Item::Struct(ref mut str) => (
|
Item::Struct(ref mut str) => (
|
||||||
str.ident.clone(),
|
str.ident.clone(),
|
||||||
generate_type(&str.ident)?,
|
generate_type(&str.ident)?,
|
||||||
|
args.module
|
||||||
|
.map(|name| generate_module(&name, &str.ident))
|
||||||
|
.transpose()?,
|
||||||
generate_cdef_structure(str)?,
|
generate_cdef_structure(str)?,
|
||||||
),
|
),
|
||||||
Item::Enum(ref mut enu) => (
|
Item::Enum(ref mut enu) => (
|
||||||
enu.ident.clone(),
|
enu.ident.clone(),
|
||||||
generate_type(&enu.ident)?,
|
generate_type(&enu.ident)?,
|
||||||
|
args.module
|
||||||
|
.map(|name| generate_module(&name, &enu.ident))
|
||||||
|
.transpose()?,
|
||||||
generate_cdef_enum(enu)?,
|
generate_cdef_enum(enu)?,
|
||||||
),
|
),
|
||||||
_ => syn_error!(item, "expected struct or enum"),
|
_ => syn_error!(item, "expected struct or enum"),
|
||||||
@@ -24,7 +32,7 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
|||||||
|
|
||||||
let mod_name = format_ident!("__{name}_cdef");
|
let mod_name = format_ident!("__{name}_cdef");
|
||||||
|
|
||||||
Ok(quote_spanned!(name.span() =>
|
Ok(quote!(
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#item
|
#item
|
||||||
@@ -35,6 +43,7 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
|||||||
mod #mod_name {
|
mod #mod_name {
|
||||||
use super::*;
|
use super::*;
|
||||||
#impl_type
|
#impl_type
|
||||||
|
#impl_module
|
||||||
#impl_cdef
|
#impl_cdef
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -42,14 +51,11 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
|||||||
|
|
||||||
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let span = ty.span();
|
let name = ty.unraw().to_string();
|
||||||
let name = LitStr::new(&ty.unraw().to_string(), span);
|
|
||||||
|
|
||||||
Ok(quote_spanned!(span =>
|
Ok(quote!(
|
||||||
unsafe impl #ffi::Type for #ty {
|
unsafe impl #ffi::Type for #ty {
|
||||||
fn name() -> impl ::std::fmt::Display {
|
fn name() -> impl ::std::fmt::Display { #name }
|
||||||
#name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ty() -> #ffi::TypeType {
|
fn ty() -> #ffi::TypeType {
|
||||||
#ffi::TypeType::Aggregate
|
#ffi::TypeType::Aggregate
|
||||||
@@ -64,6 +70,10 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl #ffi::Annotation for #ty {
|
||||||
|
fn annotation() -> impl ::std::fmt::Display { #name }
|
||||||
|
}
|
||||||
|
|
||||||
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
|
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
|
||||||
unsafe impl #ffi::IntoFfi for #ty {
|
unsafe impl #ffi::IntoFfi for #ty {
|
||||||
type Into = Self;
|
type Into = Self;
|
||||||
@@ -72,6 +82,16 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_module(name: &str, ty: &Ident) -> Result<TokenStream> {
|
||||||
|
let ffi = ffi_crate();
|
||||||
|
|
||||||
|
Ok(quote!(
|
||||||
|
impl #ffi::Module for #ty {
|
||||||
|
fn name() -> impl ::std::fmt::Display { #name }
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
str.generics.params.is_empty(),
|
str.generics.params.is_empty(),
|
||||||
@@ -81,10 +101,9 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
|||||||
|
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let ty = &str.ident;
|
let ty = &str.ident;
|
||||||
let span = ty.span();
|
|
||||||
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
||||||
|
|
||||||
Ok(quote_spanned!(span =>
|
Ok(quote!(
|
||||||
unsafe impl #ffi::Cdef for #ty {
|
unsafe impl #ffi::Cdef for #ty {
|
||||||
fn build(b: &mut #ffi::CdefBuilder) { #build }
|
fn build(b: &mut #ffi::CdefBuilder) { #build }
|
||||||
}
|
}
|
||||||
@@ -100,18 +119,16 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
|||||||
|
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let ty = &enu.ident;
|
let ty = &enu.ident;
|
||||||
let span = ty.span();
|
|
||||||
let build = enu
|
let build = enu
|
||||||
.variants
|
.variants
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
let span = variant.span();
|
|
||||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
||||||
Ok(quote_spanned!(span => b.inner_struct(|b| { #build })))
|
Ok(quote!(b.inner_struct(|b| { #build })))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
Ok(quote_spanned!(span =>
|
Ok(quote!(
|
||||||
unsafe impl #ffi::Cdef for #ty {
|
unsafe impl #ffi::Cdef for #ty {
|
||||||
fn build(b: &mut #ffi::CdefBuilder) {
|
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;)* });
|
||||||
@@ -155,12 +172,11 @@ fn parse_cfield_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
|||||||
let mut parsed = CFieldAttrs::default();
|
let mut parsed = CFieldAttrs::default();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(attr) = attrs.get(i) {
|
while let Some(attr) = attrs.get(i) {
|
||||||
if let Some(name) = attr.path().get_ident() {
|
let path = attr.path();
|
||||||
if name == "opaque" {
|
if path.is_ident("opaque") {
|
||||||
parsed.opaque = true;
|
parsed.opaque = true;
|
||||||
attrs.remove(i);
|
attrs.remove(i);
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
@@ -185,7 +201,7 @@ fn generate_cdef_build(fields: &[CField]) -> Result<TokenStream> {
|
|||||||
for (i, field) in fields.iter().enumerate() {
|
for (i, field) in fields.iter().enumerate() {
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
let offset = offset(i);
|
let offset = offset(i);
|
||||||
body.push(quote_spanned!(ty.span() =>
|
body.push(quote!(
|
||||||
// round up current offset to the alignment of field type for field offset
|
// round up current offset to the alignment of field type for field offset
|
||||||
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
||||||
align = #max(align, #align_of::<#ty>());
|
align = #max(align, #align_of::<#ty>());
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use darling::{FromMeta, ast::NestedMeta};
|
use darling::{FromMeta, ast::NestedMeta};
|
||||||
use proc_macro::TokenStream as TokenStream1;
|
use proc_macro::TokenStream as TokenStream1;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::parse_macro_input;
|
|
||||||
|
|
||||||
mod cdef;
|
mod cdef;
|
||||||
mod metatype;
|
mod metatype;
|
||||||
@@ -17,8 +16,10 @@ pub fn cdef(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn metatype(_args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
pub fn metatype(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||||
metatype::transform(parse_macro_input!(input))
|
NestedMeta::parse_meta_list(args.into())
|
||||||
|
.and_then(|meta| metatype::Args::from_list(&meta).map_err(Into::into))
|
||||||
|
.and_then(|args| metatype::transform(args, syn::parse(input)?))
|
||||||
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
|
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,26 @@ use crate::utils::{
|
|||||||
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
|
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
|
||||||
syn_assert, syn_error, ty_name,
|
syn_assert, syn_error, ty_name,
|
||||||
};
|
};
|
||||||
|
use darling::FromMeta;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||||
use std::{collections::HashSet, fmt, iter};
|
use std::{collections::HashSet, fmt, iter};
|
||||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||||
|
|
||||||
pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
|
#[derive(Debug, FromMeta)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
pub fn transform(args: Args, mut imp: ItemImpl) -> Result<TokenStream> {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
imp.generics.params.is_empty(),
|
imp.generics.params.is_empty(),
|
||||||
imp.generics,
|
imp.generics,
|
||||||
"cannot be generic (not yet implemented)"
|
"cannot be generic (not yet implemented)"
|
||||||
);
|
);
|
||||||
|
|
||||||
let impls = generate_impls(&mut imp)?;
|
let impls = generate_impls(&args, &mut imp)?;
|
||||||
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
|
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
|
||||||
|
|
||||||
Ok(quote_spanned!(imp.self_ty.span() =>
|
Ok(quote!(
|
||||||
#imp
|
#imp
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@@ -46,7 +50,7 @@ impl Registry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let ty = imp.self_ty.clone();
|
let ty = imp.self_ty.clone();
|
||||||
let ty_name = ty_name(&ty)?;
|
let ty_name = ty_name(&ty)?;
|
||||||
@@ -54,6 +58,26 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
let mut mms = HashSet::new();
|
let mut mms = HashSet::new();
|
||||||
let mut lua_drop = None;
|
let mut lua_drop = None;
|
||||||
|
|
||||||
|
// process lua includes
|
||||||
|
imp.attrs.retain(|attr| {
|
||||||
|
if attr.path().is_ident("include") {
|
||||||
|
if let Ok(path) = attr.parse_args::<LitStr>() {
|
||||||
|
registry.build.push(quote_spanned!(path.span() =>
|
||||||
|
b.include_lua(::std::include_str!(#path));
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
} else if let Ok(chunk) = attr.parse_args::<Block>() {
|
||||||
|
registry.build.push(quote_spanned!(chunk.span() =>
|
||||||
|
b.include_lua(#ffi::__internal::luaify_chunk!(#chunk));
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
// process extern "Lua-C" ffi functions
|
||||||
for func in get_ffi_functions(imp)? {
|
for func in get_ffi_functions(imp)? {
|
||||||
if let Some(mm) = func.attrs.metamethod {
|
if let Some(mm) = func.attrs.metamethod {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
@@ -66,6 +90,7 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
add_ffi_function(&mut registry, &func)?;
|
add_ffi_function(&mut registry, &func)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process extern "Lua" lua functions
|
||||||
for func in get_lua_functions(imp)? {
|
for func in get_lua_functions(imp)? {
|
||||||
if let Some(mm) = func.attrs.metamethod {
|
if let Some(mm) = func.attrs.metamethod {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
@@ -82,17 +107,19 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if no #[new] metamethod is defined, inject fallback new
|
||||||
if !mms.contains(&Metamethod::New) {
|
if !mms.contains(&Metamethod::New) {
|
||||||
inject_fallback_new(&mut registry)?;
|
inject_fallback_new(&mut registry)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inject __gc/drop
|
||||||
inject_merged_drop(&mut registry, lua_drop.as_ref())?;
|
inject_merged_drop(&mut registry, lua_drop.as_ref())?;
|
||||||
|
|
||||||
let shims = ®istry.shims;
|
let shims = ®istry.shims;
|
||||||
let build = ®istry.build;
|
let build = ®istry.build;
|
||||||
let exports = generate_ffi_exports(®istry)?;
|
let exports = generate_ffi_exports(®istry)?;
|
||||||
|
|
||||||
Ok(quote_spanned!(ty.span() =>
|
Ok(quote!(
|
||||||
impl #ty { #(#shims)* }
|
impl #ty { #(#shims)* }
|
||||||
|
|
||||||
unsafe impl #ffi::Metatype for #ty {
|
unsafe impl #ffi::Metatype for #ty {
|
||||||
@@ -192,10 +219,10 @@ impl ToTokens for Metamethod {
|
|||||||
|
|
||||||
struct FfiFunction {
|
struct FfiFunction {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
is_async: bool,
|
params: Vec<(Ident, Type)>,
|
||||||
params: Vec<PatType>,
|
|
||||||
ret: Type,
|
ret: Type,
|
||||||
attrs: FfiFunctionAttrs,
|
attrs: FfiFunctionAttrs,
|
||||||
|
is_async: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -224,30 +251,31 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
|||||||
.sig
|
.sig
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| match arg {
|
.map(|arg| {
|
||||||
FnArg::Receiver(recv) => {
|
Ok(match arg {
|
||||||
let ty = &recv.ty;
|
FnArg::Receiver(recv) => {
|
||||||
parse_quote_spanned!(ty.span() => self: #ty)
|
(Ident::new("self", recv.span()), (*recv.ty).clone())
|
||||||
}
|
}
|
||||||
FnArg::Typed(ty) => ty.clone(),
|
FnArg::Typed(ty) => (pat_ident(&ty.pat)?.clone(), (*ty.ty).clone()),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
let ret = match func.sig.output {
|
let ret = match func.sig.output {
|
||||||
ReturnType::Default => parse_quote!(()),
|
ReturnType::Default => parse_quote!(()),
|
||||||
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for param in params.iter() {
|
for (name, ty) in params.iter() {
|
||||||
// double underscores are reserved for generated glue code
|
// double underscores are reserved for glue code
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
!pat_ident(¶m.pat)?.to_string().starts_with("__"),
|
!name.to_string().starts_with("__"),
|
||||||
param.pat,
|
name,
|
||||||
"parameter names should not start with `__`"
|
"parameter names should not start with `__`"
|
||||||
);
|
);
|
||||||
|
|
||||||
// lifetime should be determined by the caller (lua)
|
// lifetime should be determined by the caller (which is lua)
|
||||||
if let Type::Reference(ref ty) = *param.ty {
|
if let Type::Reference(ty) = ty {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
ty.lifetime.is_none(),
|
ty.lifetime.is_none(),
|
||||||
ty.lifetime,
|
ty.lifetime,
|
||||||
@@ -258,15 +286,15 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
|||||||
|
|
||||||
let attrs = parse_ffi_function_attrs(&mut func.attrs)?;
|
let attrs = parse_ffi_function_attrs(&mut func.attrs)?;
|
||||||
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
||||||
|
func.sig.asyncness.is_some().then(|| document_async(func));
|
||||||
document_ffi_function(func);
|
document_ffi_function(func);
|
||||||
|
|
||||||
funcs.push(FfiFunction {
|
funcs.push(FfiFunction {
|
||||||
name: func.sig.ident.clone(),
|
name: func.sig.ident.clone(),
|
||||||
is_async: func.sig.asyncness.is_some(),
|
|
||||||
params,
|
params,
|
||||||
ret,
|
ret,
|
||||||
attrs,
|
attrs,
|
||||||
|
is_async: func.sig.asyncness.is_some(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +307,7 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(attr) = attrs.get(i) {
|
while let Some(attr) = attrs.get(i) {
|
||||||
if let Some(name) = attr.path().get_ident()
|
if let Some(name) = attr.path().get_ident()
|
||||||
&& let Ok(method) = Metamethod::try_from(name)
|
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
||||||
{
|
{
|
||||||
match method {
|
match method {
|
||||||
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
||||||
@@ -360,16 +388,8 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
let func_params = &func.params; // target function parameters
|
let func_params = &func.params; // target function parameters
|
||||||
let func_ret = &func.ret; // target function return type
|
let func_ret = &func.ret; // target function return type
|
||||||
let mut func_args = vec![]; // target function arguments
|
let mut func_args = vec![]; // target function arguments
|
||||||
|
|
||||||
let mut shim_params = vec![]; // shim function parameters
|
let mut shim_params = vec![]; // shim function parameters
|
||||||
let mut shim_ret = if func.is_async {
|
let mut asserts = vec![]; // compile-time asserts
|
||||||
// shim function return type
|
|
||||||
quote_spanned!(func_ret.span() => #ffi::future::lua_future<impl ::std::future::Future<Output = #func_ret>>)
|
|
||||||
} else {
|
|
||||||
quote_spanned!(func_ret.span() => <#func_ret as #ffi::IntoFfi>::Into)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut asserts = vec![]; // compile-time builder asserts
|
|
||||||
let mut build = vec![]; // ffi builder body
|
let mut build = vec![]; // ffi builder body
|
||||||
|
|
||||||
// for __new metamethods, ignore the first argument (ctype of self, for which there is no
|
// for __new metamethods, ignore the first argument (ctype of self, for which there is no
|
||||||
@@ -380,99 +400,102 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, param) in func_params.iter().enumerate() {
|
for (i, (name, func_param)) in func_params.iter().enumerate() {
|
||||||
let func_param = ¶m.ty;
|
let span = func_param.span();
|
||||||
|
let name = name.unraw().to_string();
|
||||||
let shim_param = format_ident!("arg{i}");
|
let shim_param = format_ident!("arg{i}");
|
||||||
let name = pat_ident(¶m.pat)?.unraw().to_string();
|
|
||||||
|
|
||||||
match get_ffi_param_type(func_param) {
|
match get_ffi_param_type(func_param) {
|
||||||
FfiParameterType::Default => {
|
FfiParameterType::Default => {
|
||||||
shim_params.push(quote_spanned!(func_param.span() =>
|
shim_params.push(quote_spanned!(span =>
|
||||||
#shim_param: <#func_param as #ffi::FromFfi>::From
|
#shim_param: <#func_param as #ffi::FromFfi>::From
|
||||||
));
|
));
|
||||||
func_args.push(quote_spanned!(param.pat.span() =>
|
func_args.push(quote_spanned!(span =>
|
||||||
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
||||||
));
|
));
|
||||||
build.push(quote_spanned!(param.pat.span() =>
|
build.push(quote_spanned!(span =>
|
||||||
b.param::<#func_param>(#name);
|
b.param::<#func_param>(#name);
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ty @ (FfiParameterType::StringLike(str) | FfiParameterType::OptionStringLike(str)) => {
|
ty @ (FfiParameterType::StringLike(str) | FfiParameterType::OptionStringLike(str)) => {
|
||||||
let shim_param_len = format_ident!("arg{i}_len");
|
let shim_param_len = format_ident!("arg{i}_len");
|
||||||
shim_params.push(quote_spanned!(func_param.span() =>
|
shim_params.push(quote_spanned!(span =>
|
||||||
#shim_param: ::std::option::Option<&::std::primitive::u8>,
|
#shim_param: ::std::option::Option<&::std::primitive::u8>,
|
||||||
#shim_param_len: ::std::primitive::usize
|
#shim_param_len: ::std::primitive::usize
|
||||||
));
|
));
|
||||||
let allow_nil = matches!(ty, FfiParameterType::OptionStringLike(_));
|
let allow_nil = matches!(ty, FfiParameterType::OptionStringLike(_));
|
||||||
let check_utf8 = matches!(str, StringLike::Str);
|
let check_utf8 = matches!(str, StringLike::Str);
|
||||||
let mut func_arg = quote_spanned!(func_param.span() =>
|
let mut func_arg = quote_spanned!(span =>
|
||||||
#shim_param.map(|s| ::std::slice::from_raw_parts(s, #shim_param_len))
|
#shim_param.map(|s| ::std::slice::from_raw_parts(s, #shim_param_len))
|
||||||
);
|
);
|
||||||
func_arg = match str {
|
func_arg = match str {
|
||||||
StringLike::SliceU8 => func_arg,
|
StringLike::SliceU8 => func_arg,
|
||||||
StringLike::BStr => {
|
StringLike::BStr => {
|
||||||
quote_spanned!(func_param.span() => #func_arg.map(::bstr::BStr::new))
|
quote_spanned!(span => #func_arg.map(::bstr::BStr::new))
|
||||||
}
|
}
|
||||||
StringLike::Str => {
|
StringLike::Str => {
|
||||||
quote_spanned!(func_param.span() => #func_arg.map(|s| {
|
quote_spanned!(span => #func_arg.map(|s| {
|
||||||
::std::debug_assert!(::std::str::from_utf8(s).is_ok());
|
::std::debug_assert!(::std::str::from_utf8(s).is_ok());
|
||||||
::std::str::from_utf8_unchecked(s)
|
::std::str::from_utf8_unchecked(s)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !allow_nil {
|
if !allow_nil {
|
||||||
func_arg = quote_spanned!(func_param.span() => {
|
func_arg = quote_spanned!(span => {
|
||||||
let arg = #func_arg;
|
let arg = #func_arg;
|
||||||
::std::debug_assert!(arg.is_some());
|
::std::debug_assert!(arg.is_some());
|
||||||
arg.unwrap_unchecked()
|
arg.unwrap_unchecked()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
func_args.push(func_arg);
|
func_args.push(func_arg);
|
||||||
build.push(quote_spanned!(param.pat.span() =>
|
build.push(quote_spanned!(span =>
|
||||||
b.param_str(#name, #allow_nil, #check_utf8);
|
b.param_str(#name, #allow_nil, #check_utf8);
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shim function body
|
let func_call = quote!(Self::#func_name(#(#func_args),*)); // target function call
|
||||||
let mut shim_body = if func.is_async {
|
|
||||||
// for async functions, wrapped the returned future in lua_future
|
|
||||||
quote_spanned!(func_name.span() => #ffi::future::lua_future::new(Self::#func_name(#(#func_args),*)))
|
|
||||||
} else {
|
|
||||||
quote_spanned!(func_name.span() => <#func_ret as #ffi::IntoFfi>::convert(Self::#func_name(#(#func_args),*)))
|
|
||||||
};
|
|
||||||
|
|
||||||
if !func.is_async {
|
// shim function body and return type
|
||||||
|
let (shim_body, shim_ret) = if func.is_async {
|
||||||
|
let span = func_ret.span();
|
||||||
|
(
|
||||||
|
// for async functions, wrapped the returned future in lua_future
|
||||||
|
quote_spanned!(span => #ffi::future::lua_future::new(#func_call)),
|
||||||
|
quote_spanned!(span => #ffi::future::lua_future<impl ::std::future::Future<Output = #func_ret>>),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let span = func_ret.span();
|
||||||
match get_ffi_ret_type(&func_ret) {
|
match get_ffi_ret_type(&func_ret) {
|
||||||
FfiReturnType::Void => {
|
FfiReturnType::Void => {
|
||||||
asserts.push(quote_spanned!(func_ret.span() =>
|
asserts.push(quote_spanned!(span =>
|
||||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Void
|
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Void
|
||||||
));
|
));
|
||||||
|
(func_call, quote_spanned!(span => ()))
|
||||||
}
|
}
|
||||||
FfiReturnType::ByValue => {
|
FfiReturnType::ByValue => {
|
||||||
asserts.push(quote_spanned!(func_ret.span() =>
|
asserts.push(quote_spanned!(span =>
|
||||||
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByValue
|
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByValue
|
||||||
));
|
));
|
||||||
|
(
|
||||||
|
quote_spanned!(span => <#func_ret as #ffi::IntoFfi>::convert(#func_call)),
|
||||||
|
quote_spanned!(span => <#func_ret as #ffi::IntoFfi>::Into),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
FfiReturnType::ByOutParam => {
|
FfiReturnType::ByOutParam => {
|
||||||
asserts.push(quote_spanned!(func_ret.span() =>
|
asserts.push(quote_spanned!(span =>
|
||||||
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByOutParam
|
<#func_ret as #ffi::IntoFfi>::convention() == #ffi::FfiReturnConvention::ByOutParam
|
||||||
));
|
));
|
||||||
|
let out = quote_spanned!(span => out: *mut <#func_ret as #ffi::IntoFfi>::Into);
|
||||||
shim_params.insert(0, quote_spanned!(func_ret.span() => out: *mut #shim_ret));
|
shim_params.insert(0, out);
|
||||||
|
(
|
||||||
(shim_body, shim_ret) = (
|
quote_spanned!(span => ::std::ptr::write(out, <#func_ret as #ffi::IntoFfi>::convert(#func_call))),
|
||||||
quote_spanned!(func_ret.span() => ::std::ptr::write(out, #shim_body)),
|
quote_spanned!(span => ()),
|
||||||
quote_spanned!(func_ret.span() => ()),
|
)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// build.push(quote_spanned!(func_name.span() =>
|
|
||||||
// b.call_inferred(#c_name, Self::#func_name);
|
|
||||||
// ));
|
|
||||||
|
|
||||||
build.push({
|
build.push({
|
||||||
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
||||||
@@ -481,23 +504,16 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
quote!(|| Self::#func_name(#(#infer_args),*))
|
quote!(|| Self::#func_name(#(#infer_args),*))
|
||||||
};
|
};
|
||||||
quote_spanned!(func_name.span() => b.call_inferred(#c_name, #infer);)
|
quote!(b.call_inferred(#c_name, #infer);)
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.build.push(quote_spanned!(func_name.span() =>
|
registry.build.push(quote!(#(::std::assert!(#asserts);)*));
|
||||||
#(::std::assert!(#asserts);)*
|
|
||||||
));
|
|
||||||
|
|
||||||
registry.build.push(match func.attrs.metamethod {
|
registry.build.push(match func.attrs.metamethod {
|
||||||
Some(ref mm) => quote_spanned!(func_name.span() =>
|
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
||||||
b.metatable(#mm, |b| { #(#build)* });
|
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
||||||
),
|
|
||||||
None => quote_spanned!(func_name.span() =>
|
|
||||||
b.index(#lua_name, |b| { #(#build)* });
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.shims.push(parse_quote_spanned!(func_name.span() =>
|
registry.shims.push(parse_quote!(
|
||||||
#[unsafe(export_name = #c_name)]
|
#[unsafe(export_name = #c_name)]
|
||||||
unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { unsafe { #shim_body } }
|
unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { unsafe { #shim_body } }
|
||||||
));
|
));
|
||||||
@@ -509,7 +525,7 @@ fn generate_ffi_exports(registry: &Registry) -> Result<TokenStream> {
|
|||||||
let ty = ®istry.ty;
|
let ty = ®istry.ty;
|
||||||
let names = registry.shims.iter().map(|f| &f.sig.ident);
|
let names = registry.shims.iter().map(|f| &f.sig.ident);
|
||||||
|
|
||||||
Ok(quote_spanned!(ty.span() =>
|
Ok(quote!(
|
||||||
// this ensures ffi function symbol exports are actually present in the resulting binary,
|
// this ensures ffi function symbol exports are actually present in the resulting binary,
|
||||||
// otherwise they may get dead code-eliminated before it reaches the linker
|
// otherwise they may get dead code-eliminated before it reaches the linker
|
||||||
#[used]
|
#[used]
|
||||||
@@ -546,27 +562,29 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
|||||||
"cannot be generic"
|
"cannot be generic"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
syn_assert!(
|
||||||
|
func.sig.constness.is_none(),
|
||||||
|
func.sig.constness,
|
||||||
|
"cannot be const"
|
||||||
|
);
|
||||||
|
|
||||||
let mut params: Vec<_> = func
|
let mut params: Vec<_> = func
|
||||||
.sig
|
.sig
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| {
|
.map(|arg| match arg {
|
||||||
Ok(match arg {
|
FnArg::Receiver(recv) => parse_quote_spanned!(recv.span() => self),
|
||||||
FnArg::Receiver(recv) => Pat::Type(parse_quote_spanned!(recv.span() =>
|
FnArg::Typed(ty) => (*ty.pat).clone(), // ignore parameter type (only used for documentation purposes)
|
||||||
self: cdata
|
|
||||||
)),
|
|
||||||
FnArg::Typed(ty) => Pat::Type(ty.clone()),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect::<Result<_>>()?;
|
.collect();
|
||||||
|
|
||||||
if let Some(ref variadic) = func.sig.variadic {
|
if let Some(ref variadic) = func.sig.variadic {
|
||||||
params.push(parse_quote_spanned!(variadic.span() => variadic!()));
|
params.push(parse_quote_spanned!(variadic.span() => variadic!())); // luaify builtin macro
|
||||||
}
|
}
|
||||||
|
|
||||||
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
|
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
|
||||||
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
||||||
|
func.sig.asyncness.is_some().then(|| document_async(func));
|
||||||
document_lua_function(func);
|
document_lua_function(func);
|
||||||
|
|
||||||
funcs.push(LuaFunction {
|
funcs.push(LuaFunction {
|
||||||
@@ -592,73 +610,52 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
|||||||
func.attrs.push(parse_quote!(#[allow(unused)]));
|
func.attrs.push(parse_quote!(#[allow(unused)]));
|
||||||
func.block.stmts.clear();
|
func.block.stmts.clear();
|
||||||
func.block.stmts.push(parse_quote!(
|
func.block.stmts.push(parse_quote!(
|
||||||
::std::unreachable!("cannot call lua function from rust");
|
const fn has_annotation<T: #ffi::Annotation>() {}
|
||||||
));
|
));
|
||||||
|
|
||||||
let inputs = &mut func.sig.inputs;
|
// convert `...` variadic to a `rest: luaffi::marker::Many` parameter
|
||||||
let output = &mut func.sig.output;
|
|
||||||
|
|
||||||
for input in inputs.iter_mut() {
|
|
||||||
if let FnArg::Typed(pat) = input
|
|
||||||
&& let Ok(stub) = stub_lua_type(&pat.ty)
|
|
||||||
{
|
|
||||||
pat.ty = stub.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ReturnType::Type(_, ty) = output
|
|
||||||
&& let Ok(stub) = stub_lua_type(ty)
|
|
||||||
{
|
|
||||||
*ty = stub.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref variadic) = func.sig.variadic {
|
if let Some(ref variadic) = func.sig.variadic {
|
||||||
let ty = quote_spanned!(variadic.span() => variadic);
|
let param = Ident::new("rest", variadic.span());
|
||||||
inputs.push(parse_quote!(rest: #ffi::__internal::stub_types::#ty));
|
let ty = quote_spanned!(variadic.span() => many);
|
||||||
func.sig.variadic = None;
|
func.sig.variadic = None;
|
||||||
|
func.sig
|
||||||
|
.inputs
|
||||||
|
.push(parse_quote!(#param: #ffi::marker::#ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for param in func.sig.inputs.iter() {
|
||||||
|
let ty = match param {
|
||||||
|
FnArg::Receiver(recv) => &recv.ty,
|
||||||
|
FnArg::Typed(ty) => &ty.ty,
|
||||||
|
};
|
||||||
|
|
||||||
|
// temporary assertion until we implement annotation generation
|
||||||
|
func.block.stmts.push(parse_quote!(
|
||||||
|
has_annotation::<#ty>();
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporary assertion until we implement annotation generation
|
||||||
|
if let ReturnType::Type(_, ref ty) = func.sig.output {
|
||||||
|
func.block.stmts.push(parse_quote!(
|
||||||
|
has_annotation::<#ty>();
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
func.block.stmts.push(parse_quote!(
|
||||||
|
::std::unreachable!(r#"cannot call extern "Lua" function from rust"#);
|
||||||
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stub_lua_type(ty: &Type) -> Result<Type> {
|
|
||||||
let ffi = ffi_crate();
|
|
||||||
let span = ty.span();
|
|
||||||
let ty = if let Type::Infer(_) = ty {
|
|
||||||
quote_spanned!(span => any)
|
|
||||||
} else {
|
|
||||||
match ty_name(ty)?.to_string().as_str() {
|
|
||||||
"any" => quote_spanned!(span => any),
|
|
||||||
"nil" => quote_spanned!(span => nil),
|
|
||||||
"boolean" => quote_spanned!(span => boolean),
|
|
||||||
"lightuserdata" => quote_spanned!(span => lightuserdata),
|
|
||||||
"number" => quote_spanned!(span => number),
|
|
||||||
"integer" => quote_spanned!(span => integer),
|
|
||||||
"string" => quote_spanned!(span => string),
|
|
||||||
"table" => quote_spanned!(span => table),
|
|
||||||
"function" => quote_spanned!(span => function),
|
|
||||||
"userdata" => quote_spanned!(span => userdata),
|
|
||||||
"thread" => quote_spanned!(span => thread),
|
|
||||||
"cdata" => quote_spanned!(span => cdata),
|
|
||||||
_ => syn_error!(ty, "unknown lua type"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(parse_quote!(#ffi::__internal::stub_types::#ty))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
||||||
let mut parsed = LuaFunctionAttrs::default();
|
let mut parsed = LuaFunctionAttrs::default();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(attr) = attrs.get(i) {
|
while let Some(attr) = attrs.get(i) {
|
||||||
if let Some(name) = attr.path().get_ident()
|
if let Some(name) = attr.path().get_ident()
|
||||||
&& let Ok(method) = Metamethod::try_from(name)
|
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
||||||
{
|
{
|
||||||
match method {
|
|
||||||
Metamethod::New => syn_error!(attr, r#"cannot be applied to a lua function"#),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.metamethod = Some(method);
|
parsed.metamethod = Some(method);
|
||||||
attrs.remove(i);
|
attrs.remove(i);
|
||||||
} else {
|
} else {
|
||||||
@@ -678,12 +675,8 @@ fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
|||||||
let name = func_name.unraw().to_string();
|
let name = func_name.unraw().to_string();
|
||||||
|
|
||||||
registry.build.push(match func.attrs.metamethod {
|
registry.build.push(match func.attrs.metamethod {
|
||||||
Some(ref mm) => quote_spanned!(func_name.span() =>
|
Some(ref mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
||||||
b.metatable_raw(#mm, #luaify(|#(#params),*| #body));
|
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
||||||
),
|
|
||||||
None => quote_spanned!(func_name.span() =>
|
|
||||||
b.index_raw(#name, #luaify(|#(#params),*| #body));
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -718,40 +711,36 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
|||||||
"finaliser must take exactly one parameter"
|
"finaliser must take exactly one parameter"
|
||||||
);
|
);
|
||||||
|
|
||||||
syn_assert!(
|
let param = pat_ident(&lua.params[0])?;
|
||||||
pat_ident(&lua.params[0])? == "self",
|
|
||||||
lua.params[0],
|
|
||||||
"finaliser parameter must be `self`"
|
|
||||||
);
|
|
||||||
|
|
||||||
let params = &lua.params;
|
|
||||||
let body = &lua.body;
|
let body = &lua.body;
|
||||||
|
|
||||||
registry.build.push(quote_spanned!(ty.span() =>
|
syn_assert!(param == "self", param, "finaliser parameter must be `self`");
|
||||||
|
|
||||||
|
registry.build.push(quote!(
|
||||||
if ::std::mem::needs_drop::<Self>() {
|
if ::std::mem::needs_drop::<Self>() {
|
||||||
// if we have both a lua-side finaliser and a rust drop, then merge the finalisers
|
// if we have both a lua-side finaliser and a rust drop, then merge the finalisers
|
||||||
// by doing the lua part first then drop rust
|
// by doing the lua part first then drop rust
|
||||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
b.declare::<#ffi::ExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||||
b.metatable_raw("gc", #luaify(|self| {
|
b.metatable_raw("gc", #luaify(|self| {
|
||||||
raw!(#luaify(#body)); // embed the lua part inside a do block
|
raw!(#luaify(#body)); // embed the lua part inside a do block
|
||||||
__C::#c_name(self);
|
__C::#c_name(self);
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// we only have a lua-side finaliser
|
// we only have a lua-side finaliser
|
||||||
b.metatable_raw("gc", #luaify(|#(#params),*| #body));
|
b.metatable_raw("gc", #luaify(|self| #body));
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
registry.build.push(quote_spanned!(ty.span() =>
|
registry.build.push(quote!(
|
||||||
if ::std::mem::needs_drop::<Self>() {
|
if ::std::mem::needs_drop::<Self>() {
|
||||||
// we only have a rust drop
|
// we only have a rust drop
|
||||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
b.declare::<#ffi::ExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||||
b.metatable_raw("gc", ::std::format_args!("__C.{}", #c_name_str));
|
b.metatable_raw("gc", ::std::format_args!("__C.{}", #c_name_str));
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.shims.push(parse_quote_spanned!(ty.span() =>
|
registry.shims.push(parse_quote!(
|
||||||
#[unsafe(export_name = #c_name_str)]
|
#[unsafe(export_name = #c_name_str)]
|
||||||
unsafe extern "C" fn #shim_name(ptr: *mut Self) {
|
unsafe extern "C" fn #shim_name(ptr: *mut Self) {
|
||||||
unsafe { ::std::ptr::drop_in_place(ptr) }
|
unsafe { ::std::ptr::drop_in_place(ptr) }
|
||||||
@@ -763,42 +752,73 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
|||||||
|
|
||||||
fn document_ffi_function(func: &mut ImplItemFn) {
|
fn document_ffi_function(func: &mut ImplItemFn) {
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
func.attrs.insert(0, parse_quote!(#[doc =
|
||||||
r#"<span class="stab" title="This is a C/FFI function." style="float: right; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">FFI</span>"#
|
r#"<span
|
||||||
|
class="stab"
|
||||||
|
title="This function is implemented in Rust and called via FFI."
|
||||||
|
style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||||
|
>FFI</span>"#
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_lua_function(func: &mut ImplItemFn) {
|
fn document_lua_function(func: &mut ImplItemFn) {
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
func.attrs.insert(0, parse_quote!(#[doc =
|
||||||
r#"<span class="stab" title="This is a Lua function." style="float: right; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Lua</span>"#
|
r#"<span
|
||||||
|
class="stab"
|
||||||
|
title="This function is implemented in Lua."
|
||||||
|
style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||||
|
>Lua</span>"#
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn document_async(func: &mut ImplItemFn) {
|
||||||
|
func.attrs.insert(0, parse_quote!(#[doc =
|
||||||
|
r#"<span
|
||||||
|
class="stab"
|
||||||
|
title="This function is asynchronous and will yield the calling thread."
|
||||||
|
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||||
|
>Async</span>"#
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
|
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
|
||||||
let s = match method {
|
func.attrs.insert(0, parse_quote!(#[doc =
|
||||||
Metamethod::Eq => "This is a metamethod which is called by the `==` operator.".into(),
|
r#"<span
|
||||||
Metamethod::Len => "This is a metamethod which is called by the `#` operator.".into(),
|
class="stab"
|
||||||
Metamethod::Lt => "This is a metamethod which is called by the `<` operator.".into(),
|
title="This function is a metamethod."
|
||||||
Metamethod::Le => "This is a metamethod which is called by the `<=` operator.".into(),
|
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
||||||
Metamethod::Concat => "This is a metamethod which is called by the `..` operator.".into(),
|
>Metamethod</span>"#
|
||||||
Metamethod::Add => "This is a metamethod which is called by the `+` operator.".into(),
|
]));
|
||||||
Metamethod::Sub => "This is a metamethod which is called by the `-` operator.".into(),
|
|
||||||
Metamethod::Mul => "This is a metamethod which is called by the `*` operator.".into(),
|
let doc = match method {
|
||||||
Metamethod::Div => "This is a metamethod which is called by the `/` operator.".into(),
|
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.",
|
||||||
Metamethod::Mod => "This is a metamethod which is called by the `%` operator.".into(),
|
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.",
|
||||||
Metamethod::Pow => "This is a metamethod which is called by the `^` operator.".into(),
|
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.",
|
||||||
Metamethod::Unm => "This is a metamethod which is called by the `-` operator.".into(),
|
Metamethod::Le => "This function is a metamethod which is called by the `<=` operator.",
|
||||||
|
Metamethod::Concat => "This function is a metamethod which is called by the `..` operator.",
|
||||||
|
Metamethod::Call => {
|
||||||
|
"This function is a metamethod which can be called by calling `(...)` on the value directly."
|
||||||
|
}
|
||||||
|
Metamethod::Add => "This function is a metamethod which is called by the `+` operator.",
|
||||||
|
Metamethod::Sub => "This function is a metamethod which is called by the `-` operator.",
|
||||||
|
Metamethod::Mul => "This function is a metamethod which is called by the `*` operator.",
|
||||||
|
Metamethod::Div => "This function is a metamethod which is called by the `/` operator.",
|
||||||
|
Metamethod::Mod => "This function is a metamethod which is called by the `%` operator.",
|
||||||
|
Metamethod::Pow => "This function is a metamethod which is called by the `^` operator.",
|
||||||
|
Metamethod::Unm => "This function is a metamethod which is called by the `-` operator.",
|
||||||
Metamethod::ToString => {
|
Metamethod::ToString => {
|
||||||
"This is a metamethod which can be called by the `tostring` built-in function.".into()
|
"This function is a metamethod which is called by the [`tostring(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-tostring) built-in function."
|
||||||
}
|
}
|
||||||
Metamethod::Pairs => {
|
Metamethod::Pairs => {
|
||||||
"This is a metamethod which can be called by the `pairs` built-in function.".into()
|
"This function is a metamethod which is called by the [`pairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) built-in function."
|
||||||
}
|
}
|
||||||
Metamethod::Ipairs => {
|
Metamethod::Ipairs => {
|
||||||
"This is a metamethod which can be called by the `ipairs` built-in function.".into()
|
"This function is a metamethod which is called by the [`ipairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) built-in function."
|
||||||
}
|
}
|
||||||
_ => format!("This is a metamethod and cannot be called directly."),
|
_ => "This function is a metamethod and cannot be called directly.",
|
||||||
};
|
};
|
||||||
|
|
||||||
func.attrs.push(parse_quote!(#[doc = ""]));
|
func.attrs.push(parse_quote!(#[doc = ""]));
|
||||||
func.attrs.push(parse_quote!(#[doc = #s]));
|
func.attrs.push(parse_quote!(#[doc = "# Metamethod"]));
|
||||||
|
func.attrs.push(parse_quote!(#[doc = ""]));
|
||||||
|
func.attrs.push(parse_quote!(#[doc = #doc]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use syn::{spanned::Spanned, *};
|
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||||
|
|
||||||
macro_rules! syn_error {
|
macro_rules! syn_error {
|
||||||
($src:expr, $($fmt:expr),+) => {{
|
($src:expr, $($fmt:expr),+) => {{
|
||||||
@@ -63,7 +63,7 @@ pub fn is_primitivelike(ty: &Type) -> bool {
|
|||||||
Type::Path(path) => {
|
Type::Path(path) => {
|
||||||
if let Some(name) = path.path.get_ident() {
|
if let Some(name) = path.path.get_ident() {
|
||||||
return matches!(
|
return matches!(
|
||||||
name.to_string().as_str(),
|
name.unraw().to_string().as_str(),
|
||||||
"bool"
|
"bool"
|
||||||
| "u8"
|
| "u8"
|
||||||
| "u16"
|
| "u16"
|
||||||
@@ -115,31 +115,37 @@ pub fn is_stringlike(ty: &Type) -> Option<StringLike> {
|
|||||||
&& ty.mutability.is_none()
|
&& ty.mutability.is_none()
|
||||||
&& ty.lifetime.is_none()
|
&& ty.lifetime.is_none()
|
||||||
{
|
{
|
||||||
match *ty.elem {
|
Some(match *ty.elem {
|
||||||
Type::Slice(ref slice) => {
|
Type::Slice(ref slice) => {
|
||||||
// match &[u8]
|
// match &[u8]
|
||||||
if let Type::Path(ref path) = *slice.elem
|
if let Type::Path(ref path) = *slice.elem
|
||||||
&& let Some(name) = path.path.get_ident()
|
&& let Some(name) = path.path.get_ident()
|
||||||
&& name == "u8"
|
|
||||||
{
|
{
|
||||||
return Some(StringLike::SliceU8);
|
match name.unraw().to_string().as_str() {
|
||||||
|
"u8" => StringLike::SliceU8,
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Path(ref path) => {
|
Type::Path(ref path) => {
|
||||||
// match &str or &BStr
|
// match &str or &BStr
|
||||||
if let Some(name) = path.path.get_ident() {
|
if let Some(name) = path.path.get_ident() {
|
||||||
match name.to_string().as_str() {
|
match name.unraw().to_string().as_str() {
|
||||||
"str" => return Some(StringLike::Str),
|
"str" => StringLike::Str,
|
||||||
"BStr" => return Some(StringLike::BStr),
|
"BStr" => StringLike::BStr,
|
||||||
_ => {}
|
_ => return None,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => return None,
|
||||||
}
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_optionlike(ty: &Type) -> Option<&Type> {
|
pub fn is_optionlike(ty: &Type) -> Option<&Type> {
|
||||||
|
|||||||
@@ -14,3 +14,6 @@ proc-macro = true
|
|||||||
proc-macro2 = "1.0.95"
|
proc-macro2 = "1.0.95"
|
||||||
quote = "1.0.40"
|
quote = "1.0.40"
|
||||||
syn = { version = "2.0.103", features = ["full", "visit-mut"] }
|
syn = { version = "2.0.103", features = ["full", "visit-mut"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.4.1"
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ pub fn generate(expr: &Expr) -> Result<TokenStream> {
|
|||||||
Ok(f.done())
|
Ok(f.done())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_chunk(block: &Block) -> Result<TokenStream> {
|
||||||
|
let mut f = Formatter::default();
|
||||||
|
generate_block(&mut f, &block, Context::stmt(false))?;
|
||||||
|
Ok(f.done())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Formatter {
|
struct Formatter {
|
||||||
buf: String,
|
buf: String,
|
||||||
@@ -910,7 +916,7 @@ fn generate_pat_typed(f: &mut Formatter, typed: &PatType, cx: PatContext) -> Res
|
|||||||
assert_no_attrs!(typed);
|
assert_no_attrs!(typed);
|
||||||
match *typed.ty {
|
match *typed.ty {
|
||||||
Type::Infer(_) => generate_pat(f, &typed.pat, cx),
|
Type::Infer(_) => generate_pat(f, &typed.pat, cx),
|
||||||
ref ty => syn_error!(ty, "cannot have type"),
|
ref ty => syn_error!(ty, "cannot specify type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::{generate::generate, transform::transform};
|
use crate::{
|
||||||
|
generate::{generate, generate_chunk},
|
||||||
|
transform::{transform, transform_chunk},
|
||||||
|
};
|
||||||
use proc_macro::TokenStream as TokenStream1;
|
use proc_macro::TokenStream as TokenStream1;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
@@ -16,3 +19,13 @@ pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
|||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
|
||||||
|
let mut block = parse_macro_input!(input);
|
||||||
|
match transform_chunk(&mut block).and_then(|()| generate_chunk(&block)) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => err.into_compile_error().into_token_stream(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::utils::{LuaType, expr_ident, pat_ident, syn_error, wrap_expr_block};
|
use crate::utils::syn_error;
|
||||||
use quote::format_ident;
|
|
||||||
use std::mem;
|
|
||||||
use syn::{spanned::*, visit_mut::*, *};
|
use syn::{spanned::*, visit_mut::*, *};
|
||||||
|
|
||||||
pub fn transform(expr: &mut Expr) -> Result<()> {
|
pub fn transform(expr: &mut Expr) -> Result<()> {
|
||||||
@@ -9,6 +7,12 @@ pub fn transform(expr: &mut Expr) -> Result<()> {
|
|||||||
visitor.result
|
visitor.result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform_chunk(block: &mut Block) -> Result<()> {
|
||||||
|
let mut visitor = Visitor::new();
|
||||||
|
visitor.visit_block_mut(block);
|
||||||
|
visitor.result
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Visitor {
|
struct Visitor {
|
||||||
result: Result<()>,
|
result: Result<()>,
|
||||||
@@ -21,27 +25,6 @@ impl Visitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VisitMut for Visitor {
|
impl VisitMut for Visitor {
|
||||||
fn visit_expr_closure_mut(&mut self, clo: &mut ExprClosure) {
|
|
||||||
match self.transform_expr_closure(clo) {
|
|
||||||
res @ Err(_) => self.result = res,
|
|
||||||
_ => visit_expr_closure_mut(self, clo),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_item_fn_mut(&mut self, func: &mut ItemFn) {
|
|
||||||
match self.transform_function(func) {
|
|
||||||
res @ Err(_) => self.result = res,
|
|
||||||
_ => visit_item_fn_mut(self, func),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_expr_mut(&mut self, expr: &mut Expr) {
|
|
||||||
match self.transform_expr(expr) {
|
|
||||||
res @ Err(_) => self.result = res,
|
|
||||||
_ => visit_expr_mut(self, expr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_expr_unary_mut(&mut self, un: &mut ExprUnary) {
|
fn visit_expr_unary_mut(&mut self, un: &mut ExprUnary) {
|
||||||
match self.transform_unary(un) {
|
match self.transform_unary(un) {
|
||||||
res @ Err(_) => self.result = res,
|
res @ Err(_) => self.result = res,
|
||||||
@@ -58,147 +41,9 @@ impl VisitMut for Visitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Visitor {
|
impl Visitor {
|
||||||
fn transform_expr_closure(&mut self, clo: &mut ExprClosure) -> Result<()> {
|
|
||||||
//
|
|
||||||
// transforms a closure expression with input type annotations by removing the annotations
|
|
||||||
// and inserting `as` casts at the start.
|
|
||||||
//
|
|
||||||
// before:
|
|
||||||
// |a: string, b: number| { ... }
|
|
||||||
// after:
|
|
||||||
// |a, b| { a as string; b as number; ... }
|
|
||||||
//
|
|
||||||
let mut checks: Vec<Stmt> = vec![];
|
|
||||||
for input in clo.inputs.iter_mut() {
|
|
||||||
match input {
|
|
||||||
Pat::Ident(_) => {}
|
|
||||||
Pat::Type(typed) => {
|
|
||||||
let ident = pat_ident(&typed.pat)?;
|
|
||||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
|
||||||
match (&*ty).try_into()? {
|
|
||||||
LuaType::Any => {}
|
|
||||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checks.is_empty() {
|
|
||||||
let mut body = wrap_expr_block(&clo.body);
|
|
||||||
body.stmts.splice(..0, checks);
|
|
||||||
clo.body = Box::new(parse_quote! { #body });
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_function(&mut self, func: &mut ItemFn) -> Result<()> {
|
|
||||||
//
|
|
||||||
// transforms a function item with input type annotations by removing the annotations
|
|
||||||
// and inserting `as` casts at the start.
|
|
||||||
//
|
|
||||||
// before:
|
|
||||||
// fn my_func(self: table, a: string) { ... }
|
|
||||||
// after:
|
|
||||||
// fn my_func(self: _, a: _) { self as table; a as string; ... }
|
|
||||||
//
|
|
||||||
let mut checks: Vec<Stmt> = vec![];
|
|
||||||
for input in func.sig.inputs.iter_mut() {
|
|
||||||
if let Some((ident, ty)) = match input {
|
|
||||||
FnArg::Receiver(recv) if recv.colon_token.is_some() => {
|
|
||||||
let ty = mem::replace(&mut recv.ty, parse_quote!(_));
|
|
||||||
recv.colon_token = None;
|
|
||||||
Some((Ident::new("self", recv.self_token.span()), ty))
|
|
||||||
}
|
|
||||||
FnArg::Typed(typed) => {
|
|
||||||
let ident = pat_ident(&typed.pat)?;
|
|
||||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
|
||||||
Some((ident, ty))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
} {
|
|
||||||
match (&*ty).try_into()? {
|
|
||||||
LuaType::Any => {}
|
|
||||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
func.block.stmts.splice(..0, checks);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_expr(&mut self, expr: &mut Expr) -> Result<()> {
|
|
||||||
self.transform_expr_cast(expr)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_expr_cast(&mut self, expr: &mut Expr) -> Result<()> {
|
|
||||||
//
|
|
||||||
// transforms an `as` cast expression into a block expression containing a runtime
|
|
||||||
// lua type check.
|
|
||||||
//
|
|
||||||
// before:
|
|
||||||
// var as string
|
|
||||||
// after:
|
|
||||||
// { if type(var) != "string" { error(...) } }
|
|
||||||
//
|
|
||||||
if let Expr::Cast(cast) = expr {
|
|
||||||
let arg = (*cast.expr).clone();
|
|
||||||
let mut prelude: Option<Stmt> = None;
|
|
||||||
let ty: LuaType = (&*cast.ty).try_into()?;
|
|
||||||
let ty_str = format!("{ty}");
|
|
||||||
let (ident, msg) = match expr_ident(&arg) {
|
|
||||||
Ok(ident) => (ident.clone(), format!("{ty} expected in '{ident}', got ")),
|
|
||||||
Err(_) => {
|
|
||||||
let ident = Ident::new("_", arg.span());
|
|
||||||
prelude = Some(parse_quote! { let #ident = #arg; });
|
|
||||||
(ident, format!("{ty} expected, got "))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tmp = format_ident!("__{ident}");
|
|
||||||
let span = cast.span();
|
|
||||||
*expr = match ty {
|
|
||||||
LuaType::Any => parse_quote_spanned!(span => {}),
|
|
||||||
LuaType::Nil => parse_quote_spanned!(span => {
|
|
||||||
#prelude
|
|
||||||
assert(#ident == (), concat!(#msg, r#type(#ident)));
|
|
||||||
}),
|
|
||||||
LuaType::Number => parse_quote_spanned!(span => {
|
|
||||||
#prelude
|
|
||||||
let #tmp = #ident;
|
|
||||||
#ident = tonumber(#ident);
|
|
||||||
assert(#ident != (), concat!(#msg, r#type(#tmp)));
|
|
||||||
}),
|
|
||||||
LuaType::Integer => parse_quote_spanned!(span => {
|
|
||||||
#prelude
|
|
||||||
let #tmp = #ident;
|
|
||||||
#ident = tonumber(#ident);
|
|
||||||
assert(#ident != () && math::floor(#ident) == #ident, concat!(#msg, r#type(#tmp)));
|
|
||||||
}),
|
|
||||||
LuaType::String => parse_quote_spanned!(span => {
|
|
||||||
#prelude
|
|
||||||
if r#type(#ident) == "number" {
|
|
||||||
#ident = tostring(#ident);
|
|
||||||
} else {
|
|
||||||
assert(r#type(#ident) == "string", concat!(#msg, r#type(#ident)));
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
_ => parse_quote_spanned!(span => {
|
|
||||||
#prelude
|
|
||||||
assert(r#type(#ident) == #ty_str, concat!(#msg, r#type(#ident)));
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_unary(&mut self, un: &mut ExprUnary) -> Result<()> {
|
fn transform_unary(&mut self, un: &mut ExprUnary) -> Result<()> {
|
||||||
//
|
//
|
||||||
// separates a nested negation unary operator with parentheses, because double hyphen
|
// separates a nested negation unary operator with parentheses, because the double hyphen
|
||||||
// `--` indicates a comment in lua.
|
// `--` indicates a comment in lua.
|
||||||
//
|
//
|
||||||
// before:
|
// before:
|
||||||
@@ -210,7 +55,7 @@ impl Visitor {
|
|||||||
&& let Expr::Unary(ref inner) = *un.expr
|
&& let Expr::Unary(ref inner) = *un.expr
|
||||||
&& let UnOp::Neg(_) = inner.op
|
&& let UnOp::Neg(_) = inner.op
|
||||||
{
|
{
|
||||||
un.expr = Box::new(parse_quote!((#inner)));
|
un.expr = Box::new(parse_quote_spanned!(inner.span() => (#inner)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::fmt;
|
use syn::*;
|
||||||
use syn::{ext::*, spanned::*, *};
|
|
||||||
|
|
||||||
macro_rules! syn_error {
|
macro_rules! syn_error {
|
||||||
($src:expr, $($fmt:expr),+) => {{
|
($src:expr, $($fmt:expr),+) => {{
|
||||||
@@ -24,90 +23,3 @@ pub fn wrap_expr_block(expr: &Expr) -> Block {
|
|||||||
expr => parse_quote!({ #expr }),
|
expr => parse_quote!({ #expr }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expr_ident(expr: &Expr) -> Result<&Ident> {
|
|
||||||
match expr {
|
|
||||||
Expr::Path(path) => path.path.require_ident(),
|
|
||||||
_ => syn_error!(expr, "expected ident"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pat_ident(pat: &Pat) -> Result<Ident> {
|
|
||||||
Ok(match pat {
|
|
||||||
Pat::Ident(ident) => match ident.subpat {
|
|
||||||
Some((_, ref subpat)) => syn_error!(subpat, "unexpected subpattern"),
|
|
||||||
None => ident.ident.clone(),
|
|
||||||
},
|
|
||||||
Pat::Wild(wild) => Ident::new("_", wild.span()),
|
|
||||||
_ => syn_error!(pat, "expected ident"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum LuaType {
|
|
||||||
Any,
|
|
||||||
Nil,
|
|
||||||
Boolean,
|
|
||||||
Lightuserdata,
|
|
||||||
Number,
|
|
||||||
Integer,
|
|
||||||
String,
|
|
||||||
Table,
|
|
||||||
Function,
|
|
||||||
Userdata,
|
|
||||||
Thread,
|
|
||||||
Cdata,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for LuaType {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
LuaType::Any => write!(f, "any"),
|
|
||||||
LuaType::Nil => write!(f, "nil"),
|
|
||||||
LuaType::Boolean => write!(f, "boolean"),
|
|
||||||
LuaType::Lightuserdata => write!(f, "lightuserdata"),
|
|
||||||
LuaType::Number => write!(f, "number"),
|
|
||||||
LuaType::Integer => write!(f, "integer"),
|
|
||||||
LuaType::String => write!(f, "string"),
|
|
||||||
LuaType::Table => write!(f, "table"),
|
|
||||||
LuaType::Function => write!(f, "function"),
|
|
||||||
LuaType::Userdata => write!(f, "userdata"),
|
|
||||||
LuaType::Thread => write!(f, "thread"),
|
|
||||||
LuaType::Cdata => write!(f, "cdata"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Ident> for LuaType {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &Ident) -> Result<Self> {
|
|
||||||
Ok(match format!("{}", value.unraw()).as_str() {
|
|
||||||
"any" => Self::Any,
|
|
||||||
"nil" => Self::Nil,
|
|
||||||
"boolean" => Self::Boolean,
|
|
||||||
"lightuserdata" => Self::Lightuserdata,
|
|
||||||
"number" => Self::Number,
|
|
||||||
"integer" => Self::Integer,
|
|
||||||
"string" => Self::String,
|
|
||||||
"table" => Self::Table,
|
|
||||||
"function" => Self::Function,
|
|
||||||
"userdata" => Self::Userdata,
|
|
||||||
"thread" => Self::Thread,
|
|
||||||
"cdata" => Self::Cdata,
|
|
||||||
_ => syn_error!(value, "invalid lua type"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Type> for LuaType {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &Type) -> Result<Self> {
|
|
||||||
match value {
|
|
||||||
Type::Infer(_) => Ok(Self::Any),
|
|
||||||
Type::Path(path) if path.qself.is_none() => path.path.require_ident()?.try_into(),
|
|
||||||
_ => syn_error!(value, "invalid lua type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use luaify::luaify;
|
use luaify::{luaify, luaify_chunk};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn raw_ident() {
|
fn raw_ident() {
|
||||||
@@ -76,13 +77,6 @@ fn local_fn() {
|
|||||||
}),
|
}),
|
||||||
r#"function(a,b)local function inner(c,d)end;return inner;end"#
|
r#"function(a,b)local function inner(c,d)end;return inner;end"#
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
luaify!(|| {
|
|
||||||
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"#
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -207,37 +201,6 @@ fn loops() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_checks() {
|
|
||||||
assert_eq!(luaify!(|s| {}), r#"function(s)end"#);
|
|
||||||
assert_eq!(
|
|
||||||
luaify!(|s: table| {}),
|
|
||||||
r#"function(s)do assert(type(s)=="table","table expected in \'s\', got "..type(s));end;end"#
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
luaify!(|s| { s as string }),
|
|
||||||
r#"function(s)do if type(s)=="number"then s=tostring(s);else assert(type(s)=="string","string expected in \'s\', got "..type(s));end;end;end"#
|
|
||||||
);
|
|
||||||
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"#
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
luaify!(|s| { s as nil }),
|
|
||||||
r#"function(s)do assert(s==nil,"nil expected in \'s\', got "..type(s));end;end"#
|
|
||||||
);
|
|
||||||
assert_eq!(luaify!(|s| { s as any }), r#"function(s)do end;end"#);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
luaify!(|s| {
|
|
||||||
let (ok, res) = coroutine::r#yield(thread);
|
|
||||||
ok as boolean;
|
|
||||||
res as nil;
|
|
||||||
}),
|
|
||||||
r#"function(s)local ok,res=coroutine.yield(thread);do assert(type(ok)=="boolean","boolean expected in \'ok\', got "..type(ok));end;do assert(res==nil,"nil expected in \'res\', got "..type(res));end;end"#
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn concat() {
|
fn concat() {
|
||||||
assert_eq!(luaify!(concat!(a)), r#"a"#);
|
assert_eq!(luaify!(concat!(a)), r#"a"#);
|
||||||
@@ -402,3 +365,19 @@ fn length() {
|
|||||||
r#"local a,b,c=#a,#b,#c;"#
|
r#"local a,b,c=#a,#b,#c;"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chunk() {
|
||||||
|
assert_eq!(
|
||||||
|
luaify_chunk!({
|
||||||
|
if a == b {
|
||||||
|
c()
|
||||||
|
} else if b == c {
|
||||||
|
a()
|
||||||
|
} else {
|
||||||
|
d()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"if a==b then c();elseif b==c then a();else d();end;"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use std::{
|
|||||||
ffi::{CStr, CString, NulError},
|
ffi::{CStr, CString, NulError},
|
||||||
fmt,
|
fmt,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
mem::ManuallyDrop,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
os::raw::{c_char, c_int, c_void},
|
os::raw::{c_char, c_int, c_void},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@@ -35,6 +36,11 @@ pub fn url() -> &'static str {
|
|||||||
LUAJIT_URL.to_str().unwrap()
|
LUAJIT_URL.to_str().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reexport constants
|
||||||
|
pub use luajit_sys::{
|
||||||
|
LUA_ENVIRONINDEX, LUA_GLOBALSINDEX, LUA_MULTRET, LUA_NOREF, LUA_REFNIL, LUA_REGISTRYINDEX,
|
||||||
|
};
|
||||||
|
|
||||||
/// Lua error.
|
/// Lua error.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@@ -477,6 +483,19 @@ pub struct Ref {
|
|||||||
key: c_int,
|
key: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Ref {
|
||||||
|
/// Consumes this ref and returns the original key used to create the ref.
|
||||||
|
///
|
||||||
|
/// This key can be used to index into the registry table ([`LUA_REGISTRYINDEX`]) to retrieve
|
||||||
|
/// the referenced value. The key can be converted back into a ref using
|
||||||
|
/// [`State::new_ref_unchecked`].
|
||||||
|
pub fn into_raw(self) -> c_int {
|
||||||
|
let Self { ref mut state, key } = *ManuallyDrop::new(self);
|
||||||
|
unsafe { ptr::drop_in_place(state) }
|
||||||
|
key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for Ref {
|
impl Drop for Ref {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// SAFETY: luaL_unref is guaranteed to not fail
|
// SAFETY: luaL_unref is guaranteed to not fail
|
||||||
@@ -871,7 +890,7 @@ impl Stack {
|
|||||||
/// array-part, these values are **not** cleared. The number of values popped is returned, which
|
/// array-part, these values are **not** cleared. The number of values popped is returned, which
|
||||||
/// is always equal to `n`.
|
/// is always equal to `n`.
|
||||||
///
|
///
|
||||||
/// This method does not invoke any metamethods.
|
/// This method does not invoke any metamethods. The table is not popped from the stack.
|
||||||
///
|
///
|
||||||
/// Equivalent to `table.pack(...)`.
|
/// Equivalent to `table.pack(...)`.
|
||||||
///
|
///
|
||||||
@@ -907,7 +926,8 @@ impl Stack {
|
|||||||
/// pushed at the top of the stack in ascending order. If `i > j`, then nothing is pushed.
|
/// pushed at the top of the stack in ascending order. If `i > j`, then nothing is pushed.
|
||||||
/// Otherwise, `j - i + 1` values are pushed, and the number of values pushed is returned.
|
/// Otherwise, `j - i + 1` values are pushed, and the number of values pushed is returned.
|
||||||
///
|
///
|
||||||
/// This method does not invoke any metamethods.
|
/// This method does not invoke any metamethods. The table is not popped from the stack or
|
||||||
|
/// altered in any way.
|
||||||
///
|
///
|
||||||
/// Equivalent to `table.unpack(list, i, j)`.
|
/// Equivalent to `table.unpack(list, i, j)`.
|
||||||
///
|
///
|
||||||
|
|||||||
18
src/lib.rs
18
src/lib.rs
@@ -1,2 +1,20 @@
|
|||||||
|
#[cfg(feature = "task")]
|
||||||
|
pub use lb::chan;
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
pub use lb::fs;
|
pub use lb::fs;
|
||||||
|
#[cfg(feature = "net")]
|
||||||
pub use lb::net;
|
pub use lb::net;
|
||||||
|
#[cfg(feature = "task")]
|
||||||
|
pub use lb::task;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
|
||||||
|
#[cfg(feature = "task")]
|
||||||
|
rt.module::<task::lb_tasklib>();
|
||||||
|
#[cfg(feature = "task")]
|
||||||
|
rt.module::<chan::lb_chanlib>();
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
rt.module::<fs::lb_fslib>();
|
||||||
|
#[cfg(feature = "net")]
|
||||||
|
rt.module::<net::lb_netlib>();
|
||||||
|
}
|
||||||
|
|||||||
141
src/main.rs
141
src/main.rs
@@ -1,7 +1,7 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use mimalloc::MiMalloc;
|
use mimalloc::MiMalloc;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use std::{backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, thread};
|
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
|
||||||
use sysexits::ExitCode;
|
use sysexits::ExitCode;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
@@ -20,24 +20,42 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
eprint!(
|
eprint!(
|
||||||
"{}:\n{trace}",
|
"{}\n{trace}",
|
||||||
format_args!(
|
format_args!(
|
||||||
"thread '{}' panicked at {location}: {msg}",
|
"thread '{}' panicked at {location}: {msg}",
|
||||||
thread::current().name().unwrap_or("<unnamed>")
|
thread::current().name().unwrap_or("<unnamed>")
|
||||||
)
|
)
|
||||||
.red()
|
.red()
|
||||||
|
.bold()
|
||||||
);
|
);
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
format_args!(
|
format_args!(
|
||||||
"This is a bug in luby. Please kindly report this at {}.",
|
"luby should never panic. Please kindly report this bug at {}.",
|
||||||
env!("CARGO_PKG_REPOSITORY")
|
env!("CARGO_PKG_REPOSITORY")
|
||||||
)
|
)
|
||||||
.yellow()
|
.yellow()
|
||||||
|
.bold()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn error_cb(err: &luajit::Error) {
|
||||||
|
match err.trace() {
|
||||||
|
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
|
||||||
|
None => eprintln!("{}", err.red().bold()),
|
||||||
|
}
|
||||||
|
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_exit<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
|
||||||
|
move |err| {
|
||||||
|
eprintln!("{}", err.red().bold());
|
||||||
|
code.exit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Paths to scripts to execute.
|
/// Paths to scripts to execute.
|
||||||
@@ -80,10 +98,12 @@ struct Args {
|
|||||||
blocking_threads: NonZero<usize>,
|
blocking_threads: NonZero<usize>,
|
||||||
|
|
||||||
/// Enable tokio-console integration.
|
/// Enable tokio-console integration.
|
||||||
|
#[cfg(feature = "tokio-console")]
|
||||||
#[clap(long, help_heading = "Debugging")]
|
#[clap(long, help_heading = "Debugging")]
|
||||||
enable_console: bool,
|
enable_console: bool,
|
||||||
|
|
||||||
/// tokio-console publish address.
|
/// tokio-console publish address.
|
||||||
|
#[cfg(feature = "tokio-console")]
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
help_heading = "Debugging",
|
help_heading = "Debugging",
|
||||||
@@ -91,7 +111,16 @@ struct Args {
|
|||||||
default_value = "127.0.0.1:6669",
|
default_value = "127.0.0.1:6669",
|
||||||
requires = "enable_console"
|
requires = "enable_console"
|
||||||
)]
|
)]
|
||||||
console_addr: SocketAddr,
|
console_addr: std::net::SocketAddr,
|
||||||
|
|
||||||
|
/// Dump internal data.
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help_heading = "Debugging",
|
||||||
|
value_name = "DATA",
|
||||||
|
value_parser = ["cdef"]
|
||||||
|
)]
|
||||||
|
dump: Vec<String>,
|
||||||
|
|
||||||
/// Print version.
|
/// Print version.
|
||||||
#[clap(long, short = 'V')]
|
#[clap(long, short = 'V')]
|
||||||
@@ -108,19 +137,13 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
|
fn main() -> ExitCode {
|
||||||
move |err| {
|
|
||||||
eprintln!("{}", err.red());
|
|
||||||
code.exit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), ExitCode> {
|
|
||||||
panic::set_hook(Box::new(panic_cb));
|
panic::set_hook(Box::new(panic_cb));
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
if args.version {
|
if args.version {
|
||||||
return Ok(print_version());
|
print_version();
|
||||||
|
return ExitCode::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
init_logger(&args);
|
init_logger(&args);
|
||||||
@@ -131,7 +154,10 @@ fn main() -> Result<(), ExitCode> {
|
|||||||
|
|
||||||
tokio.block_on(async {
|
tokio.block_on(async {
|
||||||
lua.await;
|
lua.await;
|
||||||
main.await.unwrap()
|
match main.await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => panic::resume_unwind(err.into_panic()),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +175,7 @@ fn print_version() {
|
|||||||
|
|
||||||
fn init_logger(args: &Args) {
|
fn init_logger(args: &Args) {
|
||||||
use tracing::level_filters::LevelFilter;
|
use tracing::level_filters::LevelFilter;
|
||||||
use tracing_subscriber::{Layer, util::*};
|
use tracing_subscriber::util::*;
|
||||||
|
|
||||||
let log = tracing_subscriber::fmt()
|
let log = tracing_subscriber::fmt()
|
||||||
.compact()
|
.compact()
|
||||||
@@ -163,61 +189,75 @@ fn init_logger(args: &Args) {
|
|||||||
.with_target(false)
|
.with_target(false)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
if args.enable_console {
|
#[cfg(feature = "tokio-console")]
|
||||||
|
{
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
console_subscriber::ConsoleLayer::builder()
|
console_subscriber::ConsoleLayer::builder()
|
||||||
.with_default_env()
|
.with_default_env()
|
||||||
.server_addr(args.console_addr)
|
.server_addr(args.console_addr)
|
||||||
.spawn()
|
.spawn()
|
||||||
.with_subscriber(log)
|
.with_subscriber(log)
|
||||||
.init()
|
.init();
|
||||||
} else {
|
|
||||||
log.init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tokio-console"))]
|
||||||
|
log.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
||||||
let mut rt = match args.threads.get() {
|
match args.threads.get() {
|
||||||
1 => tokio::runtime::Builder::new_current_thread(),
|
1 => tokio::runtime::Builder::new_current_thread(),
|
||||||
n => {
|
n => {
|
||||||
let mut rt = tokio::runtime::Builder::new_multi_thread();
|
let mut rt = tokio::runtime::Builder::new_multi_thread();
|
||||||
rt.worker_threads(n - 1);
|
rt.worker_threads(n - 1);
|
||||||
rt
|
rt
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
.enable_all()
|
||||||
rt.enable_all()
|
.thread_name("luby")
|
||||||
.thread_name("luby")
|
.max_blocking_threads(args.blocking_threads.get())
|
||||||
.max_blocking_threads(args.blocking_threads.get())
|
.build()
|
||||||
.build()
|
.unwrap_or_else(unwrap_exit(ExitCode::OsErr))
|
||||||
.unwrap_or_else(exit_err(ExitCode::OsErr))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
||||||
let rt = lb::runtime::Builder::new();
|
let mut rt = {
|
||||||
let mut rt = rt.build().unwrap_or_else(exit_err(ExitCode::Software));
|
let mut rt = lb::runtime::Builder::new();
|
||||||
|
luby::open(&mut rt);
|
||||||
|
|
||||||
|
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
||||||
|
print!("{}", rt.registry()); // for cdef debugging
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.unhandled_error(error_cb).build().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
for arg in args.jit.iter() {
|
for arg in args.jit.iter() {
|
||||||
let mut s = rt.guard();
|
let mut s = rt.guard();
|
||||||
if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
let res = if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
||||||
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
|
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
|
||||||
{
|
{
|
||||||
(s.push("start"), s.get(-2), s.push(flags));
|
(s.push("start"), s.get(-2), s.push(flags));
|
||||||
s.call(1, 0)
|
s.call(1, 0) // require("jit.{cmd}").start(flags)
|
||||||
} else {
|
} else {
|
||||||
s.require("jit", 1).unwrap();
|
s.require("jit", 1).unwrap();
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
cmd @ ("on" | "off" | "flush") => {
|
cmd @ ("on" | "off" | "flush") => {
|
||||||
(s.push(cmd), s.get(-2));
|
(s.push(cmd), s.get(-2));
|
||||||
s.call(0, 0)
|
s.call(0, 0) // require("jit").[on/off/flush]()
|
||||||
}
|
}
|
||||||
arg => {
|
flags => {
|
||||||
(s.push("opt"), s.get(-2));
|
(s.push("opt"), s.get(-2));
|
||||||
(s.push("start"), s.get(-2), s.push(arg));
|
(s.push("start"), s.get(-2), s.push(flags));
|
||||||
s.call(1, 0)
|
s.call(1, 0) // require("jit").opt.start(flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
drop(s);
|
||||||
|
rt.report_error(&err);
|
||||||
}
|
}
|
||||||
.unwrap_or_else(exit_err(ExitCode::Usage));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rt
|
rt
|
||||||
@@ -225,36 +265,29 @@ fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
|||||||
|
|
||||||
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
||||||
match s {
|
match s {
|
||||||
"p" => Some(("p", "Flspv10")),
|
"p" => Some(("p", "Flspv10")), // default -jp flags
|
||||||
"v" => Some(("v", "-")),
|
"v" => Some(("v", "-")), // default -jv flags
|
||||||
"dump" => Some(("dump", "tirs")),
|
"dump" => Some(("dump", "tirs")), // default -jdump flags
|
||||||
_ => s.split_once('='),
|
_ => s.split_once('='),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn main_async(args: Args, state: &mut luajit::State) -> Result<(), ExitCode> {
|
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
||||||
for ref path in args.path {
|
for ref path in args.path {
|
||||||
let mut s = state.guard();
|
|
||||||
let chunk = match std::fs::read(path) {
|
let chunk = match std::fs::read(path) {
|
||||||
Ok(chunk) => chunk,
|
Ok(chunk) => chunk,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{}", format_args!("{path}: {err}").red());
|
eprintln!("{}", format_args!("{path}: {err}").red().bold());
|
||||||
ExitCode::NoInput.exit();
|
return ExitCode::NoInput;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
s.load(&luajit::Chunk::new(chunk).path(path))
|
if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) {
|
||||||
.unwrap_or_else(exit_err(ExitCode::NoInput));
|
cx.report_error(err);
|
||||||
|
} else if let Err(ref err) = cx.call_async(0, 0).await {
|
||||||
if let Err(err) = s.call_async(0, 0).await {
|
cx.report_error(err);
|
||||||
match err.trace() {
|
|
||||||
Some(trace) => eprintln!("{}\n{trace}", err.red()), // runtime error
|
|
||||||
None => eprintln!("{}", err.red()),
|
|
||||||
}
|
|
||||||
|
|
||||||
ExitCode::DataErr.exit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
ExitCode::Ok
|
||||||
}
|
}
|
||||||
|
|||||||
6
test.lua
6
test.lua
@@ -1,6 +0,0 @@
|
|||||||
local ffi = require("ffi")
|
|
||||||
local lb = ffi.new("struct lb_core")
|
|
||||||
|
|
||||||
print(lb)
|
|
||||||
|
|
||||||
lb.spawn("")
|
|
||||||
125
tests/main.lua
Normal file
125
tests/main.lua
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call
|
||||||
|
|
||||||
|
local ok = pcall(require, "lb:task")
|
||||||
|
if not ok then error("lua test harness requires lb:task module") end
|
||||||
|
local ok, fs = pcall(require, "lb:fs")
|
||||||
|
if not ok then error("lua test harness requires lb:fs module") end
|
||||||
|
|
||||||
|
local global = _G
|
||||||
|
local colors = {
|
||||||
|
reset = "\x1b[0m",
|
||||||
|
pass = "\x1b[32;1m",
|
||||||
|
fail = "\x1b[31;1m",
|
||||||
|
}
|
||||||
|
|
||||||
|
local icons = {
|
||||||
|
check = "\u{2713}",
|
||||||
|
cross = "\u{00d7}",
|
||||||
|
chevron = "\u{203a}",
|
||||||
|
}
|
||||||
|
|
||||||
|
local function color(name, s)
|
||||||
|
return colors[name] .. s .. colors.reset
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_test(name, f, group)
|
||||||
|
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
|
||||||
|
local fenv = setmetatable({}, { __index = global })
|
||||||
|
setfenv(f, fenv)
|
||||||
|
return test
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_group(name, f, parent)
|
||||||
|
local group = { type = "group", name = name or "", parent = parent, items = {} }
|
||||||
|
local fenv = setmetatable({
|
||||||
|
describe = function(name, f)
|
||||||
|
local item = create_group(name, f, group)
|
||||||
|
table.insert(group.items, item)
|
||||||
|
return item
|
||||||
|
end,
|
||||||
|
|
||||||
|
test = function(name, f)
|
||||||
|
local item = create_test(name, f, group)
|
||||||
|
table.insert(group.items, item)
|
||||||
|
return item
|
||||||
|
end,
|
||||||
|
}, { __index = global })
|
||||||
|
|
||||||
|
setfenv(f, fenv)
|
||||||
|
f(group)
|
||||||
|
return group
|
||||||
|
end
|
||||||
|
|
||||||
|
local function name_test(test)
|
||||||
|
local name = test.name
|
||||||
|
local group = test.group
|
||||||
|
while group ~= nil do
|
||||||
|
if group.name ~= "" then name = string.format("%s %s %s", group.name, icons.chevron, name) end
|
||||||
|
group = group.parent
|
||||||
|
end
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trace(msg)
|
||||||
|
return color("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_test(test)
|
||||||
|
local ok, res = xpcall(test.f, trace, test)
|
||||||
|
if ok then
|
||||||
|
test.state = "pass"
|
||||||
|
print("", string.format("%s %s", color("pass", "PASS"), name_test(test)))
|
||||||
|
else
|
||||||
|
test.state = "fail"
|
||||||
|
print("", string.format("%s %s\n\n%s\n", color("fail", "FAIL"), name_test(test), res))
|
||||||
|
end
|
||||||
|
return test
|
||||||
|
end
|
||||||
|
|
||||||
|
local function start(cx, item)
|
||||||
|
if item.type == "test" then
|
||||||
|
table.insert(cx.tasks, spawn(run_test, item))
|
||||||
|
elseif item.type == "group" then
|
||||||
|
for _, item in ipairs(item.items) do
|
||||||
|
start(cx, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function main(item)
|
||||||
|
local cx = { tasks = {} }
|
||||||
|
local pass, fail = 0, 0
|
||||||
|
start(cx, item)
|
||||||
|
for _, task in ipairs(cx.tasks) do
|
||||||
|
if task:await().state == "pass" then
|
||||||
|
pass = pass + 1
|
||||||
|
else
|
||||||
|
fail = fail + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if fail == 0 then
|
||||||
|
print("", color("pass", string.format("%s %d tests passed", icons.check, pass)))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
print(
|
||||||
|
"",
|
||||||
|
color("pass", string.format("%s %d tests passed", icons.check, pass))
|
||||||
|
.. ", "
|
||||||
|
.. color("fail", string.format("%s %d tests failed", icons.cross, fail))
|
||||||
|
)
|
||||||
|
return 1 -- report error to cargo
|
||||||
|
end
|
||||||
|
|
||||||
|
return main(create_group("", function()
|
||||||
|
local function include(path, pat)
|
||||||
|
for entry in fs.glob_dir(path, pat) do
|
||||||
|
local path = entry:path()
|
||||||
|
local f, err = loadfile(path)
|
||||||
|
if not f then error(err) end
|
||||||
|
describe(path, f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
include("tests", "**/*.lua")
|
||||||
|
include("crates", "*/tests/**/*.lua")
|
||||||
|
end))
|
||||||
47
tests/main.rs
Normal file
47
tests/main.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use luajit::Chunk;
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
|
use std::{
|
||||||
|
fs, panic,
|
||||||
|
process::{self, ExitCode},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let tokio = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
let lua = {
|
||||||
|
let mut rt = lb::runtime::Builder::new();
|
||||||
|
luby::open(&mut rt);
|
||||||
|
rt.unhandled_error(error_cb).build().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = "tests/main.lua";
|
||||||
|
let main = lua.spawn(async move |s| {
|
||||||
|
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) {
|
||||||
|
s.report_error(err);
|
||||||
|
} else if let Err(ref err) = s.call_async(0, 1).await {
|
||||||
|
s.report_error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.slot(1).integer().unwrap_or(1) == 0 {
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
} else {
|
||||||
|
ExitCode::FAILURE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio.block_on(async move {
|
||||||
|
lua.await;
|
||||||
|
match main.await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => panic::resume_unwind(err.into_panic()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_cb(err: &luajit::Error) {
|
||||||
|
match err.trace() {
|
||||||
|
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
|
||||||
|
None => eprintln!("{}", err.red().bold()),
|
||||||
|
}
|
||||||
|
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user