Let modules decide their own name via #[cdef] macro

This commit is contained in:
lumi 2025-06-26 17:47:21 +10:00
parent 1c1753234d
commit 0cafac0948
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
11 changed files with 113 additions and 50 deletions

View File

@ -1,7 +1,7 @@
// use flume::{Receiver, Sender};
use luaffi::{cdef, metatype};
#[cdef]
#[cdef(module = "lb:chan")]
pub struct lb_chanlib;
#[metatype]

View File

@ -15,7 +15,7 @@ use tokio::fs;
/// ```lua
/// local fs = require("lb:fs");
/// ```
#[cdef]
#[cdef(module = "lb:fs")]
pub struct lb_fslib;
#[metatype]

View File

@ -37,7 +37,7 @@ type Result<T> = std::result::Result<T, Error>;
/// ```lua
/// local net = require("lb:net");
/// ```
#[cdef]
#[cdef(module = "lb:net")]
pub struct lb_netlib;
#[metatype]

View File

@ -1,7 +1,6 @@
use derive_more::{Deref, DerefMut};
use luaffi::{Registry, Type};
use luaffi::{Module, Registry};
use luajit::{Chunk, State};
use std::fmt::Display;
use tokio::{
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
task_local,
@ -23,8 +22,8 @@ impl Builder {
&self.registry
}
pub fn module<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.registry.preload::<T>(name);
pub fn module<T: Module>(&mut self) -> &mut Self {
self.registry.preload::<T>();
self
}

View File

@ -3,7 +3,7 @@ use luaffi::{cdef, metatype};
use std::{ffi::c_int, process};
use tokio::task::JoinHandle;
#[cdef]
#[cdef(module = "lb:task")]
pub struct lb_tasklib;
#[metatype]

View File

@ -158,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 {
@ -240,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);
}
@ -320,6 +324,7 @@ pub struct MetatypeBuilder<'r> {
ct: String,
cdef: String,
lua: String,
lua_includes: Vec<&'static str>,
}
impl<'r> MetatypeBuilder<'r> {
@ -329,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![],
}
}
@ -337,6 +343,11 @@ 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,
@ -377,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();
}
}
}

View File

@ -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,12 +172,11 @@ 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" {
parsed.opaque = true;
attrs.remove(i);
continue;
}
let path = attr.path();
if path.is_ident("opaque") {
parsed.opaque = true;
attrs.remove(i);
continue;
}
i += 1;
}

View File

@ -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()
}

View File

@ -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!(attr.span() =>
b.include_lua(::std::include_str!(#path));
));
return false;
} else if let Ok(chunk) = attr.parse_args::<Block>() {
registry.build.push(quote_spanned!(attr.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 = &registry.shims;
@ -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"),
@ -652,7 +679,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"#),

View File

@ -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
}
None
}
pub fn is_optionlike(ty: &Type) -> Option<&Type> {

View File

@ -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>();
}