Compare commits

...

17 Commits

27 changed files with 514 additions and 249 deletions

25
Cargo.lock generated
View File

@@ -534,6 +534,12 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@@ -1028,9 +1034,9 @@ dependencies = [
"camino", "camino",
"derive_more", "derive_more",
"luaffi", "luaffi",
"luaify",
"luajit", "luajit",
"sysexits", "sysexits",
"thiserror",
"tokio", "tokio",
"tracing", "tracing",
] ]
@@ -1138,6 +1144,7 @@ dependencies = [
name = "luaify" name = "luaify"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@@ -1410,6 +1417,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.35" version = "0.2.35"
@@ -2346,6 +2363,12 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.0" version = "0.8.0"

View File

@@ -13,9 +13,6 @@ derive_more = { version = "2.0.1", features = ["full"] }
luaffi = { path = "../luaffi" } luaffi = { path = "../luaffi" }
luajit = { path = "../luajit" } luajit = { path = "../luajit" }
sysexits = "0.9.0" sysexits = "0.9.0"
thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal", "tracing"] } tokio = { version = "1.45.1", features = ["rt", "time", "fs", "net", "process", "signal", "tracing"] }
tracing = "0.1.41" tracing = "0.1.41"
[dev-dependencies]
luaify = { path = "../luaify" }
tokio = { version = "1.45.1", features = ["full"] }

View File

@@ -2,10 +2,10 @@
use luaffi::{cdef, metatype}; use luaffi::{cdef, metatype};
#[cdef] #[cdef]
pub struct lb_libchannel; pub struct lb_chanlib;
#[metatype] #[metatype]
impl lb_libchannel { impl lb_chanlib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self

View File

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

View File

@@ -1,4 +1,4 @@
pub mod channel; pub mod chan;
pub mod fs; pub mod fs;
pub mod net; pub mod net;
pub mod runtime; pub mod runtime;

View File

@@ -1,29 +1,47 @@
//! The `lb:net` module provides an asynchronous network API for creating TCP or UDP servers and //! The `lb:net` library provides an asynchronous network API for creating TCP or UDP servers and
//! clients. //! clients.
//! //!
//! # Exports //! # Exports
//! //!
//! See [`lb_libnet`] for items exported by this module. //! See [`lb_netlib`] for items exported by this library.
use derive_more::{From, FromStr}; use derive_more::{From, FromStr};
use luaffi::{cdef, metatype}; use luaffi::{cdef, metatype};
use std::{ use std::{
io,
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
time::Duration,
}; };
use thiserror::Error;
use tokio::net::{TcpListener, TcpSocket, TcpStream}; use tokio::net::{TcpListener, TcpSocket, TcpStream};
/// Items exported by the `lb:net` module. /// Errors that can be thrown by this library.
/// ///
/// This module can be obtained by calling `require` in Lua. /// Functions which return this error will **throw** in Lua. The error message can be caught by
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)]
pub enum Error {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
InvalidAddr(#[from] AddrParseError),
#[error("socket was already converted")]
SocketConsumed,
}
type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:net` library.
///
/// This library can be obtained by calling
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
/// ///
/// ```lua /// ```lua
/// local net = require("lb:net"); /// local net = require("lb:net");
/// ``` /// ```
#[cdef] #[cdef]
pub struct lb_libnet; pub struct lb_netlib;
#[metatype] #[metatype]
impl lb_libnet { impl lb_netlib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self
@@ -59,11 +77,7 @@ impl lb_libnet {
/// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an /// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an
/// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses /// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses
/// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported. /// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported.
/// pub extern "Lua" fn ipaddr(&self, s: any) -> Result<lb_ipaddr> {
/// # 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) { if __istype(__ct.lb_ipaddr, s) {
__new(__ct.lb_ipaddr, s) // copy constructor __new(__ct.lb_ipaddr, s) // copy constructor
} else if __istype(__ct.lb_socketaddr, s) { } else if __istype(__ct.lb_socketaddr, s) {
@@ -73,8 +87,8 @@ impl lb_libnet {
} }
} }
extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr, AddrParseError> { extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result<lb_ipaddr> {
s.parse() Ok(s.parse()?)
} }
/// Creates an [`lb_socketaddr`] from the given input. /// Creates an [`lb_socketaddr`] from the given input.
@@ -86,52 +100,67 @@ impl lb_libnet {
/// socket address string. Both IPv4 and IPv6 addresses are supported. /// socket address string. Both IPv4 and IPv6 addresses are supported.
/// ///
/// If `port` is not specified, `0` is used as the default. /// If `port` is not specified, `0` is used as the default.
/// pub extern "Lua" fn socketaddr(&self, s: any, port: any) -> Result<lb_socketaddr> {
/// # 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 != () { if port != () {
self.__new_socketaddr(self.ipaddr(s), port) self.__new_skaddr(self.ipaddr(s), port)
} else { } else {
if __istype(__ct.lb_socketaddr, s) { if __istype(__ct.lb_socketaddr, s) {
__new(__ct.lb_socketaddr, s) // copy constructor __new(__ct.lb_socketaddr, s) // copy constructor
} else if __istype(__ct.lb_ipaddr, s) { } else if __istype(__ct.lb_ipaddr, s) {
self.__new_socketaddr(s, 0) // default port 0 self.__new_skaddr(s, 0) // default port 0
} else { } else {
self.__parse_socketaddr(s) self.__parse_skaddr(s)
} }
} }
} }
extern "Lua-C" fn __new_socketaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr { extern "Lua-C" fn __new_skaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
SocketAddr::new(ip.0, port).into() SocketAddr::new(ip.0, port).into()
} }
extern "Lua-C" fn __parse_socketaddr(&self, s: &str) -> Result<lb_socketaddr, AddrParseError> { extern "Lua-C" fn __parse_skaddr(&self, s: &str) -> Result<lb_socketaddr> {
s.parse() Ok(s.parse()?)
} }
/// Creates a new TCP socket configured for IPv4. /// Creates a new TCP socket configured for IPv4.
/// ///
/// See [`TcpSocket::new_v4`]. /// See [`TcpSocket::new_v4`].
/// pub extern "Lua-C" fn tcp(&self) -> Result<lb_tcpsocket> {
/// # Errors Ok(Some(TcpSocket::new_v4()?).into())
///
/// Throws if an error was encountered during the socket creation.
pub extern "Lua-C" fn tcp_v4(&self) -> io::Result<lb_tcpsocket> {
TcpSocket::new_v4().map(lb_tcpsocket)
} }
/// Creates a new TCP socket configured for IPv6. /// Creates a new TCP socket configured for IPv6.
/// ///
/// See [`TcpSocket::new_v6`]. /// See [`TcpSocket::new_v6`].
/// pub extern "Lua-C" fn tcp6(&self) -> Result<lb_tcpsocket> {
/// # Errors Ok(Some(TcpSocket::new_v6()?).into())
/// }
/// Throws if an error was encountered during the socket creation.
pub extern "Lua-C" fn tcp_v6(&self) -> io::Result<lb_tcpsocket> { pub extern "Lua" fn bind_tcp(&self, addr: any, port: any) -> Result<lb_tcpsocket> {
TcpSocket::new_v6().map(lb_tcpsocket) let addr = self.socketaddr(addr, port);
let socket;
if addr.ip().is_v6() {
socket = self.tcp6();
} else {
socket = self.tcp();
}
socket.bind(addr);
socket
}
pub 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() {
socket = self.tcp6();
} else {
socket = self.tcp();
}
socket.connect(addr)
}
pub extern "Lua" fn listen_tcp(&self, addr: any, port: any) -> Result<lb_tcplistener> {
self.bind_tcp(addr, port).listen(1024)
} }
} }
@@ -274,7 +303,7 @@ impl lb_socketaddr {
/// Sets the IP part of this address. /// Sets the IP part of this address.
/// ///
/// This function accepts the same arguments as [`ipaddr`](lb_libnet::ipaddr). /// This function accepts the same arguments as [`ipaddr`](lb_netlib::ipaddr).
pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self { pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self {
if __istype(__ct.lb_ipaddr, s) { if __istype(__ct.lb_ipaddr, s) {
self.__set_ip(s); self.__set_ip(s);
@@ -290,8 +319,8 @@ impl lb_socketaddr {
self.0.set_ip(ip.0); self.0.set_ip(ip.0);
} }
extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<(), AddrParseError> { extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<()> {
s.parse().map(|ip| self.0.set_ip(ip)) Ok(self.0.set_ip(s.parse()?))
} }
/// Returns the port part of this address. /// Returns the port part of this address.
@@ -300,7 +329,7 @@ impl lb_socketaddr {
} }
/// Sets the port part of this address. /// Sets the port part of this address.
pub extern "Lua" fn set_port(&mut self, port: number) -> &mut Self { pub extern "Lua" fn set_port(&mut self, port: integer) -> &mut Self {
self.__set_port(port); self.__set_port(port);
self self
} }
@@ -316,13 +345,125 @@ impl lb_socketaddr {
} }
} }
/// A TCP socket which has not yet been converted to a [`lb_tcpstream`] or [`lb_tcplistener`]. /// A TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
#[derive(Debug, From)] #[derive(Debug, From)]
#[cdef] #[cdef]
pub struct lb_tcpsocket(#[opaque] TcpSocket); pub struct lb_tcpsocket(#[opaque] Option<TcpSocket>);
#[metatype] #[metatype]
impl lb_tcpsocket {} impl lb_tcpsocket {
fn socket(&self) -> Result<&TcpSocket> {
self.0.as_ref().ok_or(Error::SocketConsumed)
}
/// See [`TcpSocket::keepalive`].
pub extern "Lua-C" fn keepalive(&self) -> Result<bool> {
Ok(self.socket()?.keepalive()?)
}
/// See [`TcpSocket::set_keepalive`].
pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_keepalive(enabled)?)
}
/// See [`TcpSocket::reuseaddr`].
pub extern "Lua-C" fn reuseaddr(&self) -> Result<bool> {
Ok(self.socket()?.reuseaddr()?)
}
/// See [`TcpSocket::set_reuseaddr`].
pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_reuseaddr(enabled)?)
}
/// See [`TcpSocket::reuseport`].
pub extern "Lua-C" fn reuseport(&self) -> Result<bool> {
Ok(self.socket()?.reuseport()?)
}
/// See [`TcpSocket::set_reuseport`].
pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_reuseport(enabled)?)
}
/// See [`TcpSocket::send_buffer_size`].
pub extern "Lua-C" fn sendbuf(&self) -> Result<u32> {
Ok(self.socket()?.send_buffer_size()?)
}
/// See [`TcpSocket::set_send_buffer_size`].
pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> {
Ok(self.socket()?.set_send_buffer_size(size)?)
}
/// See [`TcpSocket::recv_buffer_size`].
pub extern "Lua-C" fn recvbuf(&self) -> Result<u32> {
Ok(self.socket()?.recv_buffer_size()?)
}
/// See [`TcpSocket::set_recv_buffer_size`].
pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> {
Ok(self.socket()?.set_recv_buffer_size(size)?)
}
/// See [`TcpSocket::linger`].
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
Ok(self
.socket()?
.linger()?
.map(|n| n.as_secs_f64())
.unwrap_or(0.))
}
/// See [`TcpSocket::set_linger`].
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
Ok(self
.socket()?
.set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?)
}
/// See [`TcpSocket::nodelay`].
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
Ok(self.socket()?.nodelay()?)
}
/// See [`TcpSocket::set_nodelay`].
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_nodelay(enabled)?)
}
/// See [`TcpSocket::tos`].
pub extern "Lua-C" fn tos(&self) -> Result<u32> {
Ok(self.socket()?.tos()?)
}
/// See [`TcpSocket::set_tos`].
pub extern "Lua-C" fn set_tos(&self, tos: u32) -> Result<()> {
Ok(self.socket()?.set_tos(tos)?)
}
/// See [`TcpSocket::local_addr`].
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
Ok(self.socket()?.local_addr()?.into())
}
/// See [`TcpSocket::bind`].
pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> {
Ok(self.socket()?.bind(addr.0)?)
}
/// See [`TcpSocket::connect`].
pub async extern "Lua-C" fn connect(&mut self, addr: &lb_socketaddr) -> Result<lb_tcpstream> {
let socket = self.0.take().ok_or(Error::SocketConsumed)?;
Ok(socket.connect(addr.0).await?.into())
}
/// See [`TcpSocket::listen`].
pub extern "Lua-C" fn listen(&mut self, backlog: u32) -> Result<lb_tcplistener> {
let socket = self.0.take().ok_or(Error::SocketConsumed)?;
Ok(socket.listen(backlog)?.into())
}
}
#[derive(Debug, From)] #[derive(Debug, From)]
#[cdef] #[cdef]

