fully compiles
This commit is contained in:
@@ -1,9 +1,242 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn;
|
||||
use syn::{self, parse::Parse, parse::ParseStream, Ident, LitStr, Token, TypePath};
|
||||
use syn::punctuated::Punctuated;
|
||||
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
|
||||
/// ```ignore
|
||||
/// include_native!("Command", clap::Command, [arg, subcommand]);
|
||||
/// ```
|
||||
///
|
||||
/// Usage 2: With associated functions
|
||||
/// ```ignore
|
||||
/// include_native!("Command", clap::Command, {
|
||||
/// associated: [new],
|
||||
/// methods: [arg, subcommand]
|
||||
/// });
|
||||
/// ```
|
||||
|
||||
/// Input for the simplified syntax: include_native!("name", Type, [method1, method2, ...])
|
||||
struct IncludeNativeSimple {
|
||||
name: LitStr,
|
||||
type_path: TypePath,
|
||||
methods: Punctuated<Ident, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for IncludeNativeSimple {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
let type_path = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
let methods = Punctuated::parse_terminated(&content)?;
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
impl Parse for IncludeNativeBlock {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
let type_path = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
let content;
|
||||
syn::braced!(content in input);
|
||||
|
||||
let mut associated = Vec::new();
|
||||
let mut methods = Vec::new();
|
||||
|
||||
// Parse the content looking for associated: [...] and methods: [...]
|
||||
// We'll collect all tokens first and then parse
|
||||
let tokens: Vec<proc_macro2::TokenTree> = content.parse::<proc_macro2::TokenStream>()?.into_iter().collect();
|
||||
let mut i = 0;
|
||||
while i < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Ident(id) = &tokens[i] {
|
||||
let id_str = id.to_string();
|
||||
if id_str == "associated" {
|
||||
// Find colon
|
||||
let mut j = i + 1;
|
||||
while j < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Punct(p) = &tokens[j] {
|
||||
if p.as_char() == ':' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
j += 1; // Skip colon
|
||||
|
||||
// Find bracket group
|
||||
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());
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
i = j + 1;
|
||||
} else if id_str == "methods" {
|
||||
// Find colon
|
||||
let mut j = i + 1;
|
||||
while j < tokens.len() {
|
||||
if let proc_macro2::TokenTree::Punct(p) = &tokens[j] {
|
||||
if p.as_char() == ':' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
j += 1; // Skip colon
|
||||
|
||||
// Find bracket group
|
||||
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());
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
i = j + 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IncludeNativeBlock { name, type_path, associated, methods })
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_native(input: TokenStream) -> TokenStream {
|
||||
// Try to parse as simplified syntax: include_native!("name", Type, [m1, m2, ...])
|
||||
if let Ok(simple) = syn::parse::<IncludeNativeSimple>(input.clone()) {
|
||||
return expand_simple_include_native(simple);
|
||||
}
|
||||
|
||||
// Try to parse as block syntax: include_native!("name", Type, { associated: [...], methods: [...] })
|
||||
if let Ok(block) = syn::parse::<IncludeNativeBlock>(input) {
|
||||
return expand_block_include_native(block);
|
||||
}
|
||||
|
||||
// If both fail, return a compile error
|
||||
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] })",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
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();
|
||||
quote! {
|
||||
.method(#method_name, |args| {
|
||||
let this = mize::prelude::get_global_mize()?
|
||||
.get_config(#name)?
|
||||
.native_mut::<#type_path>()?;
|
||||
let result = this.#method();
|
||||
Ok(mize::item::ItemData::from_native(result)?)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
mize::config::opt(#name)
|
||||
#(#method_calls)*
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn expand_block_include_native(block: IncludeNativeBlock) -> TokenStream {
|
||||
let name = block.name;
|
||||
let type_path = block.type_path;
|
||||
let associated = block.associated;
|
||||
let methods = block.methods;
|
||||
|
||||
let method_calls = methods.iter().map(|method| {
|
||||
let method_name = method.to_string();
|
||||
quote! {
|
||||
.method(#method_name, |args| {
|
||||
let this = mize::prelude::get_global_mize()?
|
||||
.get_config(#name)?
|
||||
.native_mut::<#type_path>()?;
|
||||
let result = this.#method();
|
||||
Ok(mize::item::ItemData::from_native(result)?)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let associated_calls = associated.iter().map(|func| {
|
||||
let func_name = func.to_string();
|
||||
quote! {
|
||||
.method(#func_name, |args| {
|
||||
let result = <#type_path>::#func();
|
||||
Ok(mize::item::ItemData::from_native(result)?)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
mize::config::opt(#name)
|
||||
#(#associated_calls)*
|
||||
#(#method_calls)*
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////// data! macro ///////////////////////////////////////
|
||||
// A procedural macro to define data in Nix-style and expand to a `ciborium::Value` (CBOR).
|
||||
@@ -20,7 +253,7 @@ use syn;
|
||||
// ciborium = "0.2"
|
||||
// ```
|
||||
|
||||
use syn::{Ident, Lit, Token, parse::Parse, parse::ParseStream, punctuated::Punctuated, token};
|
||||
use syn::Lit;
|
||||
|
||||
// --- AST ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user