Compare commits

...

4 Commits

Author SHA1 Message Date
91302db725
Print nicer error messages 2025-06-25 21:45:29 +10:00
c249549b3c
Fix unknown feature warning 2025-06-25 21:25:02 +10:00
30596d9331
Impl generic From/IntoFfi for Option 2025-06-25 21:24:24 +10:00
530a1530ba
Call it library not module 2025-06-25 20:13:09 +10:00
10 changed files with 198 additions and 82 deletions

View File

@ -1,15 +1,15 @@
//! The `lb:fs` module provides utilities for interacting with the file system asynchronously. //! The `lb:fs` library provides utilities for interacting with the file system asynchronously.
//! //!
//! # Exports //! # Exports
//! //!
//! See [`lb_libfs`] for items exported by this module. //! See [`lb_libfs`] 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");

View File

@ -1,9 +1,9 @@
//! 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_libnet`] 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::{
@ -12,9 +12,9 @@ use std::{
}; };
use tokio::net::{TcpListener, TcpSocket, TcpStream}; use tokio::net::{TcpListener, TcpSocket, TcpStream};
/// Items exported by the `lb:net` module. /// Items exported by the `lb:net` library.
/// ///
/// This module can be obtained by calling `require` in Lua. /// This library can be obtained by calling `require` in Lua.
/// ///
/// ```lua /// ```lua
/// local net = require("lb:net"); /// local net = require("lb:net");

View File

@ -41,6 +41,7 @@ impl Builder {
let mut s = State::new()?; let mut s = State::new()?;
let mut chunk = Chunk::new(self.registry.done()); let mut chunk = Chunk::new(self.registry.done());
chunk.extend(include_bytes!("./runtime.lua")); chunk.extend(include_bytes!("./runtime.lua"));
// println!("{chunk}");
s.eval(chunk.path("[luby]"), 0, 0)?; s.eval(chunk.path("[luby]"), 0, 0)?;
s s
}, },

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

@ -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
@ -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

@ -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()
); );
} }
@ -110,17 +114,17 @@ impl Args {
fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T { fn exit_err<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
move |err| { move |err| {
eprintln!("{}", err.red()); eprintln!("{}", err.red().bold());
code.exit() code.exit()
} }
} }
fn main() -> Result<(), ExitCode> { fn main() {
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);
@ -232,13 +236,13 @@ fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
} }
} }
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();
} }
}; };
@ -248,13 +252,11 @@ async fn main_async(args: Args, state: &mut luajit::State) -> Result<(), ExitCod
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 +1,19 @@
local ffi = require("ffi") local fs = require("lb:fs")
local lb = ffi.new("struct lb_core")
print(lb) -- do
-- local start = os.clock()
-- for i = 1, 50000 do
-- fs:read("crates/luaffi_impl/src/metatype.rs")
-- end
-- local finish = os.clock()
-- print("finish in " .. (finish - start))
-- end
lb.spawn("") do
local start = os.clock()
for i = 1, 30000 do
fs:read_sync("bacon.toml")
end
local finish = os.clock()
print("finish in " .. (finish - start))
end