Implement metatype proc-macro
This commit is contained in:
parent
af81879b42
commit
1c821d8804
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -22,6 +22,7 @@ dependencies = [
|
||||
"luaify",
|
||||
"rustc-hash",
|
||||
"simdutf8",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -108,6 +109,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.103"
|
||||
|
@ -9,3 +9,4 @@ luaffi_impl = { version = "0.1.0", path = "../luaffi_impl" }
|
||||
luaify = { version = "0.1.0", path = "../luaify" }
|
||||
rustc-hash = "2.1.1"
|
||||
simdutf8 = "0.1.5"
|
||||
static_assertions = "1.1.0"
|
||||
|
@ -1,4 +1,6 @@
|
||||
pub use luaify::*;
|
||||
use rustc_hash::FxHasher;
|
||||
pub use static_assertions::*;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
fmt::{self, Display, Formatter},
|
||||
|
@ -1,114 +0,0 @@
|
||||
-- automatically generated by luaffi 0.1.0
|
||||
local table, string, math, coroutine, package, debug, jit, bit, ffi, new, clear =
|
||||
table,
|
||||
string,
|
||||
math,
|
||||
coroutine,
|
||||
package,
|
||||
debug,
|
||||
jit,
|
||||
require("bit"),
|
||||
require("ffi"),
|
||||
require("table.new"),
|
||||
require("table.clear")
|
||||
local assert, error, type, print, pcall, xpcall, getfenv, setfenv, getmetatable, setmetatable, pairs, ipairs, next, rawget, rawset, rawequal, select, tonumber, tostring, require, concat, insert, maxn, remove, sort, strlen, format, strsub, gsub, gmatch, dump, random, yield, traceback, C, cdef, typeof, metatype, cast, gc, tobit, tohex, bnot, band, bor, bxor, lshift, rshift, arshift, rol, ror, bswap =
|
||||
assert,
|
||||
error,
|
||||
type,
|
||||
print,
|
||||
pcall,
|
||||
xpcall,
|
||||
getfenv,
|
||||
setfenv,
|
||||
getmetatable,
|
||||
setmetatable,
|
||||
pairs,
|
||||
ipairs,
|
||||
next,
|
||||
rawget,
|
||||
rawset,
|
||||
rawequal,
|
||||
select,
|
||||
tonumber,
|
||||
tostring,
|
||||
require,
|
||||
table.concat,
|
||||
table.insert,
|
||||
table.maxn,
|
||||
table.remove,
|
||||
table.sort,
|
||||
string.len,
|
||||
string.format,
|
||||
string.sub,
|
||||
string.gsub,
|
||||
string.gmatch,
|
||||
string.dump,
|
||||
math.random,
|
||||
coroutine.yield,
|
||||
debug.traceback,
|
||||
ffi.C,
|
||||
ffi.cdef,
|
||||
ffi.typeof,
|
||||
ffi.metatype,
|
||||
ffi.cast,
|
||||
ffi.gc,
|
||||
bit.tobit,
|
||||
bit.tohex,
|
||||
bit.bnot,
|
||||
bit.band,
|
||||
bit.bor,
|
||||
bit.bxor,
|
||||
bit.lshift,
|
||||
bit.rshift,
|
||||
bit.arshift,
|
||||
bit.rol,
|
||||
bit.ror,
|
||||
bit.bswap
|
||||
cdef([[void (*luaffi_keep)(void const *);
|
||||
bool (*luaffi_is_utf8)(uint8_t const *, uintptr_t );
|
||||
struct Test { } __attribute__((aligned(1)));
|
||||
struct lua_buf { uint8_t *__ptr; uintptr_t __len; } __attribute__((aligned(8)));
|
||||
struct option__lua_buf { int32_t __tag; struct lua_buf __value; } __attribute__((aligned(8)));
|
||||
struct option__uint32_t { int32_t __tag; uint32_t __value; } __attribute__((aligned(4)));
|
||||
struct option__void { int32_t __tag; void __value; } __attribute__((aligned(4)));
|
||||
]])
|
||||
local Test = typeof("struct Test ")
|
||||
local lua_buf = typeof("struct lua_buf ")
|
||||
do
|
||||
local __mt, __idx = {}, {}
|
||||
__mt.__index = __idx
|
||||
metatype(lua_buf, __mt)
|
||||
end
|
||||
local option__lua_buf = typeof("struct option__lua_buf ")
|
||||
local option__uint32_t = typeof("struct option__uint32_t ")
|
||||
local option__void = typeof("struct option__void ")
|
||||
do
|
||||
local __mt, __idx = {}, {}
|
||||
__mt.__index = __idx
|
||||
__idx.method = function(my_arg, other)
|
||||
local __keep_my_arg = my_arg
|
||||
if my_arg == nil then
|
||||
my_arg = option__lua_buf()
|
||||
else
|
||||
assert(type(my_arg) == "string", "expected string in argument 'my_arg', got " .. type(my_arg))
|
||||
assert(C.luaffi_is_utf8(my_arg, #my_arg), "argument 'my_arg' must be a valid utf8 string")
|
||||
my_arg = lua_buf(my_arg, #my_arg)
|
||||
my_arg = option__lua_buf(1, my_arg)
|
||||
end
|
||||
if other == nil then
|
||||
other = option__uint32_t()
|
||||
else
|
||||
assert(type(other) == "number", "expected number in argument 'other', got " .. type(other))
|
||||
other = option__uint32_t(1, other)
|
||||
end
|
||||
local res = C.test_fn_impl(my_arg, other)
|
||||
if res.__tag == 0 then
|
||||
res = nil
|
||||
else
|
||||
res = res.__value
|
||||
end
|
||||
C.luaffi_keep(__keep_my_arg)
|
||||
return res
|
||||
end
|
||||
metatype(Test, __mt)
|
||||
end
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pub fn generate(expr: &Expr) -> Result<TokenStream> {
|
||||
Ok(f.done())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
struct Formatter {
|
||||
buf: String,
|
||||
space: bool,
|
||||
|
Loading…
x
Reference in New Issue
Block a user