View File

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

View File

@@ -1,4 +1,3 @@
use crate::{channel::lb_libchannel, fs::lb_libfs, net::lb_libnet, task::lb_libtask};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use luaffi::{Registry, Type}; use luaffi::{Registry, Type};
use luajit::{Chunk, State}; use luajit::{Chunk, State};
@@ -15,15 +14,13 @@ pub struct Builder {
impl Builder { impl Builder {
pub fn new() -> Self { pub fn new() -> Self {
let mut registry = Registry::new(); Self {
registry: Registry::new(),
}
}
registry pub fn registry(&self) -> &Registry {
.preload::<lb_libtask>("lb:task") &self.registry
.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 { pub fn module<T: Type>(&mut self, name: impl Display) -> &mut Self {
@@ -31,17 +28,11 @@ impl Builder {
self self
} }
pub fn registry(&self) -> &Registry {
&self.registry
}
pub fn build(&self) -> luajit::Result<Runtime> { pub fn build(&self) -> luajit::Result<Runtime> {
Ok(Runtime { Ok(Runtime {
state: { state: {
let mut s = State::new()?; let mut s = State::new()?;
let mut chunk = Chunk::new(self.registry.done()); s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
chunk.extend(include_bytes!("./runtime.lua"));
s.eval(chunk.path("[luby]"), 0, 0)?;
s s
}, },
tasks: LocalSet::new(), tasks: LocalSet::new(),
@@ -72,6 +63,9 @@ impl Runtime {
} }
pub fn spawn<T: 'static>(f: impl AsyncFnOnce(&mut State) -> T + 'static) -> JoinHandle<T> { 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 }) spawn_local(async move { f(&mut STATE.with(|s| s.new_thread())).await })
} }

View File

@@ -4,10 +4,10 @@ use std::{ffi::c_int, process};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
#[cdef] #[cdef]
pub struct lb_libtask; pub struct lb_tasklib;
#[metatype] #[metatype]
impl lb_libtask { impl lb_tasklib {
#[new] #[new]
extern "Lua-C" fn new() -> Self { extern "Lua-C" fn new() -> Self {
Self Self

View File

@@ -1,32 +0,0 @@
use lb::runtime;
use luaify::luaify;
use luajit::{Chunk, LoadMode};
use tokio::test;
async fn run_lua(s: &'static str) {
let rt = runtime::Builder::new().build().unwrap();
let task = rt.spawn(async move |state| {
println!("executing test chunk: {s}");
state
.load(Chunk::new(s).mode(LoadMode::TEXT))
.unwrap_or_else(|err| panic!("{err}"));
state
.call_async(0, 0)
.await
.unwrap_or_else(|err| panic!("{err}"));
});
rt.await;
task.await.unwrap_or_else(|err| panic!("{err}"));
}
#[test]
async fn ipaddr() {
run_lua(luaify!({
let net = require("lb:net");
print(net.ipaddr("127.0.0.1"));
}))
.await
}

View File

@@ -1,35 +0,0 @@
use lb::runtime;
use luaify::luaify;
use luajit::{Chunk, LoadMode};
use tokio::test;
async fn run_lua(s: &'static str) {
let rt = runtime::Builder::new().build().unwrap();
let task = rt.spawn(async move |state| {
println!("executing test chunk: {s}");
state
.load(Chunk::new(s).mode(LoadMode::TEXT))
.unwrap_or_else(|err| panic!("{err}"));
state
.call_async(0, 0)
.await
.unwrap_or_else(|err| panic!("{err}"));
});
rt.await;
task.await.unwrap_or_else(|err| panic!("{err}"));
}
#[test]
async fn task_test() {
run_lua(luaify!({
let thing = spawn(|| {
print("spawn callback!!!!!!!!!!!!!");
});
print("thing is", thing);
//
}))
.await
}

View File

@@ -7,6 +7,10 @@ authors.workspace = true
homepage.workspace = true homepage.workspace = true
repository.workspace = true repository.workspace = true
[features]
option_ref_abi = []
option_string_abi = []
[dependencies] [dependencies]
bstr = "1.12.0" bstr = "1.12.0"
luaffi_impl = { path = "../luaffi_impl" } luaffi_impl = { path = "../luaffi_impl" }

View File

@@ -10,11 +10,11 @@ use std::{
pub mod stub_types { pub mod stub_types {
pub struct any; pub struct any;
pub struct nil; pub struct nil;
pub struct boolean; pub type boolean = bool;
pub struct lightuserdata; pub struct lightuserdata;
pub struct number; pub struct number;
pub struct integer; pub struct integer;
pub struct string; pub type string = String;
pub struct table; pub struct table;
pub struct function; pub struct function;
pub struct userdata; pub struct userdata;

View File

@@ -1,3 +1,4 @@
---@diagnostic disable
local LUA_REFNIL = -1 -- lib_aux.c local LUA_REFNIL = -1 -- lib_aux.c
local FREELIST_REF = 0 local FREELIST_REF = 0

View File

@@ -11,13 +11,13 @@ use std::{
mem, mem,
}; };
pub mod future;
pub mod string;
#[doc(hidden)] #[doc(hidden)]
#[path = "./internal.rs"] #[path = "./internal.rs"]
pub mod __internal; pub mod __internal;
pub mod future;
pub mod option;
pub mod result; pub mod result;
pub mod string;
// Dummy function to ensure that strings passed to Rust via wrapper objects will not be // Dummy function to ensure that strings passed to Rust via wrapper objects will not be
// garbage-collected until the end of the function (used in string.rs when string marshalling is // garbage-collected until the end of the function (used in string.rs when string marshalling is
@@ -171,7 +171,7 @@ impl Registry {
self self
} }
pub fn done(&self) -> String { pub fn build(&self) -> String {
self.to_string() self.to_string()
} }
} }
@@ -345,12 +345,12 @@ impl<'r> MetatypeBuilder<'r> {
) -> &mut Self { ) -> &mut Self {
write!(self.lua, "__idx.{name} = ").unwrap(); write!(self.lua, "__idx.{name} = ").unwrap();
f(&mut MetatypeMethodBuilder::new(self)); f(&mut MetatypeMethodBuilder::new(self));
write!(self.lua, "; ").unwrap(); writeln!(self.lua, ";").unwrap();
self self
} }
pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self { pub fn index_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
write!(self.lua, "__idx.{name} = {value}; ").unwrap(); writeln!(self.lua, "__idx.{name} = {value};").unwrap();
self self
} }
@@ -361,12 +361,12 @@ impl<'r> MetatypeBuilder<'r> {
) -> &mut Self { ) -> &mut Self {
write!(self.lua, "__mt.__{name} = ").unwrap(); write!(self.lua, "__mt.__{name} = ").unwrap();
f(&mut MetatypeMethodBuilder::new(self)); f(&mut MetatypeMethodBuilder::new(self));
write!(self.lua, "; ").unwrap(); writeln!(self.lua, ";").unwrap();
self self
} }
pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self { pub fn metatable_raw(&mut self, name: impl Display, value: impl Display) -> &mut Self {
write!(self.lua, "__mt.__{name} = {value}; ").unwrap(); writeln!(self.lua, "__mt.__{name} = {value};").unwrap();
self self
} }
} }
@@ -857,7 +857,9 @@ macro_rules! impl_ptr_intoabi {
impl_ptr_intoabi!(*const T); impl_ptr_intoabi!(*const T);
impl_ptr_intoabi!(*mut T); impl_ptr_intoabi!(*mut T);
#[cfg(feature = "option_ref_abi")] // disabled because it conflicts with the generic Option<T> impl
impl_ptr_intoabi!(Option<&'static T>); impl_ptr_intoabi!(Option<&'static T>);
#[cfg(feature = "option_ref_abi")]
impl_ptr_intoabi!(Option<&'static mut T>); impl_ptr_intoabi!(Option<&'static mut T>);
// //

View File

@@ -0,0 +1,84 @@
use crate::{
__internal::{disp, display},
Cdef, CdefBuilder, IntoFfi, KEEP_FN, 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> Type for lua_option<T> {
fn name() -> impl Display {
display!("option__{}", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("struct {} {name}", Self::name())
}
fn build(b: &mut TypeBuilder) {
b.cdef::<Self>();
}
}
unsafe impl<T: Type> Cdef for lua_option<T> {
fn build(b: &mut CdefBuilder) {
b.field::<c_int>("__tag");
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
}
}
unsafe impl<T: IntoFfi> 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; ")
})
}
}

View File

@@ -72,12 +72,12 @@ unsafe impl<T: IntoFfi, E: Display> IntoFfi for Result<T, E> {
write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?; write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?;
write!(f, "{}", T::postlude(ret))?; write!(f, "{}", T::postlude(ret))?;
} else { } else {
// inner value is a "temporary" itself and doesn't require full ownership of // inner value is a "temporary" like a result itself and doesn't require
// itself. we just need to keep the result object alive until its postlude // full ownership of itself. we just need to keep the result object alive
// completes. // until its postlude completes.
write!(f, "local __{ret} = {ret}; {ret} = {ret}.__value; ")?; write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?;
write!(f, "do {}end; ", T::postlude(ret))?; write!(f, "do {}end; ", T::postlude(ret))?;
write!(f, "__C.{KEEP_FN}(__{ret}); ")?; // keep original result alive write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original result alive
} }
} }
} }

