Compare commits
18 Commits
31b5ff5ab9
...
40829cdfc6
Author | SHA1 | Date | |
---|---|---|---|
40829cdfc6 | |||
db4faea821 | |||
2964ebbe1b | |||
b1572cc9f1 | |||
862fcfe891 | |||
6a194e98e8 | |||
d2e06c9a70 | |||
679ffed807 | |||
549f96d4dc | |||
09d7e51345 | |||
8a74ade6a6 | |||
9338be7eb0 | |||
240e0111bf | |||
01f459ffaf | |||
9b7dbcc141 | |||
24c5e9edc2 | |||
0cafac0948 | |||
1c1753234d |
@ -1,5 +1,9 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||
"runtime.version": "LuaJIT",
|
||||
"diagnostics.disable": ["redefined-local", "lowercase-global"]
|
||||
"diagnostics.disable": [
|
||||
"undefined-global",
|
||||
"redefined-local",
|
||||
"lowercase-global"
|
||||
]
|
||||
}
|
||||
|
43
Cargo.lock
generated
43
Cargo.lock
generated
@ -691,6 +691,19 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.10"
|
||||
@ -1032,11 +1045,13 @@ name = "lb"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"globset",
|
||||
"luaffi",
|
||||
"luajit",
|
||||
"sysexits",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1578,6 +1593,15 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
@ -2069,6 +2093,16 @@ dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
@ -2120,6 +2154,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -30,6 +30,10 @@ repository.workspace = true
|
||||
dev.panic = "abort"
|
||||
release.panic = "abort"
|
||||
|
||||
[[test]]
|
||||
name = "main"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["task", "fs", "net"]
|
||||
task = ["lb/task"]
|
||||
|
@ -8,14 +8,16 @@ homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
task = []
|
||||
fs = ["tokio/fs"]
|
||||
task = ["tokio/rt", "tokio/time"]
|
||||
fs = ["tokio/fs", "dep:walkdir", "dep:globset"]
|
||||
net = ["tokio/net"]
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "2.0.1", features = ["full"] }
|
||||
globset = { version = "0.4.16", optional = true }
|
||||
luaffi = { path = "../luaffi" }
|
||||
luajit = { path = "../luajit" }
|
||||
sysexits = "0.9.0"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.1", features = ["rt"] }
|
||||
tokio = { version = "1.45.1" }
|
||||
walkdir = { version = "2.5.0", optional = true }
|
||||
|
@ -1,7 +1,22 @@
|
||||
// use flume::{Receiver, Sender};
|
||||
//! Channel library
|
||||
//!
|
||||
//! The `lb:chan` library provides primitives for asynchronous communication between tasks via
|
||||
//! message passing channels.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_chanlib`] for items exported by this library.
|
||||
use luaffi::{cdef, metatype};
|
||||
|
||||
#[cdef]
|
||||
/// Items exported by the `lb:chan` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:chan")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local chan = require("lb:chan");
|
||||
/// ```
|
||||
#[cdef(module = "lb:chan")]
|
||||
pub struct lb_chanlib;
|
||||
|
||||
#[metatype]
|
||||
|
@ -1,21 +1,49 @@
|
||||
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
|
||||
//! file system.
|
||||
//! # Filesystem library
|
||||
//!
|
||||
//! # Exports
|
||||
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
|
||||
//! filesystem.
|
||||
//!
|
||||
//! ## Asynchronous by default
|
||||
//!
|
||||
//! Filesystem operations are blocking by nature; to provide asynchronicity, luby performs blocking
|
||||
//! operations in a background thread pool by default. Synchronous complements to all asynchronous
|
||||
//! functions are always provided.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_fslib`] for items exported by this library.
|
||||
use derive_more::From;
|
||||
use luaffi::{cdef, metatype};
|
||||
use std::io;
|
||||
use tokio::fs;
|
||||
use std::{path::PathBuf, time::SystemTime};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can be thrown by this library.
|
||||
///
|
||||
/// Functions which return this error will **throw** in Lua. The error message can be caught by
|
||||
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// I/O error.
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// Walk directory error.
|
||||
#[error("{0}")]
|
||||
Walk(#[from] walkdir::Error),
|
||||
/// Glob pattern error.
|
||||
#[error("{0}")]
|
||||
Glob(#[from] globset::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Items exported by the `lb:fs` library.
|
||||
///
|
||||
/// This library can be obtained by calling `require` in Lua.
|
||||
/// This library can be acquired by calling `require` in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local fs = require("lb:fs");
|
||||
/// ```
|
||||
#[cdef]
|
||||
#[cdef(module = "lb:fs")]
|
||||
pub struct lb_fslib;
|
||||
|
||||
#[metatype]
|
||||
@ -25,19 +53,351 @@ impl lb_fslib {
|
||||
Self
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn read(&self, path: &str) -> io::Result<Vec<u8>> {
|
||||
fs::read(path).await
|
||||
pub async extern "Lua-C" fn read(&self, path: &str) -> Result<Vec<u8>> {
|
||||
Ok(tokio::fs::read(path).await?)
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn read_sync(&self, path: &str) -> io::Result<Vec<u8>> {
|
||||
std::fs::read(path)
|
||||
pub extern "Lua-C" fn read_sync(&self, path: &str) -> Result<Vec<u8>> {
|
||||
Ok(std::fs::read(path)?)
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn write(&self, path: &str, contents: &[u8]) -> io::Result<()> {
|
||||
fs::write(path, contents).await
|
||||
pub async extern "Lua-C" fn write(&self, path: &str, contents: &[u8]) -> Result<()> {
|
||||
Ok(tokio::fs::write(path, contents).await?)
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn write_sync(&self, path: &str, contents: &[u8]) -> io::Result<()> {
|
||||
std::fs::write(path, contents)
|
||||
pub extern "Lua-C" fn write_sync(&self, path: &str, contents: &[u8]) -> Result<()> {
|
||||
Ok(std::fs::write(path, contents)?)
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn read_dir(&self, path: &str) -> Result<lb_read_dir> {
|
||||
Ok(tokio::fs::read_dir(path).await?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn read_dir_sync(&self, path: &str) -> Result<lb_read_dir_sync> {
|
||||
Ok(std::fs::read_dir(path)?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn walk_dir(&self, path: &str) -> lb_walk_dir {
|
||||
walkdir::WalkDir::new(path).into_iter().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn glob(&self, pattern: &str) -> Result<lb_glob_dir> {
|
||||
self.glob_dir(".", pattern)
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn glob_dir(&self, path: &str, pattern: &str) -> Result<lb_glob_dir> {
|
||||
let prefix = PathBuf::from(path);
|
||||
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
|
||||
let matcher = globset::GlobSet::builder()
|
||||
.add(globset::Glob::new(pattern)?)
|
||||
.build()?;
|
||||
|
||||
Ok(lb_glob_dir {
|
||||
iter,
|
||||
matcher,
|
||||
prefix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the entries in a directory.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_read_dir(#[opaque] tokio::fs::ReadDir);
|
||||
|
||||
#[metatype]
|
||||
impl lb_read_dir {
|
||||
#[call]
|
||||
pub async extern "Lua-C" fn next(&mut self) -> Result<Option<lb_dir_entry>> {
|
||||
Ok(self.0.next_entry().await?.map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronous version of [`lb_read_dir`].
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_read_dir_sync(#[opaque] std::fs::ReadDir);
|
||||
|
||||
#[metatype]
|
||||
impl lb_read_dir_sync {
|
||||
#[call]
|
||||
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_dir_entry_sync>> {
|
||||
Ok(self.0.next().transpose()?.map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry inside of a directory on the filesystem.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_dir_entry(#[opaque] tokio::fs::DirEntry);
|
||||
|
||||
#[metatype]
|
||||
impl lb_dir_entry {
|
||||
pub extern "Lua-C" fn path(&self) -> String {
|
||||
self.0.path().to_string_lossy().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn name(&self) -> String {
|
||||
self.0.file_name().to_string_lossy().into()
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||
Ok(self.0.file_type().await?.into())
|
||||
}
|
||||
|
||||
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||
Ok(self.0.metadata().await?.into())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||
self.0.ino()
|
||||
}
|
||||
|
||||
#[tostring]
|
||||
pub extern "Lua" fn tostring(&self) -> String {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronous version of [`lb_dir_entry`].
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_dir_entry_sync(#[opaque] std::fs::DirEntry);
|
||||
|
||||
#[metatype]
|
||||
impl lb_dir_entry_sync {
|
||||
pub extern "Lua-C" fn path(&self) -> String {
|
||||
self.0.path().to_string_lossy().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn name(&self) -> String {
|
||||
self.0.file_name().to_string_lossy().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||
Ok(self.0.file_type()?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||
Ok(self.0.metadata()?.into())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||
use std::os::unix::fs::DirEntryExt;
|
||||
self.0.ino()
|
||||
}
|
||||
|
||||
#[tostring]
|
||||
pub extern "Lua" fn tostring(&self) -> String {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure representing the type of a file with accessors for each file type.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_file_type(#[opaque] std::fs::FileType);
|
||||
|
||||
#[metatype]
|
||||
impl lb_file_type {
|
||||
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||
self.0.is_dir()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||
self.0.is_file()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||
self.0.is_file()
|
||||
}
|
||||
|
||||
#[tostring]
|
||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||
if self.0.is_file() {
|
||||
"file"
|
||||
} else if self.0.is_dir() {
|
||||
"dir"
|
||||
} else if self.0.is_symlink() {
|
||||
"symlink"
|
||||
} else {
|
||||
"other"
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata information about a file.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_file_meta(#[opaque] std::fs::Metadata);
|
||||
|
||||
#[metatype]
|
||||
impl lb_file_meta {
|
||||
pub extern "Lua-C" fn is_dir(&self) -> bool {
|
||||
self.0.is_dir()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn is_file(&self) -> bool {
|
||||
self.0.is_file()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||
self.0.is_file()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||
self.0.file_type().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn size(&self) -> u64 {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
|
||||
self.0.permissions().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn created(&self) -> Result<f64> {
|
||||
Ok(self
|
||||
.0
|
||||
.created()?
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|dur| dur.as_secs_f64())
|
||||
.unwrap_or(0.))
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
|
||||
Ok(self
|
||||
.0
|
||||
.modified()?
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|dur| dur.as_secs_f64())
|
||||
.unwrap_or(0.))
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
|
||||
Ok(self
|
||||
.0
|
||||
.accessed()?
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|dur| dur.as_secs_f64())
|
||||
.unwrap_or(0.))
|
||||
}
|
||||
|
||||
#[tostring]
|
||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||
let ty = self.0.file_type();
|
||||
if ty.is_file() {
|
||||
format!("file {}", self.0.len())
|
||||
} else if ty.is_dir() {
|
||||
"dir".into()
|
||||
} else if ty.is_symlink() {
|
||||
"symlink".into()
|
||||
} else {
|
||||
"other".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of the various permissions on a file.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_file_perms(#[opaque] std::fs::Permissions);
|
||||
|
||||
#[metatype]
|
||||
impl lb_file_perms {
|
||||
pub extern "Lua-C" fn readonly(&self) -> bool {
|
||||
self.0.readonly()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn set_readonly(&mut self, readonly: bool) {
|
||||
self.0.set_readonly(readonly);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator for recursively descending into a directory.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_walk_dir(#[opaque] walkdir::IntoIter);
|
||||
|
||||
#[metatype]
|
||||
impl lb_walk_dir {
|
||||
#[call]
|
||||
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_walk_dir_entry>> {
|
||||
Ok(self.0.next().transpose()?.map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_walk_dir_entry(#[opaque] walkdir::DirEntry);
|
||||
|
||||
#[metatype]
|
||||
impl lb_walk_dir_entry {
|
||||
pub extern "Lua-C" fn path(&self) -> String {
|
||||
self.0.path().to_string_lossy().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn name(&self) -> String {
|
||||
self.0.file_name().to_string_lossy().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
|
||||
self.0.file_type().into()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||
Ok(self.0.metadata()?.into())
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn is_symlink(&self) -> bool {
|
||||
self.0.path_is_symlink()
|
||||
}
|
||||
|
||||
pub extern "Lua-C" fn depth(&self) -> u32 {
|
||||
self.0.depth() as u32
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub extern "Lua-C" fn ino(&self) -> u64 {
|
||||
use walkdir::DirEntryExt;
|
||||
self.0.ino()
|
||||
}
|
||||
|
||||
#[tostring]
|
||||
pub extern "Lua" fn tostring(&self) -> String {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator that yields paths from the filesystem that match a particular pattern.
|
||||
#[derive(Debug)]
|
||||
#[cdef]
|
||||
pub struct lb_glob_dir {
|
||||
#[opaque]
|
||||
iter: walkdir::IntoIter,
|
||||
#[opaque]
|
||||
matcher: globset::GlobSet,
|
||||
#[opaque]
|
||||
prefix: PathBuf,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lb_glob_dir {
|
||||
#[call]
|
||||
pub extern "Lua-C" fn next(&mut self) -> Result<Option<lb_walk_dir_entry>> {
|
||||
while let Some(res) = self.iter.next() {
|
||||
let entry = res?;
|
||||
let path = entry.path().strip_prefix(&self.prefix).unwrap();
|
||||
if self.matcher.is_match(path) {
|
||||
return Ok(Some(entry.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! luby standard library
|
||||
#![warn(missing_docs)]
|
||||
pub mod runtime;
|
||||
|
||||
#[cfg(feature = "task")]
|
||||
|
@ -1,7 +1,9 @@
|
||||
//! Networking library
|
||||
//!
|
||||
//! The `lb:net` library provides an asynchronous network API for creating TCP or UDP servers and
|
||||
//! clients.
|
||||
//!
|
||||
//! # Exports
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_netlib`] for items exported by this library.
|
||||
use derive_more::{From, FromStr};
|
||||
@ -19,10 +21,13 @@ use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// I/O error.
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// IP or socket address syntax error.
|
||||
#[error("{0}")]
|
||||
InvalidAddr(#[from] AddrParseError),
|
||||
/// Socket was already converted and cannot be used anymore.
|
||||
#[error("socket was already converted")]
|
||||
SocketConsumed,
|
||||
}
|
||||
@ -31,13 +36,13 @@ type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Items exported by the `lb:net` library.
|
||||
///
|
||||
/// This library can be obtained by calling
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net");
|
||||
/// ```
|
||||
#[cdef]
|
||||
#[cdef(module = "lb:net")]
|
||||
pub struct lb_netlib;
|
||||
|
||||
#[metatype]
|
||||
@ -136,7 +141,7 @@ impl lb_netlib {
|
||||
Ok(Some(TcpSocket::new_v6()?).into())
|
||||
}
|
||||
|
||||
pub extern "Lua" fn bind_tcp(&self, addr: any, port: any) -> Result<lb_tcpsocket> {
|
||||
pub async extern "Lua" fn bind_tcp(&self, addr: any, port: any) -> Result<lb_tcpsocket> {
|
||||
let addr = self.socketaddr(addr, port);
|
||||
let socket;
|
||||
if addr.ip().is_v6() {
|
||||
@ -148,7 +153,7 @@ impl lb_netlib {
|
||||
socket
|
||||
}
|
||||
|
||||
pub extern "Lua" fn connect_tcp(&self, addr: any, port: any) -> Result<lb_tcpstream> {
|
||||
pub async extern "Lua" fn connect_tcp(&self, addr: any, port: any) -> Result<lb_tcpstream> {
|
||||
let addr = self.socketaddr(addr, port);
|
||||
let socket;
|
||||
if addr.ip().is_v6() {
|
||||
@ -159,12 +164,12 @@ impl lb_netlib {
|
||||
socket.connect(addr)
|
||||
}
|
||||
|
||||
pub extern "Lua" fn listen_tcp(&self, addr: any, port: any) -> Result<lb_tcplistener> {
|
||||
pub async extern "Lua" fn listen_tcp(&self, addr: any, port: any) -> Result<lb_tcplistener> {
|
||||
self.bind_tcp(addr, port).listen(1024)
|
||||
}
|
||||
}
|
||||
|
||||
/// An IP address, either IPv4 or IPv6.
|
||||
/// IP address, either IPv4 or IPv6.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -289,7 +294,7 @@ impl lb_ipaddr {
|
||||
}
|
||||
}
|
||||
|
||||
/// A socket address, which is an IP address with a port number.
|
||||
/// Socket address, which is an IP address with a port number.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||
#[cdef]
|
||||
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
||||
@ -345,7 +350,7 @@ impl lb_socketaddr {
|
||||
}
|
||||
}
|
||||
|
||||
/// A TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
|
||||
/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_tcpsocket(#[opaque] Option<TcpSocket>);
|
||||
@ -465,6 +470,7 @@ impl lb_tcpsocket {
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connection between a local and a remote socket.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_tcpstream(#[opaque] TcpStream);
|
||||
@ -472,6 +478,7 @@ pub struct lb_tcpstream(#[opaque] TcpStream);
|
||||
#[metatype]
|
||||
impl lb_tcpstream {}
|
||||
|
||||
/// TCP socket server, listening for connections.
|
||||
#[derive(Debug, From)]
|
||||
#[cdef]
|
||||
pub struct lb_tcplistener(#[opaque] TcpListener);
|
||||
|
@ -1,21 +1,28 @@
|
||||
#![doc(hidden)]
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use luaffi::{Registry, Type};
|
||||
use luaffi::{Module, Registry};
|
||||
use luajit::{Chunk, State};
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
use tokio::{
|
||||
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
||||
task_local,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub type ErrorFn = dyn Fn(&luajit::Error);
|
||||
|
||||
pub struct Builder {
|
||||
registry: Registry,
|
||||
report_err: Rc<ErrorFn>,
|
||||
}
|
||||
|
||||
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}"),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,57 +30,86 @@ impl Builder {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
pub fn module<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
self.registry.preload::<T>(name);
|
||||
pub fn unhandled_error(&mut self, handler: impl Fn(&luajit::Error) + 'static) -> &mut Self {
|
||||
self.report_err = Rc::new(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn module<T: Module>(&mut self) -> &mut Self {
|
||||
self.registry.preload::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self) -> luajit::Result<Runtime> {
|
||||
Ok(Runtime {
|
||||
cx: Context {
|
||||
state: {
|
||||
let mut s = State::new()?;
|
||||
s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
|
||||
s
|
||||
},
|
||||
report_err: self.report_err.clone(),
|
||||
},
|
||||
tasks: LocalSet::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deref, DerefMut)]
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Runtime {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
state: State,
|
||||
cx: Context,
|
||||
tasks: LocalSet,
|
||||
}
|
||||
|
||||
task_local! {
|
||||
static STATE: State;
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn spawn<T: 'static>(
|
||||
&self,
|
||||
f: impl AsyncFnOnce(&mut State) -> T + 'static,
|
||||
f: impl AsyncFnOnce(&mut Context) -> T + 'static,
|
||||
) -> JoinHandle<T> {
|
||||
self.tasks
|
||||
.spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
|
||||
.spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut State) -> T + 'static) -> JoinHandle<T> {
|
||||
// SAFETY: `new_thread` must be called inside `spawn_local` because this free-standing spawn
|
||||
// function may be called via ffi from lua, and it is not safe to access the lua state within
|
||||
// ffi calls.
|
||||
spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
|
||||
}
|
||||
|
||||
impl IntoFuture for Runtime {
|
||||
type Output = ();
|
||||
type IntoFuture = TaskLocalFuture<State, LocalSet>;
|
||||
type IntoFuture = TaskLocalFuture<Context, LocalSet>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
STATE.scope(self.state, self.tasks)
|
||||
CURRENT.scope(self.cx, self.tasks)
|
||||
}
|
||||
}
|
||||
|
||||
task_local! {
|
||||
static CURRENT: Context;
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Context {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
state: State,
|
||||
report_err: Rc<ErrorFn>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new_thread(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.new_thread(),
|
||||
report_err: self.report_err.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_error(&self, err: &luajit::Error) {
|
||||
(self.report_err)(&err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut Context) -> T + 'static) -> JoinHandle<T> {
|
||||
// SAFETY: `new_thread` must be called inside `spawn_local` because this free-standing spawn
|
||||
// function may be called via ffi from lua, and it is not safe to access the lua state within
|
||||
// ffi calls.
|
||||
spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await })
|
||||
}
|
||||
|
10
crates/lb/src/task.lua
Normal file
10
crates/lb/src/task.lua
Normal file
@ -0,0 +1,10 @@
|
||||
-- include task functions in the global scope
|
||||
local task = require("lb:task")
|
||||
|
||||
function sleep(ms)
|
||||
return task:sleep(ms)
|
||||
end
|
||||
|
||||
function spawn(f, ...)
|
||||
return task:spawn(f, ...)
|
||||
end
|
@ -1,46 +1,103 @@
|
||||
//! Task library
|
||||
//!
|
||||
//! The `lb:task` library primitives for asynchronous communication between tasks via message
|
||||
//! passing channels.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_tasklib`] for items exported by this library.
|
||||
use crate::runtime::spawn;
|
||||
use luaffi::{cdef, metatype};
|
||||
use std::{ffi::c_int, process};
|
||||
use tokio::task::JoinHandle;
|
||||
use luajit::{LUA_MULTRET, Type};
|
||||
use std::{ffi::c_int, time::Duration};
|
||||
use tokio::{task::JoinHandle, time::sleep};
|
||||
|
||||
#[cdef]
|
||||
/// Items exported by the `lb:task` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local task = require("lb:task");
|
||||
/// ```
|
||||
#[cdef(module = "lb:task")]
|
||||
pub struct lb_tasklib;
|
||||
|
||||
#[metatype]
|
||||
#[include("task.lua")]
|
||||
impl lb_tasklib {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub extern "Lua" fn spawn(self, f: function, ...) {
|
||||
// pack the function and its arguments into a table and pass its ref to rust
|
||||
pub async extern "Lua-C" fn sleep(&self, ms: f64) {
|
||||
sleep(Duration::from_secs_f64(ms / 1000.)).await;
|
||||
}
|
||||
|
||||
pub extern "Lua" fn spawn(&self, f: function, ...) -> lb_task {
|
||||
// pack the function and its arguments into a table and pass its ref to rust.
|
||||
//
|
||||
// this table is used from rust-side to call the function with its args, and it's also
|
||||
// reused to store its return values that the task handle can return when awaited. the ref
|
||||
// is owned by the task handle and unref'ed when it's gc'ed.
|
||||
self.__spawn(__ref(__tpack(f, variadic!())))
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __spawn(&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) };
|
||||
let handle = spawn(async move |cx| {
|
||||
// SAFETY: key is always unique, created by __ref above.
|
||||
let arg = unsafe { cx.new_ref_unchecked(key) };
|
||||
let mut s = cx.guard();
|
||||
s.resize(0);
|
||||
s.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)
|
||||
s.push(&arg);
|
||||
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the table
|
||||
debug_assert!(s.slot(2).type_of() == Type::Function);
|
||||
match s.call_async(narg, LUA_MULTRET).await {
|
||||
Ok(nret) => {
|
||||
s.pack(1, nret); // pack the return values back into the table
|
||||
}
|
||||
println!("{s:?}");
|
||||
Err(err) => {
|
||||
drop(s);
|
||||
cx.report_error(&err);
|
||||
}
|
||||
}
|
||||
let _ = arg.into_raw(); // the original ref is owned by the task handle and unref'ed there
|
||||
});
|
||||
|
||||
lb_task { handle }
|
||||
lb_task {
|
||||
handle: Some(handle),
|
||||
__ref: key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle for an asynchronous task created by [`spawn`](lb_tasklib::spawn).
|
||||
#[cdef]
|
||||
pub struct lb_task {
|
||||
#[opaque]
|
||||
handle: JoinHandle<()>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
__ref: c_int,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lb_task {}
|
||||
impl lb_task {
|
||||
pub async extern "Lua" fn r#await(&self) -> many {
|
||||
self.__await();
|
||||
let ret = __registry[self.__ref];
|
||||
__tunpack(ret, 1, ret.n)
|
||||
}
|
||||
|
||||
async extern "Lua-C" fn __await(&mut self) {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
handle
|
||||
.await
|
||||
.unwrap_or_else(|err| panic!("task handler panicked: {err}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[gc]
|
||||
extern "Lua" fn gc(&self) {
|
||||
__unref(self.__ref);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use std::{
|
||||
#[allow(non_camel_case_types)]
|
||||
pub mod stub_types {
|
||||
pub struct any;
|
||||
pub struct many;
|
||||
pub struct nil;
|
||||
pub type boolean = bool;
|
||||
pub struct lightuserdata;
|
||||
|
@ -38,7 +38,7 @@ const CACHE_LIBS: &[(&str, &str)] = &[
|
||||
("package", "package"),
|
||||
("debug", "debug"),
|
||||
("jit", "jit"),
|
||||
// require
|
||||
// requires
|
||||
("bit", r#"require("bit")"#),
|
||||
("ffi", r#"require("ffi")"#),
|
||||
("__tnew", r#"require("table.new")"#),
|
||||
@ -47,7 +47,7 @@ const CACHE_LIBS: &[(&str, &str)] = &[
|
||||
|
||||
// https://www.lua.org/manual/5.1/manual.html#5.1
|
||||
const CACHE_LOCALS: &[(&str, &str)] = &[
|
||||
// base
|
||||
// baselib
|
||||
("assert", "assert"),
|
||||
("error", "error"),
|
||||
("type", "type"),
|
||||
@ -68,7 +68,8 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
|
||||
("tonumber", "tonumber"),
|
||||
("tostring", "tostring"),
|
||||
("require", "require"),
|
||||
// table
|
||||
("__yield", "coroutine.yield"), // (used in future.rs)
|
||||
// tablib
|
||||
("__tconcat", "table.concat"),
|
||||
("__tinsert", "table.insert"),
|
||||
("__tmaxn", "table.maxn"),
|
||||
@ -76,23 +77,21 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
|
||||
("__tsort", "table.sort"),
|
||||
("__tpack", "table.pack"),
|
||||
("__tunpack", "table.unpack"),
|
||||
// string
|
||||
// strlib
|
||||
("__slen", "string.len"),
|
||||
("__sprintf", "string.format"),
|
||||
("__ssub", "string.sub"),
|
||||
("__sgsub", "string.gsub"),
|
||||
("__sgmatch", "string.gmatch"),
|
||||
("__sdump", "string.dump"),
|
||||
// math (used in luaify! macro)
|
||||
// mathlib (used in luaify! macro)
|
||||
("__fmod", "math.fmod"),
|
||||
// coroutine (used in future.rs)
|
||||
("__yield", "coroutine.yield"),
|
||||
// package
|
||||
// loadlib
|
||||
("__preload", "package.preload"),
|
||||
// debug
|
||||
// dblib
|
||||
("__traceback", "debug.traceback"),
|
||||
("__registry", "debug.getregistry()"), // (used in lib.lua)
|
||||
// ffi
|
||||
// ffilib
|
||||
("__C", "ffi.C"),
|
||||
("__ct", "{}"),
|
||||
("__cdef", "ffi.cdef"),
|
||||
@ -105,7 +104,7 @@ const CACHE_LOCALS: &[(&str, &str)] = &[
|
||||
("__sizeof", "ffi.sizeof"),
|
||||
("__alignof", "ffi.alignof"),
|
||||
("__intern", "ffi.string"), // (used in string.rs)
|
||||
// bit (used in luaify! macro)
|
||||
// bitlib (used in luaify! macro)
|
||||
("__bnot", "bit.bnot"),
|
||||
("__band", "bit.band"),
|
||||
("__bor", "bit.bor"),
|
||||
@ -159,16 +158,16 @@ impl Registry {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
pub fn preload<T: Module>(&mut self) -> &mut Self {
|
||||
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
||||
self.include::<T>();
|
||||
let ct = T::name();
|
||||
let name = <T as Module>::name();
|
||||
let ct = <T as Type>::name();
|
||||
writeln!(
|
||||
self.lua,
|
||||
r#"__preload["{name}"] = function(...) return __ct.{ct}(...); end;"#,
|
||||
)
|
||||
.unwrap();
|
||||
self
|
||||
self.include::<T>()
|
||||
}
|
||||
|
||||
pub fn build(&self) -> String {
|
||||
@ -241,6 +240,10 @@ impl<'r> TypeBuilder<'r> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Module: Type {
|
||||
fn name() -> impl Display;
|
||||
}
|
||||
|
||||
pub unsafe trait Cdef: Type {
|
||||
fn build(b: &mut CdefBuilder);
|
||||
}
|
||||
@ -321,6 +324,7 @@ pub struct MetatypeBuilder<'r> {
|
||||
ct: String,
|
||||
cdef: String,
|
||||
lua: String,
|
||||
lua_includes: Vec<&'static str>,
|
||||
}
|
||||
|
||||
impl<'r> MetatypeBuilder<'r> {
|
||||
@ -330,6 +334,7 @@ impl<'r> MetatypeBuilder<'r> {
|
||||
ct: T::Target::name().to_string(),
|
||||
cdef: String::new(),
|
||||
lua: r#"do local __mt, __idx = {}, {}; __mt.__index = __idx; "#.into(),
|
||||
lua_includes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,13 +343,18 @@ impl<'r> MetatypeBuilder<'r> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn include_lua(&mut self, lua: &'static str) -> &mut Self {
|
||||
self.lua_includes.push(lua);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index(
|
||||
&mut self,
|
||||
name: impl Display,
|
||||
f: impl FnOnce(&mut MetatypeMethodBuilder),
|
||||
f: impl FnOnce(&mut MetatypeFunctionBuilder),
|
||||
) -> &mut Self {
|
||||
write!(self.lua, "__idx.{name} = ").unwrap();
|
||||
f(&mut MetatypeMethodBuilder::new(self));
|
||||
f(&mut MetatypeFunctionBuilder::new(self));
|
||||
writeln!(self.lua, ";").unwrap();
|
||||
self
|
||||
}
|
||||
@ -357,10 +367,10 @@ impl<'r> MetatypeBuilder<'r> {
|
||||
pub fn metatable(
|
||||
&mut self,
|
||||
name: impl Display,
|
||||
f: impl FnOnce(&mut MetatypeMethodBuilder),
|
||||
f: impl FnOnce(&mut MetatypeFunctionBuilder),
|
||||
) -> &mut Self {
|
||||
write!(self.lua, "__mt.__{name} = ").unwrap();
|
||||
f(&mut MetatypeMethodBuilder::new(self));
|
||||
f(&mut MetatypeFunctionBuilder::new(self));
|
||||
writeln!(self.lua, ";").unwrap();
|
||||
self
|
||||
}
|
||||
@ -378,12 +388,15 @@ impl<'r> Drop for MetatypeBuilder<'r> {
|
||||
ct,
|
||||
cdef,
|
||||
lua,
|
||||
..
|
||||
lua_includes: lua_postlude,
|
||||
} = self;
|
||||
|
||||
registry.cdef.push_str(cdef);
|
||||
registry.lua.push_str(lua);
|
||||
writeln!(registry.lua, r#"__metatype(__ct.{ct}, __mt); end;"#).unwrap();
|
||||
for lua in lua_postlude {
|
||||
writeln!(registry.lua, r#"do {lua} end;"#).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,16 +442,16 @@ pub unsafe trait IntoFfi: Sized {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetatypeMethodBuilder<'r, 'm> {
|
||||
pub struct MetatypeFunctionBuilder<'r, 'm> {
|
||||
metatype: &'m mut MetatypeBuilder<'r>,
|
||||
lparams: String, // parameters to the lua function
|
||||
cparams: String, // parameters to the lua function
|
||||
cargs: String, // arguments to the C call
|
||||
prelude: String, // function body prelude
|
||||
postlude: String, // function body postlude
|
||||
lparams: String, // lua function parameters
|
||||
cparams: String, // C function parameters
|
||||
cargs: String, // C call arguments
|
||||
prelude: String, // lua function body prelude
|
||||
postlude: String, // lua function body postlude
|
||||
}
|
||||
|
||||
impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
|
||||
Self {
|
||||
metatype,
|
||||
@ -517,12 +530,15 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
write!(prelude, "local __{name}_len = 0; if {name} ~= nil then ").unwrap();
|
||||
write!(prelude, r#"assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
||||
write!(prelude, r#"__{name}_len = #{name}; "#).unwrap();
|
||||
|
||||
if check_utf8 {
|
||||
write!(prelude, r#"assert(__C.{IS_UTF8_FN}({name}, __{name}_len), "argument '{name}' must be a valid utf-8 string"); "#).unwrap();
|
||||
}
|
||||
|
||||
if !allow_nil {
|
||||
write!(prelude, r#"else return error("string expected in argument '{name}', got " .. type({name})); "#).unwrap();
|
||||
}
|
||||
|
||||
write!(prelude, r#"end; "#).unwrap();
|
||||
self
|
||||
}
|
||||
|
@ -5,18 +5,26 @@ use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
pub struct Args {}
|
||||
pub struct Args {
|
||||
module: Option<String>,
|
||||
}
|
||||
|
||||
pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
let (name, impl_type, impl_cdef) = match item {
|
||||
pub fn transform(args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
let (name, impl_type, impl_module, 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"),
|
||||
@ -35,6 +43,7 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impl_type
|
||||
#impl_module
|
||||
#impl_cdef
|
||||
}
|
||||
))
|
||||
@ -47,9 +56,7 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
|
||||
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
|
||||
@ -72,6 +79,16 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_module(name: &str, ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
|
||||
Ok(quote_spanned!(ty.span() =>
|
||||
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(),
|
||||
@ -155,13 +172,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) {
|
||||
if let Some(name) = attr.path().get_ident() {
|
||||
if name == "opaque" {
|
||||
let path = attr.path();
|
||||
if path.is_ident("opaque") {
|
||||
parsed.opaque = true;
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
use darling::{FromMeta, ast::NestedMeta};
|
||||
use proc_macro::TokenStream as TokenStream1;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod cdef;
|
||||
mod metatype;
|
||||
@ -17,8 +16,10 @@ pub fn cdef(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn metatype(_args: TokenStream1, input: TokenStream1) -> TokenStream1 {
|
||||
metatype::transform(parse_macro_input!(input))
|
||||
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)?))
|
||||
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
|
||||
.into()
|
||||
}
|
||||
|
@ -2,19 +2,23 @@ use crate::utils::{
|
||||
StringLike, ffi_crate, is_optionlike, is_primitivelike, is_stringlike, is_unit, pat_ident,
|
||||
syn_assert, syn_error, ty_name,
|
||||
};
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||
use std::{collections::HashSet, fmt, iter};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
|
||||
pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
#[derive(Debug, FromMeta)]
|
||||
pub struct Args {}
|
||||
|
||||
pub fn transform(args: Args, mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
imp.generics.params.is_empty(),
|
||||
imp.generics,
|
||||
"cannot be generic (not yet implemented)"
|
||||
);
|
||||
|
||||
let impls = generate_impls(&mut imp)?;
|
||||
let impls = generate_impls(&args, &mut imp)?;
|
||||
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
|
||||
|
||||
Ok(quote_spanned!(imp.self_ty.span() =>
|
||||
@ -46,7 +50,7 @@ impl Registry {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
fn generate_impls(_args: &Args, imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let ty = imp.self_ty.clone();
|
||||
let ty_name = ty_name(&ty)?;
|
||||
@ -54,6 +58,26 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let mut mms = HashSet::new();
|
||||
let mut lua_drop = None;
|
||||
|
||||
// process lua includes
|
||||
imp.attrs.retain(|attr| {
|
||||
if attr.path().is_ident("include") {
|
||||
if let Ok(path) = attr.parse_args::<LitStr>() {
|
||||
registry.build.push(quote_spanned!(path.span() =>
|
||||
b.include_lua(::std::include_str!(#path));
|
||||
));
|
||||
return false;
|
||||
} else if let Ok(chunk) = attr.parse_args::<Block>() {
|
||||
registry.build.push(quote_spanned!(chunk.span() =>
|
||||
b.include_lua(#ffi::__internal::luaify_chunk!(#chunk));
|
||||
));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
// process extern "Lua-C" ffi functions
|
||||
for func in get_ffi_functions(imp)? {
|
||||
if let Some(mm) = func.attrs.metamethod {
|
||||
syn_assert!(
|
||||
@ -66,6 +90,7 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
add_ffi_function(&mut registry, &func)?;
|
||||
}
|
||||
|
||||
// process extern "Lua" lua functions
|
||||
for func in get_lua_functions(imp)? {
|
||||
if let Some(mm) = func.attrs.metamethod {
|
||||
syn_assert!(
|
||||
@ -82,10 +107,12 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
// if no #[new] metamethod is defined, inject fallback new
|
||||
if !mms.contains(&Metamethod::New) {
|
||||
inject_fallback_new(&mut registry)?;
|
||||
}
|
||||
|
||||
// inject __gc/drop
|
||||
inject_merged_drop(&mut registry, lua_drop.as_ref())?;
|
||||
|
||||
let shims = ®istry.shims;
|
||||
@ -258,7 +285,7 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
|
||||
let attrs = parse_ffi_function_attrs(&mut func.attrs)?;
|
||||
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
||||
|
||||
func.sig.asyncness.is_some().then(|| document_async(func));
|
||||
document_ffi_function(func);
|
||||
|
||||
funcs.push(FfiFunction {
|
||||
@ -279,7 +306,7 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(name)
|
||||
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
||||
{
|
||||
match method {
|
||||
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
||||
@ -546,6 +573,12 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
"cannot be generic"
|
||||
);
|
||||
|
||||
syn_assert!(
|
||||
func.sig.constness.is_none(),
|
||||
func.sig.constness,
|
||||
"cannot be const"
|
||||
);
|
||||
|
||||
let mut params: Vec<_> = func
|
||||
.sig
|
||||
.inputs
|
||||
@ -566,7 +599,7 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
|
||||
let attrs = parse_lua_function_attrs(&mut func.attrs)?;
|
||||
attrs.metamethod.map(|mm| document_metamethod(func, mm));
|
||||
|
||||
func.sig.asyncness.is_some().then(|| document_async(func));
|
||||
document_lua_function(func);
|
||||
|
||||
funcs.push(LuaFunction {
|
||||
@ -629,6 +662,7 @@ fn stub_lua_type(ty: &Type) -> Result<Type> {
|
||||
} else {
|
||||
match ty_name(ty)?.to_string().as_str() {
|
||||
"any" => quote_spanned!(span => any),
|
||||
"many" => quote_spanned!(span => many),
|
||||
"nil" => quote_spanned!(span => nil),
|
||||
"boolean" => quote_spanned!(span => boolean),
|
||||
"lightuserdata" => quote_spanned!(span => lightuserdata),
|
||||
@ -652,7 +686,7 @@ fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAtt
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(name)
|
||||
&& let Ok(method) = Metamethod::try_from(&name.unraw())
|
||||
{
|
||||
match method {
|
||||
Metamethod::New => syn_error!(attr, r#"cannot be applied to a lua function"#),
|
||||
@ -718,11 +752,17 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
|
||||
"finaliser must take exactly one parameter"
|
||||
);
|
||||
|
||||
match lua.params[0] {
|
||||
// should be `self: cdata` PatType
|
||||
Pat::Type(ref ty) => {
|
||||
syn_assert!(
|
||||
pat_ident(&lua.params[0])? == "self",
|
||||
pat_ident(&ty.pat)? == "self",
|
||||
lua.params[0],
|
||||
"finaliser parameter must be `self`"
|
||||
);
|
||||
}
|
||||
_ => syn_error!(lua.params[0], "finaliser parameter must be `self`"),
|
||||
}
|
||||
|
||||
let params = &lua.params;
|
||||
let body = &lua.body;
|
||||
@ -773,30 +813,39 @@ fn document_lua_function(func: &mut ImplItemFn) {
|
||||
]));
|
||||
}
|
||||
|
||||
fn document_async(func: &mut ImplItemFn) {
|
||||
func.attrs.insert(0, parse_quote!(#[doc =
|
||||
r#"<span class="stab" title="This function is asynchronous." style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Async</span>"#
|
||||
]));
|
||||
}
|
||||
|
||||
fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
|
||||
let s = match method {
|
||||
Metamethod::Eq => "This is a metamethod which is called by the `==` operator.".into(),
|
||||
Metamethod::Len => "This is a metamethod which is called by the `#` operator.".into(),
|
||||
Metamethod::Lt => "This is a metamethod which is called by the `<` operator.".into(),
|
||||
Metamethod::Le => "This is a metamethod which is called by the `<=` operator.".into(),
|
||||
Metamethod::Concat => "This is a metamethod which is called by the `..` operator.".into(),
|
||||
Metamethod::Add => "This is a metamethod which is called by the `+` operator.".into(),
|
||||
Metamethod::Sub => "This is a metamethod which is called by the `-` operator.".into(),
|
||||
Metamethod::Mul => "This is a metamethod which is called by the `*` operator.".into(),
|
||||
Metamethod::Div => "This is a metamethod which is called by the `/` operator.".into(),
|
||||
Metamethod::Mod => "This is a metamethod which is called by the `%` operator.".into(),
|
||||
Metamethod::Pow => "This is a metamethod which is called by the `^` operator.".into(),
|
||||
Metamethod::Unm => "This is a metamethod which is called by the `-` operator.".into(),
|
||||
Metamethod::Eq => "This is a metamethod which is called by the `==` operator.",
|
||||
Metamethod::Len => "This is a metamethod which is called by the `#` operator.",
|
||||
Metamethod::Lt => "This is a metamethod which is called by the `<` operator.",
|
||||
Metamethod::Le => "This is a metamethod which is called by the `<=` operator.",
|
||||
Metamethod::Concat => "This is a metamethod which is called by the `..` operator.",
|
||||
Metamethod::Call => {
|
||||
"This is a metamethod which can be called by calling `(...)` on the value directly."
|
||||
}
|
||||
Metamethod::Add => "This is a metamethod which is called by the `+` operator.",
|
||||
Metamethod::Sub => "This is a metamethod which is called by the `-` operator.",
|
||||
Metamethod::Mul => "This is a metamethod which is called by the `*` operator.",
|
||||
Metamethod::Div => "This is a metamethod which is called by the `/` operator.",
|
||||
Metamethod::Mod => "This is a metamethod which is called by the `%` operator.",
|
||||
Metamethod::Pow => "This is a metamethod which is called by the `^` operator.",
|
||||
Metamethod::Unm => "This is a metamethod which is called by the `-` operator.",
|
||||
Metamethod::ToString => {
|
||||
"This is a metamethod which can be called by the `tostring` built-in function.".into()
|
||||
"This is a metamethod which is called by the [`tostring(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-tostring) built-in function."
|
||||
}
|
||||
Metamethod::Pairs => {
|
||||
"This is a metamethod which can be called by the `pairs` built-in function.".into()
|
||||
"This is a metamethod which is called by the [`pairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) built-in function."
|
||||
}
|
||||
Metamethod::Ipairs => {
|
||||
"This is a metamethod which can be called by the `ipairs` built-in function.".into()
|
||||
"This is a metamethod which is called by the [`ipairs(...)`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) built-in function."
|
||||
}
|
||||
_ => format!("This is a metamethod and cannot be called directly."),
|
||||
_ => "This is a metamethod and cannot be called directly.",
|
||||
};
|
||||
|
||||
func.attrs.insert(0, parse_quote!(#[doc =
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::env;
|
||||
use syn::{spanned::Spanned, *};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
@ -63,7 +63,7 @@ pub fn is_primitivelike(ty: &Type) -> bool {
|
||||
Type::Path(path) => {
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
return matches!(
|
||||
name.to_string().as_str(),
|
||||
name.unraw().to_string().as_str(),
|
||||
"bool"
|
||||
| "u8"
|
||||
| "u16"
|
||||
@ -115,31 +115,37 @@ pub fn is_stringlike(ty: &Type) -> Option<StringLike> {
|
||||
&& ty.mutability.is_none()
|
||||
&& ty.lifetime.is_none()
|
||||
{
|
||||
match *ty.elem {
|
||||
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()
|
||||
&& name == "u8"
|
||||
{
|
||||
return Some(StringLike::SliceU8);
|
||||
match name.unraw().to_string().as_str() {
|
||||
"u8" => StringLike::SliceU8,
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Type::Path(ref path) => {
|
||||
// match &str or &BStr
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
match name.to_string().as_str() {
|
||||
"str" => return Some(StringLike::Str),
|
||||
"BStr" => return Some(StringLike::BStr),
|
||||
_ => {}
|
||||
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> {
|
||||
|
@ -9,6 +9,7 @@ use std::{
|
||||
ffi::{CStr, CString, NulError},
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
os::raw::{c_char, c_int, c_void},
|
||||
pin::Pin,
|
||||
@ -35,6 +36,11 @@ pub fn url() -> &'static str {
|
||||
LUAJIT_URL.to_str().unwrap()
|
||||
}
|
||||
|
||||
// reexport constants
|
||||
pub use luajit_sys::{
|
||||
LUA_ENVIRONINDEX, LUA_GLOBALSINDEX, LUA_MULTRET, LUA_NOREF, LUA_REFNIL, LUA_REGISTRYINDEX,
|
||||
};
|
||||
|
||||
/// Lua error.
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
@ -477,6 +483,19 @@ pub struct Ref {
|
||||
key: c_int,
|
||||
}
|
||||
|
||||
impl Ref {
|
||||
/// Consumes this ref and returns the original key used to create the ref.
|
||||
///
|
||||
/// This key can be used to index into the registry table ([`LUA_REGISTRYINDEX`]) to retrieve
|
||||
/// the referenced value. The key can be converted back into a ref using
|
||||
/// [`State::new_ref_unchecked`].
|
||||
pub fn into_raw(self) -> c_int {
|
||||
let Self { ref mut state, key } = *ManuallyDrop::new(self);
|
||||
unsafe { ptr::drop_in_place(state) }
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ref {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: luaL_unref is guaranteed to not fail
|
||||
@ -871,7 +890,7 @@ impl Stack {
|
||||
/// array-part, these values are **not** cleared. The number of values popped is returned, which
|
||||
/// is always equal to `n`.
|
||||
///
|
||||
/// This method does not invoke any metamethods.
|
||||
/// This method does not invoke any metamethods. The table is not popped from the stack.
|
||||
///
|
||||
/// Equivalent to `table.pack(...)`.
|
||||
///
|
||||
@ -907,7 +926,8 @@ impl Stack {
|
||||
/// pushed at the top of the stack in ascending order. If `i > j`, then nothing is pushed.
|
||||
/// Otherwise, `j - i + 1` values are pushed, and the number of values pushed is returned.
|
||||
///
|
||||
/// This method does not invoke any metamethods.
|
||||
/// This method does not invoke any metamethods. The table is not popped from the stack or
|
||||
/// altered in any way.
|
||||
///
|
||||
/// Equivalent to `table.unpack(list, i, j)`.
|
||||
///
|
||||
|
@ -10,11 +10,11 @@ pub use lb::task;
|
||||
#[doc(hidden)]
|
||||
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
|
||||
#[cfg(feature = "task")]
|
||||
rt.module::<task::lb_tasklib>("lb:task");
|
||||
rt.module::<task::lb_tasklib>();
|
||||
#[cfg(feature = "task")]
|
||||
rt.module::<chan::lb_chanlib>("lb:channel");
|
||||
rt.module::<chan::lb_chanlib>();
|
||||
#[cfg(feature = "fs")]
|
||||
rt.module::<fs::lb_fslib>("lb:fs");
|
||||
rt.module::<fs::lb_fslib>();
|
||||
#[cfg(feature = "net")]
|
||||
rt.module::<net::lb_netlib>("lb:net");
|
||||
rt.module::<net::lb_netlib>();
|
||||
}
|
||||
|
79
src/main.rs
79
src/main.rs
@ -40,6 +40,22 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
|
||||
);
|
||||
}
|
||||
|
||||
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.
|
||||
@ -121,12 +137,12 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), ExitCode> {
|
||||
panic::set_hook(Box::new(panic_cb));
|
||||
|
||||
let args = Args::parse();
|
||||
if args.version {
|
||||
return print_version();
|
||||
return Ok(print_version());
|
||||
}
|
||||
|
||||
init_logger(&args);
|
||||
@ -153,13 +169,6 @@ fn print_version() {
|
||||
);
|
||||
}
|
||||
|
||||
fn unwrap_exit<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
|
||||
move |err| {
|
||||
eprintln!("{}", err.red().bold());
|
||||
code.exit()
|
||||
}
|
||||
}
|
||||
|
||||
fn init_logger(args: &Args) {
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::util::*;
|
||||
@ -192,16 +201,15 @@ fn init_logger(args: &Args) {
|
||||
}
|
||||
|
||||
fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
||||
let mut rt = match args.threads.get() {
|
||||
match args.threads.get() {
|
||||
1 => tokio::runtime::Builder::new_current_thread(),
|
||||
n => {
|
||||
let mut rt = tokio::runtime::Builder::new_multi_thread();
|
||||
rt.worker_threads(n - 1);
|
||||
rt
|
||||
}
|
||||
};
|
||||
|
||||
rt.enable_all()
|
||||
}
|
||||
.enable_all()
|
||||
.thread_name("luby")
|
||||
.max_blocking_threads(args.blocking_threads.get())
|
||||
.build()
|
||||
@ -209,37 +217,43 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
||||
}
|
||||
|
||||
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
||||
let mut rt = {
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
|
||||
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
||||
print!("{}", rt.registry());
|
||||
print!("{}", rt.registry()); // for cdef debugging
|
||||
}
|
||||
|
||||
let mut rt = rt.build().unwrap();
|
||||
rt.unhandled_error(error_cb).build().unwrap()
|
||||
};
|
||||
|
||||
for arg in args.jit.iter() {
|
||||
let mut s = rt.guard();
|
||||
if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
||||
let res = if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
||||
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
|
||||
{
|
||||
(s.push("start"), s.get(-2), s.push(flags));
|
||||
s.call(1, 0)
|
||||
s.call(1, 0) // require("jit.{cmd}").start(flags)
|
||||
} else {
|
||||
s.require("jit", 1).unwrap();
|
||||
match arg.as_str() {
|
||||
cmd @ ("on" | "off" | "flush") => {
|
||||
(s.push(cmd), s.get(-2));
|
||||
s.call(0, 0)
|
||||
s.call(0, 0) // require("jit").[on/off/flush]()
|
||||
}
|
||||
arg => {
|
||||
flags => {
|
||||
(s.push("opt"), s.get(-2));
|
||||
(s.push("start"), s.get(-2), s.push(arg));
|
||||
s.call(1, 0)
|
||||
(s.push("start"), s.get(-2), s.push(flags));
|
||||
s.call(1, 0) // require("jit").opt.start(flags)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = res {
|
||||
drop(s);
|
||||
rt.report_error(&err);
|
||||
}
|
||||
.unwrap_or_else(unwrap_exit(ExitCode::Usage));
|
||||
}
|
||||
|
||||
rt
|
||||
@ -254,27 +268,22 @@ fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_async(args: Args, state: &mut luajit::State) {
|
||||
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> 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());
|
||||
ExitCode::NoInput.exit();
|
||||
return Err(ExitCode::NoInput);
|
||||
}
|
||||
};
|
||||
|
||||
s.load(&luajit::Chunk::new(chunk).path(path))
|
||||
.unwrap_or_else(unwrap_exit(ExitCode::NoInput));
|
||||
|
||||
if let Err(err) = s.call_async(0, 0).await {
|
||||
match err.trace() {
|
||||
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
|
||||
None => eprintln!("{}", err.red().bold()),
|
||||
if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) {
|
||||
cx.report_error(err);
|
||||
} else if let Err(ref err) = cx.call_async(0, 0).await {
|
||||
cx.report_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
97
tests/main.lua
Normal file
97
tests/main.lua
Normal file
@ -0,0 +1,97 @@
|
||||
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive main call
|
||||
local fs = require("lb:fs")
|
||||
local global = _G
|
||||
local colors = {
|
||||
reset = "\x1b[0m",
|
||||
pass = "\x1b[32;1m",
|
||||
fail = "\x1b[31;1m",
|
||||
}
|
||||
|
||||
local function color(name, s)
|
||||
return colors[name] .. s .. colors.reset
|
||||
end
|
||||
|
||||
local function create_test(name, f, group)
|
||||
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
|
||||
local fenv = setmetatable({}, { __index = global })
|
||||
setfenv(f, fenv)
|
||||
return test
|
||||
end
|
||||
|
||||
local function create_group(name, f, parent)
|
||||
local group = { type = "group", name = name or "", parent = parent, items = {} }
|
||||
local fenv = setmetatable({
|
||||
describe = function(name, f)
|
||||
local item = create_group(name, f, group)
|
||||
table.insert(group.items, item)
|
||||
return item
|
||||
end,
|
||||
|
||||
test = function(name, f)
|
||||
local item = create_test(name, f, group)
|
||||
table.insert(group.items, item)
|
||||
return item
|
||||
end,
|
||||
}, { __index = global })
|
||||
|
||||
setfenv(f, fenv)
|
||||
f(group)
|
||||
return group
|
||||
end
|
||||
|
||||
local function name_test(test)
|
||||
local name = test.name
|
||||
local group = test.group
|
||||
while group ~= nil do
|
||||
if group.name ~= "" then name = group.name .. " › " .. name end
|
||||
group = group.parent
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
local function trace(msg)
|
||||
return color("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
||||
end
|
||||
|
||||
local function run_test(test)
|
||||
local ok, res = xpcall(test.f, trace, test)
|
||||
if ok then
|
||||
test.state = "pass"
|
||||
print("", color("pass", "PASS") .. " " .. name_test(test))
|
||||
else
|
||||
test.state = "fail"
|
||||
print("", color("fail", "FAIL") .. " " .. name_test(test) .. "\n")
|
||||
print(res .. "\n")
|
||||
end
|
||||
return test
|
||||
end
|
||||
|
||||
local function start(cx, item)
|
||||
if item.type == "test" then
|
||||
table.insert(cx.tasks, spawn(run_test, item))
|
||||
elseif item.type == "group" then
|
||||
for _, item in ipairs(item.items) do
|
||||
start(cx, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function run(item)
|
||||
local cx = { tasks = {} }
|
||||
local pass = true
|
||||
start(cx, item)
|
||||
for _, task in ipairs(cx.tasks) do
|
||||
if task:await().state ~= "pass" then pass = false end
|
||||
end
|
||||
if pass then return 0 end -- report status to cargo
|
||||
return 1
|
||||
end
|
||||
|
||||
return run(create_group("", function()
|
||||
for entry in fs:glob("{tests,crates/*/tests}/**/*.lua") do
|
||||
local path = entry:path():sub(3)
|
||||
local f, err = loadfile(path)
|
||||
if not f then error(err) end
|
||||
describe(path, f)
|
||||
end
|
||||
end))
|
44
tests/main.rs
Normal file
44
tests/main.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use luajit::Chunk;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{
|
||||
fs,
|
||||
process::{self, ExitCode},
|
||||
};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let tokio = tokio::runtime::Runtime::new().unwrap();
|
||||
let lua = {
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
rt.unhandled_error(error_cb).build().unwrap()
|
||||
};
|
||||
|
||||
let path = "tests/main.lua";
|
||||
let main = lua.spawn(async move |s| {
|
||||
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) {
|
||||
s.report_error(err);
|
||||
} else if let Err(ref err) = s.call_async(0, 1).await {
|
||||
s.report_error(err);
|
||||
}
|
||||
|
||||
if s.slot(1).integer().unwrap_or(1) == 0 {
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
});
|
||||
|
||||
tokio.block_on(async move {
|
||||
lua.await;
|
||||
main.await.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
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