pub mod compactor;
pub mod error;
pub mod language_registry;
pub mod parser_cache;
pub mod semantic_index;
pub mod types;
pub use compactor::AstCompactor;
pub use compactor::CompressionLevel;
pub use error::AstError;
pub use error::AstResult;
pub use language_registry::Language;
pub use language_registry::LanguageRegistry;
pub use parser_cache::ParserCache;
pub use semantic_index::SemanticIndex;
pub use semantic_index::Symbol;
pub use semantic_index::SymbolKind;
pub use types::AstNode;
pub use types::AstNodeKind;
pub use types::ParsedAst;
pub use types::SourceLocation;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug)]
pub struct AstEngine {
registry: Arc<LanguageRegistry>,
cache: Arc<RwLock<ParserCache>>,
compactor: Arc<AstCompactor>,
semantic_index: Arc<RwLock<SemanticIndex>>,
}
impl AstEngine {
pub fn new(compression_level: CompressionLevel) -> Self {
Self {
registry: Arc::new(LanguageRegistry::new()),
cache: Arc::new(RwLock::new(ParserCache::new(1024 * 1024 * 100))), compactor: Arc::new(AstCompactor::new(compression_level)),
semantic_index: Arc::new(RwLock::new(SemanticIndex::new())),
}
}
pub async fn parse_file(&self, path: &Path) -> AstResult<ParsedAst> {
{
let mut cache = self.cache.write().await;
if let Some(ast) = cache.get(path) {
return Ok((*ast).clone());
}
}
let language = self.registry.detect_language(path)?;
let content = tokio::fs::read_to_string(path)
.await
.map_err(|e| AstError::IoError(e.to_string()))?;
let parsed = self.registry.parse(&language, &content)?;
{
let mut cache = self.cache.write().await;
cache.insert(path.to_path_buf(), parsed.clone());
}
{
let mut index = self.semantic_index.write().await;
index.index_ast(path, &parsed)?;
}
Ok(parsed)
}
pub async fn compact_code(&self, path: &Path) -> AstResult<String> {
let ast = self.parse_file(path).await?;
self.compactor.compact(&ast)
}
pub async fn search_symbols(&self, query: &str) -> AstResult<Vec<Symbol>> {
let index = self.semantic_index.read().await;
Ok(index.search(query))
}
pub async fn get_call_graph(&self, path: &Path, function_name: &str) -> AstResult<Vec<Symbol>> {
let index = self.semantic_index.read().await;
Ok(index.get_call_graph(path, function_name))
}
pub async fn clear_cache(&self) {
let mut cache = self.cache.write().await;
cache.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[tokio::test]
async fn test_ast_engine_basic() {
let engine = AstEngine::new(CompressionLevel::Medium);
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.rs");
fs::write(
&file_path,
"fn main() {\n println!(\"Hello, world!\");\n}",
)
.unwrap();
let ast = engine.parse_file(&file_path).await.unwrap();
assert!(!ast.root_node.kind.is_empty());
}
#[tokio::test]
async fn test_compression_levels() {
let light_engine = AstEngine::new(CompressionLevel::Light);
let hard_engine = AstEngine::new(CompressionLevel::Hard);
let dir = tempdir().unwrap();
let file_path = dir.path().join("test.py");
let code = r#"
def calculate_fibonacci(n):
"""Calculate fibonacci number"""
if n <= 1:
return n
else:
return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)
class MathOperations:
def __init__(self):
self.cache = {}
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
"#;
fs::write(&file_path, code).unwrap();
let light_compact = light_engine.compact_code(&file_path).await.unwrap();
let hard_compact = hard_engine.compact_code(&file_path).await.unwrap();
assert!(hard_compact.len() < light_compact.len());
}
}