View File

@@ -4,7 +4,7 @@ use crate::{
}; };
use bstr::{BStr, BString}; use bstr::{BStr, BString};
use luaffi_impl::{cdef, metatype}; use luaffi_impl::{cdef, metatype};
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice}; use std::{fmt::Display, mem::ManuallyDrop, slice};
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8"; pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer"; pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
@@ -42,6 +42,7 @@ impl lua_buf {
} }
} }
#[cfg(feature = "option_string_abi")]
pub(crate) fn null() -> Self { pub(crate) fn null() -> Self {
Self { Self {
__ptr: ptr::null(), __ptr: ptr::null(),
@@ -71,6 +72,7 @@ impl lua_buffer {
} }
} }
#[cfg(feature = "option_string_abi")]
pub(crate) fn null() -> Self { pub(crate) fn null() -> Self {
Self { Self {
__ptr: ptr::null_mut(), __ptr: ptr::null_mut(),
@@ -228,56 +230,64 @@ impl_into_via!(&'static str, &'static BStr);
impl_into_via!(BString, Vec<u8>); impl_into_via!(BString, Vec<u8>);
impl_into_via!(String, BString); impl_into_via!(String, BString);
macro_rules! impl_optional_from { // `Option<String>: From/IntoFfi` isn't implemented because it conflicts with the generic
($ty:ty) => { // `Option<T>: From/IntoFfi` impl and rust doesn't have specialisation yet (and probably not anytime
unsafe impl<'s> FromFfi for Option<$ty> { // soon). this is fine for now because we have specialisation for string-like parameters implemented
type From = <$ty as FromFfi>::From; // in the #[metatype] macro already, and string returns wrapped in `Option<T>` isn't much additional
// overhead.
#[cfg(feature = "option_string_abi")]
mod impl_option_string {
macro_rules! impl_optional_from {
($ty:ty) => {
unsafe impl<'s> FromFfi for Option<$ty> {
type From = <$ty as FromFfi>::From;
fn require_keepalive() -> bool { fn require_keepalive() -> bool {
<$ty as FromFfi>::require_keepalive() <$ty as FromFfi>::require_keepalive()
} }
fn prelude(arg: &str) -> impl Display { fn prelude(arg: &str) -> impl Display {
// just pass a null pointer if argument is nil // just pass a null pointer if argument is nil
display!( display!(
"if {arg} ~= nil then {}end; ", "if {arg} ~= nil then {}end; ",
<$ty as FromFfi>::prelude(arg) <$ty as FromFfi>::prelude(arg)
) )
} }
fn convert(from: Self::From) -> Self { fn convert(from: Self::From) -> Self {
from.map(|s| <$ty as FromFfi>::convert(Some(s))) from.map(|s| <$ty as FromFfi>::convert(Some(s)))
}
} }
} };
}; }
impl_optional_from!(&'s [u8]);
impl_optional_from!(&'s BStr);
impl_optional_from!(&'s str);
macro_rules! impl_optional_into {
($ty:ty, $null:expr) => {
unsafe impl IntoFfi for Option<$ty> {
type Into = <$ty as IntoFfi>::Into;
fn convert(self) -> Self::Into {
self.map_or($null, <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret)
)
}
}
};
}
impl_optional_into!(&'static [u8], lua_buf::null());
impl_optional_into!(&'static BStr, lua_buf::null());
impl_optional_into!(&'static str, lua_buf::null());
impl_optional_into!(Vec<u8>, lua_buffer::null());
impl_optional_into!(BString, lua_buffer::null());
impl_optional_into!(String, lua_buffer::null());
} }
impl_optional_from!(&'s [u8]);
impl_optional_from!(&'s BStr);
impl_optional_from!(&'s str);
macro_rules! impl_optional_into {
($ty:ty, $null:expr) => {
unsafe impl IntoFfi for Option<$ty> {
type Into = <$ty as IntoFfi>::Into;
fn convert(self) -> Self::Into {
self.map_or($null, <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret)
)
}
}
};
}
impl_optional_into!(&'static [u8], lua_buf::null());
impl_optional_into!(&'static BStr, lua_buf::null());
impl_optional_into!(&'static str, lua_buf::null());
impl_optional_into!(Vec<u8>, lua_buffer::null());
impl_optional_into!(BString, lua_buffer::null());
impl_optional_into!(String, lua_buffer::null());

View File

@@ -763,13 +763,13 @@ fn inject_merged_drop(registry: &mut Registry, lua: Option<&LuaFunction>) -> Res
fn document_ffi_function(func: &mut ImplItemFn) { fn document_ffi_function(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc = func.attrs.insert(0, parse_quote!(#[doc =
r#"<span class="stab" title="This is a C/FFI function." style="float: right; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">FFI</span>"# r#"<span class="stab" title="This function is implemented in Rust and called via FFI." style="float: right; background: #fff5d6; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">FFI</span>"#
])); ]));
} }
fn document_lua_function(func: &mut ImplItemFn) { fn document_lua_function(func: &mut ImplItemFn) {
func.attrs.insert(0, parse_quote!(#[doc = func.attrs.insert(0, parse_quote!(#[doc =
r#"<span class="stab" title="This is a Lua function." style="float: right; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Lua</span>"# r#"<span class="stab" title="This function is implemented in Lua." style="float: right; background: #ebf5ff; font-weight: 500; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Lua</span>"#
])); ]));
} }
@@ -799,6 +799,10 @@ fn document_metamethod(func: &mut ImplItemFn, method: Metamethod) {
_ => format!("This is a metamethod and cannot be called directly."), _ => format!("This is a metamethod and cannot be called directly."),
}; };
func.attrs.insert(0, parse_quote!(#[doc =
r#"<span class="stab" title="This function is a metamethod." style="float: right; background: #ebf5ff; margin-left: 3px; padding-left: 5px; padding-right: 5px;">Metamethod</span>"#
]));
func.attrs.push(parse_quote!(#[doc = ""])); func.attrs.push(parse_quote!(#[doc = ""]));
func.attrs.push(parse_quote!(#[doc = #s])); func.attrs.push(parse_quote!(#[doc = #s]));
} }

View File

@@ -14,3 +14,6 @@ proc-macro = true
proc-macro2 = "1.0.95" proc-macro2 = "1.0.95"
quote = "1.0.40" quote = "1.0.40"
syn = { version = "2.0.103", features = ["full", "visit-mut"] } syn = { version = "2.0.103", features = ["full", "visit-mut"] }
[dev-dependencies]
pretty_assertions = "1.4.1"

View File

@@ -14,6 +14,12 @@ pub fn generate(expr: &Expr) -> Result<TokenStream> {
Ok(f.done()) Ok(f.done())
} }
pub fn generate_chunk(block: &Block) -> Result<TokenStream> {
let mut f = Formatter::default();
generate_block(&mut f, &block, Context::stmt(false))?;
Ok(f.done())
}
#[derive(Default)] #[derive(Default)]
struct Formatter { struct Formatter {
buf: String, buf: String,

View File

@@ -1,4 +1,7 @@
use crate::{generate::generate, transform::transform}; use crate::{
generate::{generate, generate_chunk},
transform::{transform, transform_chunk},
};
use proc_macro::TokenStream as TokenStream1; use proc_macro::TokenStream as TokenStream1;
use quote::ToTokens; use quote::ToTokens;
use syn::parse_macro_input; use syn::parse_macro_input;
@@ -16,3 +19,13 @@ pub fn luaify(input: TokenStream1) -> TokenStream1 {
} }
.into() .into()
} }
#[proc_macro]
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
let mut block = parse_macro_input!(input);
match transform_chunk(&mut block).and_then(|()| generate_chunk(&block)) {
Ok(s) => s,
Err(err) => err.into_compile_error().into_token_stream(),
}
.into()
}

View File

@@ -9,6 +9,12 @@ pub fn transform(expr: &mut Expr) -> Result<()> {
visitor.result visitor.result
} }
pub fn transform_chunk(block: &mut Block) -> Result<()> {
let mut visitor = Visitor::new();
visitor.visit_block_mut(block);
visitor.result
}
#[derive(Debug)] #[derive(Debug)]
struct Visitor { struct Visitor {
result: Result<()>, result: Result<()>,

View File

@@ -1,4 +1,5 @@
use luaify::luaify; use luaify::{luaify, luaify_chunk};
use pretty_assertions::assert_eq;
#[test] #[test]
fn raw_ident() { fn raw_ident() {
@@ -402,3 +403,19 @@ fn length() {
r#"local a,b,c=#a,#b,#c;"# r#"local a,b,c=#a,#b,#c;"#
); );
} }
#[test]
fn chunk() {
assert_eq!(
luaify_chunk!({
if a == b {
c()
} else if b == c {
a()
} else {
d()
}
}),
"if a==b then c();elseif b==c then a();else d();end;"
);
}

View File

@@ -1,2 +1,14 @@
pub use lb::chan;
pub use lb::fs; pub use lb::fs;
pub use lb::net; pub use lb::net;
pub use lb::task;
#[doc(hidden)]
pub fn load_modules(runtime: &mut lb::runtime::Builder) {
// core modules
runtime
.module::<task::lb_tasklib>("lb:task")
.module::<chan::lb_chanlib>("lb:channel")
.module::<fs::lb_fslib>("lb:fs")
.module::<net::lb_netlib>("lb:net");
}

View File

@@ -1,7 +1,9 @@
use clap::Parser; use clap::Parser;
use mimalloc::MiMalloc; use mimalloc::MiMalloc;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use std::{backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, thread}; use std::{
backtrace::Backtrace, fmt::Display, net::SocketAddr, num::NonZero, panic, process, thread,
};
use sysexits::ExitCode; use sysexits::ExitCode;
#[global_allocator] #[global_allocator]
@@ -20,21 +22,23 @@ fn panic_cb(panic: &panic::PanicHookInfo) {
}; };
eprint!( eprint!(
"{}:\n{trace}", "{}\n{trace}",
format_args!( format_args!(
"thread '{}' panicked at {location}: {msg}", "thread '{}' panicked at {location}: {msg}",
thread::current().name().unwrap_or("<unnamed>") thread::current().name().unwrap_or("<unnamed>")
) )
.red() .red()
.bold()
); );
eprintln!( eprintln!(
"{}", "{}",
format_args!( format_args!(
"This is a bug in luby. Please kindly report this at {}.", "luby should never panic. Please kindly report this bug at {}.",
env!("CARGO_PKG_REPOSITORY") env!("CARGO_PKG_REPOSITORY")
) )
.yellow() .yellow()
.bold()
); );
} }
@@ -93,6 +97,15 @@ struct Args {
)] )]
console_addr: SocketAddr, console_addr: SocketAddr,
/// Dump internal data.
#[clap(
long,
help_heading = "Debugging",
value_name = "DATA",
value_parser = ["cdef"]
)]
dump: Vec<String>,
/// Print version. /// Print version.
#[clap(long, short = 'V')] #[clap(long, short = 'V')]
version: bool, version: bool,
@@ -108,19 +121,12 @@ impl Args {
} }
} }
fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T { fn main() {
move |err| {
eprintln!("{}", err.red());
code.exit()
}
}
fn main() -> Result<(), ExitCode> {
panic::set_hook(Box::new(panic_cb)); panic::set_hook(Box::new(panic_cb));
let args = Args::parse(); let args = Args::parse();
if args.version { if args.version {
return Ok(print_version()); return print_version();
} }
init_logger(&args); init_logger(&args);
@@ -147,6 +153,13 @@ 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) { fn init_logger(args: &Args) {
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::{Layer, util::*}; use tracing_subscriber::{Layer, util::*};
@@ -189,12 +202,18 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
.thread_name("luby") .thread_name("luby")
.max_blocking_threads(args.blocking_threads.get()) .max_blocking_threads(args.blocking_threads.get())
.build() .build()
.unwrap_or_else(exit_err(ExitCode::OsErr)) .unwrap_or_else(unwrap_exit(ExitCode::OsErr))
} }
fn init_lua(args: &Args) -> lb::runtime::Runtime { fn init_lua(args: &Args) -> lb::runtime::Runtime {
let rt = lb::runtime::Builder::new(); let mut rt = lb::runtime::Builder::new();
let mut rt = rt.build().unwrap_or_else(exit_err(ExitCode::Software)); luby::load_modules(&mut rt);
if args.dump.iter().find(|s| *s == "cdef").is_some() {
print!("{}", rt.registry());
}
let mut rt = rt.build().unwrap();
for arg in args.jit.iter() { for arg in args.jit.iter() {
let mut s = rt.guard(); let mut s = rt.guard();
@@ -217,7 +236,7 @@ fn init_lua(args: &Args) -> lb::runtime::Runtime {
} }
} }
} }
.unwrap_or_else(exit_err(ExitCode::Usage)); .unwrap_or_else(unwrap_exit(ExitCode::Usage));
} }
rt rt
@@ -225,36 +244,34 @@ fn init_lua(args: &Args) -> lb::runtime::Runtime {
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> { fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
match s { match s {
"p" => Some(("p", "Flspv10")), "p" => Some(("p", "Flspv10")), // default -jp flags
"v" => Some(("v", "-")), "v" => Some(("v", "-")), // default -jv flags
"dump" => Some(("dump", "tirs")), "dump" => Some(("dump", "tirs")), // default -jdump flags
_ => s.split_once('='), _ => s.split_once('='),
} }
} }
async fn main_async(args: Args, state: &mut luajit::State) -> Result<(), ExitCode> { async fn main_async(args: Args, state: &mut luajit::State) {
for ref path in args.path { for ref path in args.path {
let mut s = state.guard(); let mut s = state.guard();
let chunk = match std::fs::read(path) { let chunk = match std::fs::read(path) {
Ok(chunk) => chunk, Ok(chunk) => chunk,
Err(err) => { Err(err) => {
eprintln!("{}", format_args!("{path}: {err}").red()); eprintln!("{}", format_args!("{path}: {err}").red().bold());
ExitCode::NoInput.exit(); ExitCode::NoInput.exit();
} }
}; };
s.load(&luajit::Chunk::new(chunk).path(path)) s.load(&luajit::Chunk::new(chunk).path(path))
.unwrap_or_else(exit_err(ExitCode::NoInput)); .unwrap_or_else(unwrap_exit(ExitCode::NoInput));
if let Err(err) = s.call_async(0, 0).await { if let Err(err) = s.call_async(0, 0).await {
match err.trace() { match err.trace() {
Some(trace) => eprintln!("{}\n{trace}", err.red()), // runtime error Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
None => eprintln!("{}", err.red()), None => eprintln!("{}", err.red().bold()),
} }
ExitCode::DataErr.exit(); process::exit(1);
} }
} }
Ok(())
} }

View File

@@ -1,6 +0,0 @@
local ffi = require("ffi")
local lb = ffi.new("struct lb_core")
print(lb)
lb.spawn("")