This commit is contained in:
Sebastian Moser
2026-10-23 15:27:31 +02:00
parent 9141fc5441
commit 37cb94d1d4
11 changed files with 482 additions and 63 deletions

View File

@@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, parse::Parse, parse::ParseStream, Ident, LitStr, Token, TypePath};
use syn::{self, parse::Parse, parse::ParseStream, Ident, LitStr, Token, Type, TypePath};
use syn::punctuated::Punctuated;
use syn::token;
@@ -9,24 +9,76 @@ use syn::token;
//////////////////////////////////// include_native! macro ///////////////////////////////////////
/// A procedural macro to register methods and associated functions of a foreign type with mize.
///
/// Usage 1: Simplified syntax with method names
/// Usage 1: Simplified syntax with no-argument methods
/// ```ignore
/// include_native!("Command", clap::Command, [arg, subcommand]);
/// include_native!("Command", clap::Command, [get_name, is_empty]);
/// ```
///
/// Usage 2: With associated functions
/// Usage 2: With argument specifications
/// ```ignore
/// include_native!("Command", clap::Command, [
/// arg(arg: clap::Arg),
/// subcommand(cmd: clap::Command)
/// ]);
/// ```
///
/// Usage 3: Block syntax separating associated functions and methods
/// ```ignore
/// include_native!("Command", clap::Command, {
/// associated: [new],
/// methods: [arg, subcommand]
/// associated: [new(name: String)],
/// methods: [arg(arg: clap::Arg), subcommand(cmd: clap::Command)]
/// });
/// ```
/// A function specification: name or name(arg1: Type1, arg2: Type2, ...)
#[derive(Debug, Clone)]
struct FuncSpec {
name: Ident,
args: Vec<(Ident, Type)>,
}
impl Parse for FuncSpec {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: Ident = input.parse()?;
let mut args = Vec::new();
// Check for parentheses with arguments
if input.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in input);
// Parse comma-separated arg: Type pairs
while !content.is_empty() {
let arg_name: Ident = content.parse()?;
content.parse::<Token![:]>()?;
let arg_type: Type = content.parse()?;
args.push((arg_name, arg_type));
if !content.is_empty() {
content.parse::<Token![,]>()?;
}
}
}
Ok(FuncSpec { name, args })
}
}
/// Helper struct to parse a list of function specifications
struct FuncSpecList(Vec<FuncSpec>);
impl Parse for FuncSpecList {
fn parse(input: ParseStream) -> syn::Result<Self> {
let punctuated = Punctuated::<FuncSpec, Token![,]>::parse_terminated(input)?;
Ok(FuncSpecList(punctuated.into_iter().collect()))
}
}
/// Input for the simplified syntax: include_native!("name", Type, [method1, method2, ...])
struct IncludeNativeSimple {
name: LitStr,
type_path: TypePath,
methods: Punctuated<Ident, Token![,]>,
methods: Vec<FuncSpec>,
}
impl Parse for IncludeNativeSimple {
@@ -38,35 +90,18 @@ impl Parse for IncludeNativeSimple {
let content;
syn::bracketed!(content in input);
let methods = Punctuated::parse_terminated(&content)?;
let methods = content.parse::<FuncSpecList>()?.0;
Ok(IncludeNativeSimple { name, type_path, methods })
}
}
/// Helper struct to parse a list of identifiers
struct IdentList(Vec<Ident>);
impl Parse for IdentList {
fn parse(input: ParseStream) -> syn::Result<Self> {
let punctuated = Punctuated::<Ident, Token![,]>::parse_terminated(input)?;
Ok(IdentList(punctuated.into_iter().collect()))
}
}
/// Helper to parse a token stream as a list of identifiers (expects comma-separated idents)
fn parse_ident_list(stream: proc_macro2::TokenStream) -> Vec<Ident> {
syn::parse2::<IdentList>(stream)
.map(|list| list.0)
.unwrap_or_default()
}
/// Struct for parsing the block syntax with associated and methods
struct IncludeNativeBlock {
name: LitStr,
type_path: TypePath,
associated: Vec<Ident>,
methods: Vec<Ident>,
associated: Vec<FuncSpec>,
methods: Vec<FuncSpec>,
}
impl Parse for IncludeNativeBlock {
@@ -106,7 +141,9 @@ impl Parse for IncludeNativeBlock {
while j < tokens.len() {
if let proc_macro2::TokenTree::Group(g) = &tokens[j] {
if g.delimiter() == proc_macro2::Delimiter::Bracket {
associated = parse_ident_list(g.stream());
associated = syn::parse2::<FuncSpecList>(g.stream())
.map(|l| l.0)
.unwrap_or_default();
break;
}
}
@@ -130,7 +167,9 @@ impl Parse for IncludeNativeBlock {
while j < tokens.len() {
if let proc_macro2::TokenTree::Group(g) = &tokens[j] {
if g.delimiter() == proc_macro2::Delimiter::Bracket {
methods = parse_ident_list(g.stream());
methods = syn::parse2::<FuncSpecList>(g.stream())
.map(|l| l.0)
.unwrap_or_default();
break;
}
}
@@ -165,26 +204,47 @@ pub fn include_native(input: TokenStream) -> TokenStream {
syn::Error::new(
proc_macro2::Span::call_site(),
"Failed to parse include_native! input. Expected:\n \
include_native!(\"name\", Type, [method1, method2, ...])\n or\n \
include_native!(\"name\", Type, { associated: [func1], methods: [method1] })",
include_native!(\"name\", Type, [method1, method2(arg: Type), ...])\n or\n \
include_native!(\"name\", Type, { associated: [func1(arg: Type)], methods: [method1] })",
)
.to_compile_error()
.into()
}
/// Generate the argument extraction code for a function spec
fn generate_arg_extraction(args: &[(Ident, Type)]) -> Vec<proc_macro2::TokenStream> {
args.iter().enumerate().map(|(i, (arg_name, arg_type))| {
quote! {
let #arg_name = args.arr_nth(#i)?.native_clone::<#arg_type>()?;
}
}).collect()
}
/// Generate the argument passing code for a function call
fn generate_arg_passing(args: &[(Ident, Type)]) -> Vec<proc_macro2::TokenStream> {
args.iter().map(|(arg_name, _)| {
quote! { #arg_name }
}).collect()
}
fn expand_simple_include_native(simple: IncludeNativeSimple) -> TokenStream {
let name = simple.name;
let type_path = simple.type_path;
let methods = simple.methods;
let method_calls = methods.iter().map(|method| {
let method_name = method.to_string();
let method_name = method.name.to_string();
let method_ident = &method.name;
let arg_extractions = generate_arg_extraction(&method.args);
let arg_passings = generate_arg_passing(&method.args);
quote! {
.method(#method_name, |args| {
#(#arg_extractions)*
let this = mize::prelude::get_global_mize()?
.get_config(#name)?
.native_mut::<#type_path>()?;
let result = this.#method();
let result = this.#method_ident(#(#arg_passings),*);
Ok(mize::item::ItemData::from_native(result)?)
})
}
@@ -205,23 +265,33 @@ fn expand_block_include_native(block: IncludeNativeBlock) -> TokenStream {
let methods = block.methods;
let method_calls = methods.iter().map(|method| {
let method_name = method.to_string();
let method_name = method.name.to_string();
let method_ident = &method.name;
let arg_extractions = generate_arg_extraction(&method.args);
let arg_passings = generate_arg_passing(&method.args);
quote! {
.method(#method_name, |args| {
#(#arg_extractions)*
let this = mize::prelude::get_global_mize()?
.get_config(#name)?
.native_mut::<#type_path>()?;
let result = this.#method();
let result = this.#method_ident(#(#arg_passings),*);
Ok(mize::item::ItemData::from_native(result)?)
})
}
});
let associated_calls = associated.iter().map(|func| {
let func_name = func.to_string();
let func_name = func.name.to_string();
let func_ident = &func.name;
let arg_extractions = generate_arg_extraction(&func.args);
let arg_passings = generate_arg_passing(&func.args);
quote! {
.method(#func_name, |args| {
let result = <#type_path>::#func();
#(#arg_extractions)*
let result = <#type_path>::#func_ident(#(#arg_passings),*);
Ok(mize::item::ItemData::from_native(result)?)
})
}