fully compiles

This commit is contained in:
Sebastian Moser
2026-10-23 12:22:23 +02:00
parent 2b2d40148b
commit 9141fc5441
6 changed files with 251 additions and 83 deletions

View File

@@ -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 ---