add rust parser
This commit is contained in:
@@ -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<String>,
|
||||
traits: Vec<String>,
|
||||
fields: Vec<String>,
|
||||
methods: Vec<String>,
|
||||
return_type: Option<String>,
|
||||
parameters: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: {} <file.rs>", 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<T: Spanned>(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<RustDecl> {
|
||||
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<Pat>
|
||||
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<RustDecl> {
|
||||
let (start_line, end_line) = span_start_end(item_struct);
|
||||
|
||||
let fields: Vec<String> = 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<RustDecl> {
|
||||
let (start_line, end_line) = span_start_end(item_enum);
|
||||
|
||||
let variants: Vec<String> = 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<RustDecl> {
|
||||
let (start_line, end_line) = span_start_end(item_trait);
|
||||
|
||||
let methods: Vec<String> = 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<RustDecl> {
|
||||
let (start_line, end_line) = span_start_end(item_impl);
|
||||
|
||||
let target = type_to_string(&*item_impl.self_ty);
|
||||
|
||||
let methods: Vec<String> = 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<String> = 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<RustDecl> {
|
||||
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<Type>
|
||||
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::<Vec<_>>()
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user