Compare commits
No commits in common. "master" and "working" have entirely different histories.
@ -1,2 +1,3 @@
|
||||
[build]
|
||||
rustflags = ["--cfg", "tokio_unstable"]
|
||||
rustdocflags = ["--cfg", "tokio_unstable"]
|
||||
|
@ -1,9 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||
"runtime.version": "LuaJIT",
|
||||
"diagnostics.disable": [
|
||||
"undefined-global",
|
||||
"redefined-local",
|
||||
"lowercase-global"
|
||||
]
|
||||
"diagnostics.disable": ["redefined-local", "lowercase-global"]
|
||||
}
|
||||
|
773
Cargo.lock
generated
773
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
51
Cargo.toml
51
Cargo.toml
@ -1,8 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"crates/lb",
|
||||
"crates/lb_sqlite",
|
||||
"crates/luaffi",
|
||||
"crates/luaffi_impl",
|
||||
"crates/luaify",
|
||||
@ -10,52 +8,23 @@ members = [
|
||||
"crates/luajit-sys",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = ["luaneko <lumi@lua.re>"]
|
||||
homepage = "https://git.lua.re/luaneko/luby/"
|
||||
repository = "https://git.lua.re/luaneko/luby/"
|
||||
|
||||
[package]
|
||||
name = "luby"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[profile]
|
||||
dev.panic = "abort"
|
||||
release.panic = "abort"
|
||||
|
||||
[[test]]
|
||||
name = "main"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["task", "time", "fs", "net", "sqlite"]
|
||||
task = ["lb/task"]
|
||||
time = ["lb/time"]
|
||||
fs = ["lb/fs"]
|
||||
net = ["lb/net"]
|
||||
sqlite = ["dep:lb_sqlite"]
|
||||
tokio-console = ["dep:console-subscriber"]
|
||||
[package]
|
||||
name = "luby"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
lb = { path = "crates/lb", features = ["runtime"] }
|
||||
lb_sqlite = { path = "crates/lb_sqlite", optional = true }
|
||||
luajit = { path = "crates/luajit", features = ["runtime"] }
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
console-subscriber = "0.4.1"
|
||||
lb = { version = "0.1.0", path = "crates/lb" }
|
||||
luajit = { version = "0.1.0", path = "crates/luajit", features = ["runtime"] }
|
||||
mimalloc = "0.1.47"
|
||||
owo-colors = "4.2.1"
|
||||
sysexits = "0.9.0"
|
||||
tokio = { version = "1.45.1", features = ["rt", "rt-multi-thread"] }
|
||||
tokio = { version = "1.45.1", features = ["full", "tracing"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
|
||||
[build-dependencies]
|
||||
vergen-git2 = { version = "1.0.7", features = ["cargo", "rustc"] }
|
||||
tracing-subscriber = "0.3.19"
|
||||
|
@ -1,10 +1,6 @@
|
||||
default_job = "test"
|
||||
|
||||
[jobs.test]
|
||||
command = ["cargo", "test"]
|
||||
watch = ["*.lua"]
|
||||
command = ["cargo", "test", "--workspace"]
|
||||
need_stdout = true
|
||||
background = false
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--workspace"]
|
||||
|
13
build.rs
13
build.rs
@ -1,13 +0,0 @@
|
||||
use vergen_git2::{CargoBuilder, Emitter, Git2Builder, RustcBuilder};
|
||||
|
||||
fn main() {
|
||||
Emitter::default()
|
||||
.add_instructions(&CargoBuilder::all_cargo().unwrap())
|
||||
.unwrap()
|
||||
.add_instructions(&Git2Builder::all_git().unwrap())
|
||||
.unwrap()
|
||||
.add_instructions(&RustcBuilder::all_rustc().unwrap())
|
||||
.unwrap()
|
||||
.emit()
|
||||
.unwrap();
|
||||
}
|
@ -1,27 +1,16 @@
|
||||
[package]
|
||||
name = "lb"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
runtime = ["tokio/rt"]
|
||||
task = ["tokio/rt", "tokio/time"]
|
||||
time = []
|
||||
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
||||
net = ["tokio/net", "tokio/io-util"]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
camino = "1.1.10"
|
||||
derive_more = { version = "2.0.1", features = ["full"] }
|
||||
globset = { version = "0.4.16", optional = true }
|
||||
luaffi = { path = "../luaffi" }
|
||||
luaify = { path = "../luaify" }
|
||||
luajit = { path = "../luajit" }
|
||||
luaffi = { version = "0.1.0", path = "../luaffi" }
|
||||
luajit = { version = "0.1.0", path = "../luajit" }
|
||||
sysexits = "0.9.0"
|
||||
tempfile = { version = "3.20.0", optional = true }
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.1" }
|
||||
walkdir = { version = "2.5.0", optional = true }
|
||||
tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal"] }
|
||||
|
||||
[dev-dependencies]
|
||||
luaify = { path = "../luaify" }
|
||||
tokio = { version = "1.45.1", features = ["full"] }
|
||||
|
64
crates/lb/src/channel.rs
Normal file
64
crates/lb/src/channel.rs
Normal file
@ -0,0 +1,64 @@
|
||||
// use flume::{Receiver, Sender};
|
||||
use luaffi::{cdef, metatype};
|
||||
|
||||
#[cdef]
|
||||
pub struct lb_libchannel;
|
||||
|
||||
#[metatype]
|
||||
impl lb_libchannel {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
extern "Lua" fn unbounded(self) {
|
||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
||||
self.__unbounded(send, recv);
|
||||
(send, recv)
|
||||
}
|
||||
|
||||
extern "Lua" fn bounded(self, cap: number) {
|
||||
assert(cap >= 0, "channel capacity must be nonnegative");
|
||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
||||
self.__bounded(cap, send, recv);
|
||||
(send, recv)
|
||||
}
|
||||
|
||||
// extern "Lua-C" fn __unbounded(&self, s: *mut lb_sender, r: *mut lb_receiver) {
|
||||
// let (send, recv) = flume::unbounded();
|
||||
// unsafe {
|
||||
// ptr::write(s, lb_sender { send });
|
||||
// ptr::write(r, lb_receiver { recv });
|
||||
// }
|
||||
// }
|
||||
|
||||
// extern "Lua-C" fn __bounded(&self, cap: usize, s: *mut lb_sender, r: *mut lb_receiver) {
|
||||
// let (send, recv) = flume::bounded(cap);
|
||||
// unsafe {
|
||||
// ptr::write(s, lb_sender { send });
|
||||
// ptr::write(r, lb_receiver { recv });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// #[cdef]
|
||||
// pub struct lb_sender {
|
||||
// #[opaque]
|
||||
// send: Sender<c_int>,
|
||||
// }
|
||||
|
||||
// #[metatype]
|
||||
// impl lb_sender {
|
||||
// extern "Lua" fn send(self, value: _) {
|
||||
// let key = __ref(value);
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[cdef]
|
||||
// pub struct lb_receiver {
|
||||
// #[opaque]
|
||||
// recv: Receiver<c_int>,
|
||||
// }
|
||||
|
||||
// #[metatype]
|
||||
// impl lb_receiver {}
|
32
crates/lb/src/fs.rs
Normal file
32
crates/lb/src/fs.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! The `lb:fs` module provides utilities for interacting with the file system asynchronously.
|
||||
//!
|
||||
//! See [`lb_libfs`] for items exported by this module.
|
||||
use luaffi::{cdef, metatype};
|
||||
use std::io;
|
||||
use tokio::fs;
|
||||
|
||||
/// Items exported by the `lb:fs` module.
|
||||
///
|
||||
/// This module can be obtained by calling `require` in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local fs = require("lb:fs");
|
||||
/// ```
|
||||
#[cdef]
|
||||
pub struct lb_libfs;
|
||||
|
||||
#[metatype]
|
||||
impl lb_libfs {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub extern "Lua" fn read(&self, path: string) -> string {
|
||||
self.__read(path)
|
||||
}
|
||||
|
||||
async extern "Lua-C" fn __read(&self, path: &str) -> io::Result<Vec<u8>> {
|
||||
fs::read(path).await
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,190 +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 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)?))
|
||||
}
|
||||
}
|
@ -1,251 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
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,11 +1,5 @@
|
||||
//! luby core libraries.
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod channel;
|
||||
pub mod fs;
|
||||
#[cfg(feature = "net")]
|
||||
pub mod net;
|
||||
#[cfg(feature = "runtime")]
|
||||
pub mod runtime;
|
||||
#[cfg(feature = "task")]
|
||||
pub mod task;
|
||||
#[cfg(feature = "time")]
|
||||
pub mod time;
|
||||
|
345
crates/lb/src/net.rs
Normal file
345
crates/lb/src/net.rs
Normal file
@ -0,0 +1,345 @@
|
||||
//! The `lb:net` module provides an asynchronous network API for creating TCP or UDP servers and
|
||||
//! clients.
|
||||
//!
|
||||
//! See [`lb_libnet`] for items exported by this module.
|
||||
use derive_more::{From, FromStr};
|
||||
use luaffi::{cdef, metatype};
|
||||
use std::{
|
||||
io,
|
||||
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
};
|
||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||
|
||||
/// Items exported by the `lb:net` module.
|
||||
///
|
||||
/// This module can be obtained by calling `require` in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net");
|
||||
/// ```
|
||||
#[cdef]
|
||||
pub struct lb_libnet;
|
||||
|
||||
#[metatype]
|
||||
impl lb_libnet {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::LOCALHOST`].
|
||||
pub extern "Lua-C" fn localhost_v4(&self) -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv6Addr::LOCALHOST`].
|
||||
pub extern "Lua-C" fn localhost_v6(&self) -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::UNSPECIFIED`].
|
||||
pub extern "Lua-C" fn unspecified_v4(&self) -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv6Addr::UNSPECIFIED`].
|
||||
pub extern "Lua-C" fn unspecified_v6(&self) -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::BROADCAST`].
|
||||
pub extern "Lua-C" fn broadcast_v4(&self) -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
||||
}
|
||||
|
||||
/// Creates an [`lb_ipaddr`] from the given input.
|
||||
///
|
||||
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
|
||||
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
|
||||
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Throws if `s` cannot be parsed as an IP address.
|
||||
pub extern "Lua" fn ipaddr(&self, s: any) -> lb_ipaddr {
|
||||
if __istype(__ct.lb_ipaddr, s) {
|
||||
__new(__ct.lb_ipaddr, s) // copy constructor
|
||||
} else if __istype(__ct.lb_socketaddr, s) {
|
||||
s.ip()
|
||||
} else {
|
||||
self.__parse_ipaddr(s)
|
||||
}
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr, AddrParseError> {
|
||||
s.parse()
|
||||
}
|
||||
|
||||
/// Creates an [`lb_socketaddr`] from the given input.
|
||||
///
|
||||
/// A socket address is an IP address with a port number.
|
||||
///
|
||||
/// If `s` is an [`lb_socketaddr`], a copy of that value is returned. If `s` is an
|
||||
/// [`lb_ipaddr`], a socket address with that IP address is returned. Otherwise, parses `s` as a
|
||||
/// socket address string. Both IPv4 and IPv6 addresses are supported.
|
||||
///
|
||||
/// If `port` is not specified, `0` is used as the default.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Throws if `s` cannot be parsed as an IP or socket address.
|
||||
pub extern "Lua" fn socketaddr(&self, s: any, port: any) -> lb_socketaddr {
|
||||
if port != () {
|
||||
self.__new_socketaddr(self.ipaddr(s), port)
|
||||
} else {
|
||||
if __istype(__ct.lb_socketaddr, s) {
|
||||
__new(__ct.lb_socketaddr, s) // copy constructor
|
||||
} else if __istype(__ct.lb_ipaddr, s) {
|
||||
self.__new_socketaddr(s, 0) // default port 0
|
||||
} else {
|
||||
self.__parse_socketaddr(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __new_socketaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
||||
SocketAddr::new(ip.0, port).into()
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __parse_socketaddr(&self, s: &str) -> Result<lb_socketaddr, AddrParseError> {
|
||||
s.parse()
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket configured for IPv4.
|
||||
///
|
||||
/// See [`TcpSocket::new_v4`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Throws if an error was encountered during the socket creation.
|
||||
pub extern "Lua" fn tcp_v4(&self) -> lb_tcpsocket {
|
||||
self.__new_tcp_v4()
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket configured for IPv6.
|
||||
///
|
||||
/// See [`TcpSocket::new_v6`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Throws if an error was encountered during the socket creation.
|
||||
pub extern "Lua" fn tcp_v6(&self) -> lb_tcpsocket {
|
||||
self.__new_tcp_v6()
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __new_tcp_v4(&self) -> io::Result<lb_tcpsocket> {
|
||||
TcpSocket::new_v4().map(lb_tcpsocket)
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __new_tcp_v6(&self) -> io::Result<lb_tcpsocket> {
|
||||
TcpSocket::new_v6().map(lb_tcpsocket)
|
||||
}
|
||||
}
|
||||
|
||||
/// An IP address, either IPv4 or IPv6.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This example creates an [`lb_ipaddr`] by parsing an IP address string.
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net");
|
||||
/// local addr = net:ipaddr("127.0.0.1"); -- ipv4 loopback address
|
||||
///
|
||||
/// assert(addr:is_v4());
|
||||
/// assert(addr:is_loopback());
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||
#[cdef]
|
||||
pub struct lb_ipaddr(#[opaque] IpAddr);
|
||||
|
||||
#[metatype]
|
||||
impl lb_ipaddr {
|
||||
/// See [`IpAddr::is_unspecified`].
|
||||
pub extern "Lua-C" fn is_unspecified(&self) -> bool {
|
||||
self.0.is_unspecified()
|
||||
}
|
||||
|
||||
/// See [`IpAddr::is_loopback`].
|
||||
pub extern "Lua-C" fn is_loopback(&self) -> bool {
|
||||
self.0.is_loopback()
|
||||
}
|
||||
|
||||
/// See [`IpAddr::is_multicast`].
|
||||
pub extern "Lua-C" fn is_multicast(&self) -> bool {
|
||||
self.0.is_multicast()
|
||||
}
|
||||
|
||||
/// Returns the string `"v4"` if this is an IPv4 address or `"v6"` if this is an IPv6 address.
|
||||
pub extern "Lua" fn family(&self) -> string {
|
||||
if self.is_v6() { "v6" } else { "v4" }
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv4 address.
|
||||
pub extern "Lua-C" fn is_v4(&self) -> bool {
|
||||
self.0.is_ipv4()
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::is_private`].
|
||||
pub extern "Lua-C" fn is_v4_private(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_private(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::is_link_local`].
|
||||
pub extern "Lua-C" fn is_v4_link_local(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_link_local(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::is_broadcast`].
|
||||
pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_broadcast(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::is_documentation`].
|
||||
pub extern "Lua-C" fn is_v4_documentation(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_documentation(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv6 address.
|
||||
pub extern "Lua-C" fn is_v6(&self) -> bool {
|
||||
self.0.is_ipv6()
|
||||
}
|
||||
|
||||
/// See [`Ipv6Addr::is_unique_local`].
|
||||
pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(_) => false,
|
||||
IpAddr::V6(v6) => v6.is_unique_local(),
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`Ipv6Addr::is_unicast_link_local`].
|
||||
pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(_) => false,
|
||||
IpAddr::V6(v6) => v6.is_unicast_link_local(),
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::to_ipv6_compatible`].
|
||||
pub extern "Lua-C" fn to_v6_compat(&self) -> Self {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => Self(v4.to_ipv6_compatible().into()),
|
||||
IpAddr::V6(_) => *self,
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`Ipv4Addr::to_ipv6_mapped`].
|
||||
pub extern "Lua-C" fn to_v6_mapped(&self) -> Self {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()),
|
||||
IpAddr::V6(_) => *self,
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`IpAddr::to_canonical`].
|
||||
pub extern "Lua-C" fn canonical(&self) -> Self {
|
||||
self.0.to_canonical().into()
|
||||
}
|
||||
|
||||
/// Returns the string representation of this address.
|
||||
#[tostring]
|
||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// A socket address, which is an IP address with a port number.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||
#[cdef]
|
||||
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
||||
|
||||
#[metatype]
|
||||
impl lb_socketaddr {
|
||||
/// Returns the IP part of this address.
|
||||
pub extern "Lua-C" fn ip(&self) -> lb_ipaddr {
|
||||
self.0.ip().into()
|
||||
}
|
||||
|
||||
/// Sets the IP part of this address.
|
||||
///
|
||||
/// This function accepts the same arguments as [`ipaddr`](lb_libnet::ipaddr).
|
||||
pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self {
|
||||
if __istype(__ct.lb_ipaddr, s) {
|
||||
self.__set_ip(s);
|
||||
} else if __istype(__ct.lb_socketaddr, s) {
|
||||
self.__set_ip(s.ip());
|
||||
} else {
|
||||
self.__set_ip_parse(s);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __set_ip(&mut self, ip: &lb_ipaddr) {
|
||||
self.0.set_ip(ip.0);
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<(), AddrParseError> {
|
||||
s.parse().map(|ip| self.0.set_ip(ip))
|
||||
}
|
||||
|
||||
/// Returns the port part of this address.
|
||||
pub extern "Lua-C" fn port(&self) -> u16 {
|
||||
self.0.port()
|
||||
}
|
||||
|
||||
/// Sets the port part of this address.
|
||||
pub extern "Lua" fn set_port(&mut self, port: number) -> &mut Self {
|
||||
self.__set_port(port);
|
||||
self
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __set_port(&mut self, port: u16) {
|
||||
self.0.set_port(port)
|
||||
}
|
||||
|
||||
/// Returns the string representation of this address.
|
||||
#[tostring]
|
||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// A TCP socket which has not yet been converted to a [`lb_tcpstream`] or [`lb_tcplistener`].
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_tcpsocket(#[opaque] TcpSocket);
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcpsocket {}
|
||||
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_tcpstream(#[opaque] TcpStream);
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcpstream {}
|
||||
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_tcplistener(#[opaque] TcpListener);
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcplistener {}
|
@ -1,588 +0,0 @@
|
||||
//! 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()
|
||||
}
|
||||
}
|
@ -1,622 +0,0 @@
|
||||
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)?,
|
||||
))
|
||||
}
|
||||
}
|
5
crates/lb/src/runtime.lua
Normal file
5
crates/lb/src/runtime.lua
Normal file
@ -0,0 +1,5 @@
|
||||
local task = require("lb:task")
|
||||
|
||||
function spawn(f, ...)
|
||||
return task:spawn(f, ...)
|
||||
end
|
@ -1,206 +1,85 @@
|
||||
#![doc(hidden)]
|
||||
use crate::{channel::lb_libchannel, fs::lb_libfs, net::lb_libnet, task::lb_libtask};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use luaffi::{Module, Registry};
|
||||
use luaify::luaify_chunk;
|
||||
use luajit::{Chunk, Index, NewTable, State};
|
||||
use std::rc::Rc;
|
||||
use luaffi::{Registry, Type};
|
||||
use luajit::{Chunk, State};
|
||||
use std::fmt::Display;
|
||||
use tokio::{
|
||||
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
||||
task_local,
|
||||
};
|
||||
|
||||
pub type ErrorFn = dyn Fn(&luajit::Error);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Builder {
|
||||
registry: Registry,
|
||||
report_err: Rc<ErrorFn>,
|
||||
jit_opts: Vec<String>,
|
||||
prohibit_globals: bool,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
registry: Registry::new(),
|
||||
report_err: Rc::new(|err| match err.trace() {
|
||||
Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"),
|
||||
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,
|
||||
}
|
||||
let mut registry = Registry::new();
|
||||
|
||||
registry
|
||||
.preload::<lb_libtask>("lb:task")
|
||||
.preload::<lb_libchannel>("lb:channel")
|
||||
.preload::<lb_libfs>("lb:fs")
|
||||
.preload::<lb_libnet>("lb:net");
|
||||
|
||||
Self { registry }
|
||||
}
|
||||
|
||||
pub fn module<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
self.registry.preload::<T>(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn registry(&self) -> &Registry {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
pub fn unhandled_error(&mut self, handler: impl Fn(&luajit::Error) + 'static) -> &mut Self {
|
||||
self.report_err = Rc::new(handler);
|
||||
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 {
|
||||
self.registry.preload::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
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 {
|
||||
cx: Context {
|
||||
state,
|
||||
report_err: self.report_err.clone(),
|
||||
state: {
|
||||
let mut s = State::new()?;
|
||||
let mut chunk = Chunk::new(self.registry.done());
|
||||
chunk.extend(include_bytes!("./runtime.lua"));
|
||||
s.eval(chunk.path("[luby]"), 0, 0)?;
|
||||
s
|
||||
},
|
||||
tasks: LocalSet::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(Debug, Deref, DerefMut)]
|
||||
pub struct Runtime {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
cx: Context,
|
||||
state: State,
|
||||
tasks: LocalSet,
|
||||
}
|
||||
|
||||
task_local! {
|
||||
static STATE: State;
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn spawn<T: 'static>(
|
||||
&self,
|
||||
f: impl AsyncFnOnce(&mut Context) -> T + 'static,
|
||||
f: impl AsyncFnOnce(&mut State) -> T + 'static,
|
||||
) -> JoinHandle<T> {
|
||||
self.tasks
|
||||
.spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await })
|
||||
.spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut State) -> T + 'static) -> JoinHandle<T> {
|
||||
spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
|
||||
}
|
||||
|
||||
impl IntoFuture for Runtime {
|
||||
type Output = ();
|
||||
type IntoFuture = TaskLocalFuture<Context, LocalSet>;
|
||||
type IntoFuture = TaskLocalFuture<State, LocalSet>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
CURRENT.scope(self.cx, self.tasks)
|
||||
STATE.scope(self.state, self.tasks)
|
||||
}
|
||||
}
|
||||
|
||||
task_local! {
|
||||
static CURRENT: Context;
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Context {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
state: State,
|
||||
report_err: Rc<ErrorFn>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new_thread(&self) -> Self {
|
||||
Self {
|
||||
state: State::new_thread(&self.state),
|
||||
report_err: self.report_err.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_error(&self, err: &luajit::Error) {
|
||||
(self.report_err)(&err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut Context) -> T + 'static) -> JoinHandle<T> {
|
||||
// SAFETY: `new_thread` must be called inside `spawn_local` because this free-standing spawn
|
||||
// function may be called via ffi from lua, and it is not safe to access the lua state within
|
||||
// ffi calls.
|
||||
spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await })
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
local task = require("lb:task")
|
||||
sleep = task.sleep
|
||||
spawn = task.spawn
|
@ -1,117 +1,46 @@
|
||||
//! Task library.
|
||||
//!
|
||||
//! The `lb:task` library provides utilities for the scheduling of and communication between
|
||||
//! asynchronous tasks.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_tasklib`] for items exported by this library.
|
||||
use crate::runtime::spawn;
|
||||
use luaffi::{
|
||||
cdef,
|
||||
marker::{function, many},
|
||||
metatype,
|
||||
};
|
||||
use std::{cell::RefCell, ffi::c_int, time::Duration};
|
||||
use tokio::{task::JoinHandle, time::sleep};
|
||||
use luaffi::{cdef, metatype};
|
||||
use std::{ffi::c_int, process};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
/// Items exported by the `lb:task` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||
///
|
||||
/// ```lua
|
||||
/// local task = require("lb:task")
|
||||
/// ```
|
||||
#[cdef(module = "lb:task")]
|
||||
pub struct lb_tasklib;
|
||||
#[cdef]
|
||||
pub struct lb_libtask;
|
||||
|
||||
#[metatype]
|
||||
#[include("task.lua")]
|
||||
impl lb_tasklib {
|
||||
impl lb_libtask {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn sleep(ms: f64) {
|
||||
sleep(Duration::from_secs_f64(ms.max(0.) / 1000.)).await;
|
||||
pub extern "Lua" fn spawn(self, f: function, ...) {
|
||||
// pack the function and its arguments into a table and pass its ref to rust
|
||||
self.__spawn(__ref(__tpack(f, variadic!())))
|
||||
}
|
||||
|
||||
pub extern "Lua" fn spawn(f: function, ...) -> lb_task {
|
||||
// pack the function and its arguments into a table and pass its ref to rust.
|
||||
//
|
||||
// this "state" table is used from rust-side to call the function with its args, and it's
|
||||
// also reused to store its return values that the task handle can return when awaited. the
|
||||
// ref is owned by the task handle and unref'ed when it's gc'ed.
|
||||
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(spawn_ref: c_int, handle_ref: c_int) -> lb_task {
|
||||
let handle = spawn(async move |cx| {
|
||||
// SAFETY: handle_ref is always unique, created in Self::spawn above.
|
||||
let state = unsafe { luajit::Ref::from_raw(cx, spawn_ref) };
|
||||
let mut s = cx.guard();
|
||||
extern "Lua-C" fn __spawn(&self, key: c_int) -> lb_task {
|
||||
let handle = spawn(async move |s| {
|
||||
// SAFETY: key is always unique, created by __ref above
|
||||
let arg = unsafe { s.new_ref_unchecked(key) };
|
||||
s.resize(0);
|
||||
s.push(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 state table
|
||||
match s.call_async(narg, None).await {
|
||||
Ok(nret) => {
|
||||
s.pack(1, nret); // pack the return values back into the state table
|
||||
}
|
||||
Err(err) => {
|
||||
drop(s);
|
||||
cx.report_error(&err);
|
||||
}
|
||||
s.push(arg);
|
||||
let narg = s.unpack(1, 1, None) - 1;
|
||||
println!("{s:?}");
|
||||
if let Err(_err) = s.call_async(narg, 0).await {
|
||||
process::exit(1)
|
||||
}
|
||||
println!("{s:?}");
|
||||
});
|
||||
|
||||
// spawn_ref is owned by the task handle and unref'ed there when the handle gets gc'ed
|
||||
lb_task::new(handle, handle_ref)
|
||||
lb_task { handle }
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle for an asynchronous task created by [`spawn`](lb_tasklib::spawn).
|
||||
#[cdef]
|
||||
pub struct lb_task {
|
||||
#[opaque]
|
||||
handle: RefCell<Option<JoinHandle<()>>>,
|
||||
__ref: c_int,
|
||||
handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
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 {
|
||||
self.__await();
|
||||
let ret = __registry[self.__ref];
|
||||
__tunpack(ret, 1, ret.n)
|
||||
}
|
||||
|
||||
async extern "Lua-C" fn __await(&self) {
|
||||
if let Some(handle) = self.handle.borrow_mut().take() {
|
||||
handle
|
||||
.await // task handler should never panic
|
||||
.unwrap_or_else(|err| std::panic::resume_unwind(err.into_panic()));
|
||||
}
|
||||
}
|
||||
|
||||
#[gc]
|
||||
extern "Lua" fn gc(&self) {
|
||||
__unref(self.__ref);
|
||||
}
|
||||
}
|
||||
impl lb_task {}
|
||||
|
@ -1,49 +0,0 @@
|
||||
//! 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 +0,0 @@
|
||||
local ok, fs = pcall(require, "lb:fs")
|
||||
if not ok then return end
|
||||
|
||||
describe("temp files", function()
|
||||
test("temp_dir cleans itself", function()
|
||||
local path
|
||||
do
|
||||
local dir = fs.temp_dir()
|
||||
path = dir:path()
|
||||
assert(path and path ~= "")
|
||||
fs.write(path .. "/test.txt", "test file")
|
||||
assert(fs.read(path .. "/test.txt") == "test file")
|
||||
end
|
||||
collectgarbage()
|
||||
assert(not pcall(fs.read, path .. "/test.txt"))
|
||||
end)
|
||||
end)
|
@ -1,184 +0,0 @@
|
||||
local ok, net = pcall(require, "lb:net")
|
||||
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("socket", function()
|
||||
test("bind", function()
|
||||
local socket = net.bind_tcp("127.0.0.1")
|
||||
-- binds to the correct port
|
||||
assert(tostring(socket:local_addr():ip()) == "127.0.0.1")
|
||||
assert(socket:local_addr():port() ~= 0)
|
||||
-- should not be able to rebind socket
|
||||
assert(not pcall(socket.bind, socket, net.socketaddr("127.0.0.1")))
|
||||
end)
|
||||
|
||||
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)
|
32
crates/lb/tests/net.rs
Normal file
32
crates/lb/tests/net.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use lb::runtime;
|
||||
use luaify::luaify;
|
||||
use luajit::{Chunk, LoadMode};
|
||||
use tokio::test;
|
||||
|
||||
async fn run_lua(s: &'static str) {
|
||||
let rt = runtime::Builder::new().build().unwrap();
|
||||
let task = rt.spawn(async move |state| {
|
||||
println!("executing test chunk: {s}");
|
||||
|
||||
state
|
||||
.load(Chunk::new(s).mode(LoadMode::TEXT))
|
||||
.unwrap_or_else(|err| panic!("{err}"));
|
||||
|
||||
state
|
||||
.call_async(0, 0)
|
||||
.await
|
||||
.unwrap_or_else(|err| panic!("{err}"));
|
||||
});
|
||||
|
||||
rt.await;
|
||||
task.await.unwrap_or_else(|err| panic!("{err}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn ipaddr() {
|
||||
run_lua(luaify!({
|
||||
let net = require("lb:net");
|
||||
print(net.ipaddr("127.0.0.1"));
|
||||
}))
|
||||
.await
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
local ok, task = pcall(require, "lb:task")
|
||||
if not ok then return end
|
||||
|
||||
describe("spawn", function()
|
||||
test("callback receives args", function()
|
||||
spawn(function(...)
|
||||
assert(select("#", ...) == 0)
|
||||
end):await()
|
||||
|
||||
spawn(function(...)
|
||||
assert(select("#", ...) == 1)
|
||||
assert((...) == nil)
|
||||
end, nil):await()
|
||||
|
||||
spawn(function(...)
|
||||
assert(select("#", ...) == 4)
|
||||
local args = table.pack(...)
|
||||
assert(args[1] == 1 and args[2] == 2 and args[3] == nil and args[4] == 3)
|
||||
end, 1, 2, nil, 3):await()
|
||||
end)
|
||||
|
||||
test("await returns callback results", function()
|
||||
local res = table.pack(spawn(function()
|
||||
-- no returns
|
||||
end):await())
|
||||
assert(res.n == 0)
|
||||
|
||||
local res = table.pack(spawn(function()
|
||||
return nil
|
||||
end):await())
|
||||
assert(res.n == 1 and res[1] == nil)
|
||||
|
||||
local res = table.pack(spawn(function()
|
||||
return 1, 2, nil, 3
|
||||
end):await())
|
||||
assert(res.n == 4 and res[1] == 1 and res[2] == 2 and res[3] == nil and res[4] == 3)
|
||||
end)
|
||||
|
||||
test("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()
|
||||
local res = table.pack(spawn(function(...)
|
||||
assert(select("#", ...) == 5)
|
||||
local args = table.pack(...)
|
||||
assert(args[1] == 5 and args[2] == 4 and args[3] == nil and args[4] == 3, args[5] == nil)
|
||||
return 1, 3, nil
|
||||
end, 5, 4, nil, 3, nil):await())
|
||||
assert(res.n == 3 and res[1] == 1 and res[2] == 3 and res[3] == nil)
|
||||
end)
|
||||
|
||||
test("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()
|
||||
-- all tasks spawned in one batch should be resumed in the spawn order
|
||||
local tasks, nums = {}, {}
|
||||
for i = 1, 10 do
|
||||
table.insert(
|
||||
tasks,
|
||||
spawn(function()
|
||||
table.insert(nums, i)
|
||||
end)
|
||||
)
|
||||
end
|
||||
for i = 10, 1, -1 do
|
||||
tasks[i]:await()
|
||||
end
|
||||
assert(#nums == 10)
|
||||
for i = 1, 10 do
|
||||
assert(nums[i] == i)
|
||||
end
|
||||
end)
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
local value
|
||||
spawn(function()
|
||||
value = "value"
|
||||
end)
|
||||
assert(value == nil)
|
||||
task.sleep(100) -- implicit await: if it's synchronous, value wouldn't change
|
||||
assert(value == "value")
|
||||
end)
|
||||
|
||||
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)
|
||||
|
||||
describe("task", function()
|
||||
test("properly unrefs arg and ret table", function()
|
||||
local registry = debug.getregistry()
|
||||
local ref, t
|
||||
local cb = function()
|
||||
return "my ret"
|
||||
end
|
||||
do
|
||||
local task = spawn(cb, "my arg")
|
||||
ref = task.__ref
|
||||
t = registry[ref]
|
||||
assert(type(t) == "table")
|
||||
assert(t[1] == cb)
|
||||
assert(t[2] == "my arg")
|
||||
task:await()
|
||||
assert(registry[task.__ref] == t)
|
||||
assert(t[1] == "my ret")
|
||||
end
|
||||
collectgarbage()
|
||||
assert(registry[ref] ~= t) -- if task unref'ed it, it should be either nil or the next freelist number
|
||||
end)
|
||||
end)
|
35
crates/lb/tests/task.rs
Normal file
35
crates/lb/tests/task.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use lb::runtime;
|
||||
use luaify::luaify;
|
||||
use luajit::{Chunk, LoadMode};
|
||||
use tokio::test;
|
||||
|
||||
async fn run_lua(s: &'static str) {
|
||||
let rt = runtime::Builder::new().build().unwrap();
|
||||
let task = rt.spawn(async move |state| {
|
||||
println!("executing test chunk: {s}");
|
||||
|
||||
state
|
||||
.load(Chunk::new(s).mode(LoadMode::TEXT))
|
||||
.unwrap_or_else(|err| panic!("{err}"));
|
||||
|
||||
state
|
||||
.call_async(0, 0)
|
||||
.await
|
||||
.unwrap_or_else(|err| panic!("{err}"));
|
||||
});
|
||||
|
||||
rt.await;
|
||||
task.await.unwrap_or_else(|err| panic!("{err}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn task_test() {
|
||||
run_lua(luaify!({
|
||||
let thing = spawn(|| {
|
||||
print("spawn callback!!!!!!!!!!!!!");
|
||||
});
|
||||
print("thing is", thing);
|
||||
//
|
||||
}))
|
||||
.await
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
[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"] }
|
@ -1,3 +0,0 @@
|
||||
//! luby SQLite library.
|
||||
#[path = "mod.rs"]
|
||||
pub mod sqlite;
|
@ -1,27 +0,0 @@
|
||||
//! 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
|
||||
}
|
||||
}
|
@ -1,19 +1,11 @@
|
||||
[package]
|
||||
name = "luaffi"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
option_ref_abi = []
|
||||
option_string_abi = []
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bstr = "1.12.0"
|
||||
luaffi_impl = { path = "../luaffi_impl" }
|
||||
luaify = { path = "../luaify" }
|
||||
luaffi_impl = { version = "0.1.0", path = "../luaffi_impl" }
|
||||
luaify = { version = "0.1.0", path = "../luaify" }
|
||||
rustc-hash = "2.1.1"
|
||||
simdutf8 = "0.1.5"
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
__internal::{display, type_id},
|
||||
Cdef, CdefBuilder, ExternCFn, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type,
|
||||
TypeBuilder, TypeType,
|
||||
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
|
||||
TypeType, UnsafeExternCFn,
|
||||
};
|
||||
use luaify::luaify;
|
||||
use std::{
|
||||
@ -70,45 +70,6 @@ enum State<F: Future> {
|
||||
Complete,
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
|
||||
fn name() -> impl Display {
|
||||
display!("__future_{:x}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct {} {name}", Self::name())
|
||||
}
|
||||
|
||||
fn build(s: &mut TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
|
||||
fn build(s: &mut CdefBuilder) {
|
||||
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
|
||||
.field::<ExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
|
||||
.field::<ExternCFn<(&mut Self,), ()>>("__drop");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
||||
type Target = Self;
|
||||
|
||||
fn build(s: &mut MetatypeBuilder) {
|
||||
s.metatable_raw(
|
||||
"gc",
|
||||
luaify!(|self| {
|
||||
self.__drop();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future<Output: IntoFfi>> lua_future<F> {
|
||||
pub fn new(fut: F) -> Self {
|
||||
Self {
|
||||
@ -156,12 +117,7 @@ impl lua_pollable {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
// 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
|
||||
// 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.
|
||||
// afaik this because there is no way to find the size of a cdata payload using the C API.
|
||||
self.sig == SIGNATURE
|
||||
}
|
||||
}
|
||||
@ -175,6 +131,40 @@ impl Future for lua_pollable {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
|
||||
fn name() -> impl Display {
|
||||
display!("future__{:x}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct {} {name}", Self::name())
|
||||
}
|
||||
|
||||
fn build(s: &mut TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
|
||||
fn build(s: &mut CdefBuilder) {
|
||||
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
|
||||
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
|
||||
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
||||
type Target = Self;
|
||||
|
||||
fn build(s: &mut MetatypeBuilder) {
|
||||
s.metatable_raw("gc", luaify!(|self| self.__drop()));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
|
||||
type Into = lua_future<F>;
|
||||
|
||||
@ -187,15 +177,6 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
|
||||
self
|
||||
}
|
||||
|
||||
fn require_owned() -> bool {
|
||||
// future always requires full ownership of itself even if it's a "temporary", because we
|
||||
// must yield a full cdata to the runtime not a cdata containing a pointer to the future. if
|
||||
// this is set to false, postlude might receive a reference lua_future cdata instead of a
|
||||
// full lua_future cdata, and the runtime might incorrectly read the pointer value as
|
||||
// lua_future itself (it does not dereference it).
|
||||
true
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
// When returning a future from Rust to Lua, yield it immediately to the runtime which will
|
||||
// poll it to completion in the background, then take the fulfilled value once the thread
|
||||
|
@ -6,6 +6,23 @@ use std::{
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub mod stub_types {
|
||||
pub struct any;
|
||||
pub struct nil;
|
||||
pub struct boolean;
|
||||
pub struct lightuserdata;
|
||||
pub struct number;
|
||||
pub struct integer;
|
||||
pub struct string;
|
||||
pub struct table;
|
||||
pub struct function;
|
||||
pub struct userdata;
|
||||
pub struct thread;
|
||||
pub struct cdata;
|
||||
pub struct variadic;
|
||||
}
|
||||
|
||||
pub fn type_id<T: 'static>() -> u64 {
|
||||
let mut hash = FxHasher::default();
|
||||
TypeId::of::<T>().hash(&mut hash);
|
||||
|
@ -1,22 +1,24 @@
|
||||
---@diagnostic disable
|
||||
local LUA_REFNIL = -1 -- lib_aux.c
|
||||
local FREELIST_REF = 0
|
||||
|
||||
local function __ref(value)
|
||||
if rawequal(value, nil) then return LUA_REFNIL end
|
||||
local ref = __registry[FREELIST_REF]
|
||||
local function __ref(value, t)
|
||||
if value == nil then return LUA_REFNIL end
|
||||
if t == nil then t = __registry end
|
||||
local ref = t[FREELIST_REF]
|
||||
if ref ~= nil and ref ~= 0 then
|
||||
__registry[FREELIST_REF] = __registry[ref]
|
||||
t[FREELIST_REF] = t[ref]
|
||||
else
|
||||
ref = rawlen(__registry) + 1
|
||||
ref = #t + 1
|
||||
end
|
||||
__registry[ref] = value
|
||||
t[ref] = value
|
||||
return ref
|
||||
end
|
||||
|
||||
local function __unref(ref)
|
||||
if ref > 0 then
|
||||
__registry[ref] = __registry[FREELIST_REF]
|
||||
__registry[FREELIST_REF] = ref
|
||||
end
|
||||
local function __unref(ref, t)
|
||||
if ref < 0 then return nil end
|
||||
if t == nil then t = __registry end
|
||||
local value = t[ref]
|
||||
t[ref] = t[FREELIST_REF]
|
||||
t[FREELIST_REF] = ref
|
||||
return value
|
||||
end
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,193 +0,0 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::{
|
||||
__internal::{disp, display},
|
||||
Annotate,
|
||||
};
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
||||
enum Marker {}
|
||||
|
||||
pub struct any(Marker);
|
||||
|
||||
impl Annotate for any {
|
||||
fn annotation() -> impl Display {
|
||||
"any"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct many(Marker);
|
||||
|
||||
impl Annotate for many {
|
||||
fn annotation() -> impl Display {
|
||||
"..."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct nil(Marker);
|
||||
|
||||
impl Annotate for nil {
|
||||
fn annotation() -> impl Display {
|
||||
"nil"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct lightuserdata(Marker);
|
||||
|
||||
impl Annotate for lightuserdata {
|
||||
fn annotation() -> impl Display {
|
||||
"lightuserdata"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct table<K, V>(Marker, PhantomData<*mut [(K, V)]>);
|
||||
|
||||
impl<K, V> Annotate for table<K, V>
|
||||
where
|
||||
K: Annotate,
|
||||
V: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("table<{}, {}>", K::annotation(), V::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct function(Marker);
|
||||
|
||||
impl Annotate for function {
|
||||
fn annotation() -> impl Display {
|
||||
"function"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct fun<I, O>(Marker, PhantomData<fn(I) -> O>);
|
||||
|
||||
macro_rules! impl_fun {
|
||||
(fn($($arg:ident),*)) => {
|
||||
impl<$($arg,)*> Annotate for fun<($($arg,)*), ()>
|
||||
where
|
||||
$($arg: Annotate,)*
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "fun(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::annotation())?; _n += 1;)*
|
||||
write!(f, ")")
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||
impl<$($arg,)* $ret> Annotate for fun<($($arg,)*), $ret>
|
||||
where
|
||||
$($arg: Annotate,)*
|
||||
$ret: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "fun(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, ", ")?; } write!(f, "{}", $arg::annotation())?; _n += 1;)*
|
||||
write!(f, "): {}", $ret::annotation())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl_fun!(fn($($arg),*));
|
||||
};
|
||||
}
|
||||
|
||||
impl_fun!(fn() -> A);
|
||||
impl_fun!(fn(A) -> B);
|
||||
impl_fun!(fn(A, B) -> C);
|
||||
impl_fun!(fn(A, B, C) -> D);
|
||||
impl_fun!(fn(A, B, C, D) -> E);
|
||||
impl_fun!(fn(A, B, C, D, E) -> F);
|
||||
impl_fun!(fn(A, B, C, D, E, F) -> G);
|
||||
impl_fun!(fn(A, B, C, D, E, F, G) -> H);
|
||||
impl_fun!(fn(A, B, C, D, E, F, G, H) -> I);
|
||||
impl_fun!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||
|
||||
pub struct userdata(Marker);
|
||||
|
||||
impl Annotate for userdata {
|
||||
fn annotation() -> impl Display {
|
||||
"userdata"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct thread(Marker);
|
||||
|
||||
impl Annotate for thread {
|
||||
fn annotation() -> impl Display {
|
||||
"thread"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct cdata(Marker);
|
||||
|
||||
impl Annotate for cdata {
|
||||
fn annotation() -> impl Display {
|
||||
"cdata"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Either<T, U>(Marker, PhantomData<(T, U)>);
|
||||
|
||||
impl<T, U> Annotate for Either<T, U>
|
||||
where
|
||||
T: Annotate,
|
||||
U: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("({} | {})", T::annotation(), U::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OneOf<X>(Marker, PhantomData<X>);
|
||||
|
||||
macro_rules! impl_oneof {
|
||||
($($ty:ident),+) => {
|
||||
impl<$($ty),+> Annotate for OneOf<($($ty,)+)>
|
||||
where
|
||||
$($ty: Annotate),+
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
write!(f, "(")?;
|
||||
let mut _n = 0;
|
||||
$(if _n != 0 { write!(f, " | ")?; } write!(f, "{}", $ty::annotation())?; _n += 1;)*
|
||||
write!(f, ")")
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_oneof!(A, B);
|
||||
impl_oneof!(A, B, C);
|
||||
impl_oneof!(A, B, C, D);
|
||||
impl_oneof!(A, B, C, D, E);
|
||||
impl_oneof!(A, B, C, D, E, F);
|
||||
impl_oneof!(A, B, C, D, E, F, G);
|
||||
impl_oneof!(A, B, C, D, E, F, G, H);
|
||||
impl_oneof!(A, B, C, D, E, F, G, H, I);
|
||||
|
||||
impl<T> Annotate for Option<T>
|
||||
where
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}?", T::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Annotate for Result<T, E>
|
||||
where
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}", T::annotation())
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use crate::{
|
||||
__internal::{disp, display, type_id},
|
||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||
};
|
||||
use std::{ffi::c_int, fmt::Display};
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum lua_option<T> {
|
||||
None, // __tag = 0
|
||||
Some(T), // __tag = 1
|
||||
}
|
||||
|
||||
unsafe impl<T: Type + 'static> Type for lua_option<T> {
|
||||
fn name() -> impl Display {
|
||||
display!("__option_{:x}", type_id::<T>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct {} {name}", Self::name())
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type + 'static> Cdef for lua_option<T> {
|
||||
fn build(b: &mut CdefBuilder) {
|
||||
b.field::<c_int>("__tag");
|
||||
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type + 'static> Metatype for lua_option<T> {
|
||||
type Target = Self;
|
||||
fn build(_b: &mut MetatypeBuilder) {}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi<Into: 'static>> IntoFfi for Option<T> {
|
||||
type Into = lua_option<T::Into>;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
match self {
|
||||
Some(value) => lua_option::Some(T::convert(value)),
|
||||
None => lua_option::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn require_owned() -> bool {
|
||||
// lua_option is only used to transmit information about whether we have a value or not and
|
||||
// is forgotten immediately after use, so there is no need for an owned option
|
||||
false
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
disp(move |f| {
|
||||
write!(f, "if {ret}.__tag ~= 0 then ")?;
|
||||
match T::Into::ty() {
|
||||
TypeType::Void => write!(f, "{ret} = nil; ")?, // for void options, we don't have a __value
|
||||
TypeType::Primitive => {
|
||||
// can always copy primitives to stack
|
||||
write!(f, "{ret} = {ret}.__value; {}", T::postlude(ret))?;
|
||||
}
|
||||
TypeType::Aggregate => {
|
||||
let ct = T::Into::name();
|
||||
if T::require_owned() {
|
||||
// inner value requires ownership; copy it into its own cdata and forget
|
||||
// option.
|
||||
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
|
||||
write!(f, "{}", T::postlude(ret))?;
|
||||
} else {
|
||||
// inner value is a "temporary" like an option itself and doesn't require
|
||||
// full ownership of itself. we just need to keep the option object alive
|
||||
// until its postlude completes.
|
||||
write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
|
||||
write!(f, "do {}end; ", T::postlude(ret))?;
|
||||
write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original option alive
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(f, "else {ret} = nil; end; ")
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
__internal::{disp, display, type_id},
|
||||
Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||
__internal::{disp, display},
|
||||
Cdef, CdefBuilder, IntoFfi, Type, TypeBuilder, TypeType,
|
||||
string::{DROP_BUFFER_FN, lua_buffer},
|
||||
};
|
||||
use std::{ffi::c_int, fmt::Display};
|
||||
@ -12,9 +12,9 @@ pub enum lua_result<T> {
|
||||
Ok(T), // __tag = 1
|
||||
}
|
||||
|
||||
unsafe impl<T: Type + 'static> Type for lua_result<T> {
|
||||
unsafe impl<T: Type> Type for lua_result<T> {
|
||||
fn name() -> impl Display {
|
||||
display!("__result_{:x}", type_id::<T>())
|
||||
display!("result__{}", T::name())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
@ -26,11 +26,11 @@ unsafe impl<T: Type + 'static> Type for lua_result<T> {
|
||||
}
|
||||
|
||||
fn build(b: &mut TypeBuilder) {
|
||||
b.cdef::<Self>().metatype::<Self>();
|
||||
b.cdef::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type + 'static> Cdef for lua_result<T> {
|
||||
unsafe impl<T: Type> Cdef for lua_result<T> {
|
||||
fn build(b: &mut CdefBuilder) {
|
||||
b.field::<c_int>("__tag").inner_union(|b| {
|
||||
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||
@ -39,12 +39,7 @@ unsafe impl<T: Type + 'static> Cdef for lua_result<T> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type + 'static> Metatype for lua_result<T> {
|
||||
type Target = Self;
|
||||
fn build(_b: &mut MetatypeBuilder) {}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi<Into: 'static>, E: Display> IntoFfi for Result<T, E> {
|
||||
unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
|
||||
type Into = lua_result<T::Into>;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
@ -54,44 +49,22 @@ unsafe impl<T: IntoFfi<Into: 'static>, E: Display> IntoFfi for Result<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
fn require_owned() -> bool {
|
||||
// lua_result is only used to transmit information about whether an operation succeeded or
|
||||
// not and is forgotten immediately after use, so there is no need for an owned result
|
||||
false
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
disp(move |f| {
|
||||
let ct = T::Into::name();
|
||||
write!(f, "if {ret}.__tag ~= 0 then ")?;
|
||||
match T::Into::ty() {
|
||||
TypeType::Void => write!(f, "{ret} = nil; ")?, // for void results, we don't have a __value
|
||||
TypeType::Primitive => {
|
||||
// can always copy primitives to stack
|
||||
write!(f, "{ret} = {ret}.__value; {}", T::postlude(ret))?;
|
||||
}
|
||||
TypeType::Aggregate => {
|
||||
let ct = T::Into::name();
|
||||
if T::require_owned() {
|
||||
// inner value requires ownership; copy it into its own cdata and forget
|
||||
// result.
|
||||
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
|
||||
write!(f, "{}", T::postlude(ret))?;
|
||||
} else {
|
||||
// inner value is a "temporary" like a result itself and doesn't require
|
||||
// full ownership of itself. we just need to keep the result object alive
|
||||
// until its postlude completes.
|
||||
write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
|
||||
write!(f, "do {}end; ", T::postlude(ret))?;
|
||||
write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original result alive
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeType::Void => write!(f, "{ret} = nil; "), // for void results, we don't have a __value
|
||||
TypeType::Primitive => write!(f, "{ret} = {ret}.__value; "),
|
||||
TypeType::Aggregate => write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); "),
|
||||
}?;
|
||||
write!(f, "{}", T::postlude(ret))?;
|
||||
write!(
|
||||
f,
|
||||
"else \
|
||||
local {ret}_err = __intern({ret}.__err.__ptr, {ret}.__err.__len); \
|
||||
local __{ret}_msg = __intern({ret}.__err.__ptr, {ret}.__err.__len); \
|
||||
__C.{DROP_BUFFER_FN}({ret}.__err); \
|
||||
return error({ret}_err); \
|
||||
return error(__{ret}_msg); \
|
||||
end; "
|
||||
)
|
||||
})
|
||||
|
@ -4,18 +4,18 @@ use crate::{
|
||||
};
|
||||
use bstr::{BStr, BString};
|
||||
use luaffi_impl::{cdef, metatype};
|
||||
use std::{fmt::Display, mem::ManuallyDrop, slice};
|
||||
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice};
|
||||
|
||||
pub(crate) const IS_UTF8_FN: &str = "__lf_is_utf8";
|
||||
pub(crate) const DROP_BUFFER_FN: &str = "__lf_drop_buffer";
|
||||
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
|
||||
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
||||
|
||||
#[unsafe(export_name = "__lf_is_utf8")]
|
||||
#[unsafe(export_name = "luaffi_is_utf8")]
|
||||
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
|
||||
debug_assert!(!ptr.is_null());
|
||||
simdutf8::basic::from_utf8(unsafe { slice::from_raw_parts(ptr, len) }).is_ok()
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "__lf_drop_buffer")]
|
||||
#[unsafe(export_name = "luaffi_drop_buffer")]
|
||||
unsafe extern "C" fn __drop_buffer(buf: *mut lua_buffer) {
|
||||
debug_assert!(!buf.is_null());
|
||||
debug_assert!(!unsafe { (*buf).__ptr.is_null() });
|
||||
@ -42,7 +42,6 @@ impl lua_buf {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "option_string_abi")]
|
||||
pub(crate) fn null() -> Self {
|
||||
Self {
|
||||
__ptr: ptr::null(),
|
||||
@ -72,7 +71,6 @@ impl lua_buffer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "option_string_abi")]
|
||||
pub(crate) fn null() -> Self {
|
||||
Self {
|
||||
__ptr: ptr::null_mut(),
|
||||
@ -156,14 +154,8 @@ unsafe impl IntoFfi for &'static [u8] {
|
||||
lua_buf::new(self)
|
||||
}
|
||||
|
||||
fn require_owned() -> bool {
|
||||
// lua_buf is only used to have its contents interned then forgotten immediately; no need
|
||||
// for ownership of it
|
||||
false
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!("{ret} = __intern({ret}.__ptr, {ret}.__len); ")
|
||||
display!("{ret} = __intern({ret}.__ptr, {ret}.__len)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,15 +166,9 @@ unsafe impl IntoFfi for Vec<u8> {
|
||||
lua_buffer::new(self)
|
||||
}
|
||||
|
||||
fn require_owned() -> bool {
|
||||
// lua_buffer is only used to have its contents interned then forgotten immediately; no need
|
||||
// for ownership of it
|
||||
false
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!(
|
||||
"local {ret}_buf = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}({ret}_buf); "
|
||||
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; "
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -230,64 +216,56 @@ impl_into_via!(&'static str, &'static BStr);
|
||||
impl_into_via!(BString, Vec<u8>);
|
||||
impl_into_via!(String, BString);
|
||||
|
||||
// `Option<String>: From/IntoFfi` isn't implemented because it conflicts with the generic
|
||||
// `Option<T>: From/IntoFfi` impl and rust doesn't have specialisation yet (and probably not anytime
|
||||
// soon). this is fine for now because we have specialisation for string-like parameters implemented
|
||||
// in the #[metatype] macro already, and string returns wrapped in `Option<T>` isn't much additional
|
||||
// overhead.
|
||||
#[cfg(feature = "option_string_abi")]
|
||||
mod impl_option_string {
|
||||
macro_rules! impl_optional_from {
|
||||
($ty:ty) => {
|
||||
unsafe impl<'s> FromFfi for Option<$ty> {
|
||||
type From = <$ty as FromFfi>::From;
|
||||
macro_rules! impl_optional_from {
|
||||
($ty:ty) => {
|
||||
unsafe impl<'s> FromFfi for Option<$ty> {
|
||||
type From = <$ty as FromFfi>::From;
|
||||
|
||||
fn require_keepalive() -> bool {
|
||||
<$ty as FromFfi>::require_keepalive()
|
||||
}
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
// just pass a null pointer if argument is nil
|
||||
display!(
|
||||
"if {arg} ~= nil then {}end; ",
|
||||
<$ty as FromFfi>::prelude(arg)
|
||||
)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
from.map(|s| <$ty as FromFfi>::convert(Some(s)))
|
||||
}
|
||||
fn require_keepalive() -> bool {
|
||||
<$ty as FromFfi>::require_keepalive()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_from!(&'s [u8]);
|
||||
impl_optional_from!(&'s BStr);
|
||||
impl_optional_from!(&'s str);
|
||||
|
||||
macro_rules! impl_optional_into {
|
||||
($ty:ty, $null:expr) => {
|
||||
unsafe impl IntoFfi for Option<$ty> {
|
||||
type Into = <$ty as IntoFfi>::Into;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
self.map_or($null, <$ty as IntoFfi>::convert)
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!(
|
||||
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
|
||||
<$ty as IntoFfi>::postlude(ret)
|
||||
)
|
||||
}
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
// just pass a null pointer if argument is nil
|
||||
display!(
|
||||
"if {arg} ~= nil then {}end; ",
|
||||
<$ty as FromFfi>::prelude(arg)
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_into!(&'static [u8], lua_buf::null());
|
||||
impl_optional_into!(&'static BStr, lua_buf::null());
|
||||
impl_optional_into!(&'static str, lua_buf::null());
|
||||
impl_optional_into!(Vec<u8>, lua_buffer::null());
|
||||
impl_optional_into!(BString, lua_buffer::null());
|
||||
impl_optional_into!(String, lua_buffer::null());
|
||||
fn convert(from: Self::From) -> Self {
|
||||
from.map(|s| <$ty as FromFfi>::convert(Some(s)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_from!(&'s [u8]);
|
||||
impl_optional_from!(&'s BStr);
|
||||
impl_optional_from!(&'s str);
|
||||
|
||||
macro_rules! impl_optional_into {
|
||||
($ty:ty, $null:expr) => {
|
||||
unsafe impl IntoFfi for Option<$ty> {
|
||||
type Into = <$ty as IntoFfi>::Into;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
self.map_or($null, <$ty as IntoFfi>::convert)
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!(
|
||||
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
|
||||
<$ty as IntoFfi>::postlude(ret)
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_into!(&'static [u8], lua_buf::null());
|
||||
impl_optional_into!(&'static BStr, lua_buf::null());
|
||||
impl_optional_into!(&'static str, lua_buf::null());
|
||||
impl_optional_into!(Vec<u8>, lua_buffer::null());
|
||||
impl_optional_into!(BString, lua_buffer::null());
|
||||
impl_optional_into!(String, lua_buffer::null());
|
||||
|
@ -1,11 +1,7 @@
|
||||
[package]
|
||||
name = "luaffi_impl"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
@ -5,26 +5,18 @@ use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
pub struct Args {
|
||||
module: Option<String>,
|
||||
}
|
||||
pub struct Args {}
|
||||
|
||||
pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
let (name, impl_type, impl_module, impl_cdef) = match item {
|
||||
pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
let (name, impl_type, impl_cdef) = match item {
|
||||
Item::Struct(ref mut str) => (
|
||||
str.ident.clone(),
|
||||
generate_type(&str.ident)?,
|
||||
args.module
|
||||
.map(|name| generate_module(&name, &str.ident))
|
||||
.transpose()?,
|
||||
generate_cdef_structure(str)?,
|
||||
),
|
||||
Item::Enum(ref mut enu) => (
|
||||
enu.ident.clone(),
|
||||
generate_type(&enu.ident)?,
|
||||
args.module
|
||||
.map(|name| generate_module(&name, &enu.ident))
|
||||
.transpose()?,
|
||||
generate_cdef_enum(enu)?,
|
||||
),
|
||||
_ => syn_error!(item, "expected struct or enum"),
|
||||
@ -43,7 +35,6 @@ pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impl_type
|
||||
#impl_module
|
||||
#impl_cdef
|
||||
}
|
||||
))
|
||||
@ -51,11 +42,14 @@ pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
|
||||
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let name = ty.unraw().to_string();
|
||||
let span = ty.span();
|
||||
let name = LitStr::new(&ty.unraw().to_string(), span);
|
||||
|
||||
Ok(quote!(
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Type for #ty {
|
||||
fn name() -> impl ::std::fmt::Display { #name }
|
||||
fn name() -> impl ::std::fmt::Display {
|
||||
#name
|
||||
}
|
||||
|
||||
fn ty() -> #ffi::TypeType {
|
||||
#ffi::TypeType::Aggregate
|
||||
@ -70,10 +64,6 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
impl #ffi::Annotate for #ty {
|
||||
fn annotation() -> impl ::std::fmt::Display { #name }
|
||||
}
|
||||
|
||||
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
|
||||
unsafe impl #ffi::IntoFfi for #ty {
|
||||
type Into = Self;
|
||||
@ -82,16 +72,6 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_module(name: &str, ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
|
||||
Ok(quote!(
|
||||
impl #ffi::Module for #ty {
|
||||
fn name() -> impl ::std::fmt::Display { #name }
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
str.generics.params.is_empty(),
|
||||
@ -101,9 +81,10 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &str.ident;
|
||||
let build = generate_cdef_build(&parse_cfields(&mut str.fields)?)?;
|
||||
let span = ty.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
||||
|
||||
Ok(quote!(
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) { #build }
|
||||
}
|
||||
@ -119,16 +100,18 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &enu.ident;
|
||||
let span = ty.span();
|
||||
let build = enu
|
||||
.variants
|
||||
.iter_mut()
|
||||
.map(|variant| {
|
||||
let build = generate_cdef_build(&parse_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote!(b.inner_struct(|b| { #build })))
|
||||
let span = variant.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote_spanned!(span => b.inner_struct(|b| { #build })))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(quote!(
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) {
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build;)* });
|
||||
@ -148,7 +131,7 @@ struct CFieldAttrs {
|
||||
opaque: bool,
|
||||
}
|
||||
|
||||
fn parse_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
match fields {
|
||||
Fields::Named(fields) => fields.named.iter_mut(),
|
||||
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
||||
@ -172,11 +155,12 @@ fn parse_cfield_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
let mut parsed = CFieldAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
let path = attr.path();
|
||||
if path.is_ident("opaque") {
|
||||
parsed.opaque = true;
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
if let Some(name) = attr.path().get_ident() {
|
||||
if name == "opaque" {
|
||||
parsed.opaque = true;
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
@ -201,7 +185,7 @@ fn generate_cdef_build(fields: &[CField]) -> Result<TokenStream> {
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let ty = &field.ty;
|
||||
let offset = offset(i);
|
||||
body.push(quote!(
|
||||
body.push(quote_spanned!(ty.span() =>
|
||||
// round up current offset to the alignment of field type for field offset
|
||||
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
||||
align = #max(align, #align_of::<#ty>());
|
||||
|
@ -1,6 +1,7 @@
|
||||
use darling::{FromMeta, ast::NestedMeta};
|
||||
use proc_macro::TokenStream as TokenStream1;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod cdef;
|
||||
mod metatype;
|
||||
@ -16,10 +17,8 @@ pub fn cdef(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn metatype(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
NestedMeta::parse_meta_list(args.into())
|
||||
.and_then(|meta| metatype::Args::from_list(&meta).map_err(Into::into))
|
||||
.and_then(|args| metatype::transform(args, syn::parse(input)?))
|
||||
pub fn metatype(_args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
metatype::transform(parse_macro_input!(input))
|
||||
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
|
||||
.into()
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
use std::env;
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
use syn::{spanned::Spanned, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
@ -57,13 +57,13 @@ pub fn is_unit(ty: &Type) -> bool {
|
||||
|
||||
pub fn is_primitivelike(ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => return true, // unit type
|
||||
Type::Reference(_) | Type::Ptr(_) => return true,
|
||||
Type::Paren(paren) => return is_primitivelike(&paren.elem),
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => true, // unit type
|
||||
Type::Reference(_) | Type::Ptr(_) => true,
|
||||
Type::Paren(paren) => is_primitivelike(&paren.elem),
|
||||
Type::Path(path) => {
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
return matches!(
|
||||
name.unraw().to_string().as_str(),
|
||||
matches!(
|
||||
format!("{name}").as_str(),
|
||||
"bool"
|
||||
| "u8"
|
||||
| "u16"
|
||||
@ -94,88 +94,11 @@ pub fn is_primitivelike(ty: &Type) -> bool {
|
||||
| "c_size_t"
|
||||
| "c_ssize_t"
|
||||
| "c_ptrdiff_t"
|
||||
);
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StringLike {
|
||||
SliceU8,
|
||||
Str,
|
||||
BStr,
|
||||
}
|
||||
|
||||
pub fn is_stringlike(ty: &Type) -> Option<StringLike> {
|
||||
if let Type::Reference(ty) = ty
|
||||
&& ty.mutability.is_none()
|
||||
&& ty.lifetime.is_none()
|
||||
{
|
||||
Some(match *ty.elem {
|
||||
Type::Slice(ref slice) => {
|
||||
// match &[u8]
|
||||
if let Type::Path(ref path) = *slice.elem
|
||||
&& let Some(name) = path.path.get_ident()
|
||||
{
|
||||
match name.unraw().to_string().as_str() {
|
||||
"u8" => StringLike::SliceU8,
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Type::Path(ref path) => {
|
||||
// match &str or &BStr
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
match name.unraw().to_string().as_str() {
|
||||
"str" => StringLike::Str,
|
||||
"BStr" => StringLike::BStr,
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_optionlike(ty: &Type) -> Option<&Type> {
|
||||
if let Type::Path(path) = ty
|
||||
&& path.path.leading_colon.is_none()
|
||||
&& path.path.segments.len() == 1
|
||||
&& let Some(segment) = path.path.segments.get(0)
|
||||
&& segment.ident == "Option"
|
||||
&& let PathArguments::AngleBracketed(ref angle) = segment.arguments
|
||||
&& angle.args.len() == 1
|
||||
&& let Some(GenericArgument::Type(ty)) = angle.args.get(0)
|
||||
{
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
[package]
|
||||
name = "luaify"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
@ -14,6 +10,3 @@ proc-macro = true
|
||||
proc-macro2 = "1.0.95"
|
||||
quote = "1.0.40"
|
||||
syn = { version = "2.0.103", features = ["full", "visit-mut"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
|
@ -14,12 +14,6 @@ pub fn generate(expr: &Expr) -> Result<TokenStream> {
|
||||
Ok(f.done())
|
||||
}
|
||||
|
||||
pub fn generate_chunk(block: &Block) -> Result<TokenStream> {
|
||||
let mut f = Formatter::default();
|
||||
generate_block(&mut f, &block, Context::stmt(false))?;
|
||||
Ok(f.done())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Formatter {
|
||||
buf: String,
|
||||
@ -916,7 +910,7 @@ fn generate_pat_typed(f: &mut Formatter, typed: &PatType, cx: PatContext) -> Res
|
||||
assert_no_attrs!(typed);
|
||||
match *typed.ty {
|
||||
Type::Infer(_) => generate_pat(f, &typed.pat, cx),
|
||||
ref ty => syn_error!(ty, "cannot specify type"),
|
||||
ref ty => syn_error!(ty, "cannot have type"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,57 +1,4 @@
|
||||
//! # 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::{
|
||||
generate::{generate, generate_chunk},
|
||||
transform::{transform, transform_chunk},
|
||||
};
|
||||
use crate::{generate::generate, transform::transform};
|
||||
use proc_macro::TokenStream as TokenStream1;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
@ -60,7 +7,6 @@ mod generate;
|
||||
mod transform;
|
||||
mod utils;
|
||||
|
||||
/// Generates Lua code for the given expression.
|
||||
#[proc_macro]
|
||||
pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
||||
let mut expr = parse_macro_input!(input);
|
||||
@ -70,14 +16,3 @@ pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Generates Lua code for the given block.
|
||||
#[proc_macro]
|
||||
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
|
||||
let mut block = parse_macro_input!(input);
|
||||
match transform_chunk(&mut block).and_then(|()| generate_chunk(&block)) {
|
||||
Ok(s) => s,
|
||||
Err(err) => err.into_compile_error().into_token_stream(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::utils::syn_error;
|
||||
use crate::utils::{LuaType, expr_ident, pat_ident, syn_error, wrap_expr_block};
|
||||
use quote::format_ident;
|
||||
use std::mem;
|
||||
use syn::{spanned::*, visit_mut::*, *};
|
||||
|
||||
pub fn transform(expr: &mut Expr) -> Result<()> {
|
||||
@ -7,12 +9,6 @@ pub fn transform(expr: &mut Expr) -> Result<()> {
|
||||
visitor.result
|
||||
}
|
||||
|
||||
pub fn transform_chunk(block: &mut Block) -> Result<()> {
|
||||
let mut visitor = Visitor::new();
|
||||
visitor.visit_block_mut(block);
|
||||
visitor.result
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Visitor {
|
||||
result: Result<()>,
|
||||
@ -25,6 +21,27 @@ impl Visitor {
|
||||
}
|
||||
|
||||
impl VisitMut for Visitor {
|
||||
fn visit_expr_closure_mut(&mut self, clo: &mut ExprClosure) {
|
||||
match self.transform_expr_closure(clo) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_closure_mut(self, clo),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_fn_mut(&mut self, func: &mut ItemFn) {
|
||||
match self.transform_function(func) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_item_fn_mut(self, func),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_mut(&mut self, expr: &mut Expr) {
|
||||
match self.transform_expr(expr) {
|
||||
res @ Err(_) => self.result = res,
|
||||
_ => visit_expr_mut(self, expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_unary_mut(&mut self, un: &mut ExprUnary) {
|
||||
match self.transform_unary(un) {
|
||||
res @ Err(_) => self.result = res,
|
||||
@ -41,9 +58,147 @@ impl VisitMut for Visitor {
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
fn transform_expr_closure(&mut self, clo: &mut ExprClosure) -> Result<()> {
|
||||
//
|
||||
// transforms a closure expression with input type annotations by removing the annotations
|
||||
// and inserting `as` casts at the start.
|
||||
//
|
||||
// before:
|
||||
// |a: string, b: number| { ... }
|
||||
// after:
|
||||
// |a, b| { a as string; b as number; ... }
|
||||
//
|
||||
let mut checks: Vec<Stmt> = vec![];
|
||||
for input in clo.inputs.iter_mut() {
|
||||
match input {
|
||||
Pat::Ident(_) => {}
|
||||
Pat::Type(typed) => {
|
||||
let ident = pat_ident(&typed.pat)?;
|
||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
||||
match (&*ty).try_into()? {
|
||||
LuaType::Any => {}
|
||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !checks.is_empty() {
|
||||
let mut body = wrap_expr_block(&clo.body);
|
||||
body.stmts.splice(..0, checks);
|
||||
clo.body = Box::new(parse_quote! { #body });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_function(&mut self, func: &mut ItemFn) -> Result<()> {
|
||||
//
|
||||
// transforms a function item with input type annotations by removing the annotations
|
||||
// and inserting `as` casts at the start.
|
||||
//
|
||||
// before:
|
||||
// fn my_func(self: table, a: string) { ... }
|
||||
// after:
|
||||
// fn my_func(self: _, a: _) { self as table; a as string; ... }
|
||||
//
|
||||
let mut checks: Vec<Stmt> = vec![];
|
||||
for input in func.sig.inputs.iter_mut() {
|
||||
if let Some((ident, ty)) = match input {
|
||||
FnArg::Receiver(recv) if recv.colon_token.is_some() => {
|
||||
let ty = mem::replace(&mut recv.ty, parse_quote!(_));
|
||||
recv.colon_token = None;
|
||||
Some((Ident::new("self", recv.self_token.span()), ty))
|
||||
}
|
||||
FnArg::Typed(typed) => {
|
||||
let ident = pat_ident(&typed.pat)?;
|
||||
let ty = mem::replace(&mut typed.ty, parse_quote!(_));
|
||||
Some((ident, ty))
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
match (&*ty).try_into()? {
|
||||
LuaType::Any => {}
|
||||
_ => checks.push(parse_quote! { #ident as #ty; }),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
func.block.stmts.splice(..0, checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_expr(&mut self, expr: &mut Expr) -> Result<()> {
|
||||
self.transform_expr_cast(expr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_expr_cast(&mut self, expr: &mut Expr) -> Result<()> {
|
||||
//
|
||||
// transforms an `as` cast expression into a block expression containing a runtime
|
||||
// lua type check.
|
||||
//
|
||||
// before:
|
||||
// var as string
|
||||
// after:
|
||||
// { if type(var) != "string" { error(...) } }
|
||||
//
|
||||
if let Expr::Cast(cast) = expr {
|
||||
let arg = (*cast.expr).clone();
|
||||
let mut prelude: Option<Stmt> = None;
|
||||
let ty: LuaType = (&*cast.ty).try_into()?;
|
||||
let ty_str = format!("{ty}");
|
||||
let (ident, msg) = match expr_ident(&arg) {
|
||||
Ok(ident) => (ident.clone(), format!("{ty} expected in '{ident}', got ")),
|
||||
Err(_) => {
|
||||
let ident = Ident::new("_", arg.span());
|
||||
prelude = Some(parse_quote! { let #ident = #arg; });
|
||||
(ident, format!("{ty} expected, got "))
|
||||
}
|
||||
};
|
||||
|
||||
let tmp = format_ident!("__{ident}");
|
||||
let span = cast.span();
|
||||
*expr = match ty {
|
||||
LuaType::Any => parse_quote_spanned!(span => {}),
|
||||
LuaType::Nil => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
assert(#ident == (), concat!(#msg, r#type(#ident)));
|
||||
}),
|
||||
LuaType::Number => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
let #tmp = #ident;
|
||||
#ident = tonumber(#ident);
|
||||
assert(#ident != (), concat!(#msg, r#type(#tmp)));
|
||||
}),
|
||||
LuaType::Integer => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
let #tmp = #ident;
|
||||
#ident = tonumber(#ident);
|
||||
assert(#ident != () && math::floor(#ident) == #ident, concat!(#msg, r#type(#tmp)));
|
||||
}),
|
||||
LuaType::String => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
if r#type(#ident) == "number" {
|
||||
#ident = tostring(#ident);
|
||||
} else {
|
||||
assert(r#type(#ident) == "string", concat!(#msg, r#type(#ident)));
|
||||
}
|
||||
}),
|
||||
_ => parse_quote_spanned!(span => {
|
||||
#prelude
|
||||
assert(r#type(#ident) == #ty_str, concat!(#msg, r#type(#ident)));
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_unary(&mut self, un: &mut ExprUnary) -> Result<()> {
|
||||
//
|
||||
// separates a nested negation unary operator with parentheses, because the double hyphen
|
||||
// separates a nested negation unary operator with parentheses, because double hyphen
|
||||
// `--` indicates a comment in lua.
|
||||
//
|
||||
// before:
|
||||
@ -55,7 +210,7 @@ impl Visitor {
|
||||
&& let Expr::Unary(ref inner) = *un.expr
|
||||
&& let UnOp::Neg(_) = inner.op
|
||||
{
|
||||
un.expr = Box::new(parse_quote_spanned!(inner.span() => (#inner)));
|
||||
un.expr = Box::new(parse_quote!((#inner)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,4 +1,5 @@
|
||||
use syn::*;
|
||||
use std::fmt;
|
||||
use syn::{ext::*, spanned::*, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
@ -23,3 +24,90 @@ pub fn wrap_expr_block(expr: &Expr) -> Block {
|
||||
expr => parse_quote!({ #expr }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expr_ident(expr: &Expr) -> Result<&Ident> {
|
||||
match expr {
|
||||
Expr::Path(path) => path.path.require_ident(),
|
||||
_ => syn_error!(expr, "expected ident"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pat_ident(pat: &Pat) -> Result<Ident> {
|
||||
Ok(match pat {
|
||||
Pat::Ident(ident) => match ident.subpat {
|
||||
Some((_, ref subpat)) => syn_error!(subpat, "unexpected subpattern"),
|
||||
None => ident.ident.clone(),
|
||||
},
|
||||
Pat::Wild(wild) => Ident::new("_", wild.span()),
|
||||
_ => syn_error!(pat, "expected ident"),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LuaType {
|
||||
Any,
|
||||
Nil,
|
||||
Boolean,
|
||||
Lightuserdata,
|
||||
Number,
|
||||
Integer,
|
||||
String,
|
||||
Table,
|
||||
Function,
|
||||
Userdata,
|
||||
Thread,
|
||||
Cdata,
|
||||
}
|
||||
|
||||
impl fmt::Display for LuaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
LuaType::Any => write!(f, "any"),
|
||||
LuaType::Nil => write!(f, "nil"),
|
||||
LuaType::Boolean => write!(f, "boolean"),
|
||||
LuaType::Lightuserdata => write!(f, "lightuserdata"),
|
||||
LuaType::Number => write!(f, "number"),
|
||||
LuaType::Integer => write!(f, "integer"),
|
||||
LuaType::String => write!(f, "string"),
|
||||
LuaType::Table => write!(f, "table"),
|
||||
LuaType::Function => write!(f, "function"),
|
||||
LuaType::Userdata => write!(f, "userdata"),
|
||||
LuaType::Thread => write!(f, "thread"),
|
||||
LuaType::Cdata => write!(f, "cdata"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Ident> for LuaType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Ident) -> Result<Self> {
|
||||
Ok(match format!("{}", value.unraw()).as_str() {
|
||||
"any" => Self::Any,
|
||||
"nil" => Self::Nil,
|
||||
"boolean" => Self::Boolean,
|
||||
"lightuserdata" => Self::Lightuserdata,
|
||||
"number" => Self::Number,
|
||||
"integer" => Self::Integer,
|
||||
"string" => Self::String,
|
||||
"table" => Self::Table,
|
||||
"function" => Self::Function,
|
||||
"userdata" => Self::Userdata,
|
||||
"thread" => Self::Thread,
|
||||
"cdata" => Self::Cdata,
|
||||
_ => syn_error!(value, "invalid lua type"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Type> for LuaType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Type) -> Result<Self> {
|
||||
match value {
|
||||
Type::Infer(_) => Ok(Self::Any),
|
||||
Type::Path(path) if path.qself.is_none() => path.path.require_ident()?.try_into(),
|
||||
_ => syn_error!(value, "invalid lua type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use luaify::{luaify, luaify_chunk};
|
||||
use pretty_assertions::assert_eq;
|
||||
use luaify::luaify;
|
||||
|
||||
#[test]
|
||||
fn raw_ident() {
|
||||
@ -77,6 +76,13 @@ fn local_fn() {
|
||||
}),
|
||||
r#"function(a,b)local function inner(c,d)end;return inner;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|| {
|
||||
fn check(self: string, arg: number) {}
|
||||
inner
|
||||
}),
|
||||
r#"function()local function check(self,arg)do if type(self)=="number"then self=tostring(self);else assert(type(self)=="string","string expected in \'self\', got "..type(self));end;end;do local __arg=arg;arg=tonumber(arg);assert(arg~=nil,"number expected in \'arg\', got "..type(__arg));end;end;return inner;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -201,6 +207,37 @@ fn loops() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_checks() {
|
||||
assert_eq!(luaify!(|s| {}), r#"function(s)end"#);
|
||||
assert_eq!(
|
||||
luaify!(|s: table| {}),
|
||||
r#"function(s)do assert(type(s)=="table","table expected in \'s\', got "..type(s));end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as string }),
|
||||
r#"function(s)do if type(s)=="number"then s=tostring(s);else assert(type(s)=="string","string expected in \'s\', got "..type(s));end;end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as number }),
|
||||
r#"function(s)do local __s=s;s=tonumber(s);assert(s~=nil,"number expected in \'s\', got "..type(__s));end;end"#
|
||||
);
|
||||
assert_eq!(
|
||||
luaify!(|s| { s as nil }),
|
||||
r#"function(s)do assert(s==nil,"nil expected in \'s\', got "..type(s));end;end"#
|
||||
);
|
||||
assert_eq!(luaify!(|s| { s as any }), r#"function(s)do end;end"#);
|
||||
|
||||
assert_eq!(
|
||||
luaify!(|s| {
|
||||
let (ok, res) = coroutine::r#yield(thread);
|
||||
ok as boolean;
|
||||
res as nil;
|
||||
}),
|
||||
r#"function(s)local ok,res=coroutine.yield(thread);do assert(type(ok)=="boolean","boolean expected in \'ok\', got "..type(ok));end;do assert(res==nil,"nil expected in \'res\', got "..type(res));end;end"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat() {
|
||||
assert_eq!(luaify!(concat!(a)), r#"a"#);
|
||||
@ -365,19 +402,3 @@ fn length() {
|
||||
r#"local a,b,c=#a,#b,#c;"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chunk() {
|
||||
assert_eq!(
|
||||
luaify_chunk!({
|
||||
if a == b {
|
||||
c()
|
||||
} else if b == c {
|
||||
a()
|
||||
} else {
|
||||
d()
|
||||
}
|
||||
}),
|
||||
"if a==b then c();elseif b==c then a();else d();end;"
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
[package]
|
||||
name = "luajit-sys"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
@ -1,18 +1,3 @@
|
||||
//! 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)]
|
||||
use std::{ffi::*, ptr};
|
||||
|
||||
|
@ -1,11 +1,7 @@
|
||||
[package]
|
||||
name = "luajit"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
runtime = ["luajit-sys/runtime"]
|
||||
@ -14,5 +10,6 @@ unwind = ["luajit-sys/unwind"]
|
||||
[dependencies]
|
||||
bitflags = { version = "2.9.1", features = ["std"] }
|
||||
bstr = "1.12.0"
|
||||
luaffi = { path = "../luaffi" }
|
||||
luajit-sys = { path = "../luajit-sys" }
|
||||
luaffi = { version = "0.1.0", path = "../luaffi" }
|
||||
luajit-sys = { version = "0.1.0", path = "../luajit-sys" }
|
||||
thiserror = "2.0.12"
|
||||
|
File diff suppressed because it is too large
Load Diff
25
src/lib.rs
25
src/lib.rs
@ -1,27 +1,2 @@
|
||||
//! luby standard library.
|
||||
#[cfg(feature = "fs")]
|
||||
pub use lb::fs;
|
||||
#[cfg(feature = "net")]
|
||||
pub use lb::net;
|
||||
#[cfg(feature = "task")]
|
||||
pub use lb::task;
|
||||
#[cfg(feature = "time")]
|
||||
pub use lb::time;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use lb_sqlite::sqlite;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
|
||||
#[cfg(feature = "task")]
|
||||
rt.module::<task::lb_tasklib>();
|
||||
#[cfg(feature = "time")]
|
||||
rt.module::<time::lb_timelib>();
|
||||
#[cfg(feature = "fs")]
|
||||
rt.module::<fs::lb_fslib>();
|
||||
#[cfg(feature = "net")]
|
||||
rt.module::<net::lb_netlib>();
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
rt.module::<sqlite::lb_sqlitelib>();
|
||||
}
|
||||
|
207
src/main.rs
207
src/main.rs
@ -1,8 +1,7 @@
|
||||
use clap::Parser;
|
||||
use luajit::Chunk;
|
||||
use mimalloc::MiMalloc;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
|
||||
use std::{backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, thread};
|
||||
use sysexits::ExitCode;
|
||||
|
||||
#[global_allocator]
|
||||
@ -21,42 +20,21 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
|
||||
};
|
||||
|
||||
eprint!(
|
||||
"{}\n{trace}",
|
||||
"{}:\n{trace}",
|
||||
format_args!(
|
||||
"thread '{}' panicked at {location}: {msg}",
|
||||
thread::current().name().unwrap_or("<unnamed>")
|
||||
)
|
||||
.red()
|
||||
.bold()
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
format_args!(
|
||||
"luby should never panic. Please kindly report this bug at {}.",
|
||||
env!("CARGO_PKG_REPOSITORY")
|
||||
)
|
||||
.yellow()
|
||||
.bold()
|
||||
"This is a bug in luby. Please kindly report this at https://git.lua.re/luaneko/luby."
|
||||
.yellow()
|
||||
);
|
||||
}
|
||||
|
||||
fn error_cb(err: &luajit::Error) {
|
||||
match err.trace() {
|
||||
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
|
||||
None => eprintln!("{}", err.red().bold()),
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn unwrap_exit<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
|
||||
move |err| {
|
||||
eprintln!("{}", err.red().bold());
|
||||
code.exit()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
/// Paths to scripts to execute.
|
||||
@ -67,7 +45,7 @@ struct Args {
|
||||
#[clap(long, short = 'e', value_name = "CHUNK")]
|
||||
eval: Vec<String>,
|
||||
|
||||
/// Libraries to require.
|
||||
/// Libraries to require on startup.
|
||||
#[clap(long, short = 'l', value_name = "NAME")]
|
||||
lib: Vec<String>,
|
||||
|
||||
@ -76,65 +54,29 @@ struct Args {
|
||||
log: tracing::Level,
|
||||
|
||||
/// LuaJIT control commands.
|
||||
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
|
||||
#[clap(long, short = 'j', value_name = "CMD=FLAGS")]
|
||||
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.
|
||||
#[clap(
|
||||
long,
|
||||
short = 'T',
|
||||
help_heading = "Runtime",
|
||||
value_name = "COUNT",
|
||||
default_value_t = Self::threads()
|
||||
)]
|
||||
/// Number of tokio worker threads.
|
||||
#[clap(long, value_name = "THREADS", default_value_t = Self::threads())]
|
||||
threads: NonZero<usize>,
|
||||
|
||||
/// Number of blocking threads.
|
||||
#[clap(
|
||||
long,
|
||||
help_heading = "Runtime",
|
||||
value_name = "COUNT",
|
||||
default_value_t = Self::blocking_threads()
|
||||
)]
|
||||
/// Number of tokio blocking threads.
|
||||
#[clap(long, value_name = "THREADS", default_value_t = Self::blocking_threads())]
|
||||
blocking_threads: NonZero<usize>,
|
||||
|
||||
/// Enable tokio-console integration.
|
||||
#[cfg(feature = "tokio-console")]
|
||||
#[clap(long, help_heading = "Debugging", value_name = "ENABLED")]
|
||||
#[clap(long)]
|
||||
enable_console: bool,
|
||||
|
||||
/// tokio-console publish address.
|
||||
#[cfg(feature = "tokio-console")]
|
||||
#[clap(
|
||||
long,
|
||||
help_heading = "Debugging",
|
||||
value_name = "ADDRESS",
|
||||
default_value = "127.0.0.1:6669",
|
||||
requires = "enable_console"
|
||||
)]
|
||||
console_addr: std::net::SocketAddr,
|
||||
|
||||
/// Dump internal data.
|
||||
#[clap(
|
||||
long,
|
||||
help_heading = "Debugging",
|
||||
value_name = "DATA",
|
||||
value_parser = ["cdef"]
|
||||
)]
|
||||
dump: Vec<String>,
|
||||
|
||||
/// Print version.
|
||||
#[clap(long, short = 'V')]
|
||||
version: bool,
|
||||
console_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
@ -147,15 +89,17 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
|
||||
move |err| {
|
||||
eprintln!("{}", err.red());
|
||||
code.exit()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), ExitCode> {
|
||||
panic::set_hook(Box::new(panic_cb));
|
||||
|
||||
let args = Args::parse();
|
||||
if args.version {
|
||||
print_version();
|
||||
return ExitCode::Ok;
|
||||
}
|
||||
|
||||
init_logger(&args);
|
||||
|
||||
let tokio = init_tokio(&args);
|
||||
@ -164,28 +108,13 @@ fn main() -> ExitCode {
|
||||
|
||||
tokio.block_on(async {
|
||||
lua.await;
|
||||
match main.await {
|
||||
Ok(res) => res,
|
||||
Err(err) => panic::resume_unwind(err.into_panic()),
|
||||
}
|
||||
main.await.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn print_version() {
|
||||
println!("luby {}", env!("VERGEN_GIT_DESCRIBE"));
|
||||
println!("{}\n", env!("CARGO_PKG_HOMEPAGE"));
|
||||
println!("Compiled with {} -- {}", luajit::version(), luajit::url());
|
||||
println!(
|
||||
"Compiled with rustc {} on {} for {}",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
env!("VERGEN_RUSTC_HOST_TRIPLE"),
|
||||
env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_logger(args: &Args) {
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::util::*;
|
||||
use tracing_subscriber::{Layer, util::*};
|
||||
|
||||
let log = tracing_subscriber::fmt()
|
||||
.compact()
|
||||
@ -199,68 +128,98 @@ fn init_logger(args: &Args) {
|
||||
.with_target(false)
|
||||
.finish();
|
||||
|
||||
#[cfg(feature = "tokio-console")]
|
||||
{
|
||||
use tracing_subscriber::Layer;
|
||||
if args.enable_console {
|
||||
console_subscriber::ConsoleLayer::builder()
|
||||
.with_default_env()
|
||||
.server_addr(args.console_addr)
|
||||
.spawn()
|
||||
.with_subscriber(log)
|
||||
.init();
|
||||
.init()
|
||||
} else {
|
||||
log.init()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tokio-console"))]
|
||||
log.init();
|
||||
}
|
||||
|
||||
fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
||||
match args.threads.get() {
|
||||
let mut rt = match args.threads.get() {
|
||||
1 => tokio::runtime::Builder::new_current_thread(),
|
||||
n => {
|
||||
let mut rt = tokio::runtime::Builder::new_multi_thread();
|
||||
rt.worker_threads(n - 1);
|
||||
rt
|
||||
}
|
||||
}
|
||||
.enable_all()
|
||||
.thread_name("luby")
|
||||
.max_blocking_threads(args.blocking_threads.get())
|
||||
.build()
|
||||
.unwrap_or_else(unwrap_exit(ExitCode::OsErr))
|
||||
};
|
||||
|
||||
rt.enable_all()
|
||||
.thread_name("luby")
|
||||
.max_blocking_threads(args.blocking_threads.get())
|
||||
.build()
|
||||
.unwrap_or_else(exit_err(ExitCode::OsErr))
|
||||
}
|
||||
|
||||
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
let rt = lb::runtime::Builder::new();
|
||||
let mut rt = rt.build().unwrap_or_else(exit_err(ExitCode::Software));
|
||||
|
||||
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
||||
print!("{}", rt.registry()); // for cdef debugging
|
||||
for arg in args.jit.iter() {
|
||||
let mut s = rt.guard();
|
||||
if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
||||
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
|
||||
{
|
||||
(s.push("start"), s.get(-2), s.push(flags));
|
||||
s.call(1, 0)
|
||||
} else {
|
||||
s.require("jit", 1).unwrap();
|
||||
match arg.as_str() {
|
||||
cmd @ ("on" | "off" | "flush") => {
|
||||
(s.push(cmd), s.get(-2));
|
||||
s.call(0, 0)
|
||||
}
|
||||
arg => {
|
||||
(s.push("opt"), s.get(-2));
|
||||
(s.push("start"), s.get(-2), s.push(arg));
|
||||
s.call(1, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.unwrap_or_else(exit_err(ExitCode::Usage));
|
||||
}
|
||||
|
||||
rt.unhandled_error(error_cb)
|
||||
.prohibit_globals(!args.allow_globals)
|
||||
.jit_opts(args.jit.iter())
|
||||
.build()
|
||||
.unwrap()
|
||||
rt
|
||||
}
|
||||
|
||||
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
||||
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
||||
match s {
|
||||
"p" => Some(("p", "Flspv10")),
|
||||
"v" => Some(("v", "-")),
|
||||
"dump" => Some(("dump", "tirs")),
|
||||
_ => s.split_once('='),
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_async(args: Args, state: &mut luajit::State) -> Result<(), ExitCode> {
|
||||
for ref path in args.path {
|
||||
let mut s = state.guard();
|
||||
let chunk = match std::fs::read(path) {
|
||||
Ok(chunk) => chunk,
|
||||
Err(err) => {
|
||||
eprintln!("{}", format_args!("{path}: {err}").red().bold());
|
||||
return ExitCode::NoInput;
|
||||
eprintln!("{}", format_args!("{path}: {err}").red());
|
||||
ExitCode::NoInput.exit();
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(ref err) = cx.load(&Chunk::new(chunk).with_path(path)) {
|
||||
cx.report_error(err);
|
||||
} else if let Err(ref err) = cx.call_async(0, Some(0)).await {
|
||||
cx.report_error(err);
|
||||
s.load(&luajit::Chunk::new(chunk).path(path))
|
||||
.unwrap_or_else(exit_err(ExitCode::NoInput));
|
||||
|
||||
if let Err(err) = s.call_async(0, 0).await {
|
||||
match err.trace() {
|
||||
Some(trace) => eprintln!("{}\n{trace}", err.red()), // runtime error
|
||||
None => eprintln!("{}", err.red()),
|
||||
}
|
||||
|
||||
ExitCode::DataErr.exit();
|
||||
}
|
||||
}
|
||||
|
||||
ExitCode::Ok
|
||||
Ok(())
|
||||
}
|
||||
|
6
test.lua
Normal file
6
test.lua
Normal file
@ -0,0 +1,6 @@
|
||||
local ffi = require("ffi")
|
||||
local lb = ffi.new("struct lb_core")
|
||||
|
||||
print(lb)
|
||||
|
||||
lb.spawn("")
|
188
tests/main.lua
188
tests/main.lua
@ -1,188 +0,0 @@
|
||||
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call
|
||||
|
||||
local ok = pcall(require, "lb:task")
|
||||
if not ok then error("lua test harness requires 'lb:task'") 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")
|
||||
if not ok then error("lua test harness requires 'lb:fs'") end
|
||||
|
||||
local global = _G
|
||||
local color = {
|
||||
reset = "\x1b[0m",
|
||||
pass = "\x1b[32;1m", -- green
|
||||
fail = "\x1b[31;1m", -- red
|
||||
faint = "\x1b[2;39;49m", -- faint
|
||||
}
|
||||
|
||||
local icon = {
|
||||
check = "\u{2713}",
|
||||
cross = "\u{00d7}",
|
||||
chevron = "\u{203a}",
|
||||
}
|
||||
|
||||
local function style(name, s)
|
||||
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
|
||||
|
||||
local function create_test(name, f, group)
|
||||
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
|
||||
local fenv = setmetatable({}, { __index = global, __newindex = global })
|
||||
setfenv(f, fenv)
|
||||
return test
|
||||
end
|
||||
|
||||
local function create_group(name, f, parent)
|
||||
local group = { type = "group", name = name or "", parent = parent, items = {} }
|
||||
local fenv = setmetatable({
|
||||
describe = function(name, f)
|
||||
local item = create_group(name, f, group)
|
||||
table.insert(group.items, item)
|
||||
return item
|
||||
end,
|
||||
|
||||
test = function(name, f)
|
||||
local item = create_test(name, f, group)
|
||||
table.insert(group.items, item)
|
||||
return item
|
||||
end,
|
||||
}, { __index = global, __newindex = global })
|
||||
|
||||
setfenv(f, fenv)
|
||||
f(group)
|
||||
return group
|
||||
end
|
||||
|
||||
local function name_test(test)
|
||||
local name = test.name
|
||||
local group = test.group
|
||||
while group ~= nil do
|
||||
if group.name ~= "" then name = ("%s %s %s"):format(group.name, icon.chevron, name) end
|
||||
group = group.parent
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
local function trace(msg)
|
||||
return style("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
||||
end
|
||||
|
||||
local function run_test(test)
|
||||
local ok, trace = xpcall(test.f, trace, test)
|
||||
if ok then
|
||||
test.state = "pass"
|
||||
print(("%s %s"):format(style("pass", rjust("PASS")), name_test(test)))
|
||||
else
|
||||
test.state = "fail"
|
||||
print(("%s %s\n\n%s\n"):format(style("fail", rjust("!!! FAIL")), name_test(test), trace))
|
||||
end
|
||||
collectgarbage() -- gc after each test to test destructors
|
||||
return test
|
||||
end
|
||||
|
||||
local function start(cx, item)
|
||||
if item.type == "test" then
|
||||
table.insert(cx.tasks, spawn(run_test, item))
|
||||
elseif item.type == "group" then
|
||||
for _, item in ipairs(item.items) do
|
||||
start(cx, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function 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 cx = { tasks = {} }
|
||||
local time, pass, fail = time.instant(), 0, 0
|
||||
start(cx, item)
|
||||
for _, task in ipairs(cx.tasks) do
|
||||
if task:await().state == "pass" then
|
||||
pass = pass + 1
|
||||
else
|
||||
fail = fail + 1
|
||||
end
|
||||
end
|
||||
local elapsed = time:elapsed_secs()
|
||||
local retcode
|
||||
if fail == 0 then
|
||||
print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
|
||||
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
|
||||
print(style("faint", ("\t%s completed in %.2fs"):format(icon.chevron, elapsed)))
|
||||
cx = nil
|
||||
collectgarbage()
|
||||
check_refs() -- check that all refs were properly unref'ed in destructors
|
||||
return retcode -- report error to cargo
|
||||
end
|
||||
|
||||
return main(create_group("", function()
|
||||
local function include(path, pat)
|
||||
for entry in fs.glob_dir(path, pat) do
|
||||
local path = entry:path()
|
||||
local f, err = loadfile(path)
|
||||
if not f then error(err) end
|
||||
describe(path, f)
|
||||
end
|
||||
end
|
||||
|
||||
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("crates", "*/tests/**/*.lua")
|
||||
include_doctest("src", "**/*.rs")
|
||||
include_doctest("crates", "*/src/**/*.rs")
|
||||
end))
|
@ -1,50 +0,0 @@
|
||||
use luajit::Chunk;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{
|
||||
fs, panic,
|
||||
process::{self, ExitCode},
|
||||
};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let tokio = tokio::runtime::Runtime::new().unwrap();
|
||||
let lua = {
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
rt.unhandled_error(error_cb)
|
||||
.prohibit_globals(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let path = "tests/main.lua";
|
||||
let main = lua.spawn(async move |s| {
|
||||
if let Err(ref err) = s.load(&Chunk::new(fs::read(path).unwrap()).with_path(path)) {
|
||||
s.report_error(err);
|
||||
} else if let Err(ref err) = s.call_async(0, Some(1)).await {
|
||||
s.report_error(err);
|
||||
}
|
||||
|
||||
if s.slot(1).integer().unwrap_or(1) == 0 {
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
});
|
||||
|
||||
tokio.block_on(async move {
|
||||
lua.await;
|
||||
match main.await {
|
||||
Ok(res) => res,
|
||||
Err(err) => panic::resume_unwind(err.into_panic()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn error_cb(err: &luajit::Error) {
|
||||
match err.trace() {
|
||||
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
|
||||
None => eprintln!("{}", err.red().bold()),
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user