autozig_parser/
lib.rs

1//! Parser for autozig! macro input
2//!
3//! This module parses the mixed Zig/Rust syntax within autozig! blocks
4
5#![forbid(unsafe_code)]
6
7use proc_macro2::TokenStream;
8use syn::{
9    parse::{
10        Parse,
11        ParseStream,
12    },
13    ItemEnum,
14    ItemImpl,
15    ItemStruct,
16    Result as ParseResult,
17    Signature,
18};
19
20/// Configuration parsed from autozig! macro
21#[derive(Debug, Clone)]
22pub struct AutoZigConfig {
23    /// Raw Zig code to be compiled (for embedded mode)
24    pub zig_code: String,
25    /// External Zig file path (for include mode)
26    pub external_file: Option<String>,
27    /// Rust function signatures for safe wrappers
28    pub rust_signatures: Vec<RustFunctionSignature>,
29    /// Rust struct definitions for FFI types
30    pub rust_structs: Vec<RustStructDefinition>,
31    /// Rust enum definitions for FFI types
32    pub rust_enums: Vec<RustEnumDefinition>,
33    /// Rust trait implementations (Phase 1: stateless traits)
34    pub rust_trait_impls: Vec<RustTraitImpl>,
35}
36
37/// Generic parameter definition (Phase 3)
38#[derive(Debug, Clone)]
39pub struct GenericParam {
40    /// Parameter name (e.g., "T")
41    pub name: String,
42    /// Type bounds (e.g., Copy, Clone)
43    pub bounds: Vec<String>,
44}
45
46/// A Rust function signature that will have a safe wrapper generated
47#[derive(Clone)]
48pub struct RustFunctionSignature {
49    pub sig: Signature,
50    /// Generic parameters (Phase 3: Generics support)
51    pub generic_params: Vec<GenericParam>,
52    /// Whether this is an async function (Phase 3: Async support)
53    pub is_async: bool,
54    /// Monomorphization attribute types (e.g., #[monomorphize(i32, f64)])
55    pub monomorphize_types: Vec<String>,
56}
57
58/// A Rust struct definition for FFI types
59#[derive(Clone)]
60pub struct RustStructDefinition {
61    pub item: ItemStruct,
62}
63
64/// A Rust enum definition for FFI types
65#[derive(Clone)]
66pub struct RustEnumDefinition {
67    pub item: ItemEnum,
68}
69
70/// A Rust trait implementation (impl Trait for Type)
71#[derive(Clone)]
72pub struct RustTraitImpl {
73    /// The trait being implemented (e.g., "Calculator")
74    pub trait_name: String,
75    /// The type implementing the trait (e.g., "ZigCalculator")
76    pub target_type: String,
77    /// Methods in this trait implementation
78    pub methods: Vec<TraitMethod>,
79    /// Whether the target type is a zero-sized type (stateless)
80    pub is_zst: bool,
81    /// Whether the target type is an opaque pointer (stateful) - Phase 2
82    pub is_opaque: bool,
83    /// Constructor method for opaque types - Phase 2
84    pub constructor: Option<TraitMethod>,
85    /// Destructor method for opaque types - Phase 2
86    pub destructor: Option<TraitMethod>,
87}
88
89/// A method within a trait implementation
90#[derive(Clone)]
91pub struct TraitMethod {
92    /// Method name (e.g., "add")
93    pub name: String,
94    /// Method signature
95    pub sig: Signature,
96    /// Zig function name that this method calls (e.g., "zig_add")
97    pub zig_function: String,
98    /// Original method body (for complex wrapper logic)
99    pub body: Option<syn::Block>,
100    /// Zig function's actual return type (extracted from Zig code)
101    pub zig_return_type: Option<syn::ReturnType>,
102    /// Whether this is a constructor (#[constructor]) - Phase 2
103    pub is_constructor: bool,
104    /// Whether this is a destructor (#[destructor]) - Phase 2
105    pub is_destructor: bool,
106}
107
108impl std::fmt::Debug for RustStructDefinition {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.debug_struct("RustStructDefinition")
111            .field("ident", &self.item.ident.to_string())
112            .finish()
113    }
114}
115
116impl std::fmt::Debug for RustEnumDefinition {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("RustEnumDefinition")
119            .field("ident", &self.item.ident.to_string())
120            .finish()
121    }
122}
123
124impl std::fmt::Debug for RustTraitImpl {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("RustTraitImpl")
127            .field("trait_name", &self.trait_name)
128            .field("target_type", &self.target_type)
129            .field("methods", &self.methods.len())
130            .field("is_zst", &self.is_zst)
131            .field("is_opaque", &self.is_opaque)
132            .finish()
133    }
134}
135
136impl std::fmt::Debug for TraitMethod {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        f.debug_struct("TraitMethod")
139            .field("name", &self.name)
140            .field("zig_function", &self.zig_function)
141            .finish()
142    }
143}
144
145impl std::fmt::Debug for RustFunctionSignature {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        f.debug_struct("RustFunctionSignature")
148            .field("sig", &self.sig.ident.to_string())
149            .finish()
150    }
151}
152
153impl Parse for AutoZigConfig {
154    fn parse(input: ParseStream) -> ParseResult<Self> {
155        // Strategy: Parse everything as a token stream, then split by "---" separator
156        let tokens: TokenStream = input.parse()?;
157        let token_str = tokens.to_string();
158
159
160        // TokenStream.to_string() may add spaces: "---" becomes "- - -"
161        // Try multiple separator patterns
162        let separators = ["---", "- - -", "-- -", "- --"];
163        let mut parts: Vec<&str> = vec![&token_str];
164        for sep in &separators {
165            let test_split: Vec<&str> = token_str.split(sep).collect();
166            if test_split.len() > 1 {
167                parts = test_split;
168                break;
169            }
170        }
171
172
173        if parts.len() == 1 {
174            // No separator, treat entire input as Zig code
175            Ok(AutoZigConfig {
176                zig_code: parts[0].trim().to_string(),
177                external_file: None,
178                rust_signatures: Vec::new(),
179                rust_structs: Vec::new(),
180                rust_enums: Vec::new(),
181                rust_trait_impls: Vec::new(),
182            })
183        } else if parts.len() >= 2 {
184            // Has separator: first part is Zig, second is Rust definitions
185            let zig_code = parts[0].trim().to_string();
186
187
188            // Parse Rust definitions (enums, structs, function signatures, and trait impls)
189            // from second part
190            let (rust_enums, rust_structs, rust_signatures, rust_trait_impls) =
191                parse_rust_definitions(parts[1])?;
192
193
194            Ok(AutoZigConfig {
195                zig_code,
196                external_file: None,
197                rust_signatures,
198                rust_structs,
199                rust_enums,
200                rust_trait_impls,
201            })
202        } else {
203            Err(syn::Error::new(input.span(), "autozig! macro parsing error"))
204        }
205    }
206}
207
208/// Parse Rust definitions (enums, structs, function signatures, and trait
209/// impls) from a string
210fn parse_rust_definitions(
211    input: &str,
212) -> ParseResult<(
213    Vec<RustEnumDefinition>,
214    Vec<RustStructDefinition>,
215    Vec<RustFunctionSignature>,
216    Vec<RustTraitImpl>,
217)> {
218    let mut enums = Vec::new();
219    let mut structs = Vec::new();
220    let mut signatures = Vec::new();
221    let mut trait_impls = Vec::new();
222    let mut trait_impl_types = std::collections::HashSet::new();
223
224
225    // First, try to parse the entire input as a token stream
226    // This handles struct definitions better
227    let input_normalized = input.replace(['\n', '\r'], " ");
228    let input_normalized = input_normalized
229        .split_whitespace()
230        .collect::<Vec<_>>()
231        .join(" ");
232
233    // Check if input is already wrapped in braces (from include_zig! macro)
234    let input_content =
235        if input_normalized.trim().starts_with('{') && input_normalized.trim().ends_with('}') {
236            // Remove outer braces
237            let trimmed = input_normalized.trim();
238            &trimmed[1..trimmed.len() - 1]
239        } else {
240            input_normalized.as_str()
241        };
242
243    // Try to parse as a file (multiple items)
244    let file_str = format!("mod temp {{ {} }}", input_content);
245
246    if let Ok(parsed_file) = syn::parse_str::<syn::File>(&file_str) {
247        eprintln!("Parser: Successfully parsed file with {} items", parsed_file.items.len());
248        for item in parsed_file.items {
249            if let syn::Item::Mod(item_mod) = item {
250                if let Some((_, items)) = item_mod.content {
251                    eprintln!("Parser: Module has {} items", items.len());
252                    // First pass: collect opaque struct definitions
253                    let mut opaque_types = std::collections::HashSet::new();
254                    for inner_item in &items {
255                        if let syn::Item::Struct(item_struct) = inner_item {
256                            eprintln!("Parser: Found struct: {}", item_struct.ident);
257                            if is_opaque_struct(item_struct) {
258                                eprintln!("Parser:   -> Marked as OPAQUE");
259                                opaque_types.insert(item_struct.ident.to_string());
260                            }
261                        }
262                    }
263                    eprintln!("Parser: Total opaque types: {}", opaque_types.len());
264
265                    // Second pass: collect trait impls and inherent impls, mark opaque types
266                    for inner_item in &items {
267                        if let syn::Item::Impl(item_impl) = inner_item {
268                            eprintln!("Parser: Found impl block");
269                            // Try parsing as trait impl
270                            if let Some(mut trait_impl) = parse_trait_impl(item_impl.clone()) {
271                                eprintln!(
272                                    "Parser:   -> Parsed as TRAIT impl for {}",
273                                    trait_impl.target_type
274                                );
275                                // Mark as opaque if the type was declared as opaque
276                                if opaque_types.contains(&trait_impl.target_type) {
277                                    trait_impl.is_opaque = true;
278                                    trait_impl.is_zst = false; // Opaque types
279                                                               // are not ZST
280                                }
281                                trait_impl_types.insert(trait_impl.target_type.clone());
282                                trait_impls.push(trait_impl);
283                            } else {
284                                // Try parsing as inherent impl (for constructor/destructor)
285                                eprintln!("Parser:   -> Trying as INHERENT impl");
286                                if let Some(inherent_impl) =
287                                    parse_inherent_impl(item_impl.clone(), &opaque_types)
288                                {
289                                    eprintln!(
290                                        "Parser:   -> SUCCESS: Parsed inherent impl for {}",
291                                        inherent_impl.target_type
292                                    );
293                                    trait_impl_types.insert(inherent_impl.target_type.clone());
294                                    trait_impls.push(inherent_impl);
295                                } else {
296                                    eprintln!("Parser:   -> FAILED to parse as inherent impl");
297                                }
298                            }
299                        }
300                    }
301                    eprintln!("Parser: Total trait impls collected: {}", trait_impls.len());
302
303                    // Third pass: collect everything else, skipping structs that will be generated
304                    for inner_item in items {
305                        match inner_item {
306                            syn::Item::Enum(item_enum) => {
307                                enums.push(RustEnumDefinition { item: item_enum });
308                            },
309                            syn::Item::Struct(item_struct) => {
310                                // Skip opaque struct declarations (they will be generated by macro)
311                                // Skip structs that will be generated by trait impl
312                                let struct_name = item_struct.ident.to_string();
313                                if !trait_impl_types.contains(&struct_name)
314                                    && !is_opaque_struct(&item_struct)
315                                {
316                                    structs.push(RustStructDefinition { item: item_struct });
317                                }
318                            },
319                            syn::Item::Fn(item_fn) => {
320                                signatures
321                                    .push(parse_function_signature(item_fn.sig, &item_fn.attrs));
322                            },
323                            syn::Item::Impl(_) => {
324                                // Already processed in first pass
325                            },
326                            syn::Item::ForeignMod(foreign_mod) => {
327                                for foreign_item in foreign_mod.items {
328                                    if let syn::ForeignItem::Fn(fn_item) = foreign_item {
329                                        signatures.push(parse_function_signature(
330                                            fn_item.sig,
331                                            &fn_item.attrs,
332                                        ));
333                                    }
334                                }
335                            },
336                            syn::Item::Verbatim(tokens) => {
337                                // Verbatim items are unparsed token streams
338                                // Try to parse as a function signature
339                                let tokens_str = tokens.to_string();
340                                if tokens_str.trim().starts_with("fn ")
341                                    || tokens_str.trim().starts_with("async fn ")
342                                    || tokens_str.contains("fn ")
343                                {
344                                    // Try adding a body and parsing as ItemFn
345                                    let fn_with_body = format!(
346                                        "{} {{ unimplemented!() }}",
347                                        tokens_str.trim_end_matches(';').trim()
348                                    );
349                                    if let Ok(item_fn) =
350                                        syn::parse_str::<syn::ItemFn>(&fn_with_body)
351                                    {
352                                        signatures.push(parse_function_signature(
353                                            item_fn.sig,
354                                            &item_fn.attrs,
355                                        ));
356                                    }
357                                }
358                            },
359                            _ => {
360                                // Skip other item types (use, const, etc.)
361                            },
362                        }
363                    }
364                }
365            }
366        }
367    }
368
369    Ok((enums, structs, signatures, trait_impls))
370}
371
372/// Parse a function signature with generics and async support (Phase 3)
373fn parse_function_signature(sig: Signature, attrs: &[syn::Attribute]) -> RustFunctionSignature {
374    // Extract generic parameters
375    let generic_params = sig
376        .generics
377        .params
378        .iter()
379        .filter_map(|param| {
380            if let syn::GenericParam::Type(type_param) = param {
381                Some(GenericParam {
382                    name: type_param.ident.to_string(),
383                    bounds: type_param
384                        .bounds
385                        .iter()
386                        .filter_map(|bound| {
387                            if let syn::TypeParamBound::Trait(trait_bound) = bound {
388                                trait_bound
389                                    .path
390                                    .segments
391                                    .last()
392                                    .map(|s| s.ident.to_string())
393                            } else {
394                                None
395                            }
396                        })
397                        .collect(),
398                })
399            } else {
400                None
401            }
402        })
403        .collect();
404
405    // Check if function is async
406    let is_async = sig.asyncness.is_some();
407
408    // Extract monomorphize types from attributes
409    let monomorphize_types = extract_monomorphize_types(attrs);
410
411    RustFunctionSignature {
412        sig,
413        generic_params,
414        is_async,
415        monomorphize_types,
416    }
417}
418
419/// Extract types from #[monomorphize(T1, T2, ...)] attribute
420fn extract_monomorphize_types(attrs: &[syn::Attribute]) -> Vec<String> {
421    for attr in attrs {
422        if let syn::Meta::List(meta_list) = &attr.meta {
423            if meta_list.path.is_ident("monomorphize") {
424                // Parse the token stream: (i32, f64, u8)
425                let tokens = &meta_list.tokens;
426                let tokens_str = tokens.to_string();
427                // Simple comma-separated parsing
428                return tokens_str
429                    .split(',')
430                    .map(|s| s.trim().to_string())
431                    .filter(|s| !s.is_empty())
432                    .collect();
433            }
434        }
435    }
436    Vec::new()
437}
438
439/// Parse a trait implementation (impl Trait for Type)
440fn parse_trait_impl(item_impl: ItemImpl) -> Option<RustTraitImpl> {
441    // Check if this is a trait implementation (has a trait path)
442    let trait_path = item_impl.trait_.as_ref()?;
443    let trait_name = trait_path.1.segments.last()?.ident.to_string();
444
445    // Get the target type name
446    let target_type = if let syn::Type::Path(type_path) = &*item_impl.self_ty {
447        type_path.path.segments.last()?.ident.to_string()
448    } else {
449        return None;
450    };
451
452    // Check if this is a zero-sized type (ZST) - no fields
453    // For Phase 1, we assume all trait impl types are ZST
454    let is_zst = true;
455
456    // Phase 2: Check for opaque pointer type
457    let is_opaque = false; // Will be detected from struct definition in second pass
458
459    // Parse methods from impl block
460    let mut methods = Vec::new();
461    let mut constructor = None;
462    let mut destructor = None;
463
464    for impl_item in &item_impl.items {
465        if let syn::ImplItem::Fn(method) = impl_item {
466            // Check for #[constructor] or #[destructor] attributes
467            let is_constructor_attr = has_attribute(&method.attrs, "constructor");
468            let is_destructor_attr = has_attribute(&method.attrs, "destructor");
469
470            // Extract Zig function name from method body
471            if let Some(zig_function) = extract_zig_function_call(&method.block) {
472                let trait_method = TraitMethod {
473                    name: method.sig.ident.to_string(),
474                    sig: method.sig.clone(),
475                    zig_function,
476                    body: Some(method.block.clone()),
477                    zig_return_type: None, // Will be filled by macro with Zig code analysis
478                    is_constructor: is_constructor_attr,
479                    is_destructor: is_destructor_attr,
480                };
481
482                if is_constructor_attr {
483                    constructor = Some(trait_method.clone());
484                } else if is_destructor_attr {
485                    destructor = Some(trait_method.clone());
486                } else {
487                    methods.push(trait_method);
488                }
489            }
490        }
491    }
492
493    if methods.is_empty() && constructor.is_none() && destructor.is_none() {
494        return None;
495    }
496
497    Some(RustTraitImpl {
498        trait_name,
499        target_type,
500        methods,
501        is_zst,
502        is_opaque,
503        constructor,
504        destructor,
505    })
506}
507
508/// Extract Zig function call from a method body
509/// Looks for patterns like: zig_add(a, b)
510/// Also handles bodies with conditionals like divide that checks result
511fn extract_zig_function_call(block: &syn::Block) -> Option<String> {
512    // Try to find any function call in the block that looks like a Zig function
513    // (starts with "zig_")
514    for stmt in &block.stmts {
515        if let Some(zig_fn) = extract_zig_function_from_stmt(stmt) {
516            return Some(zig_fn);
517        }
518    }
519    None
520}
521
522/// Helper to extract Zig function name from a statement
523fn extract_zig_function_from_stmt(stmt: &syn::Stmt) -> Option<String> {
524    match stmt {
525        syn::Stmt::Expr(expr, _) => extract_zig_function_from_expr(expr),
526        syn::Stmt::Local(local) => {
527            if let Some(init) = &local.init {
528                extract_zig_function_from_expr(&init.expr)
529            } else {
530                None
531            }
532        },
533        _ => None,
534    }
535}
536
537/// Helper to recursively extract Zig function name from an expression
538fn extract_zig_function_from_expr(expr: &syn::Expr) -> Option<String> {
539    match expr {
540        syn::Expr::Call(call) => {
541            if let syn::Expr::Path(path) = &*call.func {
542                let fn_name = path.path.segments.last()?.ident.to_string();
543                // Accept any function call (not just zig_* prefix)
544                // This allows for Phase 2 naming like hasher_new, hasher_free, etc.
545                return Some(fn_name);
546            }
547            None
548        },
549        syn::Expr::Block(block) => {
550            for stmt in &block.block.stmts {
551                if let Some(zig_fn) = extract_zig_function_from_stmt(stmt) {
552                    return Some(zig_fn);
553                }
554            }
555            None
556        },
557        syn::Expr::If(if_expr) => {
558            // Check condition
559            if let Some(zig_fn) = extract_zig_function_from_expr(&if_expr.cond) {
560                return Some(zig_fn);
561            }
562            // Check then branch
563            for stmt in &if_expr.then_branch.stmts {
564                if let Some(zig_fn) = extract_zig_function_from_stmt(stmt) {
565                    return Some(zig_fn);
566                }
567            }
568            // Check else branch
569            if let Some((_, else_branch)) = &if_expr.else_branch {
570                if let Some(zig_fn) = extract_zig_function_from_expr(else_branch) {
571                    return Some(zig_fn);
572                }
573            }
574            None
575        },
576        syn::Expr::Let(let_expr) => extract_zig_function_from_expr(&let_expr.expr),
577        _ => None,
578    }
579}
580
581/// Check if a method has a specific attribute (e.g., #[constructor])
582fn has_attribute(attrs: &[syn::Attribute], name: &str) -> bool {
583    attrs.iter().any(|attr| {
584        if let syn::Meta::Path(path) = &attr.meta {
585            path.is_ident(name)
586        } else {
587            false
588        }
589    })
590}
591
592/// Parse an inherent impl block (impl Type { ... }) for constructor/destructor
593fn parse_inherent_impl(
594    item_impl: ItemImpl,
595    opaque_types: &std::collections::HashSet<String>,
596) -> Option<RustTraitImpl> {
597    // Check that this is NOT a trait implementation (no trait path)
598    if item_impl.trait_.is_some() {
599        return None;
600    }
601
602    // Get the target type name
603    let target_type = if let syn::Type::Path(type_path) = &*item_impl.self_ty {
604        type_path.path.segments.last()?.ident.to_string()
605    } else {
606        return None;
607    };
608
609    // Only process if this is an opaque type
610    if !opaque_types.contains(&target_type) {
611        return None;
612    }
613
614    // Parse methods from impl block looking for constructor/destructor
615    let mut constructor = None;
616    let mut destructor = None;
617
618    eprintln!("Parser: parse_inherent_impl: Scanning {} methods", item_impl.items.len());
619    for impl_item in &item_impl.items {
620        if let syn::ImplItem::Fn(method) = impl_item {
621            eprintln!("Parser: parse_inherent_impl:   Method: {}", method.sig.ident);
622            let is_constructor_attr = has_attribute(&method.attrs, "constructor");
623            let is_destructor_attr = has_attribute(&method.attrs, "destructor");
624            eprintln!(
625                "Parser: parse_inherent_impl:     constructor={}, destructor={}",
626                is_constructor_attr, is_destructor_attr
627            );
628
629            if is_constructor_attr || is_destructor_attr {
630                // Extract Zig function name from method body
631                eprintln!("Parser: parse_inherent_impl:     Extracting zig function...");
632                if let Some(zig_function) = extract_zig_function_call(&method.block) {
633                    eprintln!(
634                        "Parser: parse_inherent_impl:     Found zig function: {}",
635                        zig_function
636                    );
637                    let trait_method = TraitMethod {
638                        name: method.sig.ident.to_string(),
639                        sig: method.sig.clone(),
640                        zig_function,
641                        body: Some(method.block.clone()),
642                        zig_return_type: None,
643                        is_constructor: is_constructor_attr,
644                        is_destructor: is_destructor_attr,
645                    };
646
647                    if is_constructor_attr {
648                        constructor = Some(trait_method);
649                    } else if is_destructor_attr {
650                        destructor = Some(trait_method);
651                    }
652                }
653            }
654        }
655    }
656
657    // Must have at least constructor or destructor
658    if constructor.is_none() && destructor.is_none() {
659        return None;
660    }
661
662    // Create a "pseudo trait impl" for the inherent impl
663    // This allows us to generate the constructor/destructor without a real trait
664    Some(RustTraitImpl {
665        trait_name: String::new(), // No trait for inherent impl
666        target_type,
667        methods: Vec::new(), // No regular methods in inherent impl
668        is_zst: false,
669        is_opaque: true,
670        constructor,
671        destructor,
672    })
673}
674
675/// Check if a struct is marked as opaque: struct Name(opaque);
676fn is_opaque_struct(item: &ItemStruct) -> bool {
677    // Check for tuple struct with single field named "opaque"
678    if let syn::Fields::Unnamed(fields) = &item.fields {
679        if fields.unnamed.len() == 1 {
680            if let Some(field) = fields.unnamed.first() {
681                if let syn::Type::Path(type_path) = &field.ty {
682                    if let Some(ident) = type_path.path.get_ident() {
683                        return ident == "opaque";
684                    }
685                }
686            }
687        }
688    }
689    false
690}
691
692/// Configuration for include_zig! macro (external file mode)
693#[derive(Debug, Clone)]
694pub struct IncludeZigConfig {
695    /// Path to external Zig file (relative to cargo manifest dir)
696    pub file_path: String,
697    /// Rust function signatures for safe wrappers
698    pub rust_signatures: Vec<RustFunctionSignature>,
699    /// Rust struct definitions for FFI types
700    pub rust_structs: Vec<RustStructDefinition>,
701    /// Rust enum definitions for FFI types
702    pub rust_enums: Vec<RustEnumDefinition>,
703    /// Rust trait implementations
704    pub rust_trait_impls: Vec<RustTraitImpl>,
705}
706
707impl Parse for IncludeZigConfig {
708    fn parse(input: ParseStream) -> ParseResult<Self> {
709        // Format: include_zig!("path/to/file.zig", { Rust signatures })
710        // Or: include_zig!("path/to/file.zig")
711
712        // Parse the file path (must be a string literal)
713        let file_path_lit: syn::LitStr = input.parse()?;
714        let file_path = file_path_lit.value();
715
716        // Check if there's a comma and more content
717        if input.peek(syn::Token![,]) {
718            let _: syn::Token![,] = input.parse()?;
719
720            // Parse the rest as Rust definitions
721            let tokens: TokenStream = input.parse()?;
722            let token_str = tokens.to_string();
723
724            let (rust_enums, rust_structs, rust_signatures, rust_trait_impls) =
725                parse_rust_definitions(&token_str)?;
726
727            Ok(IncludeZigConfig {
728                file_path,
729                rust_signatures,
730                rust_structs,
731                rust_enums,
732                rust_trait_impls,
733            })
734        } else {
735            // No signatures provided
736            Ok(IncludeZigConfig {
737                file_path,
738                rust_signatures: Vec::new(),
739                rust_structs: Vec::new(),
740                rust_enums: Vec::new(),
741                rust_trait_impls: Vec::new(),
742            })
743        }
744    }
745}
746
747impl IncludeZigConfig {
748    /// Get the module name for generated bindings
749    pub fn get_mod_name(&self) -> &str {
750        "ffi"
751    }
752
753    /// Get a unique module name based on the file path
754    /// Example: "zig/math.zig" -> "ffi_zig_math"
755    pub fn get_unique_mod_name(&self) -> String {
756        // Remove extension and convert path separators to underscores
757        let path_without_ext = self
758            .file_path
759            .trim_end_matches(".zig")
760            .replace(['/', '\\', '.', '-'], "_");
761        format!("ffi_{}", path_without_ext)
762    }
763
764    /// Check if this config has any Rust signatures
765    pub fn has_rust_signatures(&self) -> bool {
766        !self.rust_signatures.is_empty()
767    }
768}
769
770impl AutoZigConfig {
771    /// Get the module name for generated bindings
772    pub fn get_mod_name(&self) -> &str {
773        "ffi"
774    }
775
776    /// Check if this config has any Rust signatures
777    pub fn has_rust_signatures(&self) -> bool {
778        !self.rust_signatures.is_empty()
779    }
780
781    /// Check if this config uses external file
782    pub fn is_external_mode(&self) -> bool {
783        self.external_file.is_some()
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    use quote::quote;
790
791    use super::*;
792
793    #[test]
794    fn test_parse_zig_only() {
795        let input = quote! {
796            const std = @import("std");
797            export fn add(a: i32, b: i32) i32 {
798                return a + b;
799            }
800        };
801
802        let config: AutoZigConfig = syn::parse2(input).unwrap();
803        assert!(!config.zig_code.is_empty());
804        assert_eq!(config.rust_signatures.len(), 0);
805    }
806
807    #[test]
808    fn test_parse_with_separator() {
809        let input = quote! {
810            const std = @import("std");
811            export fn add(a: i32, b: i32) i32 {
812                return a + b;
813            }
814            ---
815            fn add(a: i32, b: i32) -> i32;
816        };
817
818        let config: AutoZigConfig = syn::parse2(input).unwrap();
819        assert!(!config.zig_code.is_empty());
820        assert_eq!(config.rust_signatures.len(), 1);
821    }
822
823    #[test]
824    fn test_parse_generic_function() {
825        let input = quote! {
826            export fn process_i32(ptr: [*]const i32, len: usize) usize {
827                return len;
828            }
829            ---
830            #[monomorphize(i32, f64)]
831            fn process<T>(data: &[T]) -> usize;
832        };
833
834        let config: AutoZigConfig = syn::parse2(input).unwrap();
835        assert_eq!(config.rust_signatures.len(), 1);
836        let sig = &config.rust_signatures[0];
837        assert_eq!(sig.generic_params.len(), 1);
838        assert_eq!(sig.generic_params[0].name, "T");
839        assert_eq!(sig.monomorphize_types, vec!["i32", "f64"]);
840    }
841
842    #[test]
843    fn test_parse_async_function() {
844        let input = quote! {
845            export fn async_compute(ptr: [*]const u8, len: usize) void {}
846            ---
847            async fn async_compute(data: &[u8]) -> Result<Vec<u8>, i32>;
848        };
849
850        let config: AutoZigConfig = syn::parse2(input).unwrap();
851        assert_eq!(config.rust_signatures.len(), 1);
852        let sig = &config.rust_signatures[0];
853        assert!(sig.is_async);
854    }
855}