Implement metatype proc-macro
This commit is contained in:
@@ -51,8 +51,8 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
#fmt(#cdecl_fmt)
|
||||
}
|
||||
|
||||
fn build(s: &mut #ffi::TypeBuilder) {
|
||||
s.cdef::<Self>().metatype::<Self>();
|
||||
fn build(b: &mut #ffi::TypeBuilder) {
|
||||
b.cdef::<Self>().metatype::<Self>();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -71,7 +71,7 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
|
||||
Ok(quote! {
|
||||
unsafe impl #ffi::CDef for #ty {
|
||||
fn build(s: &mut #ffi::CDefBuilder) { #build }
|
||||
fn build(b: &mut #ffi::CDefBuilder) { #build }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -90,20 +90,19 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
.iter_mut()
|
||||
.map(|variant| {
|
||||
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote! { s.inner_struct(|s| { #build }); })
|
||||
Ok(quote! { b.inner_struct(|b| { #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)* });
|
||||
fn build(b: &mut #ffi::CDefBuilder) {
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build)* });
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CField {
|
||||
name: String,
|
||||
ty: Type,
|
||||
@@ -130,7 +129,7 @@ fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
struct CFieldAttrs {
|
||||
opaque: bool,
|
||||
}
|
||||
@@ -189,14 +188,14 @@ fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
|
||||
if field.attrs.opaque {
|
||||
body.push(if i == len - 1 {
|
||||
let a = offset(i);
|
||||
quote! { s.field_opaque(size - #a); } // last field
|
||||
quote! { b.field_opaque(size - #a); } // last field
|
||||
} else {
|
||||
let a = offset(i);
|
||||
let b = offset(i + 1);
|
||||
quote! { s.field_opaque(#b - #a); }
|
||||
quote! { b.field_opaque(#b - #a); }
|
||||
});
|
||||
} else {
|
||||
body.push(quote! { s.field::<#ty>(#name); });
|
||||
body.push(quote! { b.field::<#ty>(#name); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
use crate::utils::{ffi_crate, syn_assert, syn_error, ty_name};
|
||||
use crate::utils::{ffi_crate, is_primitive, is_unit, pat_ident, 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> {
|
||||
pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
item.generics.params.is_empty(),
|
||||
item.generics,
|
||||
imp.generics.params.is_empty(),
|
||||
imp.generics,
|
||||
"cannot be generic (not yet implemented)"
|
||||
);
|
||||
|
||||
let impls = generate_impls(&mut item)?;
|
||||
let mod_name = format_ident!("__metatype__{}", ty_name(&item.self_ty)?);
|
||||
let impls = generate_impls(&mut imp)?;
|
||||
let mod_name = format_ident!("__metatype__{}", ty_name(&imp.self_ty)?);
|
||||
|
||||
Ok(quote! {
|
||||
#item
|
||||
#imp
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
@@ -27,23 +27,246 @@ pub fn transform(mut item: ItemImpl) -> Result<TokenStream> {
|
||||
|
||||
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let ffi_funcs = get_ffi_functions(imp)?;
|
||||
let ffi_wrappers: Vec<_> = ffi_funcs
|
||||
.iter()
|
||||
.map(generate_ffi_wrapper)
|
||||
.collect::<Result<_>>()?;
|
||||
let ffi_register: Vec<_> = ffi_funcs
|
||||
.iter()
|
||||
.map(generate_ffi_register)
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let lua_funcs = get_lua_functions(imp)?;
|
||||
let lua_register: Vec<_> = lua_funcs
|
||||
.iter()
|
||||
.map(generate_lua_register)
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
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) {}
|
||||
|
||||
fn build(b: &mut #ffi::MetatypeBuilder) {
|
||||
#(#ffi_register)*
|
||||
#(#lua_register)*
|
||||
}
|
||||
}
|
||||
|
||||
impl #ty {
|
||||
impl #ty { #(#ffi_wrappers)* }
|
||||
})
|
||||
}
|
||||
|
||||
struct FfiFunction {
|
||||
name: Ident,
|
||||
rust_name: Ident,
|
||||
lua_name: String,
|
||||
c_name: String,
|
||||
params: Vec<PatType>,
|
||||
ret: Type,
|
||||
ret_out: bool,
|
||||
}
|
||||
|
||||
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
let mut funcs = vec![];
|
||||
|
||||
for item in imp.items.iter_mut() {
|
||||
if let ImplItem::Fn(func) = item
|
||||
&& let Some(ref abi) = func.sig.abi
|
||||
&& let Some(ref abi) = abi.name
|
||||
&& abi.value() == "Lua-C"
|
||||
{
|
||||
func.sig.abi = None;
|
||||
|
||||
let params = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(match arg {
|
||||
FnArg::Receiver(recv) => {
|
||||
let ty = &recv.ty;
|
||||
parse_quote! { self: #ty }
|
||||
}
|
||||
FnArg::Typed(ty) => ty.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let ret = match func.sig.output {
|
||||
ReturnType::Default => parse_quote!(()),
|
||||
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
||||
};
|
||||
|
||||
// whether to use out-param for return values
|
||||
let ret_out = !is_primitive(&ret);
|
||||
|
||||
funcs.push(FfiFunction {
|
||||
name: func.sig.ident.clone(),
|
||||
rust_name: format_ident!("__ffi_{}", func.sig.ident),
|
||||
lua_name: format!("{}", func.sig.ident),
|
||||
c_name: format!("{}_{}", ty_name(&imp.self_ty)?, func.sig.ident),
|
||||
params,
|
||||
ret,
|
||||
ret_out,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(funcs)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FfiArgType {
|
||||
Default,
|
||||
}
|
||||
|
||||
fn get_ffi_arg_type(ty: &Type) -> FfiArgType {
|
||||
FfiArgType::Default
|
||||
}
|
||||
|
||||
fn escape_self(name: &Ident) -> Ident {
|
||||
if name == "self" {
|
||||
format_ident!("__self")
|
||||
} else {
|
||||
name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let name = &func.name;
|
||||
let rust_name = &func.rust_name;
|
||||
let c_name = &func.c_name;
|
||||
let mut params = vec![];
|
||||
let mut args = vec![];
|
||||
|
||||
for param in func.params.iter() {
|
||||
let name = escape_self(pat_ident(¶m.pat)?);
|
||||
let ty = ¶m.ty;
|
||||
|
||||
match get_ffi_arg_type(ty) {
|
||||
FfiArgType::Default => {
|
||||
params.push(quote! { #name: <#ty as #ffi::FromFfi>::From });
|
||||
args.push(quote! { <#ty as #ffi::FromFfi>::convert(#name) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make return by out-param the first parameter
|
||||
let (ret, do_ret) = if func.ret_out {
|
||||
let ret = &func.ret;
|
||||
params.insert(0, quote! { __ret_out: *mut #ret });
|
||||
(
|
||||
quote! { () },
|
||||
quote! { unsafe { ::std::ptr::write(__ret_out, __ret) }; },
|
||||
)
|
||||
} else {
|
||||
let ret = &func.ret;
|
||||
(quote! { #ret }, quote! { return __ret; })
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[unsafe(export_name = #c_name)]
|
||||
unsafe extern "C" fn #rust_name(#(#params),*) -> #ret {
|
||||
let __ret = Self::#name(#(#args),*);
|
||||
#do_ret
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FfiFunction {}
|
||||
fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let lua_name = &func.lua_name;
|
||||
let c_name = &func.c_name;
|
||||
let mut params = vec![];
|
||||
let mut asserts = vec![];
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LuaFunction {}
|
||||
for param in func.params.iter() {
|
||||
let name = format!("{}", pat_ident(¶m.pat)?);
|
||||
let ty = ¶m.ty;
|
||||
|
||||
params.push(match get_ffi_arg_type(ty) {
|
||||
FfiArgType::Default => quote! { b.param::<#ty>(#name); },
|
||||
});
|
||||
}
|
||||
|
||||
let ret = &func.ret;
|
||||
let ret_conv = if is_unit(ret) {
|
||||
quote! { #ffi::FfiReturnConvention::Void }
|
||||
} else if func.ret_out {
|
||||
asserts.push(quote! { #ffi::__internal::assert_type_ne_all!(#ret, ()); });
|
||||
quote! { #ffi::FfiReturnConvention::OutParam }
|
||||
} else {
|
||||
asserts.push(quote! { #ffi::__internal::assert_type_ne_all!(#ret, ()); });
|
||||
quote! { #ffi::FfiReturnConvention::ByValue }
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
b.index(#lua_name, |b| {
|
||||
#(#asserts)*
|
||||
#(#params)*
|
||||
b.call::<#ret>(#c_name, #ret_conv);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
struct LuaFunction {
|
||||
name: String,
|
||||
params: Vec<PatType>,
|
||||
body: Block,
|
||||
}
|
||||
|
||||
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
let mut funcs = vec![];
|
||||
let mut i = 0;
|
||||
|
||||
while i < imp.items.len() {
|
||||
if let ImplItem::Fn(ref mut func) = imp.items[i]
|
||||
&& let Some(ref abi) = func.sig.abi
|
||||
&& let Some(ref abi) = abi.name
|
||||
&& abi.value() == "Lua"
|
||||
{
|
||||
let params = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(match arg {
|
||||
FnArg::Receiver(recv) => {
|
||||
syn_assert!(ty_name(&recv.ty)? == "Self", recv, "must be `self`");
|
||||
syn_assert!(recv.mutability.is_none(), recv, "cannot be mut");
|
||||
parse_quote! { self: cdata }
|
||||
}
|
||||
FnArg::Typed(ty) => ty.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
funcs.push(LuaFunction {
|
||||
name: format!("{}", func.sig.ident),
|
||||
body: func.block.clone(),
|
||||
params,
|
||||
});
|
||||
|
||||
imp.items.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(funcs)
|
||||
}
|
||||
|
||||
fn generate_lua_register(func: &LuaFunction) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let name = &func.name;
|
||||
let params = &func.params;
|
||||
let body = &func.body;
|
||||
|
||||
Ok(quote! {
|
||||
b.index_raw(#name, #ffi::__internal::luaify!(|#(#params),*| #body));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{env, fmt};
|
||||
use syn::{ext::*, spanned::*, *};
|
||||
use std::env;
|
||||
use syn::{spanned::*, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
@@ -33,3 +33,68 @@ pub fn ty_name(ty: &Type) -> Result<&Ident> {
|
||||
_ => syn_error!(ty, "expected ident"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pat_ident(pat: &Pat) -> Result<&Ident> {
|
||||
match pat {
|
||||
Pat::Ident(ident) => Ok(&ident.ident),
|
||||
_ => syn_error!(pat, "expected ident"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_unit(ty: &Type) -> bool {
|
||||
if let Type::Tuple(tuple) = ty
|
||||
&& tuple.elems.is_empty()
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_primitive(ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => true, // unit type
|
||||
Type::Reference(_) | Type::Ptr(_) => true,
|
||||
Type::Paren(paren) => is_primitive(&paren.elem),
|
||||
Type::Path(path) => {
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
matches!(
|
||||
format!("{name}").as_str(),
|
||||
"bool"
|
||||
| "u8"
|
||||
| "u16"
|
||||
| "u32"
|
||||
| "u64"
|
||||
| "usize"
|
||||
| "i8"
|
||||
| "i16"
|
||||
| "i32"
|
||||
| "i64"
|
||||
| "isize"
|
||||
| "f32"
|
||||
| "f64"
|
||||
| "char"
|
||||
| "c_char"
|
||||
| "c_schar"
|
||||
| "c_uchar"
|
||||
| "c_short"
|
||||
| "c_ushort"
|
||||
| "c_int"
|
||||
| "c_uint"
|
||||
| "c_long"
|
||||
| "c_ulong"
|
||||
| "c_longlong"
|
||||
| "c_ulonglong"
|
||||
| "c_float"
|
||||
| "c_double"
|
||||
| "c_size_t"
|
||||
| "c_ssize_t"
|
||||
| "c_ptrdiff_t"
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user