...
This commit is contained in:
@@ -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)?)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user