diff --git a/tools/src/main.rs b/tools/src/main.rs new file mode 100644 index 0000000..6e43b5f --- /dev/null +++ b/tools/src/main.rs @@ -0,0 +1,357 @@ +// tools/src/main.rs +use serde::Serialize; +use std::env; +use std::fs; +use std::process; +use syn::{ + spanned::Spanned, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, ItemTrait, Pat, + Type, Visibility, +}; + +#[derive(Serialize)] +struct RustDecl { + name: String, + type_: String, + start_line: usize, + end_line: usize, + visibility: String, + is_async: bool, + is_unsafe: bool, + generics: Vec, + traits: Vec, + fields: Vec, + methods: Vec, + return_type: Option, + parameters: Vec, +} + +fn main() { + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: {} ", args[0]); + process::exit(1); + } + + let filename = &args[1]; + let content = match fs::read_to_string(filename) { + Ok(content) => content, + Err(e) => { + eprintln!("ERR: Failed to read file {}: {}", filename, e); + process::exit(2); + } + }; + + let syntax = match syn::parse_file(&content) { + Ok(syntax) => syntax, + Err(e) => { + eprintln!("ERR: Failed to parse Rust file {}: {}", filename, e); + process::exit(3); + } + }; + + let mut decls = Vec::new(); + + // Process declarations + for item in syntax.items { + match item { + Item::Fn(item_fn) => { + if let Some(decl) = parse_function(&item_fn) { + decls.push(decl); + } + } + Item::Struct(item_struct) => { + if let Some(decl) = parse_struct(&item_struct) { + decls.push(decl); + } + } + Item::Enum(item_enum) => { + if let Some(decl) = parse_enum(&item_enum) { + decls.push(decl); + } + } + Item::Trait(item_trait) => { + if let Some(decl) = parse_trait(&item_trait) { + decls.push(decl); + } + } + Item::Impl(item_impl) => { + if let Some(decl) = parse_impl(&item_impl) { + decls.push(decl); + } + } + Item::Mod(item_mod) => { + if let Some(decl) = parse_mod(&item_mod) { + decls.push(decl); + } + } + _ => {} + } + } + + let output = serde_json::to_string(&decls).unwrap_or_else(|_| "[]".to_string()); + println!("{}", output); +} + +fn span_start_end(node: &T) -> (usize, usize) { + // Requires proc-macro2 "span-locations" feature. + let span = node.span(); + let start = span.start().line; + let end = span.end().line; + (start, end) +} + +fn parse_function(item_fn: &ItemFn) -> Option { + let (start_line, end_line) = span_start_end(item_fn); + + let mut parameters = Vec::new(); + for input in &item_fn.sig.inputs { + if let FnArg::Typed(pat_type) = input { + // pat_type.pat is Box + if let Pat::Ident(pat_ident) = &*pat_type.pat { + parameters.push(pat_ident.ident.to_string()); + } else { + // fallback: try to stringify the pattern + parameters.push(format!("{}", quote::ToTokens::to_token_stream(&pat_type.pat))); + } + } else if let FnArg::Receiver(_) = input { + parameters.push("self".to_string()); + } + } + + let return_type = match &item_fn.sig.output { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, ty) => Some(type_to_string(&*ty)), + }; + + Some(RustDecl { + name: item_fn.sig.ident.to_string(), + type_: "function".to_string(), + start_line, + end_line, + visibility: visibility_to_string(&item_fn.vis), + is_async: item_fn.sig.asyncness.is_some(), + is_unsafe: item_fn.sig.unsafety.is_some(), + generics: item_fn + .sig + .generics + .params + .iter() + .map(generic_param_to_string) + .collect(), + traits: Vec::new(), + fields: Vec::new(), + methods: Vec::new(), + return_type, + parameters, + }) +} + +fn parse_struct(item_struct: &ItemStruct) -> Option { + let (start_line, end_line) = span_start_end(item_struct); + + let fields: Vec = item_struct + .fields + .iter() + .filter_map(|field| field.ident.as_ref().map(|ident| ident.to_string())) + .collect(); + + Some(RustDecl { + name: item_struct.ident.to_string(), + type_: "struct".to_string(), + start_line, + end_line, + visibility: visibility_to_string(&item_struct.vis), + is_async: false, + is_unsafe: false, + generics: item_struct + .generics + .params + .iter() + .map(generic_param_to_string) + .collect(), + traits: Vec::new(), + fields, + methods: Vec::new(), + return_type: None, + parameters: Vec::new(), + }) +} + +fn parse_enum(item_enum: &ItemEnum) -> Option { + let (start_line, end_line) = span_start_end(item_enum); + + let variants: Vec = item_enum + .variants + .iter() + .map(|variant| variant.ident.to_string()) + .collect(); + + Some(RustDecl { + name: item_enum.ident.to_string(), + type_: "enum".to_string(), + start_line, + end_line, + visibility: visibility_to_string(&item_enum.vis), + is_async: false, + is_unsafe: false, + generics: item_enum + .generics + .params + .iter() + .map(generic_param_to_string) + .collect(), + traits: Vec::new(), + fields: variants, + methods: Vec::new(), + return_type: None, + parameters: Vec::new(), + }) +} + +fn parse_trait(item_trait: &ItemTrait) -> Option { + let (start_line, end_line) = span_start_end(item_trait); + + let methods: Vec = item_trait + .items + .iter() + .filter_map(|item| { + if let syn::TraitItem::Fn(method) = item { + Some(method.sig.ident.to_string()) + } else { + None + } + }) + .collect(); + + Some(RustDecl { + name: item_trait.ident.to_string(), + type_: "trait".to_string(), + start_line, + end_line, + visibility: visibility_to_string(&item_trait.vis), + is_async: false, + is_unsafe: false, + generics: item_trait + .generics + .params + .iter() + .map(generic_param_to_string) + .collect(), + traits: Vec::new(), + fields: Vec::new(), + methods, + return_type: None, + parameters: Vec::new(), + }) +} + +fn parse_impl(item_impl: &ItemImpl) -> Option { + let (start_line, end_line) = span_start_end(item_impl); + + let target = type_to_string(&*item_impl.self_ty); + + let methods: Vec = item_impl + .items + .iter() + .filter_map(|item| { + if let syn::ImplItem::Fn(method) = item { + Some(method.sig.ident.to_string()) + } else { + None + } + }) + .collect(); + + // If this impl implements a trait, extract its path + let traits: Vec = item_impl + .trait_ + .as_ref() + .map(|(_, path, _)| path_to_string(path)) + .into_iter() + .collect(); + + Some(RustDecl { + name: format!("impl_{}", target), + type_: "impl".to_string(), + start_line, + end_line, + visibility: "".to_string(), + is_async: false, + is_unsafe: item_impl.unsafety.is_some(), + generics: item_impl + .generics + .params + .iter() + .map(generic_param_to_string) + .collect(), + traits, + fields: Vec::new(), + methods, + return_type: None, + parameters: Vec::new(), + }) +} + +fn parse_mod(item_mod: &ItemMod) -> Option { + let (start_line, end_line) = span_start_end(item_mod); + + Some(RustDecl { + name: item_mod.ident.to_string(), + type_: "module".to_string(), + start_line, + end_line, + visibility: visibility_to_string(&item_mod.vis), + is_async: false, + is_unsafe: false, + generics: Vec::new(), + traits: Vec::new(), + fields: Vec::new(), + methods: Vec::new(), + return_type: None, + parameters: Vec::new(), + }) +} + +fn visibility_to_string(vis: &Visibility) -> String { + match vis { + Visibility::Public(_) => "pub".to_string(), + Visibility::Restricted(r) => { + // pub(crate) or pub(in ...) + format!("pub({})", quote::ToTokens::to_token_stream(r).to_string()) + } + _ => "private".to_string(), + } +} + +fn type_to_string(ty: &Type) -> String { + // Simple string representation for common types + match ty { + Type::Path(type_path) => type_path + .path + .segments + .last() + .map(|seg| seg.ident.to_string()) + .unwrap_or_else(|| "unknown".to_string()), + Type::Reference(type_ref) => { + // type_ref.elem is Box + format!("&{}", type_to_string(&*type_ref.elem)) + } + _ => quote::ToTokens::to_token_stream(ty).to_string(), + } +} + +fn path_to_string(path: &syn::Path) -> String { + path.segments + .iter() + .map(|seg| seg.ident.to_string()) + .collect::>() + .join("::") +} + +fn generic_param_to_string(param: &syn::GenericParam) -> String { + match param { + syn::GenericParam::Type(ty) => ty.ident.to_string(), + syn::GenericParam::Lifetime(lf) => lf.lifetime.to_string(), + syn::GenericParam::Const(cnst) => cnst.ident.to_string(), + } +}