Add ffi crate
This commit is contained in:
12
crates/luaffi_impl/Cargo.toml
Normal file
12
crates/luaffi_impl/Cargo.toml
Normal 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"] }
|
||||
204
crates/luaffi_impl/src/cdef.rs
Normal file
204
crates/luaffi_impl/src/cdef.rs
Normal 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)* })
|
||||
}
|
||||
21
crates/luaffi_impl/src/lib.rs
Normal file
21
crates/luaffi_impl/src/lib.rs
Normal 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()
|
||||
}
|
||||
49
crates/luaffi_impl/src/metatype.rs
Normal file
49
crates/luaffi_impl/src/metatype.rs
Normal 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 {}
|
||||
35
crates/luaffi_impl/src/utils.rs
Normal file
35
crates/luaffi_impl/src/utils.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user