1#![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#[derive(Debug, Clone)]
22pub struct AutoZigConfig {
23 pub zig_code: String,
25 pub external_file: Option<String>,
27 pub rust_signatures: Vec<RustFunctionSignature>,
29 pub rust_structs: Vec<RustStructDefinition>,
31 pub rust_enums: Vec<RustEnumDefinition>,
33 pub rust_trait_impls: Vec<RustTraitImpl>,
35}
36
37#[derive(Debug, Clone)]
39pub struct GenericParam {
40 pub name: String,
42 pub bounds: Vec<String>,
44}
45
46#[derive(Clone)]
48pub struct RustFunctionSignature {
49 pub sig: Signature,
50 pub generic_params: Vec<GenericParam>,
52 pub is_async: bool,
54 pub monomorphize_types: Vec<String>,
56}
57
58#[derive(Clone)]
60pub struct RustStructDefinition {
61 pub item: ItemStruct,
62}
63
64#[derive(Clone)]
66pub struct RustEnumDefinition {
67 pub item: ItemEnum,
68}
69
70#[derive(Clone)]
72pub struct RustTraitImpl {
73 pub trait_name: String,
75 pub target_type: String,
77 pub methods: Vec<TraitMethod>,
79 pub is_zst: bool,
81 pub is_opaque: bool,
83 pub constructor: Option<TraitMethod>,
85 pub destructor: Option<TraitMethod>,
87}
88
89#[derive(Clone)]
91pub struct TraitMethod {
92 pub name: String,
94 pub sig: Signature,
96 pub zig_function: String,
98 pub body: Option<syn::Block>,
100 pub zig_return_type: Option<syn::ReturnType>,
102 pub is_constructor: bool,
104 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 let tokens: TokenStream = input.parse()?;
157 let token_str = tokens.to_string();
158
159
160 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 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 let zig_code = parts[0].trim().to_string();
186
187
188 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
208fn 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 let input_normalized = input.replace(['\n', '\r'], " ");
228 let input_normalized = input_normalized
229 .split_whitespace()
230 .collect::<Vec<_>>()
231 .join(" ");
232
233 let input_content =
235 if input_normalized.trim().starts_with('{') && input_normalized.trim().ends_with('}') {
236 let trimmed = input_normalized.trim();
238 &trimmed[1..trimmed.len() - 1]
239 } else {
240 input_normalized.as_str()
241 };
242
243 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 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 for inner_item in &items {
267 if let syn::Item::Impl(item_impl) = inner_item {
268 eprintln!("Parser: Found impl block");
269 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 if opaque_types.contains(&trait_impl.target_type) {
277 trait_impl.is_opaque = true;
278 trait_impl.is_zst = false; }
281 trait_impl_types.insert(trait_impl.target_type.clone());
282 trait_impls.push(trait_impl);
283 } else {
284 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 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 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 },
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 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 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 },
362 }
363 }
364 }
365 }
366 }
367 }
368
369 Ok((enums, structs, signatures, trait_impls))
370}
371
372fn parse_function_signature(sig: Signature, attrs: &[syn::Attribute]) -> RustFunctionSignature {
374 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 let is_async = sig.asyncness.is_some();
407
408 let monomorphize_types = extract_monomorphize_types(attrs);
410
411 RustFunctionSignature {
412 sig,
413 generic_params,
414 is_async,
415 monomorphize_types,
416 }
417}
418
419fn 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 let tokens = &meta_list.tokens;
426 let tokens_str = tokens.to_string();
427 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
439fn parse_trait_impl(item_impl: ItemImpl) -> Option<RustTraitImpl> {
441 let trait_path = item_impl.trait_.as_ref()?;
443 let trait_name = trait_path.1.segments.last()?.ident.to_string();
444
445 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 let is_zst = true;
455
456 let is_opaque = false; 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 let is_constructor_attr = has_attribute(&method.attrs, "constructor");
468 let is_destructor_attr = has_attribute(&method.attrs, "destructor");
469
470 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, 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
508fn extract_zig_function_call(block: &syn::Block) -> Option<String> {
512 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
522fn 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
537fn 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 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 if let Some(zig_fn) = extract_zig_function_from_expr(&if_expr.cond) {
560 return Some(zig_fn);
561 }
562 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 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
581fn 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
592fn parse_inherent_impl(
594 item_impl: ItemImpl,
595 opaque_types: &std::collections::HashSet<String>,
596) -> Option<RustTraitImpl> {
597 if item_impl.trait_.is_some() {
599 return None;
600 }
601
602 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 if !opaque_types.contains(&target_type) {
611 return None;
612 }
613
614 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 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 if constructor.is_none() && destructor.is_none() {
659 return None;
660 }
661
662 Some(RustTraitImpl {
665 trait_name: String::new(), target_type,
667 methods: Vec::new(), is_zst: false,
669 is_opaque: true,
670 constructor,
671 destructor,
672 })
673}
674
675fn is_opaque_struct(item: &ItemStruct) -> bool {
677 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#[derive(Debug, Clone)]
694pub struct IncludeZigConfig {
695 pub file_path: String,
697 pub rust_signatures: Vec<RustFunctionSignature>,
699 pub rust_structs: Vec<RustStructDefinition>,
701 pub rust_enums: Vec<RustEnumDefinition>,
703 pub rust_trait_impls: Vec<RustTraitImpl>,
705}
706
707impl Parse for IncludeZigConfig {
708 fn parse(input: ParseStream) -> ParseResult<Self> {
709 let file_path_lit: syn::LitStr = input.parse()?;
714 let file_path = file_path_lit.value();
715
716 if input.peek(syn::Token![,]) {
718 let _: syn::Token![,] = input.parse()?;
719
720 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 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 pub fn get_mod_name(&self) -> &str {
750 "ffi"
751 }
752
753 pub fn get_unique_mod_name(&self) -> String {
756 let path_without_ext = self
758 .file_path
759 .trim_end_matches(".zig")
760 .replace(['/', '\\', '.', '-'], "_");
761 format!("ffi_{}", path_without_ext)
762 }
763
764 pub fn has_rust_signatures(&self) -> bool {
766 !self.rust_signatures.is_empty()
767 }
768}
769
770impl AutoZigConfig {
771 pub fn get_mod_name(&self) -> &str {
773 "ffi"
774 }
775
776 pub fn has_rust_signatures(&self) -> bool {
778 !self.rust_signatures.is_empty()
779 }
780
781 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}