Files
ragmcp/tools/parse_rust_ast.rs
T
2025-11-13 23:24:57 +00:00

353 lines
9.6 KiB
Rust

// tools/src/main.rs
use serde::Serialize;
use std::env;
use std::fs;
use std::process;
use syn::{
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, &content) {
decls.push(decl);
}
}
Item::Struct(item_struct) => {
if let Some(decl) = parse_struct(item_struct, &content) {
decls.push(decl);
}
}
Item::Enum(item_enum) => {
if let Some(decl) = parse_enum(item_enum, &content) {
decls.push(decl);
}
}
Item::Trait(item_trait) => {
if let Some(decl) = parse_trait(item_trait, &content) {
decls.push(decl);
}
}
Item::Impl(item_impl) => {
if let Some(decl) = parse_impl(item_impl, &content) {
decls.push(decl);
}
}
Item::Mod(item_mod) => {
if let Some(decl) = parse_mod(item_mod, &content) {
decls.push(decl);
}
}
_ => {}
}
}
let output = serde_json::to_string(&decls).unwrap_or_else(|_| "[]".to_string());
println!("{}", output);
}
fn parse_function(item_fn: ItemFn, _content: &str) -> Option<RustDecl> {
let start_line = item_fn.sig.ident.span().start().line;
let end_line = item_fn.sig.ident.span().end().line;
let mut parameters = Vec::new();
for input in &item_fn.sig.inputs {
if let FnArg::Typed(pat_type) = input {
if let Pat::Ident(pat_ident) = &*pat_type.pat {
parameters.push(pat_ident.ident.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, _content: &str) -> Option<RustDecl> {
let start_line = item_struct.ident.span().start().line;
let end_line = item_struct.ident.span().end().line;
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, _content: &str) -> Option<RustDecl> {
let start_line = item_enum.ident.span().start().line;
let end_line = item_enum.ident.span().end().line;
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, _content: &str) -> Option<RustDecl> {
let start_line = item_trait.ident.span().start().line;
let end_line = item_trait.ident.span().end().line;
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, _content: &str) -> Option<RustDecl> {
// For impl blocks, use the span of the first item or the impl keyword
let start_line = if let Some(first_item) = item_impl.items.first() {
first_item.span().start().line
} else {
// Fallback: use a reasonable default
1
};
let end_line = if let Some(last_item) = item_impl.items.last() {
last_item.span().end().line
} else {
start_line
};
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();
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, _content: &str) -> Option<RustDecl> {
let start_line = item_mod.ident.span().start().line;
let end_line = item_mod.ident.span().end().line;
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(),
_ => "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) => {
format!("&{}", type_to_string(&type_ref.elem))
}
_ => "unknown".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(),
}
}