Add ffi crate

This commit is contained in:
2025-06-19 17:02:12 +10:00
parent 86380a0957
commit 16ab2be6f4
15 changed files with 1718 additions and 10 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "luaffi_impl"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.95"
quote = "1.0.40"
syn = { version = "2.0.103", features = ["full", "visit-mut"] }

View File

@@ -0,0 +1,204 @@
use crate::utils::{ffi_crate, syn_assert, syn_error};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{spanned::*, *};
pub fn transform(mut item: Item) -> Result<TokenStream> {
let (name, impl_type, impl_cdef) = match item {
Item::Struct(ref mut str) => (
str.ident.clone(),
generate_type(&str.ident)?,
generate_cdef_structure(str)?,
),
Item::Enum(ref mut enu) => (
enu.ident.clone(),
generate_type(&enu.ident)?,
generate_cdef_enum(enu)?,
),
_ => syn_error!(item, "expected struct or enum"),
};
let mod_name = format_ident!("__cdef__{name}");
Ok(quote! {
#[repr(C)]
#[allow(non_camel_case_types)]
#item
#[doc(hidden)]
#[allow(unused, non_snake_case)]
mod #mod_name {
use super::*;
#impl_type
#impl_cdef
}
})
}
fn generate_type(ty: &Ident) -> Result<TokenStream> {
let ffi = ffi_crate();
let fmt = quote!(::std::format!);
let name_fmt = LitStr::new(&format!("{ty}"), ty.span());
let cdecl_fmt = LitStr::new(&format!("struct {ty} {{name}}"), ty.span());
Ok(quote! {
unsafe impl #ffi::Type for #ty {
fn name() -> ::std::string::String {
#fmt(#name_fmt)
}
fn cdecl(name: impl ::std::fmt::Display) -> ::std::string::String {
#fmt(#cdecl_fmt)
}
fn build(s: &mut #ffi::TypeBuilder) {
s.cdef::<Self>().metatype::<Self>();
}
}
})
}
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
syn_assert!(
str.generics.params.is_empty(),
str.generics,
"cannot be generic (not yet implemented)"
);
let ffi = ffi_crate();
let ty = &str.ident;
let build = generate_build_cdef(&to_cfields(&mut str.fields)?)?;
Ok(quote! {
unsafe impl #ffi::CDef for #ty {
fn build(s: &mut #ffi::CDefBuilder) { #build }
}
})
}
fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
syn_assert!(
enu.generics.params.is_empty(),
enu.generics,
"cannot be generic (not yet implemented)"
);
let ffi = ffi_crate();
let ty = &enu.ident;
let build = enu
.variants
.iter_mut()
.map(|variant| {
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
Ok(quote! { s.inner_struct(|s| { #build }); })
})
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
unsafe impl #ffi::CDef for #ty {
fn build(s: &mut #ffi::CDefBuilder) {
s.field::<::std::ffi::c_int>("__tag").inner_union(|s| { #(#build)* });
}
}
})
}
#[derive(Debug)]
struct CField {
name: String,
ty: Type,
attrs: CFieldAttrs,
}
fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
match fields {
Fields::Named(fields) => fields.named.iter_mut(),
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
Fields::Unit => return Ok(vec![]),
}
.enumerate()
.map(|(i, field)| {
Ok(CField {
name: match field.ident {
Some(ref name) => format!("{name}"),
None => format!("__{i}"),
},
ty: field.ty.clone(),
attrs: parse_attrs(&mut field.attrs)?,
})
})
.collect()
}
#[derive(Debug, Default)]
struct CFieldAttrs {
opaque: bool,
}
fn parse_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;
}
}
i += 1;
}
Ok(parsed)
}
fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
let mut body = vec![quote! {
let mut offset = 0;
let mut align = 1;
}];
fn offset(i: usize) -> Ident {
format_ident!("offset{i}")
}
let max = quote!(::std::cmp::Ord::max);
let size_of = quote!(::std::mem::size_of);
let align_of = quote!(::std::mem::align_of);
for (i, field) in fields.iter().enumerate() {
let ty = &field.ty;
let offset = offset(i);
body.push(quote! {
// round up current offset to the alignment of field type for field offset
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
align = #max(align, #align_of::<#ty>());
let #offset = offset;
offset += #size_of::<#ty>();
});
}
body.push(quote! {
// round up final offset to the total alignment of struct for struct size
let size = (offset + align - 1) & !(align - 1);
});
let len = fields.len();
for (i, field) in fields.iter().enumerate() {
let name = &field.name;
let ty = &field.ty;
if field.attrs.opaque {
body.push(if i == len - 1 {
let a = offset(i);
quote! { s.field_opaque(size - #a); } // last field
} else {
let a = offset(i);
let b = offset(i + 1);
quote! { s.field_opaque(#b - #a); }
});
} else {
body.push(quote! { s.field::<#ty>(#name); });
}
}
Ok(quote! { #(#body)* })
}

View File

@@ -0,0 +1,21 @@
use proc_macro::TokenStream as TokenStream1;
use quote::ToTokens;
use syn::parse_macro_input;
mod cdef;
mod metatype;
mod utils;
#[proc_macro_attribute]
pub fn cdef(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
cdef::transform(parse_macro_input!(input))
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
.into()
}
#[proc_macro_attribute]
pub fn metatype(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
metatype::transform(parse_macro_input!(input))
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
.into()
}

View File

@@ -0,0 +1,49 @@
use crate::utils::{ffi_crate, syn_assert, syn_error, ty_name};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{spanned::*, *};
pub fn transform(mut item: ItemImpl) -> Result<TokenStream> {
syn_assert!(
item.generics.params.is_empty(),
item.generics,
"cannot be generic (not yet implemented)"
);
let impls = generate_impls(&mut item)?;
let mod_name = format_ident!("__metatype__{}", ty_name(&item.self_ty)?);
Ok(quote! {
#item
#[doc(hidden)]
#[allow(unused, non_snake_case)]
mod #mod_name {
use super::*;
#impls
}
})
}
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ffi = ffi_crate();
let ty = &*imp.self_ty;
let name = ty_name(&ty)?;
Ok(quote! {
unsafe impl #ffi::Metatype for #ty {
type Target = Self;
fn build(s: &mut #ffi::MetatypeBuilder) {}
}
impl #ty {
}
})
}
#[derive(Debug)]
struct FfiFunction {}
#[derive(Debug)]
struct LuaFunction {}

View File

@@ -0,0 +1,35 @@
use std::{env, fmt};
use syn::{ext::*, spanned::*, *};
macro_rules! syn_error {
($src:expr, $($fmt:expr),+) => {{
return Err(syn::Error::new($src.span(), format!($($fmt),*)));
}};
}
macro_rules! syn_assert {
($cond:expr, $src:expr, $($fmt:expr),+) => {{
if !$cond {
syn_error!($src, $($fmt),+);
}
}};
}
pub(crate) use {syn_assert, syn_error};
pub fn ffi_crate() -> Path {
match (
env::var("CARGO_PKG_NAME").ok(),
env::var("CARGO_TARGET_TMPDIR").ok(), // ignore tests/**/*.rs
) {
(Some(name), None) if name == "luaffi" => parse_quote!(crate),
_ => parse_quote!(::luaffi),
}
}
pub fn ty_name(ty: &Type) -> Result<&Ident> {
match ty {
Type::Path(path) => path.path.require_ident(),
_ => syn_error!(ty, "expected ident"),
}
}