Compare commits
30 Commits
27c40c3244
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
da2598b534
|
|||
|
81cd901ea6
|
|||
|
f2a13df169
|
|||
|
9d7e35094c
|
|||
|
e03ef8a495
|
|||
|
fc4d27abf1
|
|||
|
3de17cb77e
|
|||
|
57f391a950
|
|||
|
99aa11e903
|
|||
|
5c257b0f74
|
|||
|
1808bee82a
|
|||
|
7768c5ec56
|
|||
|
fcdee34b42
|
|||
|
5846220e35
|
|||
|
b0efc9f783
|
|||
|
8c406a46b3
|
|||
|
263ca1cf48
|
|||
|
505364a661
|
|||
|
d5e85f2c30
|
|||
|
cea9bc0813
|
|||
|
85ed0d8318
|
|||
|
eaa40ff3bc
|
|||
|
124e9bedfe
|
|||
|
a2cbb24a75
|
|||
|
36300b07d3
|
|||
|
db9d611ff7
|
|||
|
a90c36a260
|
|||
|
c760d12c39
|
|||
|
ef811ecfa9
|
|||
|
d9ef6c7806
|
68
Cargo.lock
generated
68
Cargo.lock
generated
@@ -579,6 +579,18 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -601,6 +613,12 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -740,6 +758,18 @@ name = "hashbrown"
|
|||||||
version = "0.15.4"
|
version = "0.15.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hdrhistogram"
|
name = "hdrhistogram"
|
||||||
@@ -1053,6 +1083,7 @@ dependencies = [
|
|||||||
"derive_more",
|
"derive_more",
|
||||||
"globset",
|
"globset",
|
||||||
"luaffi",
|
"luaffi",
|
||||||
|
"luaify",
|
||||||
"luajit",
|
"luajit",
|
||||||
"sysexits",
|
"sysexits",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -1061,6 +1092,15 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lb_sqlite"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"lb",
|
||||||
|
"luaffi",
|
||||||
|
"rusqlite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.174"
|
version = "0.2.174"
|
||||||
@@ -1099,6 +1139,18 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libz-sys"
|
name = "libz-sys"
|
||||||
version = "1.1.22"
|
version = "1.1.22"
|
||||||
@@ -1168,7 +1220,6 @@ dependencies = [
|
|||||||
"bstr",
|
"bstr",
|
||||||
"luaffi",
|
"luaffi",
|
||||||
"luajit-sys",
|
"luajit-sys",
|
||||||
"thiserror",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1187,6 +1238,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
"lb",
|
"lb",
|
||||||
|
"lb_sqlite",
|
||||||
"luajit",
|
"luajit",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
@@ -1554,6 +1606,20 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.25"
|
version = "0.1.25"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = [
|
members = [
|
||||||
"crates/lb",
|
"crates/lb",
|
||||||
|
"crates/lb_sqlite",
|
||||||
"crates/luaffi",
|
"crates/luaffi",
|
||||||
"crates/luaffi_impl",
|
"crates/luaffi_impl",
|
||||||
"crates/luaify",
|
"crates/luaify",
|
||||||
@@ -35,16 +36,19 @@ name = "main"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["task", "fs", "net"]
|
default = ["task", "time", "fs", "net", "sqlite"]
|
||||||
task = ["lb/task"]
|
task = ["lb/task"]
|
||||||
|
time = ["lb/time"]
|
||||||
fs = ["lb/fs"]
|
fs = ["lb/fs"]
|
||||||
net = ["lb/net"]
|
net = ["lb/net"]
|
||||||
|
sqlite = ["dep:lb_sqlite"]
|
||||||
tokio-console = ["dep:console-subscriber"]
|
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 = { version = "0.4.1", optional = true }
|
console-subscriber = { version = "0.4.1", optional = true }
|
||||||
lb = { path = "crates/lb", features = ["runtime"] }
|
lb = { path = "crates/lb", features = ["runtime"] }
|
||||||
|
lb_sqlite = { path = "crates/lb_sqlite", optional = true }
|
||||||
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"
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
default_job = "test"
|
||||||
|
|
||||||
|
[jobs.test]
|
||||||
|
command = ["cargo", "test"]
|
||||||
|
watch = ["*.lua"]
|
||||||
|
need_stdout = true
|
||||||
|
background = false
|
||||||
|
|
||||||
[jobs.doc]
|
[jobs.doc]
|
||||||
command = ["cargo", "doc", "--workspace"]
|
command = ["cargo", "doc", "--workspace"]
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ repository.workspace = true
|
|||||||
[features]
|
[features]
|
||||||
runtime = ["tokio/rt"]
|
runtime = ["tokio/rt"]
|
||||||
task = ["tokio/rt", "tokio/time"]
|
task = ["tokio/rt", "tokio/time"]
|
||||||
|
time = []
|
||||||
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
||||||
net = ["tokio/net"]
|
net = ["tokio/net", "tokio/io-util"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive_more = { version = "2.0.1", features = ["full"] }
|
derive_more = { version = "2.0.1", features = ["full"] }
|
||||||
globset = { version = "0.4.16", optional = true }
|
globset = { version = "0.4.16", optional = true }
|
||||||
luaffi = { path = "../luaffi" }
|
luaffi = { path = "../luaffi" }
|
||||||
|
luaify = { path = "../luaify" }
|
||||||
luajit = { path = "../luajit" }
|
luajit = { path = "../luajit" }
|
||||||
sysexits = "0.9.0"
|
sysexits = "0.9.0"
|
||||||
tempfile = { version = "3.20.0", optional = true }
|
tempfile = { version = "3.20.0", optional = true }
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
//! # 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};
|
|
||||||
|
|
||||||
/// Items exported by the `lb:chan` library.
|
|
||||||
///
|
|
||||||
/// 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]
|
|
||||||
impl lb_chanlib {
|
|
||||||
#[new]
|
|
||||||
extern "Lua-C" fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua" fn unbounded(self) {
|
|
||||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
|
||||||
self.__unbounded(send, recv);
|
|
||||||
(send, recv)
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua" fn bounded(self, cap: u32) {
|
|
||||||
assert(cap >= 0, "channel capacity must be nonnegative");
|
|
||||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
|
||||||
self.__bounded(cap, send, recv);
|
|
||||||
(send, recv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extern "Lua-C" fn __unbounded(&self, s: *mut lb_sender, r: *mut lb_receiver) {
|
|
||||||
// let (send, recv) = flume::unbounded();
|
|
||||||
// unsafe {
|
|
||||||
// ptr::write(s, lb_sender { send });
|
|
||||||
// ptr::write(r, lb_receiver { recv });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// extern "Lua-C" fn __bounded(&self, cap: usize, s: *mut lb_sender, r: *mut lb_receiver) {
|
|
||||||
// let (send, recv) = flume::bounded(cap);
|
|
||||||
// unsafe {
|
|
||||||
// ptr::write(s, lb_sender { send });
|
|
||||||
// ptr::write(r, lb_receiver { recv });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[cdef]
|
|
||||||
// pub struct lb_sender {
|
|
||||||
// #[opaque]
|
|
||||||
// send: Sender<c_int>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[metatype]
|
|
||||||
// impl lb_sender {
|
|
||||||
// extern "Lua" fn send(self, value: _) {
|
|
||||||
// let key = __ref(value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[cdef]
|
|
||||||
// pub struct lb_receiver {
|
|
||||||
// #[opaque]
|
|
||||||
// recv: Receiver<c_int>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[metatype]
|
|
||||||
// impl lb_receiver {}
|
|
||||||
@@ -1,429 +0,0 @@
|
|||||||
//! # Filesystem library
|
|
||||||
//!
|
|
||||||
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
|
|
||||||
//! filesystem.
|
|
||||||
//!
|
|
||||||
//! ## 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 std::{path::PathBuf, time::SystemTime};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Errors that can be thrown by this library.
|
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
/// local fs = require("lb:fs");
|
|
||||||
/// ```
|
|
||||||
#[cdef(module = "lb:fs")]
|
|
||||||
pub struct lb_fslib;
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
impl lb_fslib {
|
|
||||||
#[new]
|
|
||||||
extern "Lua-C" fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
|
||||||
Ok(tokio::fs::read(path).await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
83
crates/lb/src/fs/async.rs
Normal file
83
crates/lb/src/fs/async.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use super::*;
|
||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use tokio::fs::{DirEntry, ReadDir};
|
||||||
|
|
||||||
|
/// Iterator over the entries in a directory.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_read_dir(#[opaque] RefCell<ReadDir>);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_read_dir {
|
||||||
|
pub(super) fn new(iter: ReadDir) -> Self {
|
||||||
|
Self(RefCell::new(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry in the directory, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory could not be read.
|
||||||
|
#[call]
|
||||||
|
pub async extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry>> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.try_borrow_mut()?
|
||||||
|
.next_entry()
|
||||||
|
.await?
|
||||||
|
.map(lb_dir_entry::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry inside of a directory on the filesystem.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_dir_entry(#[opaque] DirEntry);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_dir_entry {
|
||||||
|
pub(super) fn new(entry: DirEntry) -> Self {
|
||||||
|
Self(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of this entry.
|
||||||
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
|
self.0.file_name().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file type could not be determined.
|
||||||
|
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||||
|
Ok(lb_file_type::new(self.0.file_type().await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the metadata could not be retrieved.
|
||||||
|
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
|
Ok(lb_file_meta::new(self.0.metadata().await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number for this entry.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
|
self.0.ino()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
46
crates/lb/src/fs/glob.rs
Normal file
46
crates/lb/src/fs/glob.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use super::*;
|
||||||
|
use globset::GlobSet;
|
||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use walkdir::IntoIter;
|
||||||
|
|
||||||
|
/// Iterator that yields paths from the filesystem that match a particular pattern.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_glob_dir {
|
||||||
|
#[opaque]
|
||||||
|
iter: RefCell<IntoIter>,
|
||||||
|
#[opaque]
|
||||||
|
matcher: GlobSet,
|
||||||
|
#[opaque]
|
||||||
|
prefix: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_glob_dir {
|
||||||
|
pub(super) fn new(iter: IntoIter, matcher: GlobSet, prefix: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
iter: RefCell::new(iter),
|
||||||
|
matcher,
|
||||||
|
prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry matching the glob pattern, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory could not be read.
|
||||||
|
#[call]
|
||||||
|
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
|
||||||
|
while let Some(res) = self.iter.try_borrow_mut()?.next() {
|
||||||
|
let entry = res?;
|
||||||
|
let path = entry.path().strip_prefix(&self.prefix).unwrap();
|
||||||
|
if self.matcher.is_match(path) {
|
||||||
|
return Ok(Some(lb_walk_dir_entry::new(entry)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
190
crates/lb/src/fs/mod.rs
Normal file
190
crates/lb/src/fs/mod.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
//! Filesystem library.
|
||||||
|
//!
|
||||||
|
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
|
||||||
|
//! filesystem.
|
||||||
|
//!
|
||||||
|
//! ## 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 luaffi::{cdef, metatype};
|
||||||
|
use std::{
|
||||||
|
cell::{BorrowError, BorrowMutError},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
mod r#async;
|
||||||
|
mod glob;
|
||||||
|
mod sync;
|
||||||
|
mod temp;
|
||||||
|
mod walk;
|
||||||
|
|
||||||
|
pub use r#async::*;
|
||||||
|
pub use glob::*;
|
||||||
|
pub use sync::*;
|
||||||
|
pub use temp::*;
|
||||||
|
pub use walk::*;
|
||||||
|
|
||||||
|
/// Errors that can be thrown by this library.
|
||||||
|
///
|
||||||
|
/// Functions which return this error will **throw**. 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 {
|
||||||
|
/// Attempt to access an object while it is being modified.
|
||||||
|
#[error("cannot access object while it is being modified")]
|
||||||
|
Borrow(#[from] BorrowError),
|
||||||
|
/// Attempt to modify an object while it is in use.
|
||||||
|
#[error("cannot modify object while it is in use")]
|
||||||
|
BorrowMut(#[from] BorrowMutError),
|
||||||
|
/// 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).
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local fs = require("lb:fs")
|
||||||
|
/// ```
|
||||||
|
#[cdef(module = "lb:fs")]
|
||||||
|
pub struct lb_fslib;
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_fslib {
|
||||||
|
#[new]
|
||||||
|
extern "Lua-C" fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the entire contents of a file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file does not exist or could not be read.
|
||||||
|
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
||||||
|
Ok(tokio::fs::read(path).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the entire contents of a file synchronously.
|
||||||
|
///
|
||||||
|
/// This is a synchronous complement to [`read`](Self::read).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file does not exist or could not be read.
|
||||||
|
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
||||||
|
Ok(std::fs::read(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the given contents to a file, replacing its contents if it exists.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file could not be written.
|
||||||
|
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
|
||||||
|
Ok(tokio::fs::write(path, contents).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the given contents to a file synchronously, replacing its contents if it exists.
|
||||||
|
///
|
||||||
|
/// This is a synchronous complement to [`write`](Self::write).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file could not be written.
|
||||||
|
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
|
||||||
|
Ok(std::fs::write(path, contents)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the entries in a directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory could not be read.
|
||||||
|
pub async extern "Lua-C" fn read_dir(path: &str) -> Result<lb_read_dir> {
|
||||||
|
Ok(lb_read_dir::new(tokio::fs::read_dir(path).await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the entries in a directory synchronously.
|
||||||
|
///
|
||||||
|
/// This is a synchronous complement to [`read_dir`](Self::read_dir).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory could not be read.
|
||||||
|
pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result<lb_read_dir_sync> {
|
||||||
|
Ok(lb_read_dir_sync::new(std::fs::read_dir(path)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively walks the current directory, yielding all entries within it.
|
||||||
|
pub extern "Lua" fn walk(pattern: &str) -> Result<lb_walk_dir> {
|
||||||
|
Self::walk_dir(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively walks a directory, yielding all entries within it.
|
||||||
|
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
|
||||||
|
lb_walk_dir::new(walkdir::WalkDir::new(path).into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively walks the current directory, yielding all entries within it that match the given
|
||||||
|
/// glob pattern.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the pattern is invalid.
|
||||||
|
pub extern "Lua" fn glob(pattern: &str) -> Result<lb_glob_dir> {
|
||||||
|
Self::glob_dir(".", pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively walks a directory, yielding all entries within it that match the given glob
|
||||||
|
/// pattern.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the pattern is invalid.
|
||||||
|
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::new(iter, matcher, prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new temporary directory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the temporary directory could not be created.
|
||||||
|
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
|
||||||
|
Ok(lb_temp_dir::new(tempfile::tempdir()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new temporary directory inside the specified path.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the temporary directory could not be created.
|
||||||
|
pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result<lb_temp_dir> {
|
||||||
|
Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
251
crates/lb/src/fs/sync.rs
Normal file
251
crates/lb/src/fs/sync.rs
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
use super::*;
|
||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
fs::{DirEntry, FileType, Metadata, Permissions, ReadDir},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Structure representing the type of a file with accessors for each file type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_file_type(#[opaque] FileType);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_file_type {
|
||||||
|
pub(super) fn new(ty: FileType) -> Self {
|
||||||
|
Self(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file type is a directory.
|
||||||
|
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||||
|
self.0.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file type is a regular file.
|
||||||
|
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file type is a symbolic link.
|
||||||
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the string `"file"` if this is a regular file, `"dir"` if this is a directory,
|
||||||
|
/// `"symlink"` if this is a symbolic link, or `"other"` if it is some other type of 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)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_file_meta(#[opaque] Metadata);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_file_meta {
|
||||||
|
pub(super) fn new(meta: Metadata) -> Self {
|
||||||
|
Self(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file is a directory.
|
||||||
|
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||||
|
self.0.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file is a regular file.
|
||||||
|
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this file is a symbolic link.
|
||||||
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
|
self.0.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this file.
|
||||||
|
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||||
|
lb_file_type::new(self.0.file_type())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the size of this file in bytes.
|
||||||
|
pub extern "Lua-C" fn size(&self) -> u64 {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the permissions of this file.
|
||||||
|
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
|
||||||
|
lb_file_perms::new(self.0.permissions())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the creation time of this file as seconds since the Unix epoch.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the creation time could not be retrieved.
|
||||||
|
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.))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the modification time of this file as seconds since the Unix epoch.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the modification time could not be retrieved.
|
||||||
|
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.))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the last access time of this file as seconds since the Unix epoch.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the access time could not be retrieved.
|
||||||
|
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.))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a string representation of this file's metadata.
|
||||||
|
#[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)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_file_perms(#[opaque] RefCell<Permissions>);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_file_perms {
|
||||||
|
pub(super) fn new(perms: Permissions) -> Self {
|
||||||
|
Self(RefCell::new(perms))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the readonly flag is set.
|
||||||
|
pub extern "Lua-C" fn readonly(&self) -> bool {
|
||||||
|
self.0.borrow().readonly()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the readonly flag.
|
||||||
|
pub extern "Lua-C" fn set_readonly(&self, readonly: bool) {
|
||||||
|
self.0.borrow_mut().set_readonly(readonly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Synchronous version of [`lb_read_dir`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_read_dir_sync(#[opaque] RefCell<ReadDir>);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_read_dir_sync {
|
||||||
|
pub(super) fn new(iter: ReadDir) -> Self {
|
||||||
|
Self(RefCell::new(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry in the directory, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory could not be read.
|
||||||
|
#[call]
|
||||||
|
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry_sync>> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.try_borrow_mut()?
|
||||||
|
.next()
|
||||||
|
.transpose()?
|
||||||
|
.map(lb_dir_entry_sync::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Synchronous version of [`lb_dir_entry`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_dir_entry_sync(#[opaque] DirEntry);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_dir_entry_sync {
|
||||||
|
pub(super) fn new(entry: DirEntry) -> Self {
|
||||||
|
Self(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of this entry.
|
||||||
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
|
self.0.file_name().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the file type could not be determined.
|
||||||
|
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||||
|
Ok(lb_file_type::new(self.0.file_type()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the metadata could not be retrieved.
|
||||||
|
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
|
Ok(lb_file_meta::new(self.0.metadata()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number for this entry.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
|
use std::os::unix::fs::DirEntryExt;
|
||||||
|
self.0.ino()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
25
crates/lb/src/fs/temp.rs
Normal file
25
crates/lb/src/fs/temp.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
/// Directory in the filesystem that is automatically deleted when it is garbage-collected.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_temp_dir(#[opaque] TempDir);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_temp_dir {
|
||||||
|
pub(super) fn new(dir: TempDir) -> Self {
|
||||||
|
Self(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this temporary directory.
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this temporary directory.
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
90
crates/lb/src/fs/walk.rs
Normal file
90
crates/lb/src/fs/walk.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
use super::*;
|
||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use walkdir::{DirEntry, IntoIter};
|
||||||
|
|
||||||
|
/// Iterator for recursively descending into a directory.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_walk_dir(#[opaque] RefCell<IntoIter>);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_walk_dir {
|
||||||
|
pub(super) fn new(iter: IntoIter) -> Self {
|
||||||
|
Self(RefCell::new(iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next entry in the walk, or `nil` if there are no more entries.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the directory could not be read.
|
||||||
|
#[call]
|
||||||
|
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
|
||||||
|
Ok(self
|
||||||
|
.0
|
||||||
|
.try_borrow_mut()?
|
||||||
|
.next()
|
||||||
|
.transpose()?
|
||||||
|
.map(lb_walk_dir_entry::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_walk_dir_entry(#[opaque] DirEntry);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_walk_dir_entry {
|
||||||
|
pub(super) fn new(entry: DirEntry) -> Self {
|
||||||
|
Self(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
|
pub extern "Lua-C" fn path(&self) -> String {
|
||||||
|
self.0.path().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of this entry.
|
||||||
|
pub extern "Lua-C" fn name(&self) -> String {
|
||||||
|
self.0.file_name().to_string_lossy().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this entry.
|
||||||
|
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||||
|
lb_file_type::new(self.0.file_type())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for this entry.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw if the metadata could not be retrieved.
|
||||||
|
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||||
|
Ok(lb_file_meta::new(self.0.metadata()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this entry was created from a symbolic link.
|
||||||
|
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||||
|
self.0.path_is_symlink()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the depth of this entry in the walk.
|
||||||
|
pub extern "Lua-C" fn depth(&self) -> u32 {
|
||||||
|
self.0.depth() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number for this entry.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||||
|
use walkdir::DirEntryExt;
|
||||||
|
self.0.ino()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full path of this entry.
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua" fn tostring(&self) -> String {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
//! luby standard library
|
//! luby core libraries.
|
||||||
#![warn(missing_docs)]
|
|
||||||
#[cfg(feature = "task")]
|
|
||||||
pub mod chan;
|
|
||||||
#[cfg(feature = "fs")]
|
#[cfg(feature = "fs")]
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
#[cfg(feature = "net")]
|
#[cfg(feature = "net")]
|
||||||
@@ -10,3 +7,5 @@ pub mod net;
|
|||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
#[cfg(feature = "task")]
|
#[cfg(feature = "task")]
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
pub mod time;
|
||||||
|
|||||||
@@ -1,508 +0,0 @@
|
|||||||
//! # Networking library
|
|
||||||
//!
|
|
||||||
//! The `lb:net` library provides an asynchronous network API for creating TCP or UDP servers and
|
|
||||||
//! clients.
|
|
||||||
//!
|
|
||||||
//! ## Exports
|
|
||||||
//!
|
|
||||||
//! See [`lb_netlib`] for items exported by this library.
|
|
||||||
use derive_more::{From, FromStr};
|
|
||||||
use luaffi::{cdef, marker::OneOf, metatype};
|
|
||||||
use std::{
|
|
||||||
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
|
||||||
|
|
||||||
/// Errors that can be thrown by this library.
|
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
/// local net = require("lb:net");
|
|
||||||
/// ```
|
|
||||||
#[cdef(module = "lb:net")]
|
|
||||||
pub struct lb_netlib;
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
impl lb_netlib {
|
|
||||||
#[new]
|
|
||||||
extern "Lua-C" fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An IPv4 address representing localhost: `127.0.0.1`
|
|
||||||
pub extern "Lua-C" fn localhost_v4() -> lb_ipaddr {
|
|
||||||
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An IPv6 address representing localhost: `::1`
|
|
||||||
pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr {
|
|
||||||
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An IPv4 address representing an unspecified address: `0.0.0.0`
|
|
||||||
pub extern "Lua-C" fn unspecified_v4() -> lb_ipaddr {
|
|
||||||
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An IPv6 address representing an unspecified address: `::`
|
|
||||||
pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr {
|
|
||||||
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An IPv4 address representing the broadcast address: `255.255.255.255`
|
|
||||||
pub extern "Lua-C" fn broadcast_v4() -> lb_ipaddr {
|
|
||||||
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an [`lb_ipaddr`] from the given input.
|
|
||||||
///
|
|
||||||
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
|
|
||||||
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
|
|
||||||
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
|
|
||||||
pub extern "Lua" fn ipaddr(
|
|
||||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
|
||||||
) -> Result<lb_ipaddr> {
|
|
||||||
if __istype(__ct.lb_ipaddr, addr) {
|
|
||||||
__new(__ct.lb_ipaddr, addr) // copy constructor
|
|
||||||
} else if __istype(__ct.lb_socketaddr, addr) {
|
|
||||||
s.ip()
|
|
||||||
} else {
|
|
||||||
Self::__parse_ipaddr(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result<lb_ipaddr> {
|
|
||||||
Ok(addr.parse()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an [`lb_socketaddr`] from the given input.
|
|
||||||
///
|
|
||||||
/// A socket address is an IP address with a port number.
|
|
||||||
///
|
|
||||||
/// If `s` is an [`lb_socketaddr`], a copy of that value is returned. If `s` is an
|
|
||||||
/// [`lb_ipaddr`], a socket address with that IP address is returned. Otherwise, parses `s` as a
|
|
||||||
/// socket address string. Both IPv4 and IPv6 addresses are supported.
|
|
||||||
///
|
|
||||||
/// If `port` is not specified, `0` is used as the default.
|
|
||||||
pub extern "Lua" fn socketaddr(
|
|
||||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
|
||||||
port: Option<u16>,
|
|
||||||
) -> Result<lb_socketaddr> {
|
|
||||||
if port != () {
|
|
||||||
Self::__new_skaddr(Self::ipaddr(addr), port)
|
|
||||||
} else {
|
|
||||||
if __istype(__ct.lb_socketaddr, addr) {
|
|
||||||
__new(__ct.lb_socketaddr, addr) // copy constructor
|
|
||||||
} else if __istype(__ct.lb_ipaddr, addr) {
|
|
||||||
Self::__new_skaddr(addr, 0) // default port 0
|
|
||||||
} else {
|
|
||||||
Self::__parse_skaddr(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
|
||||||
SocketAddr::new(ip.0, port).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result<lb_socketaddr> {
|
|
||||||
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.
|
|
||||||
///
|
|
||||||
/// See [`TcpSocket::new_v4`].
|
|
||||||
pub extern "Lua-C" fn tcp() -> Result<lb_tcpsocket> {
|
|
||||||
Ok(Some(TcpSocket::new_v4()?).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new TCP socket configured for IPv6.
|
|
||||||
///
|
|
||||||
/// See [`TcpSocket::new_v6`].
|
|
||||||
pub extern "Lua-C" fn tcp6() -> Result<lb_tcpsocket> {
|
|
||||||
Ok(Some(TcpSocket::new_v6()?).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async extern "Lua" fn bind_tcp(
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// IP address, either IPv4 or IPv6.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// This example creates an [`lb_ipaddr`] by parsing an IP address string.
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// local net = require("lb:net");
|
|
||||||
/// local addr = net.ipaddr("127.0.0.1"); -- ipv4 loopback address
|
|
||||||
///
|
|
||||||
/// assert(addr:is_v4());
|
|
||||||
/// assert(addr:is_loopback());
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
|
||||||
#[cdef]
|
|
||||||
pub struct lb_ipaddr(#[opaque] IpAddr);
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
impl lb_ipaddr {
|
|
||||||
/// See [`IpAddr::is_unspecified`].
|
|
||||||
pub extern "Lua-C" fn is_unspecified(&self) -> bool {
|
|
||||||
self.0.is_unspecified()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`IpAddr::is_loopback`].
|
|
||||||
pub extern "Lua-C" fn is_loopback(&self) -> bool {
|
|
||||||
self.0.is_loopback()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`IpAddr::is_multicast`].
|
|
||||||
pub extern "Lua-C" fn is_multicast(&self) -> bool {
|
|
||||||
self.0.is_multicast()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the string `"v4"` if this is an IPv4 address or `"v6"` if this is an IPv6 address.
|
|
||||||
pub extern "Lua" fn family(&self) -> String {
|
|
||||||
if self.is_v6() { "v6" } else { "v4" }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this is an IPv4 address.
|
|
||||||
pub extern "Lua-C" fn is_v4(&self) -> bool {
|
|
||||||
self.0.is_ipv4()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv4Addr::is_private`].
|
|
||||||
pub extern "Lua-C" fn is_v4_private(&self) -> bool {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(v4) => v4.is_private(),
|
|
||||||
IpAddr::V6(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv4Addr::is_link_local`].
|
|
||||||
pub extern "Lua-C" fn is_v4_link_local(&self) -> bool {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(v4) => v4.is_link_local(),
|
|
||||||
IpAddr::V6(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv4Addr::is_broadcast`].
|
|
||||||
pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(v4) => v4.is_broadcast(),
|
|
||||||
IpAddr::V6(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv4Addr::is_documentation`].
|
|
||||||
pub extern "Lua-C" fn is_v4_documentation(&self) -> bool {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(v4) => v4.is_documentation(),
|
|
||||||
IpAddr::V6(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this is an IPv6 address.
|
|
||||||
pub extern "Lua-C" fn is_v6(&self) -> bool {
|
|
||||||
self.0.is_ipv6()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv6Addr::is_unique_local`].
|
|
||||||
pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(_) => false,
|
|
||||||
IpAddr::V6(v6) => v6.is_unique_local(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv6Addr::is_unicast_link_local`].
|
|
||||||
pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(_) => false,
|
|
||||||
IpAddr::V6(v6) => v6.is_unicast_link_local(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv4Addr::to_ipv6_compatible`].
|
|
||||||
pub extern "Lua-C" fn to_v6_compat(&self) -> Self {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(v4) => Self(v4.to_ipv6_compatible().into()),
|
|
||||||
IpAddr::V6(_) => *self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`Ipv4Addr::to_ipv6_mapped`].
|
|
||||||
pub extern "Lua-C" fn to_v6_mapped(&self) -> Self {
|
|
||||||
match self.0 {
|
|
||||||
IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()),
|
|
||||||
IpAddr::V6(_) => *self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [`IpAddr::to_canonical`].
|
|
||||||
pub extern "Lua-C" fn canonical(&self) -> Self {
|
|
||||||
self.0.to_canonical().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the string representation of this address.
|
|
||||||
#[tostring]
|
|
||||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
|
||||||
self.0.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Socket address, which is an IP address with a port number.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
|
||||||
#[cdef]
|
|
||||||
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
impl lb_socketaddr {
|
|
||||||
/// Returns the IP part of this address.
|
|
||||||
pub extern "Lua-C" fn ip(&self) -> lb_ipaddr {
|
|
||||||
self.0.ip().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the IP part of this address.
|
|
||||||
///
|
|
||||||
/// This function accepts the same arguments as [`ipaddr`](lb_netlib::ipaddr).
|
|
||||||
pub extern "Lua" fn set_ip(
|
|
||||||
&mut self,
|
|
||||||
addr: OneOf<(&lb_ipaddr, &lb_socketaddr, &str)>,
|
|
||||||
) -> &mut Self {
|
|
||||||
if __istype(__ct.lb_ipaddr, addr) {
|
|
||||||
self.__set_ip(addr);
|
|
||||||
} else if __istype(__ct.lb_socketaddr, addr) {
|
|
||||||
self.__set_ip(addr.ip());
|
|
||||||
} else {
|
|
||||||
self.__set_ip_parse(addr);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua-C" fn __set_ip(&mut self, ip: &lb_ipaddr) {
|
|
||||||
self.0.set_ip(ip.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua-C" fn __set_ip_parse(&mut self, addr: &str) -> Result<()> {
|
|
||||||
Ok(self.0.set_ip(addr.parse()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the port part of this address.
|
|
||||||
pub extern "Lua-C" fn port(&self) -> u16 {
|
|
||||||
self.0.port()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the port part of this address.
|
|
||||||
pub extern "Lua" fn set_port(&mut self, port: u16) -> &mut Self {
|
|
||||||
self.__set_port(port);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Lua-C" fn __set_port(&mut self, port: u16) {
|
|
||||||
self.0.set_port(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the string representation of this address.
|
|
||||||
#[tostring]
|
|
||||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
|
||||||
self.0.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
|
|
||||||
#[derive(Debug, From)]
|
|
||||||
#[cdef]
|
|
||||||
pub struct lb_tcpsocket(#[opaque] Option<TcpSocket>);
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
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)]
|
|
||||||
#[cdef]
|
|
||||||
pub struct lb_tcpstream(#[opaque] TcpStream);
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
impl lb_tcpstream {}
|
|
||||||
|
|
||||||
/// TCP socket server, listening for connections.
|
|
||||||
#[derive(Debug, From)]
|
|
||||||
#[cdef]
|
|
||||||
pub struct lb_tcplistener(#[opaque] TcpListener);
|
|
||||||
|
|
||||||
#[metatype]
|
|
||||||
impl lb_tcplistener {}
|
|
||||||
588
crates/lb/src/net/mod.rs
Normal file
588
crates/lb/src/net/mod.rs
Normal file
@@ -0,0 +1,588 @@
|
|||||||
|
//! Network library.
|
||||||
|
//!
|
||||||
|
//! The `lb:net` library provides an asynchronous network API for woring with TCP, UDP and IPC
|
||||||
|
//! sockets.
|
||||||
|
//!
|
||||||
|
//! ## Exports
|
||||||
|
//!
|
||||||
|
//! See [`lb_netlib`] for items exported by this library.
|
||||||
|
use derive_more::{From, FromStr};
|
||||||
|
use luaffi::{cdef, marker::OneOf, metatype};
|
||||||
|
use std::{
|
||||||
|
cell::{BorrowError, BorrowMutError},
|
||||||
|
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
|
mod tcp;
|
||||||
|
|
||||||
|
pub use tcp::*;
|
||||||
|
|
||||||
|
/// Errors that can be thrown by this library.
|
||||||
|
///
|
||||||
|
/// Functions which return this error will **throw**. 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 {
|
||||||
|
/// Attempt to access an object while it is being modified.
|
||||||
|
#[error("cannot access object while it is being modified")]
|
||||||
|
Borrow(#[from] BorrowError),
|
||||||
|
/// Attempt to modify an object while it is in use.
|
||||||
|
#[error("cannot modify object while it is in use")]
|
||||||
|
BorrowMut(#[from] BorrowMutError),
|
||||||
|
/// 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 can no longer be used.
|
||||||
|
#[error("socket was already converted")]
|
||||||
|
SocketConsumed,
|
||||||
|
/// Socket was closed and can no longer be used.
|
||||||
|
#[error("socket was closed")]
|
||||||
|
SocketClosed,
|
||||||
|
/// Specified socket half was not `"read"` or `"write"`.
|
||||||
|
#[error("invalid socket half")]
|
||||||
|
InvalidSocketHalf,
|
||||||
|
}
|
||||||
|
|
||||||
|
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).
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// ```
|
||||||
|
#[cdef(module = "lb:net")]
|
||||||
|
pub struct lb_netlib;
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_netlib {
|
||||||
|
#[new]
|
||||||
|
extern "Lua-C" fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An IPv4 address representing localhost: `127.0.0.1`
|
||||||
|
pub extern "Lua-C" fn localhost() -> lb_ipaddr {
|
||||||
|
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An IPv6 address representing localhost: `::1`
|
||||||
|
pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr {
|
||||||
|
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An IPv4 address representing an unspecified address: `0.0.0.0`
|
||||||
|
pub extern "Lua-C" fn unspecified() -> lb_ipaddr {
|
||||||
|
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An IPv6 address representing an unspecified address: `::`
|
||||||
|
pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr {
|
||||||
|
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An IPv4 address representing the broadcast address: `255.255.255.255`
|
||||||
|
pub extern "Lua-C" fn broadcast() -> lb_ipaddr {
|
||||||
|
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an IP address from the given input.
|
||||||
|
///
|
||||||
|
/// If `addr` is an [`lb_ipaddr`], a copy of that value is returned. If `addr` is an
|
||||||
|
/// [`lb_socketaddr`], the IP part of the socket address is returned. Otherwise, parses `addr`
|
||||||
|
/// as an IP address string. Both IPv4 and IPv6 addresses are accepted.
|
||||||
|
///
|
||||||
|
/// An address string may be something like `127.0.0.1`.
|
||||||
|
///
|
||||||
|
/// The type of the parsed address can be checked using [`is_v4`](lb_ipaddr::is_v4) or
|
||||||
|
/// [`is_v6`](lb_ipaddr::is_v6) functions on the returned [`lb_ipaddr`](lb_ipaddr) value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will throw an error if the input syntax is invalid.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local addr = net.ipaddr("192.168.1.1")
|
||||||
|
///
|
||||||
|
/// assert(addr:is_v4())
|
||||||
|
/// ```
|
||||||
|
pub extern "Lua" fn ipaddr(
|
||||||
|
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||||
|
) -> Result<lb_ipaddr> {
|
||||||
|
if __istype(__ct.lb_ipaddr, addr) {
|
||||||
|
__new(__ct.lb_ipaddr, addr) // copy constructor
|
||||||
|
} else if __istype(__ct.lb_socketaddr, addr) {
|
||||||
|
s.ip()
|
||||||
|
} else {
|
||||||
|
Self::__parse_ipaddr(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result<lb_ipaddr> {
|
||||||
|
Ok(addr.parse()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a socket address from the given input.
|
||||||
|
///
|
||||||
|
/// A socket address is an IP address with a prescribed port number.
|
||||||
|
///
|
||||||
|
/// If `addr` is an [`lb_socketaddr`], a copy of that value is returned. If `addr` is an
|
||||||
|
/// [`lb_ipaddr`], a socket address with that IP part is returned. Otherwise, parses `addr` as a
|
||||||
|
/// socket address string. Both IPv4 and IPv6 addresses are accepted.
|
||||||
|
///
|
||||||
|
/// If `port` is specified, it is always used as the port part of the returned socket address.
|
||||||
|
/// Otherwise, `0` is used as the default.
|
||||||
|
///
|
||||||
|
/// An address string may be something like `127.0.0.1:3000`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will throw an error if the input syntax is invalid.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local addr = net.socketaddr("::1", 8080)
|
||||||
|
///
|
||||||
|
/// assert(addr:ip():is_v6() and addr:ip():is_loopback())
|
||||||
|
/// assert(addr:port() == 8080)
|
||||||
|
/// ```
|
||||||
|
pub extern "Lua" fn socketaddr(
|
||||||
|
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_socketaddr> {
|
||||||
|
if port != () {
|
||||||
|
Self::__new_skaddr(Self::ipaddr(addr), port)
|
||||||
|
} else {
|
||||||
|
if __istype(__ct.lb_socketaddr, addr) {
|
||||||
|
__new(__ct.lb_socketaddr, addr) // copy constructor
|
||||||
|
} else if __istype(__ct.lb_ipaddr, addr) {
|
||||||
|
Self::__new_skaddr(addr, 0) // default port 0
|
||||||
|
} else {
|
||||||
|
Self::__parse_skaddr(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
||||||
|
SocketAddr::new(ip.0, port).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result<lb_socketaddr> {
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// This calls `socket(2)` with `AF_INET` and `SOCK_STREAM`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw an error if the socket could not be created.
|
||||||
|
pub extern "Lua-C" fn tcp() -> Result<lb_tcpsocket> {
|
||||||
|
Ok(lb_tcpsocket::new(TcpSocket::new_v4()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new TCP socket configured for IPv6.
|
||||||
|
///
|
||||||
|
/// This calls `socket(2)` with `AF_INET6` and `SOCK_STREAM`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw an error if the socket could not be created.
|
||||||
|
pub extern "Lua-C" fn tcp_v6() -> Result<lb_tcpsocket> {
|
||||||
|
Ok(lb_tcpsocket::new(TcpSocket::new_v6()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new TCP socket bound to the given address and port.
|
||||||
|
///
|
||||||
|
/// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a
|
||||||
|
/// new TCP socket configured to use either IPv4 or IPv6 depending on the type of the given
|
||||||
|
/// address, and binds it to that address.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw an error if the socket could not be created or bound to the
|
||||||
|
/// specified address.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local socket = net.bind_tcp("127.0.0.1")
|
||||||
|
///
|
||||||
|
/// assert(socket:local_addr():ip() == net.ipaddr("127.0.0.1"))
|
||||||
|
/// socket:set_nodelay(true)
|
||||||
|
/// ```
|
||||||
|
pub extern "Lua" fn bind_tcp(
|
||||||
|
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_tcpsocket> {
|
||||||
|
let addr = Self::socketaddr(addr, port);
|
||||||
|
let socket;
|
||||||
|
if addr.ip().is_v6() {
|
||||||
|
socket = Self::tcp_v6();
|
||||||
|
} else {
|
||||||
|
socket = Self::tcp();
|
||||||
|
}
|
||||||
|
socket.bind(addr);
|
||||||
|
socket
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new TCP socket listening on the given address and port.
|
||||||
|
///
|
||||||
|
/// This is a convenience function that combines [`bind_tcp`](Self::bind_tcp) and
|
||||||
|
/// [`listen`](lb_tcpsocket::listen). It accepts the same arguments as
|
||||||
|
/// [`socketaddr`](Self::socketaddr).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw an error if the socket could not be created, bound to the specified
|
||||||
|
/// address, or could not transition to the listening state.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua,no_run
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local listener = net.listen_tcp("127.0.0.1")
|
||||||
|
///
|
||||||
|
/// assert(listener:local_addr():ip() == net.ipaddr("127.0.0.1"))
|
||||||
|
///
|
||||||
|
/// for stream in listener do
|
||||||
|
/// print("client connected: ", stream:peer_addr())
|
||||||
|
/// end
|
||||||
|
/// ```
|
||||||
|
pub extern "Lua" fn listen_tcp(
|
||||||
|
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_tcplistener> {
|
||||||
|
Self::bind_tcp(addr, port).listen(1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establishes a new TCP connection to the server at the given address and port.
|
||||||
|
///
|
||||||
|
/// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a
|
||||||
|
/// new TCP socket and connects it to the specified address.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw an error if the socket could not be created or connected to the
|
||||||
|
/// specified address.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local listener = net.listen_tcp("127.0.0.1")
|
||||||
|
/// local stream = net.connect_tcp("127.0.0.1", listener:local_addr():port())
|
||||||
|
///
|
||||||
|
/// assert(stream:peer_addr():ip() == net.ipaddr("127.0.0.1"))
|
||||||
|
/// stream:write("Hello, server!\n")
|
||||||
|
/// ```
|
||||||
|
pub async extern "Lua" fn connect_tcp(
|
||||||
|
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> Result<lb_tcpstream> {
|
||||||
|
let addr = Self::socketaddr(addr, port);
|
||||||
|
let socket;
|
||||||
|
if addr.ip().is_v6() {
|
||||||
|
socket = Self::tcp_v6();
|
||||||
|
} else {
|
||||||
|
socket = Self::tcp();
|
||||||
|
}
|
||||||
|
socket.connect(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IP address, either IPv4 or IPv6.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example creates an [`lb_ipaddr`] by parsing an IP address string.
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local addr = net.ipaddr("127.0.0.1") -- ipv4 loopback address
|
||||||
|
///
|
||||||
|
/// assert(addr:is_v4() and addr:is_loopback())
|
||||||
|
/// assert(tostring(addr) == "127.0.0.1")
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_ipaddr(#[opaque] IpAddr);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_ipaddr {
|
||||||
|
/// Returns `true` if this is an IPv4 address.
|
||||||
|
pub extern "Lua-C" fn is_v4(&self) -> bool {
|
||||||
|
self.0.is_ipv4()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv6 address.
|
||||||
|
pub extern "Lua-C" fn is_v6(&self) -> bool {
|
||||||
|
self.0.is_ipv6()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the string `"v4"` if this is an IPv4 address, or `"v6"` if this is an IPv6 address.
|
||||||
|
pub extern "Lua" fn family(&self) -> String {
|
||||||
|
if self.is_v6() { "v6" } else { "v4" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is a loopback address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is any address in the `127.0.0.0/8` range.
|
||||||
|
///
|
||||||
|
/// For IPv6, this is the address `::1`.
|
||||||
|
pub extern "Lua-C" fn is_loopback(&self) -> bool {
|
||||||
|
self.0.is_loopback()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this address is unspecified.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is the address `0.0.0.0`.
|
||||||
|
///
|
||||||
|
/// For IPv6, this is the address `::`.
|
||||||
|
pub extern "Lua-C" fn is_unspecified(&self) -> bool {
|
||||||
|
self.0.is_unspecified()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this address is a multicast address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is any address in the `224.0.0.0/4` range.
|
||||||
|
///
|
||||||
|
/// For IPv6, this is any address in the `ff00::/8` range.
|
||||||
|
pub extern "Lua-C" fn is_multicast(&self) -> bool {
|
||||||
|
self.0.is_multicast()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv4 private address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is any address in one of these ranges:
|
||||||
|
///
|
||||||
|
/// - `10.0.0.0/8`
|
||||||
|
/// - `172.16.0.0/12`
|
||||||
|
/// - `192.168.0.0/16`
|
||||||
|
///
|
||||||
|
/// For IPv6, this always returns `false`.
|
||||||
|
pub extern "Lua-C" fn is_v4_private(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(v4) => v4.is_private(),
|
||||||
|
IpAddr::V6(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv4 link-local address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is any address in the `169.254.0.0/16` range.
|
||||||
|
///
|
||||||
|
/// For IPv6, this always returns `false`.
|
||||||
|
pub extern "Lua-C" fn is_v4_link_local(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(v4) => v4.is_link_local(),
|
||||||
|
IpAddr::V6(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv4 broadcast address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is the address `255.255.255.255`.
|
||||||
|
///
|
||||||
|
/// For IPv6, this always returns `false`.
|
||||||
|
pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(v4) => v4.is_broadcast(),
|
||||||
|
IpAddr::V6(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv4 documentation address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this is any address in one of these ranges:
|
||||||
|
///
|
||||||
|
/// - `192.0.2.0/24` (TEST-NET-1)
|
||||||
|
/// - `198.51.100.0/24` (TEST-NET-2)
|
||||||
|
/// - `203.0.113.0/24` (TEST-NET-3)
|
||||||
|
///
|
||||||
|
/// For IPv6, this always returns `false`.
|
||||||
|
pub extern "Lua-C" fn is_v4_documentation(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(v4) => v4.is_documentation(),
|
||||||
|
IpAddr::V6(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv6 unique local address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this always returns `false`.
|
||||||
|
///
|
||||||
|
/// For IPv6, this is any address in the `fc00::/7` range.
|
||||||
|
pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(_) => false,
|
||||||
|
IpAddr::V6(v6) => v6.is_unique_local(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is an IPv6 unicast address with link-local scope.
|
||||||
|
///
|
||||||
|
/// For IPv4, this always returns `false`.
|
||||||
|
///
|
||||||
|
/// For IPv6, this is any address in the `fe80::/10` range.
|
||||||
|
pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(_) => false,
|
||||||
|
IpAddr::V6(v6) => v6.is_unicast_link_local(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this address to IPv4.
|
||||||
|
///
|
||||||
|
/// For IPv4, this returns the address unchanged.
|
||||||
|
///
|
||||||
|
/// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible
|
||||||
|
/// IPv6 address. Otherwise, this returns `nil`.
|
||||||
|
pub extern "Lua-C" fn to_v4(&self) -> Option<Self> {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(_) => Some(*self),
|
||||||
|
IpAddr::V6(v6) => v6.to_ipv4().map(|v| Self(v.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this address to IPv6.
|
||||||
|
///
|
||||||
|
/// For IPv4, this returns the IPv4-mapped IPv6 address as defined by
|
||||||
|
/// [`Ipv4Addr::to_ipv6_mapped`].
|
||||||
|
///
|
||||||
|
/// For IPv6, this returns the address unchanged.
|
||||||
|
pub extern "Lua-C" fn to_v6(&self) -> Self {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()),
|
||||||
|
IpAddr::V6(_) => *self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the canonical form of this address.
|
||||||
|
///
|
||||||
|
/// For IPv4, this returns the address unchanged.
|
||||||
|
///
|
||||||
|
/// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible
|
||||||
|
/// IPv6 address. Otherwise, this returns the address unchanged.
|
||||||
|
pub extern "Lua-C" fn to_canonical(&self) -> Self {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V4(_) => *self,
|
||||||
|
IpAddr::V6(v6) => v6.to_ipv4().map_or(*self, |v| Self(v.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the given addresses are equal.
|
||||||
|
///
|
||||||
|
/// Two addresses are considered equal if they are of the same family (IPv4 or IPv6) and
|
||||||
|
/// represent the same address in octets.
|
||||||
|
#[eq]
|
||||||
|
pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool {
|
||||||
|
left.0 == right.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the left address is less than the right address.
|
||||||
|
///
|
||||||
|
/// IPv4 addresses are always less than IPv6 addresses.
|
||||||
|
#[lt]
|
||||||
|
pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool {
|
||||||
|
left.0 < right.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the left address is less than or equal to the right address.
|
||||||
|
///
|
||||||
|
/// IPv4 addresses are always less than IPv6 addresses.
|
||||||
|
#[le]
|
||||||
|
pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool {
|
||||||
|
left.0 <= right.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the string representation of this address.
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Socket address, which is an IP address with a port number.
|
||||||
|
///
|
||||||
|
/// This represents an IP address with a prescribed port, such as `127.0.0.1:8080` or `[::1]:443`.
|
||||||
|
/// It is used to specify endpoints for network connections and listeners, and can be constructed by
|
||||||
|
/// [`socketaddr`](lb_netlib::socketaddr).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local addr = net.socketaddr("127.0.0.1:8080")
|
||||||
|
///
|
||||||
|
/// assert(addr:ip() == net.ipaddr("127.0.0.1") and addr:port() == 8080)
|
||||||
|
/// assert(tostring(addr) == "127.0.0.1:8080")
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_socketaddr {
|
||||||
|
/// The IP part of this address.
|
||||||
|
pub extern "Lua-C" fn ip(&self) -> lb_ipaddr {
|
||||||
|
self.0.ip().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The port part of this address.
|
||||||
|
pub extern "Lua-C" fn port(&self) -> u16 {
|
||||||
|
self.0.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new socket address with the given IP address and the same port.
|
||||||
|
pub extern "Lua-C" fn with_ip(&self, ip: &lb_ipaddr) -> Self {
|
||||||
|
SocketAddr::new(ip.0, self.port()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new socket address with the given port and the same IP address.
|
||||||
|
pub extern "Lua-C" fn with_port(&self, port: u16) -> Self {
|
||||||
|
SocketAddr::new(self.ip().0, port).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the given addresses are equal.
|
||||||
|
#[eq]
|
||||||
|
pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool {
|
||||||
|
left.0 == right.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the left address is less than the right address.
|
||||||
|
#[lt]
|
||||||
|
pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool {
|
||||||
|
left.0 < right.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the left address is less than or equal to the right address.
|
||||||
|
#[le]
|
||||||
|
pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool {
|
||||||
|
left.0 <= right.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the string representation of this address.
|
||||||
|
#[tostring]
|
||||||
|
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
622
crates/lb/src/net/tcp.rs
Normal file
622
crates/lb/src/net/tcp.rs
Normal file
@@ -0,0 +1,622 @@
|
|||||||
|
use super::*;
|
||||||
|
use luaffi::{cdef, marker::fun, metatype};
|
||||||
|
use luajit::LUA_NOREF;
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::{
|
||||||
|
cell::{Ref, RefCell, RefMut},
|
||||||
|
ffi::c_int,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncReadExt, AsyncWriteExt, Interest},
|
||||||
|
net::{TcpListener, TcpSocket, TcpStream},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
|
||||||
|
///
|
||||||
|
/// This type represents a TCP socket in its initial state, before it is connected or set to listen.
|
||||||
|
/// It can be configured (e.g., socket options, bind address) before being converted to an
|
||||||
|
/// [`lb_tcpstream`] (via [`connect`](lb_tcpsocket::connect)) or [`lb_tcplistener`] (via
|
||||||
|
/// [`listen`](lb_tcpsocket::listen)), after which it can no longer be used.
|
||||||
|
///
|
||||||
|
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_tcpsocket(#[opaque] RefCell<Option<TcpSocket>>);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_tcpsocket {
|
||||||
|
pub(super) fn new(socket: TcpSocket) -> Self {
|
||||||
|
Self(RefCell::new(Some(socket)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn socket<'s>(&'s self) -> Result<Ref<'s, TcpSocket>> {
|
||||||
|
let socket = self.0.borrow();
|
||||||
|
match *socket {
|
||||||
|
Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())),
|
||||||
|
None => Err(Error::SocketConsumed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_KEEPALIVE` option on this socket.
|
||||||
|
pub extern "Lua-C" fn keepalive(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.keepalive()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets value for the `SO_KEEPALIVE` option on this socket.
|
||||||
|
///
|
||||||
|
/// This enables or disables periodic keepalive messages on the connection.
|
||||||
|
pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_keepalive(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_REUSEADDR` option on this socket.
|
||||||
|
pub extern "Lua-C" fn reuseaddr(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.reuseaddr()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets value for the `SO_REUSEADDR` option on this socket.
|
||||||
|
///
|
||||||
|
/// This allows the socket to bind to an address that is already in use.
|
||||||
|
pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_reuseaddr(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_REUSEPORT` option on this socket.
|
||||||
|
pub extern "Lua-C" fn reuseport(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.reuseport()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets value for the `SO_REUSEPORT` option on this socket.
|
||||||
|
///
|
||||||
|
/// This allows multiple sockets to bind to the same port.
|
||||||
|
pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_reuseport(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_SNDBUF` option on this socket.
|
||||||
|
pub extern "Lua-C" fn sendbuf(&self) -> Result<u32> {
|
||||||
|
Ok(self.socket()?.send_buffer_size()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets value for the `SO_SNDBUF` option on this socket.
|
||||||
|
///
|
||||||
|
/// This sets the size of the send buffer in bytes.
|
||||||
|
pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_send_buffer_size(size)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_RCVBUF` option on this socket.
|
||||||
|
pub extern "Lua-C" fn recvbuf(&self) -> Result<u32> {
|
||||||
|
Ok(self.socket()?.recv_buffer_size()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets value for the `SO_RCVBUF` option on this socket.
|
||||||
|
///
|
||||||
|
/// This sets the size of the receive buffer in bytes.
|
||||||
|
pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_recv_buffer_size(size)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_LINGER` option on this socket, in seconds.
|
||||||
|
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
|
||||||
|
Ok(self
|
||||||
|
.socket()?
|
||||||
|
.linger()?
|
||||||
|
.map(|n| n.as_secs_f64())
|
||||||
|
.unwrap_or(0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the `SO_LINGER` option on this socket.
|
||||||
|
///
|
||||||
|
/// This controls how long the socket will remain open after close if unsent data is present.
|
||||||
|
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
|
||||||
|
let secs = secs.max(0.);
|
||||||
|
Ok(self
|
||||||
|
.socket()?
|
||||||
|
.set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `TCP_NODELAY` option on this socket.
|
||||||
|
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
|
||||||
|
Ok(self.socket()?.nodelay()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the `TCP_NODELAY` option on this socket.
|
||||||
|
///
|
||||||
|
/// This enables or disables Nagle's algorithm, which delays sending small packets.
|
||||||
|
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.socket()?.set_nodelay(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the local address that this socket is bound to.
|
||||||
|
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.socket()?.local_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Binds this socket to the given local address.
|
||||||
|
pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> {
|
||||||
|
Ok(self.socket()?.bind(addr.0)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transitions this socket to the listening state.
|
||||||
|
///
|
||||||
|
/// This consumes the socket and returns a new [`lb_tcplistener`] that can accept incoming
|
||||||
|
/// connections. This socket object can no longer be used after this call.
|
||||||
|
pub extern "Lua-C" fn listen(&self, backlog: u32) -> Result<lb_tcplistener> {
|
||||||
|
let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?;
|
||||||
|
Ok(lb_tcplistener::new(socket.listen(backlog)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects this socket to the given remote socket address, transitioning it to an established
|
||||||
|
/// state.
|
||||||
|
///
|
||||||
|
/// This consumes the socket and returns a new [`lb_tcpstream`] that can be used to send and
|
||||||
|
/// receive data. This socket object can no longer be used after this call.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function may throw an error if connection could not be established to the given remote
|
||||||
|
/// address.
|
||||||
|
pub async extern "Lua-C" fn connect(&self, addr: &lb_socketaddr) -> Result<lb_tcpstream> {
|
||||||
|
let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?;
|
||||||
|
Ok(lb_tcpstream::new(socket.connect(addr.0).await?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TCP connection between a local and a remote socket.
|
||||||
|
///
|
||||||
|
/// This represents an established TCP connection. It is created by connecting an [`lb_tcpsocket`]
|
||||||
|
/// to a remote socket (via [`connect`](lb_tcpsocket::connect)) or accepting a connection from an
|
||||||
|
/// [`lb_tcplistener`] (via [`accept`](lb_tcplistener::accept)). It provides methods for reading
|
||||||
|
/// from and writing to the stream asynchronously.
|
||||||
|
///
|
||||||
|
/// The stream supports reading and writing data in both directions concurrently. Typically you
|
||||||
|
/// would spawn one reader task and one writer task to handle incoming and outgoing data
|
||||||
|
/// respectively. Connection is closed when this object goes out of scope and gets garbage
|
||||||
|
/// collected, or when [`close`](Self::close) is explicitly called.
|
||||||
|
///
|
||||||
|
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This examples spawns a reader task and a writer task to operate on the stream concurrently.
|
||||||
|
///
|
||||||
|
/// ```lua,no_run
|
||||||
|
/// local task = require("lb:task")
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local socket = net.connect_tcp("127.0.0.1:1234")
|
||||||
|
///
|
||||||
|
/// print("local address: ", socket:local_addr())
|
||||||
|
/// print("remote address: ", socket:peer_addr())
|
||||||
|
///
|
||||||
|
/// local reader = spawn(function()
|
||||||
|
/// for chunk in socket, 1024 do
|
||||||
|
/// print("received: ", chunk)
|
||||||
|
/// end
|
||||||
|
///
|
||||||
|
/// print("done reading")
|
||||||
|
/// end)
|
||||||
|
///
|
||||||
|
/// local writer = spawn(function()
|
||||||
|
/// for i = 1, 10 do
|
||||||
|
/// local msg = ("message %d"):format(i)
|
||||||
|
/// socket:write(msg)
|
||||||
|
/// print("sent: ", msg)
|
||||||
|
/// end
|
||||||
|
///
|
||||||
|
/// print("done writing")
|
||||||
|
/// end)
|
||||||
|
///
|
||||||
|
/// task.join(reader, writer)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The above example uses the socket as an iterator in a generic `for` loop to read data in chunks
|
||||||
|
/// of up to 1024 bytes. It is equivalent to the following:
|
||||||
|
///
|
||||||
|
/// ```lua,no_run
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local socket = net.connect_tcp("127.0.0.1:1234")
|
||||||
|
///
|
||||||
|
/// while true do
|
||||||
|
/// local chunk = socket:read_partial(1024)
|
||||||
|
/// if chunk == nil then break end
|
||||||
|
/// print("received: ", chunk)
|
||||||
|
/// end
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_tcpstream {
|
||||||
|
#[opaque]
|
||||||
|
read: RefCell<Option<OwnedReadHalf>>,
|
||||||
|
#[opaque]
|
||||||
|
write: RefCell<Option<OwnedWriteHalf>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_tcpstream {
|
||||||
|
pub(super) fn new(stream: TcpStream) -> Self {
|
||||||
|
let (read, write) = stream.into_split();
|
||||||
|
Self {
|
||||||
|
read: RefCell::new(Some(read)),
|
||||||
|
write: RefCell::new(Some(write)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_half<'s>(&'s self) -> Result<RefMut<'s, OwnedReadHalf>> {
|
||||||
|
let read = self.read.try_borrow_mut()?;
|
||||||
|
match *read {
|
||||||
|
Some(_) => Ok(RefMut::map(read, |s| s.as_mut().unwrap())),
|
||||||
|
None => Err(Error::SocketClosed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_half<'s>(&'s self) -> Result<RefMut<'s, OwnedWriteHalf>> {
|
||||||
|
let write = self.write.try_borrow_mut()?;
|
||||||
|
match *write {
|
||||||
|
Some(_) => Ok(RefMut::map(write, |s| s.as_mut().unwrap())),
|
||||||
|
None => Err(Error::SocketClosed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The local socket address that this stream is bound to.
|
||||||
|
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.read_half()?.local_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The remote socket address that this stream is connected to.
|
||||||
|
pub extern "Lua-C" fn peer_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.read_half()?.peer_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for this stream to be ready in the given half.
|
||||||
|
///
|
||||||
|
/// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half,
|
||||||
|
/// or `nil` for both.
|
||||||
|
pub async extern "Lua-C" fn ready(&self, half: Option<&str>) -> Result<()> {
|
||||||
|
self.read_half()?
|
||||||
|
.ready(match half {
|
||||||
|
Some("read") => Interest::READABLE,
|
||||||
|
Some("write") => Interest::WRITABLE,
|
||||||
|
None => Interest::READABLE | Interest::WRITABLE,
|
||||||
|
_ => Err(Error::InvalidSocketHalf)?,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Closes this stream in the given half.
|
||||||
|
///
|
||||||
|
/// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half,
|
||||||
|
/// or `nil` for both.
|
||||||
|
///
|
||||||
|
/// Once the half is closed, it can no longer be used for reading or writing for that half. Once
|
||||||
|
/// both halves are closed, the stream is fully shut down.
|
||||||
|
pub extern "Lua-C" fn close(&self, half: Option<&str>) -> Result<()> {
|
||||||
|
Ok(match half {
|
||||||
|
Some("read") => drop(self.read.try_borrow_mut()?.take()),
|
||||||
|
Some("write") => drop(self.write.try_borrow_mut()?.take()),
|
||||||
|
None => drop((
|
||||||
|
self.read.try_borrow_mut()?.take(),
|
||||||
|
self.write.try_borrow_mut()?.take(),
|
||||||
|
)),
|
||||||
|
_ => Err(Error::InvalidSocketHalf)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_disc(err: ErrorKind) -> bool {
|
||||||
|
matches!(
|
||||||
|
err,
|
||||||
|
ErrorKind::ConnectionReset // graceful shutdown
|
||||||
|
| ErrorKind::BrokenPipe // abrupt shutdown
|
||||||
|
| ErrorKind::UnexpectedEof // could not read requested amount of data
|
||||||
|
| ErrorKind::WriteZero // could not write requested amount of data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads exactly `len` bytes from this stream.
|
||||||
|
///
|
||||||
|
/// If the connection was closed, this returns `nil`.
|
||||||
|
pub async extern "Lua-C" fn read(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||||
|
let mut buf = vec![0; len as usize];
|
||||||
|
Ok(match self.read_half()?.read_exact(&mut buf).await {
|
||||||
|
Ok(_) => Some(buf),
|
||||||
|
Err(err) if Self::is_disc(err.kind()) => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads up to `len` bytes from this stream.
|
||||||
|
///
|
||||||
|
/// The returned bytes may be less than `len` in length if the stream had less data available in
|
||||||
|
/// queue. If there was no data available or the connection was closed, this returns `nil`.
|
||||||
|
pub async extern "Lua-C" fn read_partial(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||||
|
let mut buf = vec![0; len as usize];
|
||||||
|
Ok(match self.read_half()?.read(&mut buf).await {
|
||||||
|
Ok(0) => None,
|
||||||
|
Ok(n) => Some({
|
||||||
|
buf.truncate(n);
|
||||||
|
buf
|
||||||
|
}),
|
||||||
|
Err(err) if Self::is_disc(err.kind()) => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to read up to `len` bytes from this stream without waiting.
|
||||||
|
///
|
||||||
|
/// The returned bytes may be less than `len` in length if the stream had less data available in
|
||||||
|
/// queue. If there was no data available or the connection was closed, this returns `nil`.
|
||||||
|
pub extern "Lua-C" fn try_read(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||||
|
let mut buf = vec![0; len as usize];
|
||||||
|
Ok(match self.read_half()?.try_read(&mut buf) {
|
||||||
|
Ok(0) => None,
|
||||||
|
Ok(n) => Some({
|
||||||
|
buf.truncate(n);
|
||||||
|
buf
|
||||||
|
}),
|
||||||
|
Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes exactly the given bytes to this stream.
|
||||||
|
///
|
||||||
|
/// If the connection was closed, this returns `false`.
|
||||||
|
pub async extern "Lua-C" fn write(&self, buf: &[u8]) -> Result<bool> {
|
||||||
|
Ok(match self.write_half()?.write_all(buf).await {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(err) if Self::is_disc(err.kind()) => false,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the given bytes to this stream, and returns the number of bytes successfully written.
|
||||||
|
///
|
||||||
|
/// The returned number may be less than the length of `buf` if there was not enough space in
|
||||||
|
/// queue. If the connection was closed, this returns `nil`.
|
||||||
|
pub async extern "Lua-C" fn write_partial(&self, buf: &[u8]) -> Result<Option<u32>> {
|
||||||
|
Ok(match self.write_half()?.write(buf).await {
|
||||||
|
Ok(n) => Some(n as u32),
|
||||||
|
Err(err) if Self::is_disc(err.kind()) => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to write the given bytes to this stream without waiting, and returns the number of
|
||||||
|
/// bytes successfully written.
|
||||||
|
///
|
||||||
|
/// The returned number may be less than the length of `buf` if there was not enough space in
|
||||||
|
/// queue. If the connection was closed, this returns `nil`.
|
||||||
|
pub extern "Lua-C" fn try_write(&self, buf: &[u8]) -> Result<Option<u32>> {
|
||||||
|
Ok(match self.write_half()?.try_write(buf) {
|
||||||
|
Ok(n) => Some(n as u32),
|
||||||
|
Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peeks up to `len` bytes at incoming data without consuming it.
|
||||||
|
///
|
||||||
|
/// Successive calls will return the same data until it is consumed by the [`read*`](Self::read)
|
||||||
|
/// family of functions.
|
||||||
|
pub async extern "Lua-C" fn peek(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||||
|
let mut buf = vec![0; len as usize];
|
||||||
|
Ok(match self.read_half()?.peek(&mut buf).await {
|
||||||
|
Ok(0) => None,
|
||||||
|
Ok(n) => Some({
|
||||||
|
buf.truncate(n);
|
||||||
|
buf
|
||||||
|
}),
|
||||||
|
Err(err) if Self::is_disc(err.kind()) => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alias for [`read_partial`](Self::read_partial).
|
||||||
|
#[call]
|
||||||
|
pub async extern "Lua" fn call(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||||
|
self.read_partial(len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TCP socket server, listening for connections.
|
||||||
|
///
|
||||||
|
/// This type represents a TCP server socket that can accept incoming connections. It is created by
|
||||||
|
/// transitioning an [`lb_tcpsocket`] to the listening state via [`listen`](lb_tcpsocket::listen).
|
||||||
|
///
|
||||||
|
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// The listener can be used as an iterator in a generic `for` loop to accept incoming connections:
|
||||||
|
///
|
||||||
|
/// ```lua,no_run
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local listener = net.listen_tcp("127.0.0.1")
|
||||||
|
///
|
||||||
|
/// print("listening on: ", listener:local_addr())
|
||||||
|
///
|
||||||
|
/// for stream in listener do
|
||||||
|
/// print("accepted connection from: ", stream:peer_addr())
|
||||||
|
/// print("local address: ", stream:local_addr())
|
||||||
|
///
|
||||||
|
/// spawn(function()
|
||||||
|
/// stream:write("hello from server\n")
|
||||||
|
/// stream:close()
|
||||||
|
/// end)
|
||||||
|
/// end
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_tcplistener {
|
||||||
|
#[opaque]
|
||||||
|
listener: TcpListener,
|
||||||
|
__on_accept_ref: c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_tcplistener {
|
||||||
|
pub(super) fn new(listener: TcpListener) -> Self {
|
||||||
|
Self {
|
||||||
|
listener,
|
||||||
|
__on_accept_ref: LUA_NOREF,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The local socket address that this listener is bound to.
|
||||||
|
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.listener.local_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a callback to be invoked with each new incoming connection before it is converted
|
||||||
|
/// to an [`lb_tcpstream`].
|
||||||
|
///
|
||||||
|
/// The callback receives a temporary [`lb_tcplistener_stream`] object, which can be used to log
|
||||||
|
/// incoming connections or configure socket options (such as
|
||||||
|
/// [`set_nodelay`](lb_tcplistener_stream), [`set_linger`](lb_tcplistener_stream), etc.) before
|
||||||
|
/// it is converted to an [`lb_tcpstream`]. The callback is called synchronously during
|
||||||
|
/// [`accept`](Self::accept) and should complete as quickly as possible. The provided
|
||||||
|
/// configurable object is only valid within the callback and is converted to an
|
||||||
|
/// [`lb_tcpstream`] as soon as it returns.
|
||||||
|
///
|
||||||
|
/// If a callback already exists, it is replaced with the new one.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local net = require("lb:net")
|
||||||
|
/// local listener = net.listen_tcp("127.0.0.1")
|
||||||
|
///
|
||||||
|
/// listener:on_accept(function(stream)
|
||||||
|
/// print("accepted connection from: ", stream:peer_addr())
|
||||||
|
/// print("local address: ", stream:local_addr())
|
||||||
|
///
|
||||||
|
/// stream:set_nodelay(true)
|
||||||
|
/// end)
|
||||||
|
/// ```
|
||||||
|
pub extern "Lua" fn on_accept(&self, cb: fun<(&lb_tcplistener_stream,), ()>) {
|
||||||
|
assert(
|
||||||
|
rawequal(cb, ()) || r#type(cb) == "function",
|
||||||
|
concat!("function expected in argument 'cb', got ", r#type(cb)),
|
||||||
|
);
|
||||||
|
__unref(self.__on_accept_ref);
|
||||||
|
self.__on_accept_ref = __ref(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accepts a new incoming TCP connection.
|
||||||
|
///
|
||||||
|
/// If an [`on_accept`](Self::on_accept) callback is registered, it is invoked with a temporary
|
||||||
|
/// [`lb_tcplistener_stream`] object representing the new connection. This allows configuration
|
||||||
|
/// of socket options for this specific connection, before the stream is converted to an
|
||||||
|
/// [`lb_tcpstream`] and returned for the connection to be read from or written to.
|
||||||
|
pub async extern "Lua" fn accept(&self) -> Result<lb_tcpstream> {
|
||||||
|
let stream = self.__accept();
|
||||||
|
let on_accept = __registry[self.__on_accept_ref];
|
||||||
|
if !rawequal(on_accept, ()) {
|
||||||
|
on_accept(stream);
|
||||||
|
}
|
||||||
|
stream.__convert()
|
||||||
|
}
|
||||||
|
|
||||||
|
async extern "Lua-C" fn __accept(&self) -> Result<lb_tcplistener_stream> {
|
||||||
|
let (stream, _) = self.listener.accept().await?;
|
||||||
|
Ok(lb_tcplistener_stream::new(stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alias for [`accept`](Self::accept).
|
||||||
|
#[call]
|
||||||
|
pub async extern "Lua" fn call(&self) -> Result<lb_tcpstream> {
|
||||||
|
self.accept()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gc]
|
||||||
|
extern "Lua" fn gc(&self) {
|
||||||
|
__unref(self.__on_accept_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TCP connection that has just been accepted by [`lb_tcplistener`].
|
||||||
|
///
|
||||||
|
/// This type is passed to the [`on_accept`](lb_tcplistener::on_accept) callback on
|
||||||
|
/// [`lb_tcplistener`], allowing socket options to be set before the stream is converted to an
|
||||||
|
/// [`lb_tcpstream`]. After conversion, this object can no longer be used.
|
||||||
|
///
|
||||||
|
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_tcplistener_stream(#[opaque] RefCell<Option<TcpStream>>);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_tcplistener_stream {
|
||||||
|
fn new(stream: TcpStream) -> Self {
|
||||||
|
Self(RefCell::new(Some(stream)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream<'s>(&'s self) -> Result<Ref<'s, TcpStream>> {
|
||||||
|
let socket = self.0.borrow();
|
||||||
|
match *socket {
|
||||||
|
Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())),
|
||||||
|
None => Err(Error::SocketConsumed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The local socket address that the listener is bound to.
|
||||||
|
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.stream()?.local_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The remote socket address that this stream is connected to.
|
||||||
|
pub extern "Lua-C" fn peer_addr(&self) -> Result<lb_socketaddr> {
|
||||||
|
Ok(self.stream()?.peer_addr()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `TCP_NODELAY` option on this stream.
|
||||||
|
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
|
||||||
|
Ok(self.stream()?.nodelay()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the `TCP_NODELAY` option on this stream.
|
||||||
|
///
|
||||||
|
/// This enables or disables Nagle's algorithm, which delays sending small packets.
|
||||||
|
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
|
||||||
|
Ok(self.stream()?.set_nodelay(enabled)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `SO_LINGER` option on this stream, in seconds.
|
||||||
|
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
|
||||||
|
Ok(self
|
||||||
|
.stream()?
|
||||||
|
.linger()?
|
||||||
|
.map(|n| n.as_secs_f64())
|
||||||
|
.unwrap_or(0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the `SO_LINGER` option on this stream.
|
||||||
|
///
|
||||||
|
/// This controls how long the stream will remain open after close if unsent data is present.
|
||||||
|
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
|
||||||
|
let secs = secs.max(0.);
|
||||||
|
Ok(self
|
||||||
|
.stream()?
|
||||||
|
.set_linger((secs != 0.).then_some(std::time::Duration::from_secs_f64(secs)))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the `IP_TTL` option for this stream.
|
||||||
|
pub extern "Lua-C" fn ttl(&self) -> Result<u32> {
|
||||||
|
Ok(self.stream()?.ttl()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value for the `IP_TTL` option on this stream.
|
||||||
|
pub extern "Lua-C" fn set_ttl(&self, ttl: u32) -> Result<()> {
|
||||||
|
Ok(self.stream()?.set_ttl(ttl)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "Lua-C" fn __convert(&self) -> Result<lb_tcpstream> {
|
||||||
|
Ok(lb_tcpstream::new(
|
||||||
|
self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#![doc(hidden)]
|
#![doc(hidden)]
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use luaffi::{Module, Registry};
|
use luaffi::{Module, Registry};
|
||||||
use luajit::{Chunk, State};
|
use luaify::luaify_chunk;
|
||||||
|
use luajit::{Chunk, Index, NewTable, State};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
||||||
@@ -13,6 +14,8 @@ pub type ErrorFn = dyn Fn(&luajit::Error);
|
|||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
report_err: Rc<ErrorFn>,
|
report_err: Rc<ErrorFn>,
|
||||||
|
jit_opts: Vec<String>,
|
||||||
|
prohibit_globals: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@@ -23,6 +26,15 @@ impl Builder {
|
|||||||
Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"),
|
Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"),
|
||||||
None => eprintln!("unhandled lua error: {err}"),
|
None => eprintln!("unhandled lua error: {err}"),
|
||||||
}),
|
}),
|
||||||
|
jit_opts: vec![
|
||||||
|
// more aggressive jit options based on OpenResty's defaults
|
||||||
|
// https://github.com/openresty/luajit2#updated-jit-default-parameters
|
||||||
|
"maxtrace=8000".into(),
|
||||||
|
"maxrecord=16000".into(),
|
||||||
|
"minstitch=3".into(),
|
||||||
|
"maxmcode=40960".into(),
|
||||||
|
],
|
||||||
|
prohibit_globals: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,19 +47,44 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prohibit_globals(&mut self, enabled: bool) -> &mut Self {
|
||||||
|
self.prohibit_globals = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jit_opt(&mut self, opt: impl AsRef<str>) -> &mut Self {
|
||||||
|
self.jit_opts.push(opt.as_ref().into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jit_opts(&mut self, opts: impl IntoIterator<Item: AsRef<str>>) -> &mut Self {
|
||||||
|
for opt in opts {
|
||||||
|
self.jit_opt(opt);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn module<T: Module>(&mut self) -> &mut Self {
|
pub fn module<T: Module>(&mut self) -> &mut Self {
|
||||||
self.registry.preload::<T>();
|
self.registry.preload::<T>();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> luajit::Result<Runtime> {
|
pub fn build(&self) -> luajit::Result<Runtime> {
|
||||||
|
let mut state = State::new()?;
|
||||||
|
let chunk = Chunk::new(self.registry.build()).with_path("[luby]");
|
||||||
|
state.eval(&chunk, 0, Some(0))?;
|
||||||
|
|
||||||
|
if self.prohibit_globals {
|
||||||
|
state_prohibit_globals(&mut state)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in self.jit_opts.iter() {
|
||||||
|
state_set_jitopt(&mut state, opt)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Runtime {
|
Ok(Runtime {
|
||||||
cx: Context {
|
cx: Context {
|
||||||
state: {
|
state,
|
||||||
let mut s = State::new()?;
|
|
||||||
s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
|
|
||||||
s
|
|
||||||
},
|
|
||||||
report_err: self.report_err.clone(),
|
report_err: self.report_err.clone(),
|
||||||
},
|
},
|
||||||
tasks: LocalSet::new(),
|
tasks: LocalSet::new(),
|
||||||
@@ -55,6 +92,60 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn state_prohibit_globals(state: &mut State) -> luajit::Result<()> {
|
||||||
|
let mut s = state.guard();
|
||||||
|
let chunk = Chunk::new(luaify_chunk!({
|
||||||
|
return |self, key, value| {
|
||||||
|
error(("undeclared local variable '%s'").format(key), 2);
|
||||||
|
};
|
||||||
|
}))
|
||||||
|
.with_path("[luby]");
|
||||||
|
s.eval(&chunk, 0, Some(1)).unwrap();
|
||||||
|
s.push(NewTable::new());
|
||||||
|
(s.push("__index"), s.push_idx(-3), s.set(-3));
|
||||||
|
(s.push("__newindex"), s.push_idx(-3), s.set(-3));
|
||||||
|
s.set_metatable(Index::globals());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_set_jitopt(state: &mut State, opt: &str) -> luajit::Result<()> {
|
||||||
|
let mut s = state.guard();
|
||||||
|
if let Some((cmd, opt)) = parse_jitlib_cmd(opt)
|
||||||
|
&& let Ok(_) = s.require(format!("jit.{cmd}"), Some(1))
|
||||||
|
{
|
||||||
|
// require("jit.{cmd}").start(opt)
|
||||||
|
(s.push("start"), s.get(-2));
|
||||||
|
s.push(opt);
|
||||||
|
s.call(1, Some(0))?;
|
||||||
|
} else {
|
||||||
|
s.require("jit", Some(1)).unwrap();
|
||||||
|
match opt {
|
||||||
|
cmd @ ("on" | "off" | "flush") => {
|
||||||
|
// require("jit").<on|off|flush>()
|
||||||
|
(s.push(cmd), s.get(-2));
|
||||||
|
s.call(0, Some(0))?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// require("jit").opt.start(opt)
|
||||||
|
(s.push("opt"), s.get(-2));
|
||||||
|
(s.push("start"), s.get(-2));
|
||||||
|
s.push(opt);
|
||||||
|
s.call(1, Some(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_jitlib_cmd(cmd: &str) -> Option<(&str, &str)> {
|
||||||
|
match cmd {
|
||||||
|
"p" => Some(("p", "Flspv10")), // default -jp flags
|
||||||
|
"v" => Some(("v", "-")), // default -jv flags
|
||||||
|
"dump" => Some(("dump", "tirs")), // default -jdump flags
|
||||||
|
_ => cmd.split_once('='),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deref, DerefMut)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
#[deref]
|
#[deref]
|
||||||
@@ -97,7 +188,7 @@ pub struct Context {
|
|||||||
impl Context {
|
impl Context {
|
||||||
pub fn new_thread(&self) -> Self {
|
pub fn new_thread(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: self.state.new_thread(),
|
state: State::new_thread(&self.state),
|
||||||
report_err: self.report_err.clone(),
|
report_err: self.report_err.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! # Task library
|
//! Task library.
|
||||||
//!
|
//!
|
||||||
//! The `lb:task` library primitives for asynchronous communication between tasks via message
|
//! The `lb:task` library provides utilities for the scheduling of and communication between
|
||||||
//! passing channels.
|
//! asynchronous tasks.
|
||||||
//!
|
//!
|
||||||
//! ## Exports
|
//! ## Exports
|
||||||
//!
|
//!
|
||||||
@@ -12,17 +12,16 @@ use luaffi::{
|
|||||||
marker::{function, many},
|
marker::{function, many},
|
||||||
metatype,
|
metatype,
|
||||||
};
|
};
|
||||||
use luajit::{LUA_MULTRET, Type};
|
use std::{cell::RefCell, ffi::c_int, time::Duration};
|
||||||
use std::{ffi::c_int, time::Duration};
|
|
||||||
use tokio::{task::JoinHandle, time::sleep};
|
use tokio::{task::JoinHandle, time::sleep};
|
||||||
|
|
||||||
/// Items exported by the `lb:task` library.
|
/// Items exported by the `lb:task` library.
|
||||||
///
|
///
|
||||||
/// This library can be acquired by calling
|
/// This library can be acquired by calling
|
||||||
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||||
///
|
///
|
||||||
/// ```lua
|
/// ```lua
|
||||||
/// local task = require("lb:task");
|
/// local task = require("lb:task")
|
||||||
/// ```
|
/// ```
|
||||||
#[cdef(module = "lb:task")]
|
#[cdef(module = "lb:task")]
|
||||||
pub struct lb_tasklib;
|
pub struct lb_tasklib;
|
||||||
@@ -36,43 +35,47 @@ impl lb_tasklib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async extern "Lua-C" fn sleep(ms: f64) {
|
pub async extern "Lua-C" fn sleep(ms: f64) {
|
||||||
sleep(Duration::from_secs_f64(ms / 1000.)).await;
|
sleep(Duration::from_secs_f64(ms.max(0.) / 1000.)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Lua" fn spawn(f: function, ...) -> lb_task {
|
pub extern "Lua" fn spawn(f: function, ...) -> lb_task {
|
||||||
// pack the function and its arguments into a table and pass its ref to rust.
|
// pack the function and its arguments into a table and pass its ref to rust.
|
||||||
//
|
//
|
||||||
// this table is used from rust-side to call the function with its args, and it's also
|
// this "state" table is used from rust-side to call the function with its args, and it's
|
||||||
// reused to store its return values that the task handle can return when awaited. the ref
|
// also reused to store its return values that the task handle can return when awaited. the
|
||||||
// is owned by the task handle and unref'ed when it's gc'ed.
|
// ref is owned by the task handle and unref'ed when it's gc'ed.
|
||||||
Self::__spawn(__ref(__tpack(f, variadic!())))
|
assert(
|
||||||
|
r#type(f) == "function",
|
||||||
|
concat!("function expected in argument 'f', got ", r#type(f)),
|
||||||
|
);
|
||||||
|
// we need two refs: one for the spawn call, and the other for the task handle. this is to
|
||||||
|
// ensure the task handle isn't gc'ed and the state table unref'ed before the spawn callback
|
||||||
|
// runs and puts the state table on the stack.
|
||||||
|
let state = __tpack(f, variadic!());
|
||||||
|
Self::__spawn(__ref(state), __ref(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Lua-C" fn __spawn(key: c_int) -> lb_task {
|
extern "Lua-C" fn __spawn(spawn_ref: c_int, handle_ref: c_int) -> lb_task {
|
||||||
let handle = spawn(async move |cx| {
|
let handle = spawn(async move |cx| {
|
||||||
// SAFETY: key is always unique, created by __ref above.
|
// SAFETY: handle_ref is always unique, created in Self::spawn above.
|
||||||
let arg = unsafe { cx.new_ref_unchecked(key) };
|
let state = unsafe { luajit::Ref::from_raw(cx, spawn_ref) };
|
||||||
let mut s = cx.guard();
|
let mut s = cx.guard();
|
||||||
s.resize(0);
|
s.resize(0);
|
||||||
s.push(&arg);
|
s.push(state); // this drops the state table ref, but the table is still on the stack
|
||||||
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the table
|
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the state table
|
||||||
debug_assert!(s.slot(2).type_of() == Type::Function);
|
match s.call_async(narg, None).await {
|
||||||
match s.call_async(narg, LUA_MULTRET).await {
|
|
||||||
Ok(nret) => {
|
Ok(nret) => {
|
||||||
s.pack(1, nret); // pack the return values back into the table
|
s.pack(1, nret); // pack the return values back into the state table
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
drop(s);
|
drop(s);
|
||||||
cx.report_error(&err);
|
cx.report_error(&err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = arg.into_raw(); // the original ref is owned by the task handle and unref'ed there
|
|
||||||
});
|
});
|
||||||
|
|
||||||
lb_task {
|
// spawn_ref is owned by the task handle and unref'ed there when the handle gets gc'ed
|
||||||
handle: Some(handle),
|
lb_task::new(handle, handle_ref)
|
||||||
__ref: key,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,23 +83,30 @@ impl lb_tasklib {
|
|||||||
#[cdef]
|
#[cdef]
|
||||||
pub struct lb_task {
|
pub struct lb_task {
|
||||||
#[opaque]
|
#[opaque]
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: RefCell<Option<JoinHandle<()>>>,
|
||||||
__ref: c_int,
|
__ref: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[metatype]
|
#[metatype]
|
||||||
impl lb_task {
|
impl lb_task {
|
||||||
|
fn new(handle: JoinHandle<()>, ref_key: c_int) -> Self {
|
||||||
|
lb_task {
|
||||||
|
handle: RefCell::new(Some(handle)),
|
||||||
|
__ref: ref_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async extern "Lua" fn r#await(&self) -> many {
|
pub async extern "Lua" fn r#await(&self) -> many {
|
||||||
self.__await();
|
self.__await();
|
||||||
let ret = __registry[self.__ref];
|
let ret = __registry[self.__ref];
|
||||||
__tunpack(ret, 1, ret.n)
|
__tunpack(ret, 1, ret.n)
|
||||||
}
|
}
|
||||||
|
|
||||||
async extern "Lua-C" fn __await(&mut self) {
|
async extern "Lua-C" fn __await(&self) {
|
||||||
if let Some(handle) = self.handle.take() {
|
if let Some(handle) = self.handle.borrow_mut().take() {
|
||||||
handle
|
handle
|
||||||
.await
|
.await // task handler should never panic
|
||||||
.unwrap_or_else(|err| panic!("task handler panicked: {err}"));
|
.unwrap_or_else(|err| std::panic::resume_unwind(err.into_panic()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
crates/lb/src/time.rs
Normal file
49
crates/lb/src/time.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//! Time library.
|
||||||
|
//!
|
||||||
|
//! The `lb:time` library provides utilities for working with the date and time and its related
|
||||||
|
//! constructs.
|
||||||
|
//!
|
||||||
|
//! ## Exports
|
||||||
|
//!
|
||||||
|
//! See [`lb_timelib`] for items exported by this library.
|
||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
|
||||||
|
/// Items exported by the `lb:time` library.
|
||||||
|
///
|
||||||
|
/// This library can be acquired by calling
|
||||||
|
/// [`require("lb:time")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local time = require("lb:time")
|
||||||
|
/// ```
|
||||||
|
#[cdef(module = "lb:time")]
|
||||||
|
pub struct lb_timelib;
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_timelib {
|
||||||
|
#[new]
|
||||||
|
extern "Lua-C" fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an instant object that represents the current time at the time of calling.
|
||||||
|
pub extern "Lua-C" fn instant() -> lb_instant {
|
||||||
|
lb_instant::new(std::time::Instant::now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the measurement of a monotonically nondecreasing clock.
|
||||||
|
#[cdef]
|
||||||
|
pub struct lb_instant(#[opaque] std::time::Instant);
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_instant {
|
||||||
|
fn new(instant: std::time::Instant) -> Self {
|
||||||
|
Self(instant)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of seconds elapsed since this instant was measured.
|
||||||
|
pub extern "Lua-C" fn elapsed_secs(&self) -> f64 {
|
||||||
|
self.0.elapsed().as_secs_f64()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,184 @@
|
|||||||
local ok, net = pcall(require, "lb:net")
|
local ok, net = pcall(require, "lb:net")
|
||||||
if not ok then return end
|
if not ok then return end
|
||||||
|
|
||||||
|
describe("ipaddr", function()
|
||||||
|
test("invalid ipaddr throws", function()
|
||||||
|
assert(not pcall(net.ipaddr, "invalid ip"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("comparison", function()
|
||||||
|
local a = net.ipaddr("10.0.0.1")
|
||||||
|
local b = net.ipaddr("10.0.0.1")
|
||||||
|
local c = net.ipaddr("10.0.0.2")
|
||||||
|
assert(a ~= nil and a ~= {} and a ~= "10.0.0.1" and a ~= 167772161)
|
||||||
|
assert(a == a and a == b and a ~= c and b ~= c and c == c and c ~= a)
|
||||||
|
assert(a <= b and a < c and a <= c and b < c and b <= c and a <= a and c <= c)
|
||||||
|
assert(not (a < b or a > b or a > c or b > c or a >= c or b >= c))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("tostring", function()
|
||||||
|
local ip = net.ipaddr("10.0.0.1")
|
||||||
|
assert(tostring(ip) == "10.0.0.1")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe("tcp", function()
|
describe("tcp", function()
|
||||||
describe("socket", function()
|
describe("socket", function()
|
||||||
test("bind", function()
|
test("bind", function()
|
||||||
local socket = net.bind_tcp("127.0.0.1")
|
local socket = net.bind_tcp("127.0.0.1")
|
||||||
|
|
||||||
-- binds to the correct port
|
-- binds to the correct port
|
||||||
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
||||||
assert(socket:local_addr():port() ~= 0)
|
assert(socket:local_addr():port() ~= 0)
|
||||||
|
|
||||||
-- should not be able to rebind socket
|
-- should not be able to rebind socket
|
||||||
assert(not pcall(socket.bind, socket, net.socketaddr("127.0.0.1")))
|
assert(not pcall(socket.bind, socket, net.socketaddr("127.0.0.1")))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("options", function()
|
||||||
|
local socket = net.tcp()
|
||||||
|
-- keepalive
|
||||||
|
socket:set_keepalive(true)
|
||||||
|
assert(socket:keepalive() == true)
|
||||||
|
socket:set_keepalive(false)
|
||||||
|
assert(socket:keepalive() == false)
|
||||||
|
-- reuseaddr
|
||||||
|
socket:set_reuseaddr(true)
|
||||||
|
assert(socket:reuseaddr() == true)
|
||||||
|
socket:set_reuseaddr(false)
|
||||||
|
assert(socket:reuseaddr() == false)
|
||||||
|
-- reuseport not always supported on all platforms
|
||||||
|
-- sendbuf
|
||||||
|
socket:set_sendbuf(4096)
|
||||||
|
assert(socket:sendbuf() >= 4096)
|
||||||
|
assert(not pcall(socket.set_sendbuf, socket, 0))
|
||||||
|
assert(not pcall(socket.set_sendbuf, socket, -1))
|
||||||
|
-- recvbuf
|
||||||
|
socket:set_recvbuf(4096)
|
||||||
|
assert(socket:recvbuf() >= 4096)
|
||||||
|
assert(not pcall(socket.set_recvbuf, socket, 0))
|
||||||
|
assert(not pcall(socket.set_recvbuf, socket, -1))
|
||||||
|
-- linger
|
||||||
|
socket:set_linger(0)
|
||||||
|
assert(socket:linger() == 0)
|
||||||
|
socket:set_linger(2)
|
||||||
|
assert(math.abs(socket:linger() - 2) < 0.1)
|
||||||
|
socket:set_linger(-1)
|
||||||
|
assert(socket:linger() == 0)
|
||||||
|
-- nodelay
|
||||||
|
socket:set_nodelay(true)
|
||||||
|
assert(socket:nodelay() == true)
|
||||||
|
socket:set_nodelay(false)
|
||||||
|
assert(socket:nodelay() == false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("can't use socket after conversion", function()
|
||||||
|
local socket = net.tcp()
|
||||||
|
socket:bind(net.socketaddr("127.0.0.1"))
|
||||||
|
socket:listen(10) -- convert to listener
|
||||||
|
assert(not pcall(socket.listen, socket, 10)) -- socket consumed
|
||||||
|
assert(not pcall(socket.local_addr, socket))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("stream", function()
|
||||||
|
test("no concurrent two reads/writes", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local reader = spawn(function()
|
||||||
|
assert(client:read(1) == nil) -- this should block first, then return nil from disconnection
|
||||||
|
end)
|
||||||
|
spawn(function()
|
||||||
|
assert(not pcall(client.read, client, 1)) -- this should fail, since the first task is still reading
|
||||||
|
end):await()
|
||||||
|
server:close()
|
||||||
|
reader:await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("allow concurrent read/write", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local reader = spawn(function()
|
||||||
|
assert(client:read(1) == nil) -- this should block first, then return nil from disconnection
|
||||||
|
end)
|
||||||
|
spawn(function()
|
||||||
|
client:write("hello") -- should be able to write while the first task is reading
|
||||||
|
end):await()
|
||||||
|
server:close()
|
||||||
|
reader:await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("stop reading from disconnected stream", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local reader = spawn(function()
|
||||||
|
while client:read(4) ~= nil do
|
||||||
|
end
|
||||||
|
assert(client:try_read(4) == nil)
|
||||||
|
assert(client:read_partial(4) == nil)
|
||||||
|
assert(client:read(4) == nil)
|
||||||
|
end)
|
||||||
|
for _ = 1, 10 do
|
||||||
|
assert(server:write("ping") == true)
|
||||||
|
end
|
||||||
|
sleep(100)
|
||||||
|
server:close()
|
||||||
|
reader:await()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("stop writing to disconnected stream", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local client = net.connect_tcp(listener:local_addr())
|
||||||
|
local server = listener()
|
||||||
|
local writer = spawn(function()
|
||||||
|
while client:write("pong") do
|
||||||
|
end
|
||||||
|
assert(client:try_write("pong") == nil)
|
||||||
|
assert(client:write_partial("pong") == nil)
|
||||||
|
assert(client:write("pong") == false)
|
||||||
|
end)
|
||||||
|
for _ = 1, 10 do
|
||||||
|
assert(server:read(4) == "pong")
|
||||||
|
end
|
||||||
|
sleep(100)
|
||||||
|
server:close()
|
||||||
|
writer:await()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("listener", function()
|
||||||
|
test("accept", function()
|
||||||
|
local listener = net.listen_tcp(net.localhost())
|
||||||
|
local addr = listener:local_addr()
|
||||||
|
local accepted = false
|
||||||
|
local client = net.tcp()
|
||||||
|
local accepted_stream
|
||||||
|
listener:on_accept(function(stream)
|
||||||
|
accepted = true
|
||||||
|
accepted_stream = stream
|
||||||
|
-- configure stream
|
||||||
|
stream:set_nodelay(true)
|
||||||
|
assert(stream:nodelay() == true)
|
||||||
|
end)
|
||||||
|
-- connect client
|
||||||
|
local client_stream = client:connect(addr)
|
||||||
|
local server_stream = listener()
|
||||||
|
assert(accepted)
|
||||||
|
assert(accepted_stream ~= nil)
|
||||||
|
-- check addresses
|
||||||
|
assert(server_stream:local_addr() ~= nil)
|
||||||
|
assert(server_stream:peer_addr() ~= nil)
|
||||||
|
assert(client_stream:local_addr() ~= nil)
|
||||||
|
assert(client_stream:peer_addr() ~= nil)
|
||||||
|
-- test data transfer
|
||||||
|
server_stream:write("hello")
|
||||||
|
local buf = client_stream:read(5)
|
||||||
|
assert(buf ~= nil and #buf == 5)
|
||||||
|
assert(buf == "hello")
|
||||||
|
-- close
|
||||||
|
server_stream:close()
|
||||||
|
client_stream:close()
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ describe("spawn", function()
|
|||||||
assert(res.n == 4 and res[1] == 1 and res[2] == 2 and res[3] == nil and res[4] == 3)
|
assert(res.n == 4 and res[1] == 1 and res[2] == 2 and res[3] == nil and res[4] == 3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("handles invalid args", function()
|
||||||
|
assert(not pcall(spawn))
|
||||||
|
assert(not pcall(spawn, 123))
|
||||||
|
assert(not pcall(spawn, 1, 2, 3))
|
||||||
|
assert(not pcall(spawn, {}, 2, 3))
|
||||||
|
end)
|
||||||
|
|
||||||
test("callback args and results", function()
|
test("callback args and results", function()
|
||||||
local res = table.pack(spawn(function(...)
|
local res = table.pack(spawn(function(...)
|
||||||
assert(select("#", ...) == 5)
|
assert(select("#", ...) == 5)
|
||||||
@@ -46,6 +53,25 @@ describe("spawn", function()
|
|||||||
assert(res.n == 3 and res[1] == 1 and res[2] == 3 and res[3] == nil)
|
assert(res.n == 3 and res[1] == 1 and res[2] == 3 and res[3] == nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("large number of args", function()
|
||||||
|
local args = {}
|
||||||
|
for i = 1, 1000 do
|
||||||
|
args[i] = i
|
||||||
|
end
|
||||||
|
local res = table.pack(spawn(function(...)
|
||||||
|
return ...
|
||||||
|
end, table.unpack(args)):await())
|
||||||
|
assert(res.n == 1000 and res[1] == 1 and res[1000] == 1000)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("callback closes over upvalues", function()
|
||||||
|
local x = 42
|
||||||
|
local function f()
|
||||||
|
return x
|
||||||
|
end
|
||||||
|
assert(spawn(f):await() == 42)
|
||||||
|
end)
|
||||||
|
|
||||||
test("order is consistent", function()
|
test("order is consistent", function()
|
||||||
-- all tasks spawned in one batch should be resumed in the spawn order
|
-- all tasks spawned in one batch should be resumed in the spawn order
|
||||||
local tasks, nums = {}, {}
|
local tasks, nums = {}, {}
|
||||||
@@ -65,9 +91,31 @@ describe("spawn", function()
|
|||||||
assert(nums[i] == i)
|
assert(nums[i] == i)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("nested spawns", function()
|
||||||
|
local result = {}
|
||||||
|
local function inner()
|
||||||
|
table.insert(result, "inner")
|
||||||
|
return "done"
|
||||||
|
end
|
||||||
|
local function outer()
|
||||||
|
table.insert(result, "outer")
|
||||||
|
local v = spawn(inner):await()
|
||||||
|
table.insert(result, v)
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
local v = spawn(outer):await()
|
||||||
|
assert(v == "done")
|
||||||
|
assert(result[1] == "outer" and result[2] == "inner" and result[3] == "done")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("sleep", function()
|
describe("sleep", function()
|
||||||
|
test("invalid arg", function()
|
||||||
|
assert(not pcall(task.sleep, "invalid"))
|
||||||
|
task.sleep(-1) -- negative sleep should just become 0ms
|
||||||
|
end)
|
||||||
|
|
||||||
test("sleep is asynchronous", function()
|
test("sleep is asynchronous", function()
|
||||||
local value
|
local value
|
||||||
spawn(function()
|
spawn(function()
|
||||||
@@ -77,6 +125,44 @@ describe("sleep", function()
|
|||||||
task.sleep(100) -- implicit await: if it's synchronous, value wouldn't change
|
task.sleep(100) -- implicit await: if it's synchronous, value wouldn't change
|
||||||
assert(value == "value")
|
assert(value == "value")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("sleep in nested spawns", function()
|
||||||
|
local value1, value2, value3 = nil, nil, nil
|
||||||
|
local results = {}
|
||||||
|
local function inner()
|
||||||
|
task.sleep(30)
|
||||||
|
value1 = "set by inner"
|
||||||
|
table.insert(results, "inner")
|
||||||
|
return value1
|
||||||
|
end
|
||||||
|
local function middle()
|
||||||
|
task.sleep(20)
|
||||||
|
value2 = "set by middle"
|
||||||
|
local v = spawn(inner):await()
|
||||||
|
table.insert(results, v)
|
||||||
|
table.insert(results, "middle")
|
||||||
|
return v, value2
|
||||||
|
end
|
||||||
|
local function outer()
|
||||||
|
task.sleep(10)
|
||||||
|
value3 = "set by outer"
|
||||||
|
local v1, v2 = spawn(middle):await()
|
||||||
|
table.insert(results, v1)
|
||||||
|
table.insert(results, v2)
|
||||||
|
table.insert(results, "outer")
|
||||||
|
return v1, v2, value3
|
||||||
|
end
|
||||||
|
assert(value1 == nil and value2 == nil and value3 == nil)
|
||||||
|
local r1, r2, r3 = spawn(outer):await()
|
||||||
|
assert(r1 == "set by inner" and r2 == "set by middle" and r3 == "set by outer")
|
||||||
|
assert(value1 == "set by inner" and value2 == "set by middle" and value3 == "set by outer")
|
||||||
|
assert(results[1] == "inner")
|
||||||
|
assert(results[2] == "set by inner")
|
||||||
|
assert(results[3] == "middle")
|
||||||
|
assert(results[4] == "set by inner")
|
||||||
|
assert(results[5] == "set by middle")
|
||||||
|
assert(results[6] == "outer")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("task", function()
|
describe("task", function()
|
||||||
|
|||||||
13
crates/lb_sqlite/Cargo.toml
Normal file
13
crates/lb_sqlite/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "lb_sqlite"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lb = { path = "../lb", features = ["time"] }
|
||||||
|
luaffi = { version = "0.0.1", path = "../luaffi" }
|
||||||
|
rusqlite = { version = "0.36.0", features = ["bundled", "load_extension", "backup", "blob", "limits", "window", "series", "session", "collation", "serialize"] }
|
||||||
3
crates/lb_sqlite/src/lib.rs
Normal file
3
crates/lb_sqlite/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//! luby SQLite library.
|
||||||
|
#[path = "mod.rs"]
|
||||||
|
pub mod sqlite;
|
||||||
27
crates/lb_sqlite/src/mod.rs
Normal file
27
crates/lb_sqlite/src/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//! SQLite library.
|
||||||
|
//!
|
||||||
|
//! The `lb:sqlite` library provides an interface to SQLite databases.
|
||||||
|
//!
|
||||||
|
//! ## Exports
|
||||||
|
//!
|
||||||
|
//! See [`lb_sqlitelib`] for items exported by this library.
|
||||||
|
use luaffi::{cdef, metatype};
|
||||||
|
|
||||||
|
/// Items exported by the `lb:sqlite` library.
|
||||||
|
///
|
||||||
|
/// This library can be acquired by calling
|
||||||
|
/// [`require("lb:sqlite")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||||
|
///
|
||||||
|
/// ```lua
|
||||||
|
/// local sqlite = require("lb:sqlite")
|
||||||
|
/// ```
|
||||||
|
#[cdef(module = "lb:sqlite")]
|
||||||
|
pub struct lb_sqlitelib;
|
||||||
|
|
||||||
|
#[metatype]
|
||||||
|
impl lb_sqlitelib {
|
||||||
|
#[new]
|
||||||
|
extern "Lua-C" fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -156,7 +156,12 @@ impl lua_pollable {
|
|||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
// TODO: signature check can currently read out-of-bounds if lua code for some reason yields
|
// TODO: signature check can currently read out-of-bounds if lua code for some reason yields
|
||||||
// a cdata of size less than 8 bytes that is not a lua_future. there is no easy way to fix
|
// a cdata of size less than 8 bytes that is not a lua_future. there is no easy way to fix
|
||||||
// afaik this because there is no way to find the size of a cdata payload using the C API.
|
// AFAIK this because there is no way to find the size of a cdata payload using the C API.
|
||||||
|
// unfortunately we have to trust that the user won't do that right now.
|
||||||
|
//
|
||||||
|
// the only "saving grace" is that the user should never be running untrusted code anyway,
|
||||||
|
// because this whole project is based on the ffi library which should never be exposed to
|
||||||
|
// untrusted code in the first place.
|
||||||
self.sig == SIGNATURE
|
self.sig == SIGNATURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,21 @@
|
|||||||
local LUA_REFNIL = -1 -- lib_aux.c
|
local LUA_REFNIL = -1 -- lib_aux.c
|
||||||
local FREELIST_REF = 0
|
local FREELIST_REF = 0
|
||||||
|
|
||||||
local function __ref(value, t)
|
local function __ref(value)
|
||||||
if value == nil then return LUA_REFNIL end
|
if rawequal(value, nil) then return LUA_REFNIL end
|
||||||
if t == nil then t = __registry end
|
local ref = __registry[FREELIST_REF]
|
||||||
local ref = t[FREELIST_REF]
|
|
||||||
if ref ~= nil and ref ~= 0 then
|
if ref ~= nil and ref ~= 0 then
|
||||||
t[FREELIST_REF] = t[ref]
|
__registry[FREELIST_REF] = __registry[ref]
|
||||||
else
|
else
|
||||||
ref = #t + 1
|
ref = rawlen(__registry) + 1
|
||||||
end
|
end
|
||||||
t[ref] = value
|
__registry[ref] = value
|
||||||
return ref
|
return ref
|
||||||
end
|
end
|
||||||
|
|
||||||
local function __unref(ref, t)
|
local function __unref(ref)
|
||||||
if ref < 0 then return nil end
|
if ref > 0 then
|
||||||
if t == nil then t = __registry end
|
__registry[ref] = __registry[FREELIST_REF]
|
||||||
local value = t[ref]
|
__registry[FREELIST_REF] = ref
|
||||||
t[ref] = t[FREELIST_REF]
|
end
|
||||||
t[FREELIST_REF] = ref
|
|
||||||
return value
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
string::{DROP_BUFFER_FN, IS_UTF8_FN, lua_buffer},
|
string::{DROP_BUFFER_FN, IS_UTF8_FN, lua_buffer},
|
||||||
};
|
};
|
||||||
pub use luaffi_impl::*;
|
pub use luaffi_impl::*;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
|
||||||
ffi::{c_double, c_float, c_void},
|
ffi::{c_double, c_float, c_void},
|
||||||
fmt::{self, Display, Formatter, Write},
|
fmt::{self, Display, Formatter, Write},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
@@ -26,8 +26,8 @@ pub mod string;
|
|||||||
// `ffi.keep(obj)`.
|
// `ffi.keep(obj)`.
|
||||||
//
|
//
|
||||||
// https://github.com/LuaJIT/LuaJIT/issues/1167
|
// https://github.com/LuaJIT/LuaJIT/issues/1167
|
||||||
pub(crate) const KEEP_FN: &str = "luaffi_keep";
|
pub(crate) const KEEP_FN: &str = "__lf_keep";
|
||||||
#[unsafe(export_name = "luaffi_keep")]
|
#[unsafe(export_name = "__lf_keep")]
|
||||||
extern "C" fn __keep(_ptr: *const c_void) {}
|
extern "C" fn __keep(_ptr: *const c_void) {}
|
||||||
export![__keep];
|
export![__keep];
|
||||||
|
|
||||||
@@ -128,8 +128,8 @@ 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: FxHashSet<String>,
|
||||||
decls: HashSet<String>,
|
decls: FxHashSet<String>,
|
||||||
cdef: String,
|
cdef: String,
|
||||||
lua: String,
|
lua: String,
|
||||||
}
|
}
|
||||||
@@ -458,6 +458,7 @@ pub struct MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
cargs: String, // C call arguments
|
cargs: String, // C call arguments
|
||||||
prelude: String, // lua function body prelude
|
prelude: String, // lua function body prelude
|
||||||
postlude: String, // lua function body postlude
|
postlude: String, // lua function body postlude
|
||||||
|
is_eqmm: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||||
@@ -469,15 +470,16 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
cargs: String::new(),
|
cargs: String::new(),
|
||||||
prelude: String::new(),
|
prelude: String::new(),
|
||||||
postlude: String::new(),
|
postlude: String::new(),
|
||||||
|
is_eqmm: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
|
pub fn set_eqmm(&mut self, eq: bool) -> &mut Self {
|
||||||
assert!(
|
self.is_eqmm = eq; // are we building an __eq metamethod?
|
||||||
T::From::ty() != TypeType::Void,
|
self
|
||||||
"cannot declare void parameter"
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
|
||||||
let Self {
|
let Self {
|
||||||
metatype: MetatypeBuilder { reg, .. },
|
metatype: MetatypeBuilder { reg, .. },
|
||||||
lparams,
|
lparams,
|
||||||
@@ -488,6 +490,14 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
T::From::ty() != TypeType::Void,
|
||||||
|
"cannot declare void parameter"
|
||||||
|
);
|
||||||
|
|
||||||
|
// should already be prevented by #[metatype] macro
|
||||||
|
debug_assert!(!name.to_string().starts_with("__"));
|
||||||
|
|
||||||
reg.include::<T::From>();
|
reg.include::<T::From>();
|
||||||
|
|
||||||
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
(!lparams.is_empty()).then(|| lparams.push_str(", "));
|
||||||
@@ -507,6 +517,26 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn param_ctchecked<T: FromFfi, U: Type + Metatype<Target = U>>(
|
||||||
|
&mut self,
|
||||||
|
name: impl Display,
|
||||||
|
) -> &mut Self {
|
||||||
|
let ct = U::name();
|
||||||
|
if self.is_eqmm {
|
||||||
|
// special case for building __eq metamethods which are expected to return false if the
|
||||||
|
// arguments aren't of the expected type, because otherwise the `==` and `~=` operators
|
||||||
|
// could throw instead of returning false.
|
||||||
|
write!(
|
||||||
|
self.prelude,
|
||||||
|
r#"if not __istype(__ct.{ct}, {name}) then return false; end; "#
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(self.prelude, r#"assert(__istype(__ct.{ct}, {name})); "#)
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
self.param::<T>(name)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn param_str(
|
pub fn param_str(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Display,
|
name: impl Display,
|
||||||
@@ -517,7 +547,9 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
//
|
//
|
||||||
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
|
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
|
||||||
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
|
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
|
||||||
// temporary cdata to pass the string and its length in one argument.
|
// temporary cdata to pass the string and its length in one argument. this is used when the
|
||||||
|
// #[metatype] macro detects a string-like parameter, currently defined to be &[u8], &str,
|
||||||
|
// &BStr (from the bstr crate), or those types wrapped in Option<T>.
|
||||||
let Self {
|
let Self {
|
||||||
lparams,
|
lparams,
|
||||||
cparams,
|
cparams,
|
||||||
@@ -526,6 +558,9 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
// should already be prevented by #[metatype] macro
|
||||||
|
debug_assert!(!name.to_string().starts_with("__"));
|
||||||
|
|
||||||
let param_ptr = <*const u8>::cdecl(&name);
|
let param_ptr = <*const u8>::cdecl(&name);
|
||||||
let param_len = usize::cdecl(format!("{name}_len"));
|
let param_len = usize::cdecl(format!("{name}_len"));
|
||||||
|
|
||||||
@@ -536,7 +571,11 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
write!(lparams, "{name}").unwrap();
|
write!(lparams, "{name}").unwrap();
|
||||||
write!(cparams, "{param_ptr}, {param_len}").unwrap();
|
write!(cparams, "{param_ptr}, {param_len}").unwrap();
|
||||||
write!(cargs, "{name}, __{name}_len").unwrap();
|
write!(cargs, "{name}, __{name}_len").unwrap();
|
||||||
write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").unwrap();
|
write!(
|
||||||
|
prelude,
|
||||||
|
"local __{name}_len = 0; if not rawequal({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();
|
||||||
|
|
||||||
@@ -577,22 +616,22 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
if T::Into::ty() == TypeType::Void {
|
if T::Into::ty() == TypeType::Void {
|
||||||
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
|
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
|
||||||
} else {
|
} else {
|
||||||
let check = T::postlude("__ret");
|
let check = T::postlude("ret");
|
||||||
write!(lua, "local __ret = __C.{func}({cargs}); ").unwrap();
|
write!(lua, "local ret = __C.{func}({cargs}); ").unwrap();
|
||||||
write!(lua, "{check}{postlude}return __ret; end").unwrap();
|
write!(lua, "{check}{postlude}return ret; end").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
|
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
|
||||||
}
|
}
|
||||||
FfiReturnConvention::ByOutParam => {
|
FfiReturnConvention::ByOutParam => {
|
||||||
let ct = T::Into::name();
|
let ct = T::Into::name();
|
||||||
let check = T::postlude("__out");
|
let check = T::postlude("__ret");
|
||||||
write!(lua, "local __out = __new(__ct.{ct}); __C.{func}(__out").unwrap();
|
write!(lua, "local __ret = __new(__ct.{ct}); __C.{func}(__ret").unwrap();
|
||||||
if !cargs.is_empty() {
|
if !cargs.is_empty() {
|
||||||
write!(lua, ", {cargs}").unwrap();
|
write!(lua, ", {cargs}").unwrap();
|
||||||
}
|
}
|
||||||
write!(lua, "); {check}{postlude}return __out; end").unwrap();
|
write!(lua, "); {check}{postlude}return __ret; end").unwrap();
|
||||||
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("out")).unwrap();
|
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("__out")).unwrap();
|
||||||
if !cparams.is_empty() {
|
if !cparams.is_empty() {
|
||||||
write!(cdef, ", {cparams}").unwrap();
|
write!(cdef, ", {cparams}").unwrap();
|
||||||
}
|
}
|
||||||
@@ -606,7 +645,7 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Annotation {
|
pub trait Annotate {
|
||||||
fn annotation() -> impl Display;
|
fn annotation() -> impl Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,7 +699,7 @@ macro_rules! impl_primitive {
|
|||||||
fn build(_b: &mut TypeBuilder) {}
|
fn build(_b: &mut TypeBuilder) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Annotation for $rty {
|
impl Annotate for $rty {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
$lty
|
$lty
|
||||||
}
|
}
|
||||||
@@ -770,8 +809,7 @@ macro_rules! impl_bigint_intoabi {
|
|||||||
fn postlude(ret: &str) -> impl Display {
|
fn postlude(ret: &str) -> impl Display {
|
||||||
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
|
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
|
||||||
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
|
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
|
||||||
// precision here and that 53 bits of precision for big integers are enough. (the
|
// precision here and that 53 bits of precision for big integers are enough.
|
||||||
// vain of Lua 5.3 integer subtype ;D )
|
|
||||||
display!("{ret} = tonumber({ret}); ")
|
display!("{ret} = tonumber({ret}); ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -827,27 +865,11 @@ impl_ptr!(*mut T, true);
|
|||||||
impl_ptr!(Option<&T>, false);
|
impl_ptr!(Option<&T>, false);
|
||||||
impl_ptr!(Option<&mut T>, true);
|
impl_ptr!(Option<&mut T>, true);
|
||||||
|
|
||||||
macro_rules! impl_ptr_annotation {
|
|
||||||
($ty:ty) => {
|
|
||||||
impl<T> Annotation for $ty
|
|
||||||
where
|
|
||||||
T: Annotation,
|
|
||||||
{
|
|
||||||
fn annotation() -> impl Display {
|
|
||||||
"lightuserdata"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_ptr_annotation!(*const T);
|
|
||||||
impl_ptr_annotation!(*mut T);
|
|
||||||
|
|
||||||
macro_rules! impl_ref_annotation {
|
macro_rules! impl_ref_annotation {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
impl<T> Annotation for $ty
|
impl<T> Annotate for $ty
|
||||||
where
|
where
|
||||||
T: Annotation,
|
T: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("{}", T::annotation())
|
display!("{}", T::annotation())
|
||||||
@@ -919,75 +941,43 @@ impl_ptr_intoabi!(Option<&'static T>);
|
|||||||
#[cfg(feature = "option_ref_abi")]
|
#[cfg(feature = "option_ref_abi")]
|
||||||
impl_ptr_intoabi!(Option<&'static mut T>);
|
impl_ptr_intoabi!(Option<&'static mut T>);
|
||||||
|
|
||||||
//
|
unsafe impl<'s, T> FromFfi for &'s T
|
||||||
// SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust code
|
where
|
||||||
// called via FFI can be running at the same time on the same OS thread (no Lua reentrancy).
|
T: Type,
|
||||||
//
|
{
|
||||||
// i.e. The call stack will always look something like this:
|
type From = Option<&'s T>;
|
||||||
//
|
|
||||||
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI): This is SAFE and the only use case we
|
|
||||||
// support. All references (mutable or not) to Rust user objects will be dropped before
|
|
||||||
// returning to Lua.
|
|
||||||
//
|
|
||||||
// * Runtime (LuaJIT/Rust) -> Lua (via C) -> Rust (via FFI) -> Lua (via callback): This is UNSAFE
|
|
||||||
// because we cannot prevent the Lua callback from calling back into Rust code via FFI which
|
|
||||||
// could violate exclusive borrow semantics. This is prevented by not implementing `FromFfi` for
|
|
||||||
// function pointers (see below).
|
|
||||||
//
|
|
||||||
// The runtime does not keep any references to Rust user objects boxed in cdata (futures are the
|
|
||||||
// only exception; their ownership is transferred to the runtime via yield).
|
|
||||||
//
|
|
||||||
macro_rules! impl_ref_fromabi {
|
|
||||||
($ty:ty) => {
|
|
||||||
unsafe impl<'s, T> FromFfi for $ty
|
|
||||||
where
|
|
||||||
T: Type,
|
|
||||||
{
|
|
||||||
type From = Option<$ty>;
|
|
||||||
|
|
||||||
fn prelude(arg: &str) -> impl Display {
|
fn prelude(arg: &str) -> impl Display {
|
||||||
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
|
display!(r#"assert(not rawequal({arg}, nil), "argument '{arg}' cannot be nil"); "#)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert(from: Self::From) -> Self {
|
fn convert(from: Self::From) -> Self {
|
||||||
// SAFETY: we already checked that the reference is nonnull from the lua side
|
// SAFETY: we already checked that the reference is nonnull from the lua side
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
from.is_some(),
|
from.is_some(),
|
||||||
"<{}>::convert() called on a null reference when it was checked to be nonnull",
|
"<{}>::convert() called on a null reference when it was checked to be nonnull",
|
||||||
stringify!($ty),
|
stringify!($ty),
|
||||||
);
|
);
|
||||||
|
|
||||||
unsafe { from.unwrap_unchecked() }
|
unsafe { from.unwrap_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_ref_fromabi!(&'s T);
|
|
||||||
impl_ref_fromabi!(&'s mut T);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// SAFETY: `IntoFfi` only for 'static references because we cannot guarantee that the pointer will
|
// SAFETY: `IntoFfi` only for 'static references because we cannot guarantee that the pointer will
|
||||||
// not outlive the pointee otherwise.
|
// not outlive the pointee otherwise.
|
||||||
//
|
//
|
||||||
macro_rules! impl_ref_intoabi {
|
unsafe impl<T> IntoFfi for &'static T
|
||||||
($ty:ty) => {
|
where
|
||||||
unsafe impl<T> IntoFfi for $ty
|
T: Type,
|
||||||
where
|
{
|
||||||
T: Type,
|
type Into = Self;
|
||||||
{
|
|
||||||
type Into = Self;
|
|
||||||
|
|
||||||
fn convert(self) -> Self::Into {
|
fn convert(self) -> Self::Into {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_ref_intoabi!(&'static T);
|
|
||||||
impl_ref_intoabi!(&'static mut T);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// SAFETY: No `FromFfi` and `IntoFfi` for arrays because passing or returning them by value is not a
|
// SAFETY: No `FromFfi` and `IntoFfi` for arrays because passing or returning them by value is not a
|
||||||
// thing in C (they are just pointers).
|
// thing in C (they are just pointers).
|
||||||
@@ -1015,9 +1005,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Annotation for [T]
|
impl<T> Annotate for [T]
|
||||||
where
|
where
|
||||||
T: Annotation,
|
T: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("{}[]", T::annotation())
|
display!("{}[]", T::annotation())
|
||||||
@@ -1045,9 +1035,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, const N: usize> Annotation for [T; N]
|
impl<T, const N: usize> Annotate for [T; N]
|
||||||
where
|
where
|
||||||
T: Annotation,
|
T: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("{}[]", T::annotation())
|
display!("{}[]", T::annotation())
|
||||||
@@ -1062,13 +1052,6 @@ macro_rules! impl_externcfn {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
|
($ty:ident, fn($($arg:ident),*) -> $ret:ident) => {
|
||||||
//
|
|
||||||
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
|
|
||||||
// in `&mut T`).
|
|
||||||
//
|
|
||||||
// We also can't implement `IntoFfi` because we can't call `FromFfi` and `IntoFfi` for the
|
|
||||||
// function's respective argument and return values.
|
|
||||||
//
|
|
||||||
unsafe impl<$($arg,)* $ret> Type for $ty<($($arg,)*), $ret>
|
unsafe impl<$($arg,)* $ret> Type for $ty<($($arg,)*), $ret>
|
||||||
where
|
where
|
||||||
$($arg: Type,)*
|
$($arg: Type,)*
|
||||||
@@ -1123,13 +1106,25 @@ 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);
|
||||||
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||||
|
|
||||||
impl<'s> Annotation for &'s str {
|
impl<'s> Annotate for &'s [u8] {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"string"
|
"string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Annotation for String {
|
impl<'s> Annotate for &'s str {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Annotate for Vec<u8> {
|
||||||
|
fn annotation() -> impl Display {
|
||||||
|
"string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Annotate for String {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"string"
|
"string"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
use crate::{
|
use crate::{
|
||||||
__internal::{disp, display},
|
__internal::{disp, display},
|
||||||
Annotation,
|
Annotate,
|
||||||
};
|
};
|
||||||
use std::{fmt::Display, marker::PhantomData};
|
use std::{fmt::Display, marker::PhantomData};
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ enum Marker {}
|
|||||||
|
|
||||||
pub struct any(Marker);
|
pub struct any(Marker);
|
||||||
|
|
||||||
impl Annotation for any {
|
impl Annotate for any {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"any"
|
"any"
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ impl Annotation for any {
|
|||||||
|
|
||||||
pub struct many(Marker);
|
pub struct many(Marker);
|
||||||
|
|
||||||
impl Annotation for many {
|
impl Annotate for many {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"..."
|
"..."
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ impl Annotation for many {
|
|||||||
|
|
||||||
pub struct nil(Marker);
|
pub struct nil(Marker);
|
||||||
|
|
||||||
impl Annotation for nil {
|
impl Annotate for nil {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"nil"
|
"nil"
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ impl Annotation for nil {
|
|||||||
|
|
||||||
pub struct lightuserdata(Marker);
|
pub struct lightuserdata(Marker);
|
||||||
|
|
||||||
impl Annotation for lightuserdata {
|
impl Annotate for lightuserdata {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"lightuserdata"
|
"lightuserdata"
|
||||||
}
|
}
|
||||||
@@ -41,10 +41,10 @@ impl Annotation for lightuserdata {
|
|||||||
|
|
||||||
pub struct table<K, V>(Marker, PhantomData<*mut [(K, V)]>);
|
pub struct table<K, V>(Marker, PhantomData<*mut [(K, V)]>);
|
||||||
|
|
||||||
impl<K, V> Annotation for table<K, V>
|
impl<K, V> Annotate for table<K, V>
|
||||||
where
|
where
|
||||||
K: Annotation,
|
K: Annotate,
|
||||||
V: Annotation,
|
V: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("table<{}, {}>", K::annotation(), V::annotation())
|
display!("table<{}, {}>", K::annotation(), V::annotation())
|
||||||
@@ -53,7 +53,7 @@ where
|
|||||||
|
|
||||||
pub struct function(Marker);
|
pub struct function(Marker);
|
||||||
|
|
||||||
impl Annotation for function {
|
impl Annotate for function {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"function"
|
"function"
|
||||||
}
|
}
|
||||||
@@ -63,9 +63,9 @@ pub struct fun<I, O>(Marker, PhantomData<fn(I) -> O>);
|
|||||||
|
|
||||||
macro_rules! impl_fun {
|
macro_rules! impl_fun {
|
||||||
(fn($($arg:ident),*)) => {
|
(fn($($arg:ident),*)) => {
|
||||||
impl<$($arg,)*> Annotation for fun<($($arg,)*), ()>
|
impl<$($arg,)*> Annotate for fun<($($arg,)*), ()>
|
||||||
where
|
where
|
||||||
$($arg: Annotation,)*
|
$($arg: Annotate,)*
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
disp(|f| {
|
disp(|f| {
|
||||||
@@ -79,10 +79,10 @@ macro_rules! impl_fun {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(fn($($arg:ident),*) -> $ret:ident) => {
|
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||||
impl<$($arg,)* $ret> Annotation for fun<($($arg,)*), $ret>
|
impl<$($arg,)* $ret> Annotate for fun<($($arg,)*), $ret>
|
||||||
where
|
where
|
||||||
$($arg: Annotation,)*
|
$($arg: Annotate,)*
|
||||||
$ret: Annotation,
|
$ret: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
disp(|f| {
|
disp(|f| {
|
||||||
@@ -111,7 +111,7 @@ impl_fun!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
|||||||
|
|
||||||
pub struct userdata(Marker);
|
pub struct userdata(Marker);
|
||||||
|
|
||||||
impl Annotation for userdata {
|
impl Annotate for userdata {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"userdata"
|
"userdata"
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ impl Annotation for userdata {
|
|||||||
|
|
||||||
pub struct thread(Marker);
|
pub struct thread(Marker);
|
||||||
|
|
||||||
impl Annotation for thread {
|
impl Annotate for thread {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"thread"
|
"thread"
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ impl Annotation for thread {
|
|||||||
|
|
||||||
pub struct cdata(Marker);
|
pub struct cdata(Marker);
|
||||||
|
|
||||||
impl Annotation for cdata {
|
impl Annotate for cdata {
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
"cdata"
|
"cdata"
|
||||||
}
|
}
|
||||||
@@ -135,10 +135,10 @@ impl Annotation for cdata {
|
|||||||
|
|
||||||
pub struct Either<T, U>(Marker, PhantomData<(T, U)>);
|
pub struct Either<T, U>(Marker, PhantomData<(T, U)>);
|
||||||
|
|
||||||
impl<T, U> Annotation for Either<T, U>
|
impl<T, U> Annotate for Either<T, U>
|
||||||
where
|
where
|
||||||
T: Annotation,
|
T: Annotate,
|
||||||
U: Annotation,
|
U: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("({} | {})", T::annotation(), U::annotation())
|
display!("({} | {})", T::annotation(), U::annotation())
|
||||||
@@ -149,9 +149,9 @@ pub struct OneOf<X>(Marker, PhantomData<X>);
|
|||||||
|
|
||||||
macro_rules! impl_oneof {
|
macro_rules! impl_oneof {
|
||||||
($($ty:ident),+) => {
|
($($ty:ident),+) => {
|
||||||
impl<$($ty),+> Annotation for OneOf<($($ty,)+)>
|
impl<$($ty),+> Annotate for OneOf<($($ty,)+)>
|
||||||
where
|
where
|
||||||
$($ty: Annotation),+
|
$($ty: Annotate),+
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
disp(|f| {
|
disp(|f| {
|
||||||
@@ -174,18 +174,18 @@ 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);
|
||||||
impl_oneof!(A, B, C, D, E, F, G, H, I);
|
impl_oneof!(A, B, C, D, E, F, G, H, I);
|
||||||
|
|
||||||
impl<T> Annotation for Option<T>
|
impl<T> Annotate for Option<T>
|
||||||
where
|
where
|
||||||
T: Annotation,
|
T: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("{}?", T::annotation())
|
display!("{}?", T::annotation())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> Annotation for Result<T, E>
|
impl<T, E> Annotate for Result<T, E>
|
||||||
where
|
where
|
||||||
T: Annotation,
|
T: Annotate,
|
||||||
{
|
{
|
||||||
fn annotation() -> impl Display {
|
fn annotation() -> impl Display {
|
||||||
display!("{}", T::annotation())
|
display!("{}", T::annotation())
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ use bstr::{BStr, BString};
|
|||||||
use luaffi_impl::{cdef, metatype};
|
use luaffi_impl::{cdef, metatype};
|
||||||
use std::{fmt::Display, mem::ManuallyDrop, 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 = "__lf_is_utf8";
|
||||||
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
pub(crate) const DROP_BUFFER_FN: &str = "__lf_drop_buffer";
|
||||||
|
|
||||||
#[unsafe(export_name = "luaffi_is_utf8")]
|
#[unsafe(export_name = "__lf_is_utf8")]
|
||||||
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
|
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
|
||||||
debug_assert!(!ptr.is_null());
|
debug_assert!(!ptr.is_null());
|
||||||
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
|
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(export_name = "luaffi_drop_buffer")]
|
#[unsafe(export_name = "__lf_drop_buffer")]
|
||||||
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
|
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
|
||||||
debug_assert!(!buf.is_null());
|
debug_assert!(!buf.is_null());
|
||||||
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
|
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #ffi::Annotation for #ty {
|
impl #ffi::Annotate for #ty {
|
||||||
fn annotation() -> impl ::std::fmt::Display { #name }
|
fn annotation() -> impl ::std::fmt::Display { #name }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ 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 build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
let build = generate_cdef_build(&parse_cfields(&mut str.fields)?)?;
|
||||||
|
|
||||||
Ok(quote!(
|
Ok(quote!(
|
||||||
unsafe impl #ffi::Cdef for #ty {
|
unsafe impl #ffi::Cdef for #ty {
|
||||||
@@ -123,7 +123,7 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
|||||||
.variants
|
.variants
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
let build = generate_cdef_build(&parse_cfields(&mut variant.fields)?)?;
|
||||||
Ok(quote!(b.inner_struct(|b| { #build })))
|
Ok(quote!(b.inner_struct(|b| { #build })))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
@@ -148,7 +148,7 @@ struct CFieldAttrs {
|
|||||||
opaque: bool,
|
opaque: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
fn parse_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||||
match fields {
|
match fields {
|
||||||
Fields::Named(fields) => fields.named.iter_mut(),
|
Fields::Named(fields) => fields.named.iter_mut(),
|
||||||
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
|
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_resultlike, is_stringlike, is_unit,
|
||||||
syn_assert, syn_error, ty_name,
|
pat_ident, syn_assert, syn_error, ty_name,
|
||||||
};
|
};
|
||||||
use darling::FromMeta;
|
use darling::FromMeta;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
@@ -78,7 +78,7 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// process extern "Lua-C" ffi functions
|
// process extern "Lua-C" ffi functions
|
||||||
for func in get_ffi_functions(imp)? {
|
for func in parse_ffi_functions(imp)? {
|
||||||
if let Some(mm) = func.attrs.metamethod {
|
if let Some(mm) = func.attrs.metamethod {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
mms.insert(mm),
|
mms.insert(mm),
|
||||||
@@ -87,11 +87,11 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_ffi_function(&mut registry, &func)?;
|
generate_ffi_function(&mut registry, &func)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// process extern "Lua" lua functions
|
// process extern "Lua" lua functions
|
||||||
for func in get_lua_functions(imp)? {
|
for func in parse_lua_functions(imp)? {
|
||||||
if let Some(mm) = func.attrs.metamethod {
|
if let Some(mm) = func.attrs.metamethod {
|
||||||
syn_assert!(
|
syn_assert!(
|
||||||
mms.insert(mm),
|
mms.insert(mm),
|
||||||
@@ -100,10 +100,10 @@ fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Metamethod::Gc) = func.attrs.metamethod {
|
if func.attrs.metamethod == Some(Metamethod::Gc) {
|
||||||
lua_drop = Some(func);
|
lua_drop = Some(func);
|
||||||
} else {
|
} else {
|
||||||
add_lua_function(&mut registry, &func)?;
|
generate_lua_function(&mut registry, &func)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +155,25 @@ enum Metamethod {
|
|||||||
Ipairs,
|
Ipairs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Metamethod {
|
||||||
|
fn narg(&self) -> Option<usize> {
|
||||||
|
Some(match *self {
|
||||||
|
Self::Eq
|
||||||
|
| Self::Lt
|
||||||
|
| Self::Le
|
||||||
|
| Self::Concat
|
||||||
|
| Self::Add
|
||||||
|
| Self::Sub
|
||||||
|
| Self::Mul
|
||||||
|
| Self::Div
|
||||||
|
| Self::Mod
|
||||||
|
| Self::Pow => 2,
|
||||||
|
Self::Gc | Self::Len | Self::Unm | Self::ToString | Self::Pairs | Self::Ipairs => 1,
|
||||||
|
Self::Call | Self::New => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<&Ident> for Metamethod {
|
impl TryFrom<&Ident> for Metamethod {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -230,8 +249,8 @@ struct FfiFunctionAttrs {
|
|||||||
metamethod: Option<Metamethod>,
|
metamethod: Option<Metamethod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
fn parse_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||||
let mut funcs = vec![];
|
let mut parsed = vec![];
|
||||||
|
|
||||||
for item in imp.items.iter_mut() {
|
for item in imp.items.iter_mut() {
|
||||||
if let ImplItem::Fn(func) = item
|
if let ImplItem::Fn(func) = item
|
||||||
@@ -285,21 +304,30 @@ 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));
|
|
||||||
func.sig.asyncness.is_some().then(|| document_async(func));
|
|
||||||
document_ffi_function(func);
|
|
||||||
|
|
||||||
funcs.push(FfiFunction {
|
if let Some(mm) = attrs.metamethod
|
||||||
|
&& let Some(narg) = mm.narg()
|
||||||
|
{
|
||||||
|
syn_assert!(
|
||||||
|
params.len() == narg,
|
||||||
|
func.sig.inputs,
|
||||||
|
"expected {narg} parameters for `{mm}` metamethod"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push(FfiFunction {
|
||||||
name: func.sig.ident.clone(),
|
name: func.sig.ident.clone(),
|
||||||
params,
|
params,
|
||||||
ret,
|
ret,
|
||||||
attrs,
|
attrs,
|
||||||
is_async: func.sig.asyncness.is_some(),
|
is_async: func.sig.asyncness.is_some(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document_ffi_function(func, parsed.last().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(funcs)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> {
|
fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> {
|
||||||
@@ -307,14 +335,10 @@ 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.unraw())
|
&& let Ok(mm) = Metamethod::try_from(&name.unraw())
|
||||||
{
|
{
|
||||||
match method {
|
syn_assert!(mm != Metamethod::Gc, attr, "implement `Drop` instead");
|
||||||
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
parsed.metamethod = Some(mm);
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.metamethod = Some(method);
|
|
||||||
attrs.remove(i);
|
attrs.remove(i);
|
||||||
} else {
|
} else {
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -373,7 +397,7 @@ fn get_ffi_ret_type(ty: &Type) -> FfiReturnType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
fn generate_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let ty = ®istry.ty;
|
let ty = ®istry.ty;
|
||||||
let func_name = &func.name;
|
let func_name = &func.name;
|
||||||
@@ -392,12 +416,22 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
let mut asserts = vec![]; // compile-time asserts
|
let mut asserts = vec![]; // compile-time 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
|
match func.attrs.metamethod {
|
||||||
// equivalent in C)
|
Some(Metamethod::New) => {
|
||||||
if func.attrs.metamethod == Some(Metamethod::New) {
|
// for __new metamethods, ignore the first argument (ctype of self, for which there is
|
||||||
build.push(quote!(
|
// no equivalent in C)
|
||||||
b.param_ignored();
|
build.push(quote!(
|
||||||
));
|
b.param_ignored();
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(Metamethod::Eq) => {
|
||||||
|
// for __eq metamethods, set special eq mode to return false on argument type mismatch
|
||||||
|
// instead of throwing
|
||||||
|
build.push(quote!(
|
||||||
|
b.set_eqmm(true);
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, (name, func_param)) in func_params.iter().enumerate() {
|
for (i, (name, func_param)) in func_params.iter().enumerate() {
|
||||||
@@ -413,9 +447,18 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
func_args.push(quote_spanned!(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!(span =>
|
build.push(if func.attrs.metamethod == Some(Metamethod::Eq) {
|
||||||
b.param::<#func_param>(#name);
|
quote_spanned!(span =>
|
||||||
));
|
// preliminary `return false` for type mismatch in __eq mode. we just check
|
||||||
|
// against the ctype of Self because that's what __eq should be comparing
|
||||||
|
// anyway.
|
||||||
|
b.param_ctchecked::<#func_param, Self>(#name);
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
quote_spanned!(span =>
|
||||||
|
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");
|
||||||
@@ -497,19 +540,21 @@ fn add_ffi_function(registry: &mut Registry, func: &FfiFunction) -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
build.push({
|
build.push(if func.is_async {
|
||||||
|
// we can't name the future from an async function, so instead we provide a closure with a
|
||||||
|
// "dummy call" that never gets called so that the builder can infer the return type from
|
||||||
|
// the closure
|
||||||
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
let infer_args = iter::repeat_n(quote!(::std::unreachable!()), func_params.len());
|
||||||
let infer = if func.is_async {
|
quote!(b.call_inferred(#c_name, || {
|
||||||
quote!(|| #ffi::future::lua_future::new(Self::#func_name(#(#infer_args),*)))
|
#ffi::future::lua_future::new(Self::#func_name(#(#infer_args),*))
|
||||||
} else {
|
});)
|
||||||
quote!(|| Self::#func_name(#(#infer_args),*))
|
} else {
|
||||||
};
|
quote!(b.call::<#func_ret>(#c_name);)
|
||||||
quote!(b.call_inferred(#c_name, #infer);)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.build.push(quote!(#(::std::assert!(#asserts);)*));
|
registry.build.push(quote!(#(::std::assert!(#asserts);)*));
|
||||||
registry.build.push(match func.attrs.metamethod {
|
registry.build.push(match func.attrs.metamethod {
|
||||||
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
Some(mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
||||||
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -547,8 +592,8 @@ struct LuaFunctionAttrs {
|
|||||||
metamethod: Option<Metamethod>,
|
metamethod: Option<Metamethod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
fn parse_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||||
let mut funcs = vec![];
|
let mut parsed = vec![];
|
||||||
|
|
||||||
for item in imp.items.iter_mut() {
|
for item in imp.items.iter_mut() {
|
||||||
if let ImplItem::Fn(func) = item
|
if let ImplItem::Fn(func) = item
|
||||||
@@ -579,26 +624,53 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
|||||||
.collect();
|
.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!())); // luaify builtin macro
|
// need to transform variadic to the `varidic!()` luaify builtin macro because we
|
||||||
|
// luaify a closure below, and closures don't have variadics.
|
||||||
|
params.push(parse_quote_spanned!(variadic.span() => variadic!()));
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
|
||||||
func.sig.asyncness.is_some().then(|| document_async(func));
|
|
||||||
document_lua_function(func);
|
|
||||||
|
|
||||||
funcs.push(LuaFunction {
|
if let Some(mm) = attrs.metamethod
|
||||||
|
&& let Some(narg) = mm.narg()
|
||||||
|
{
|
||||||
|
syn_assert!(
|
||||||
|
params.len() == narg,
|
||||||
|
func.sig.inputs,
|
||||||
|
"expected {narg} parameters for `{mm}` metamethod"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push(LuaFunction {
|
||||||
name: func.sig.ident.clone(),
|
name: func.sig.ident.clone(),
|
||||||
params,
|
params,
|
||||||
body: func.block.clone(),
|
body: func.block.clone(),
|
||||||
attrs,
|
attrs,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document_lua_function(func, parsed.last().unwrap());
|
||||||
stub_lua_function(func)?;
|
stub_lua_function(func)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(funcs)
|
Ok(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
||||||
|
let mut parsed = LuaFunctionAttrs::default();
|
||||||
|
let mut i = 0;
|
||||||
|
while let Some(attr) = attrs.get(i) {
|
||||||
|
if let Some(name) = attr.path().get_ident()
|
||||||
|
&& let Ok(mm) = Metamethod::try_from(&name.unraw())
|
||||||
|
{
|
||||||
|
parsed.metamethod = Some(mm);
|
||||||
|
attrs.remove(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
||||||
@@ -610,7 +682,7 @@ 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!(
|
||||||
const fn has_annotation<T: #ffi::Annotation>() {}
|
const fn has_annotation<T: #ffi::Annotate>() {}
|
||||||
));
|
));
|
||||||
|
|
||||||
// convert `...` variadic to a `rest: luaffi::marker::Many` parameter
|
// convert `...` variadic to a `rest: luaffi::marker::Many` parameter
|
||||||
@@ -649,24 +721,7 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
fn generate_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
||||||
let mut parsed = LuaFunctionAttrs::default();
|
|
||||||
let mut i = 0;
|
|
||||||
while let Some(attr) = attrs.get(i) {
|
|
||||||
if let Some(name) = attr.path().get_ident()
|
|
||||||
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
|
||||||
{
|
|
||||||
parsed.metamethod = Some(method);
|
|
||||||
attrs.remove(i);
|
|
||||||
} else {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_lua_function(registry: &mut Registry, func: &LuaFunction) -> Result<()> {
|
|
||||||
let ffi = ffi_crate();
|
let ffi = ffi_crate();
|
||||||
let luaify = quote!(#ffi::__internal::luaify!);
|
let luaify = quote!(#ffi::__internal::luaify!);
|
||||||
let func_name = &func.name;
|
let func_name = &func.name;
|
||||||
@@ -675,7 +730,7 @@ 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!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
Some(mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
||||||
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -701,16 +756,10 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
|||||||
let luaify = quote!(#ffi::__internal::luaify!);
|
let luaify = quote!(#ffi::__internal::luaify!);
|
||||||
let ty = ®istry.ty;
|
let ty = ®istry.ty;
|
||||||
let shim_name = format_ident!("__ffi_drop");
|
let shim_name = format_ident!("__ffi_drop");
|
||||||
let c_name = format_ident!("{}_drop", ty.unraw());
|
let c_name = format_ident!("__{}_drop", ty.unraw());
|
||||||
let c_name_str = c_name.to_string();
|
let c_name_str = c_name.to_string();
|
||||||
|
|
||||||
if let Some(lua) = lua {
|
if let Some(lua) = lua {
|
||||||
syn_assert!(
|
|
||||||
lua.params.len() == 1,
|
|
||||||
lua.name,
|
|
||||||
"finaliser must take exactly one parameter"
|
|
||||||
);
|
|
||||||
|
|
||||||
let param = pat_ident(&lua.params[0])?;
|
let param = pat_ident(&lua.params[0])?;
|
||||||
let body = &lua.body;
|
let body = &lua.body;
|
||||||
|
|
||||||
@@ -750,75 +799,176 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_ffi_function(func: &mut ImplItemFn) {
|
// the transparency makes it work in dark mode too
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
// const FFI_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
|
||||||
r#"<span
|
// const LUA_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
|
||||||
class="stab"
|
const FALLIBLE_COLOR: &str = "rgba(255, 168, 168, 0.3)"; // red
|
||||||
title="This function is implemented in Rust and called via FFI."
|
const ASYNC_COLOR: &str = "rgba(188, 222, 255, 0.3)"; // blue
|
||||||
style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
const METAMETHOD_COLOR: &str = "rgba(255, 222, 118, 0.3)"; // yellow
|
||||||
>FFI</span>"#
|
|
||||||
]));
|
struct StabConfig {
|
||||||
|
text: &'static str,
|
||||||
|
desc: &'static str,
|
||||||
|
color: &'static str,
|
||||||
|
strong: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_lua_function(func: &mut ImplItemFn) {
|
fn generate_stab(
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
StabConfig {
|
||||||
r#"<span
|
text,
|
||||||
class="stab"
|
desc,
|
||||||
title="This function is implemented in Lua."
|
color,
|
||||||
style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
strong,
|
||||||
>Lua</span>"#
|
}: StabConfig,
|
||||||
]));
|
) -> Attribute {
|
||||||
|
let attrs = vec![
|
||||||
|
("class", "stab".into()),
|
||||||
|
("title", desc.into()),
|
||||||
|
("aria-label", desc.into()),
|
||||||
|
("style", {
|
||||||
|
let mut style = vec![
|
||||||
|
("float", "right"),
|
||||||
|
("margin", "1px"),
|
||||||
|
("margin-left", "4px"),
|
||||||
|
("padding-left", "5px"),
|
||||||
|
("padding-right", "5px"),
|
||||||
|
("background", color),
|
||||||
|
];
|
||||||
|
|
||||||
|
if strong {
|
||||||
|
style.push(("font-weight", "600"));
|
||||||
|
}
|
||||||
|
|
||||||
|
style
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| format!("{k}: {v}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("; ")
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
let attrs = attrs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| format!(r#"{k}="{v}""#))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
let tag = format!(r#"<span {attrs}>{text}</span>"#);
|
||||||
|
parse_quote!(#[doc = #tag])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_async(func: &mut ImplItemFn) {
|
fn document_ffi_function(item: &mut ImplItemFn, func: &FfiFunction) {
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
let mut attrs = vec![];
|
||||||
r#"<span
|
|
||||||
class="stab"
|
// attrs.push(generate_stab(StabConfig {
|
||||||
title="This function is asynchronous and will yield the calling thread."
|
// text: "FFI",
|
||||||
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
// desc: "This function is implemented in Rust and called via FFI.",
|
||||||
>Async</span>"#
|
// color: FFI_COLOR,
|
||||||
]));
|
// strong: true,
|
||||||
|
// }));
|
||||||
|
|
||||||
|
generate_stab_fallible(item).map(|stab| attrs.push(stab));
|
||||||
|
generate_stab_async(item).map(|stab| attrs.push(stab));
|
||||||
|
generate_stab_metamethod(func.attrs.metamethod).map(|(stab, help)| {
|
||||||
|
attrs.push(stab);
|
||||||
|
item.attrs.push(help);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.attrs.splice(0..0, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
|
fn document_lua_function(item: &mut ImplItemFn, func: &LuaFunction) {
|
||||||
func.attrs.insert(0, parse_quote!(#[doc =
|
let mut attrs = vec![];
|
||||||
r#"<span
|
|
||||||
class="stab"
|
|
||||||
title="This function is a metamethod."
|
|
||||||
style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;"
|
|
||||||
>Metamethod</span>"#
|
|
||||||
]));
|
|
||||||
|
|
||||||
let doc = match method {
|
// attrs.push(generate_stab(StabConfig {
|
||||||
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.",
|
// text: "Lua",
|
||||||
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.",
|
// desc: "This function is implemented in Lua.",
|
||||||
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.",
|
// color: LUA_COLOR,
|
||||||
Metamethod::Le => "This function is a metamethod which is called by the `<=` operator.",
|
// strong: true,
|
||||||
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 => {
|
|
||||||
"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 => {
|
|
||||||
"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 => {
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
_ => "This function is a metamethod and cannot be called directly.",
|
|
||||||
};
|
|
||||||
|
|
||||||
func.attrs.push(parse_quote!(#[doc = ""]));
|
generate_stab_fallible(item).map(|stab| attrs.push(stab));
|
||||||
func.attrs.push(parse_quote!(#[doc = "# Metamethod"]));
|
generate_stab_async(item).map(|stab| attrs.push(stab));
|
||||||
func.attrs.push(parse_quote!(#[doc = ""]));
|
generate_stab_metamethod(func.attrs.metamethod).map(|(stab, help)| {
|
||||||
func.attrs.push(parse_quote!(#[doc = #doc]));
|
attrs.push(stab);
|
||||||
|
item.attrs.push(help);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.attrs.splice(0..0, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stab_async(item: &ImplItemFn) -> Option<Attribute> {
|
||||||
|
item.sig.asyncness.as_ref().map(|_| {
|
||||||
|
generate_stab(StabConfig {
|
||||||
|
text: "Async",
|
||||||
|
desc: "This function is asynchronous and will yield the calling thread.",
|
||||||
|
color: ASYNC_COLOR,
|
||||||
|
strong: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stab_fallible(item: &ImplItemFn) -> Option<Attribute> {
|
||||||
|
match item.sig.output {
|
||||||
|
ReturnType::Default => None,
|
||||||
|
ReturnType::Type(_, ref ty) => is_resultlike(ty).map(|_| {
|
||||||
|
generate_stab(StabConfig {
|
||||||
|
text: "Fallible",
|
||||||
|
desc: "This function is fallible and may throw an error on failure.",
|
||||||
|
color: FALLIBLE_COLOR,
|
||||||
|
strong: false,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stab_metamethod(mm: Option<Metamethod>) -> Option<(Attribute, Attribute)> {
|
||||||
|
mm.map(|mm| {
|
||||||
|
let stab = generate_stab(StabConfig {
|
||||||
|
text: "Metamethod",
|
||||||
|
desc: "This function is a metamethod.",
|
||||||
|
color: METAMETHOD_COLOR,
|
||||||
|
strong: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let help = match mm {
|
||||||
|
Metamethod::Eq => "This function is a metamethod which is called by the `==` operator.",
|
||||||
|
Metamethod::Len => "This function is a metamethod which is called by the `#` operator.",
|
||||||
|
Metamethod::Lt => "This function is a metamethod which is called by the `<` operator.",
|
||||||
|
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 => {
|
||||||
|
"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 => {
|
||||||
|
"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 => {
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
_ => "This function is a metamethod and cannot be called directly.",
|
||||||
|
};
|
||||||
|
|
||||||
|
let help = format!("\n# Metamethod\n\n{help}");
|
||||||
|
(stab, parse_quote!(#[doc = #help]))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,3 +163,19 @@ pub fn is_optionlike(ty: &Type) -> Option<&Type> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_resultlike(ty: &Type) -> Option<&Type> {
|
||||||
|
if let Type::Path(path) = ty
|
||||||
|
&& path.path.leading_colon.is_none()
|
||||||
|
&& path.path.segments.len() == 1
|
||||||
|
&& let Some(segment) = path.path.segments.get(0)
|
||||||
|
&& segment.ident == "Result"
|
||||||
|
&& let PathArguments::AngleBracketed(ref angle) = segment.arguments
|
||||||
|
&& angle.args.len() <= 2 // allow Result<T, E> or Result<T>
|
||||||
|
&& let Some(GenericArgument::Type(ty)) = angle.args.get(0)
|
||||||
|
{
|
||||||
|
Some(ty)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,53 @@
|
|||||||
|
//! # luaify
|
||||||
|
//!
|
||||||
|
//! A macro for generating Lua code from Rust syntax.
|
||||||
|
//!
|
||||||
|
//! This macro performs a direct one-to-one translation of Rust expressions and blocks into
|
||||||
|
//! equivalent Lua code. For example,
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use luaify::luaify;
|
||||||
|
//!
|
||||||
|
//! luaify!(|a, b| {
|
||||||
|
//! let c = a + b;
|
||||||
|
//!
|
||||||
|
//! fn inner_function(c: _) {
|
||||||
|
//! print(concat!("the sum of ", a, " and ", b, " is ", c));
|
||||||
|
//! c
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! inner_function(c);
|
||||||
|
//! });
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! will translate to the following equivalent Lua code (embedded as an [`&str`] or [`String`] in
|
||||||
|
//! Rust):
|
||||||
|
//!
|
||||||
|
//! ```lua
|
||||||
|
//! function(a, b)
|
||||||
|
//! local c = a + b
|
||||||
|
//!
|
||||||
|
//! local function inner_function(c)
|
||||||
|
//! print("the sum of " .. a .. " and " .. b .. " is " .. c)
|
||||||
|
//! return c
|
||||||
|
//! end
|
||||||
|
//!
|
||||||
|
//! inner_function(c)
|
||||||
|
//! end
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This macro accepts a smaller subset of all valid Rust syntax due to the difference between
|
||||||
|
//! Rust's expression-based syntax and Lua's statement-based syntax. Most Rust syntax are translated
|
||||||
|
//! to their equivalents in Lua, however the following features are not supported and will result in
|
||||||
|
//! a compile-time error:
|
||||||
|
//!
|
||||||
|
//! - static typing (typed local variables, parameters and return types in function signatures,
|
||||||
|
//! etc.)
|
||||||
|
//! - pattern matching (e.g. `match`, `if let`, `while let`)
|
||||||
|
//! - items other than statements or functions (e.g. `struct`, `enum`, `trait`, `impl`)
|
||||||
|
//! - block statements in expression position (e.g. `if`, `while`, `for`, `loop` that evaluate to a
|
||||||
|
//! value)
|
||||||
|
//! - expressions in statement position (except for function calls and assignments)
|
||||||
use crate::{
|
use crate::{
|
||||||
generate::{generate, generate_chunk},
|
generate::{generate, generate_chunk},
|
||||||
transform::{transform, transform_chunk},
|
transform::{transform, transform_chunk},
|
||||||
@@ -10,6 +60,7 @@ mod generate;
|
|||||||
mod transform;
|
mod transform;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
/// Generates Lua code for the given expression.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
||||||
let mut expr = parse_macro_input!(input);
|
let mut expr = parse_macro_input!(input);
|
||||||
@@ -20,6 +71,7 @@ pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates Lua code for the given block.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
|
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
|
||||||
let mut block = parse_macro_input!(input);
|
let mut block = parse_macro_input!(input);
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
//! Raw bindings for LuaJIT generated using [bindgen](https://docs.rs/bindgen/).
|
||||||
|
//!
|
||||||
|
//! ## Feature flags
|
||||||
|
//!
|
||||||
|
//! Features flags are used to control certain features of LuaJIT. They are not enabled by default
|
||||||
|
//! unless otherwise specified below.
|
||||||
|
//!
|
||||||
|
//! - `runtime`: links the target with `libluajit`. This should only be enabled by binary targets.
|
||||||
|
//! - `jit`: enables the JIT compiler. *This is enabled by default.*
|
||||||
|
//! - `ffi`: enables the FFI library. *This is enabled by default.*
|
||||||
|
//! - `unwind`: configures LuaJIT to use stack unwinding instead of `longjmp` for error handling.
|
||||||
|
//! **This MUST be enabled if `panic = "unwind"` is set.**
|
||||||
|
//! - `bundled-alloc`: configures LuaJIT to include its own bundled allocator. If this is not
|
||||||
|
//! enabled, LuaJIT will use the system allocator by default.
|
||||||
|
//! - `lua52`: enables Lua 5.2 compatibility mode. *This is enabled by default.*
|
||||||
#![allow(nonstandard_style)]
|
#![allow(nonstandard_style)]
|
||||||
use std::{ffi::*, ptr};
|
use std::{ffi::*, ptr};
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ bitflags = { version = "2.9.1", features = ["std"] }
|
|||||||
bstr = "1.12.0"
|
bstr = "1.12.0"
|
||||||
luaffi = { path = "../luaffi" }
|
luaffi = { path = "../luaffi" }
|
||||||
luajit-sys = { path = "../luajit-sys" }
|
luajit-sys = { path = "../luajit-sys" }
|
||||||
thiserror = "2.0.12"
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
15
src/lib.rs
15
src/lib.rs
@@ -1,20 +1,27 @@
|
|||||||
#[cfg(feature = "task")]
|
//! luby standard library.
|
||||||
pub use lb::chan;
|
|
||||||
#[cfg(feature = "fs")]
|
#[cfg(feature = "fs")]
|
||||||
pub use lb::fs;
|
pub use lb::fs;
|
||||||
#[cfg(feature = "net")]
|
#[cfg(feature = "net")]
|
||||||
pub use lb::net;
|
pub use lb::net;
|
||||||
#[cfg(feature = "task")]
|
#[cfg(feature = "task")]
|
||||||
pub use lb::task;
|
pub use lb::task;
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
pub use lb::time;
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
pub use lb_sqlite::sqlite;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
|
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
|
||||||
#[cfg(feature = "task")]
|
#[cfg(feature = "task")]
|
||||||
rt.module::<task::lb_tasklib>();
|
rt.module::<task::lb_tasklib>();
|
||||||
#[cfg(feature = "task")]
|
#[cfg(feature = "time")]
|
||||||
rt.module::<chan::lb_chanlib>();
|
rt.module::<time::lb_timelib>();
|
||||||
#[cfg(feature = "fs")]
|
#[cfg(feature = "fs")]
|
||||||
rt.module::<fs::lb_fslib>();
|
rt.module::<fs::lb_fslib>();
|
||||||
#[cfg(feature = "net")]
|
#[cfg(feature = "net")]
|
||||||
rt.module::<net::lb_netlib>();
|
rt.module::<net::lb_netlib>();
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
rt.module::<sqlite::lb_sqlitelib>();
|
||||||
}
|
}
|
||||||
|
|||||||
75
src/main.rs
75
src/main.rs
@@ -1,4 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use luajit::Chunk;
|
||||||
use mimalloc::MiMalloc;
|
use mimalloc::MiMalloc;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
|
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
|
||||||
@@ -78,12 +79,21 @@ struct Args {
|
|||||||
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
|
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
|
||||||
jit: Vec<String>,
|
jit: Vec<String>,
|
||||||
|
|
||||||
|
/// Allow global variables.
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help_heading = "Runtime",
|
||||||
|
value_name = "ENABLED",
|
||||||
|
default_value_t = true
|
||||||
|
)]
|
||||||
|
allow_globals: bool,
|
||||||
|
|
||||||
/// Number of worker threads.
|
/// Number of worker threads.
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
short = 'T',
|
short = 'T',
|
||||||
help_heading = "Runtime",
|
help_heading = "Runtime",
|
||||||
value_name = "THREADS",
|
value_name = "COUNT",
|
||||||
default_value_t = Self::threads()
|
default_value_t = Self::threads()
|
||||||
)]
|
)]
|
||||||
threads: NonZero<usize>,
|
threads: NonZero<usize>,
|
||||||
@@ -92,14 +102,14 @@ struct Args {
|
|||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
help_heading = "Runtime",
|
help_heading = "Runtime",
|
||||||
value_name = "THREADS",
|
value_name = "COUNT",
|
||||||
default_value_t = Self::blocking_threads()
|
default_value_t = Self::blocking_threads()
|
||||||
)]
|
)]
|
||||||
blocking_threads: NonZero<usize>,
|
blocking_threads: NonZero<usize>,
|
||||||
|
|
||||||
/// Enable tokio-console integration.
|
/// Enable tokio-console integration.
|
||||||
#[cfg(feature = "tokio-console")]
|
#[cfg(feature = "tokio-console")]
|
||||||
#[clap(long, help_heading = "Debugging")]
|
#[clap(long, help_heading = "Debugging", value_name = "ENABLED")]
|
||||||
enable_console: bool,
|
enable_console: bool,
|
||||||
|
|
||||||
/// tokio-console publish address.
|
/// tokio-console publish address.
|
||||||
@@ -221,55 +231,18 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
||||||
let mut rt = {
|
let mut rt = lb::runtime::Builder::new();
|
||||||
let mut rt = lb::runtime::Builder::new();
|
luby::open(&mut rt);
|
||||||
luby::open(&mut rt);
|
|
||||||
|
|
||||||
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
||||||
print!("{}", rt.registry()); // for cdef debugging
|
print!("{}", rt.registry()); // for cdef debugging
|
||||||
}
|
|
||||||
|
|
||||||
rt.unhandled_error(error_cb).build().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
for arg in args.jit.iter() {
|
|
||||||
let mut s = rt.guard();
|
|
||||||
let res = if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
|
||||||
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
|
|
||||||
{
|
|
||||||
(s.push("start"), s.get(-2), s.push(flags));
|
|
||||||
s.call(1, 0) // require("jit.{cmd}").start(flags)
|
|
||||||
} else {
|
|
||||||
s.require("jit", 1).unwrap();
|
|
||||||
match arg.as_str() {
|
|
||||||
cmd @ ("on" | "off" | "flush") => {
|
|
||||||
(s.push(cmd), s.get(-2));
|
|
||||||
s.call(0, 0) // require("jit").[on/off/flush]()
|
|
||||||
}
|
|
||||||
flags => {
|
|
||||||
(s.push("opt"), s.get(-2));
|
|
||||||
(s.push("start"), s.get(-2), s.push(flags));
|
|
||||||
s.call(1, 0) // require("jit").opt.start(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = res {
|
|
||||||
drop(s);
|
|
||||||
rt.report_error(&err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rt
|
rt.unhandled_error(error_cb)
|
||||||
}
|
.prohibit_globals(!args.allow_globals)
|
||||||
|
.jit_opts(args.jit.iter())
|
||||||
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
.build()
|
||||||
match s {
|
.unwrap()
|
||||||
"p" => Some(("p", "Flspv10")), // default -jp flags
|
|
||||||
"v" => Some(("v", "-")), // default -jv flags
|
|
||||||
"dump" => Some(("dump", "tirs")), // default -jdump flags
|
|
||||||
_ => s.split_once('='),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
||||||
@@ -282,9 +255,9 @@ async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) {
|
if let Err(ref err) = cx.load(&Chunk::new(chunk).with_path(path)) {
|
||||||
cx.report_error(err);
|
cx.report_error(err);
|
||||||
} else if let Err(ref err) = cx.call_async(0, 0).await {
|
} else if let Err(ref err) = cx.call_async(0, Some(0)).await {
|
||||||
cx.report_error(err);
|
cx.report_error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
113
tests/main.lua
113
tests/main.lua
@@ -1,30 +1,39 @@
|
|||||||
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call
|
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call
|
||||||
|
|
||||||
local ok = pcall(require, "lb:task")
|
local ok = pcall(require, "lb:task")
|
||||||
if not ok then error("lua test harness requires lb:task module") end
|
if not ok then error("lua test harness requires 'lb:task'") end
|
||||||
|
local ok, time = pcall(require, "lb:time")
|
||||||
|
if not ok then error("lua test harness requires 'lb:time'") end
|
||||||
local ok, fs = pcall(require, "lb:fs")
|
local ok, fs = pcall(require, "lb:fs")
|
||||||
if not ok then error("lua test harness requires lb:fs module") end
|
if not ok then error("lua test harness requires 'lb:fs'") end
|
||||||
|
|
||||||
local global = _G
|
local global = _G
|
||||||
local colors = {
|
local color = {
|
||||||
reset = "\x1b[0m",
|
reset = "\x1b[0m",
|
||||||
pass = "\x1b[32;1m",
|
pass = "\x1b[32;1m", -- green
|
||||||
fail = "\x1b[31;1m",
|
fail = "\x1b[31;1m", -- red
|
||||||
|
faint = "\x1b[2;39;49m", -- faint
|
||||||
}
|
}
|
||||||
|
|
||||||
local icons = {
|
local icon = {
|
||||||
check = "\u{2713}",
|
check = "\u{2713}",
|
||||||
cross = "\u{00d7}",
|
cross = "\u{00d7}",
|
||||||
chevron = "\u{203a}",
|
chevron = "\u{203a}",
|
||||||
}
|
}
|
||||||
|
|
||||||
local function color(name, s)
|
local function style(name, s)
|
||||||
return colors[name] .. s .. colors.reset
|
return ("%s%s%s"):format(color[name], s, color.reset)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rjust(s, w)
|
||||||
|
if w == nil then w = 12 end
|
||||||
|
if #s >= w then return s end
|
||||||
|
return (" "):rep(w - #s) .. s
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_test(name, f, group)
|
local function create_test(name, f, group)
|
||||||
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
|
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
|
||||||
local fenv = setmetatable({}, { __index = global })
|
local fenv = setmetatable({}, { __index = global, __newindex = global })
|
||||||
setfenv(f, fenv)
|
setfenv(f, fenv)
|
||||||
return test
|
return test
|
||||||
end
|
end
|
||||||
@@ -43,7 +52,7 @@ local function create_group(name, f, parent)
|
|||||||
table.insert(group.items, item)
|
table.insert(group.items, item)
|
||||||
return item
|
return item
|
||||||
end,
|
end,
|
||||||
}, { __index = global })
|
}, { __index = global, __newindex = global })
|
||||||
|
|
||||||
setfenv(f, fenv)
|
setfenv(f, fenv)
|
||||||
f(group)
|
f(group)
|
||||||
@@ -54,25 +63,26 @@ local function name_test(test)
|
|||||||
local name = test.name
|
local name = test.name
|
||||||
local group = test.group
|
local group = test.group
|
||||||
while group ~= nil do
|
while group ~= nil do
|
||||||
if group.name ~= "" then name = string.format("%s %s %s", group.name, icons.chevron, name) end
|
if group.name ~= "" then name = ("%s %s %s"):format(group.name, icon.chevron, name) end
|
||||||
group = group.parent
|
group = group.parent
|
||||||
end
|
end
|
||||||
return name
|
return name
|
||||||
end
|
end
|
||||||
|
|
||||||
local function trace(msg)
|
local function trace(msg)
|
||||||
return color("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
return style("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run_test(test)
|
local function run_test(test)
|
||||||
local ok, res = xpcall(test.f, trace, test)
|
local ok, trace = xpcall(test.f, trace, test)
|
||||||
if ok then
|
if ok then
|
||||||
test.state = "pass"
|
test.state = "pass"
|
||||||
print("", string.format("%s %s", color("pass", "PASS"), name_test(test)))
|
print(("%s %s"):format(style("pass", rjust("PASS")), name_test(test)))
|
||||||
else
|
else
|
||||||
test.state = "fail"
|
test.state = "fail"
|
||||||
print("", string.format("%s %s\n\n%s\n", color("fail", "FAIL"), name_test(test), res))
|
print(("%s %s\n\n%s\n"):format(style("fail", rjust("!!! FAIL")), name_test(test), trace))
|
||||||
end
|
end
|
||||||
|
collectgarbage() -- gc after each test to test destructors
|
||||||
return test
|
return test
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -86,9 +96,26 @@ local function start(cx, item)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function check_refs()
|
||||||
|
-- ensure all refs were properly unref'ed
|
||||||
|
local registry = debug.getregistry()
|
||||||
|
local count = #registry
|
||||||
|
local ref = 0 -- FREELIST_REF
|
||||||
|
while type(registry[ref]) == "number" do
|
||||||
|
local next = registry[ref]
|
||||||
|
registry[ref], ref = nil, next
|
||||||
|
end
|
||||||
|
for i = 1, count do
|
||||||
|
local value = registry[i]
|
||||||
|
if type(value) ~= "thread" then -- ignore threads pinned by the runtime
|
||||||
|
assert(rawequal(registry[i], nil), ("ref %d not unref'ed: %s"):format(i, registry[i]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function main(item)
|
local function main(item)
|
||||||
local cx = { tasks = {} }
|
local cx = { tasks = {} }
|
||||||
local pass, fail = 0, 0
|
local time, pass, fail = time.instant(), 0, 0
|
||||||
start(cx, item)
|
start(cx, item)
|
||||||
for _, task in ipairs(cx.tasks) do
|
for _, task in ipairs(cx.tasks) do
|
||||||
if task:await().state == "pass" then
|
if task:await().state == "pass" then
|
||||||
@@ -97,17 +124,25 @@ local function main(item)
|
|||||||
fail = fail + 1
|
fail = fail + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local elapsed = time:elapsed_secs()
|
||||||
|
local retcode
|
||||||
if fail == 0 then
|
if fail == 0 then
|
||||||
print("", color("pass", string.format("%s %d tests passed", icons.check, pass)))
|
print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
|
||||||
return 0
|
retcode = 0
|
||||||
|
else
|
||||||
|
print(
|
||||||
|
("\t%s, %s"):format(
|
||||||
|
style("pass", ("%s %d tests passed"):format(icon.check, pass)),
|
||||||
|
style("fail", ("%s %d tests failed"):format(icon.cross, fail))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
retcode = 1
|
||||||
end
|
end
|
||||||
print(
|
print(style("faint", ("\t%s completed in %.2fs"):format(icon.chevron, elapsed)))
|
||||||
"",
|
cx = nil
|
||||||
color("pass", string.format("%s %d tests passed", icons.check, pass))
|
collectgarbage()
|
||||||
.. ", "
|
check_refs() -- check that all refs were properly unref'ed in destructors
|
||||||
.. color("fail", string.format("%s %d tests failed", icons.cross, fail))
|
return retcode -- report error to cargo
|
||||||
)
|
|
||||||
return 1 -- report error to cargo
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return main(create_group("", function()
|
return main(create_group("", function()
|
||||||
@@ -120,6 +155,34 @@ return main(create_group("", function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function include_doctest(path, pat)
|
||||||
|
for entry in fs.glob_dir(path, pat) do
|
||||||
|
local line, doctest = 0, nil
|
||||||
|
for s in fs.read(entry:path()):gmatch("([^\n]*)\n?") do
|
||||||
|
line = line + 1
|
||||||
|
local prefix = s:match("^%s*///")
|
||||||
|
s = prefix and s:sub(#prefix + 1)
|
||||||
|
if s and not s:match("^%s*```%s*$") then
|
||||||
|
if s:match("^%s*```lua$") then
|
||||||
|
doctest = { line = line, col = #prefix + 2 }
|
||||||
|
elseif doctest then
|
||||||
|
table.insert(doctest, (" "):rep(#prefix) .. s)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if doctest then
|
||||||
|
local name = ("%s:%d:%d"):format(entry:path(), doctest.line, doctest.col)
|
||||||
|
local f, err = loadstring(table.concat(doctest, "\n"), "@" .. name)
|
||||||
|
if not f then error(err) end
|
||||||
|
test(("%s %s"):format(name, style("faint", "(doctest)")), f)
|
||||||
|
end
|
||||||
|
doctest = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
include("tests", "**/*.lua")
|
include("tests", "**/*.lua")
|
||||||
include("crates", "*/tests/**/*.lua")
|
include("crates", "*/tests/**/*.lua")
|
||||||
|
include_doctest("src", "**/*.rs")
|
||||||
|
include_doctest("crates", "*/src/**/*.rs")
|
||||||
end))
|
end))
|
||||||
|
|||||||
@@ -10,14 +10,17 @@ fn main() -> ExitCode {
|
|||||||
let lua = {
|
let lua = {
|
||||||
let mut rt = lb::runtime::Builder::new();
|
let mut rt = lb::runtime::Builder::new();
|
||||||
luby::open(&mut rt);
|
luby::open(&mut rt);
|
||||||
rt.unhandled_error(error_cb).build().unwrap()
|
rt.unhandled_error(error_cb)
|
||||||
|
.prohibit_globals(true)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = "tests/main.lua";
|
let path = "tests/main.lua";
|
||||||
let main = lua.spawn(async move |s| {
|
let main = lua.spawn(async move |s| {
|
||||||
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) {
|
if let Err(ref err) = s.load(&Chunk::new(fs::read(path).unwrap()).with_path(path)) {
|
||||||
s.report_error(err);
|
s.report_error(err);
|
||||||
} else if let Err(ref err) = s.call_async(0, 1).await {
|
} else if let Err(ref err) = s.call_async(0, Some(1)).await {
|
||||||
s.report_error(err);
|
s.report_error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user