use crate::{analyzer, inference, lexer, parser};
use std::path::PathBuf;
#[salsa::db]
#[derive(Clone)]
pub struct CompilerDatabase {
storage: salsa::Storage<Self>,
}
impl CompilerDatabase {
pub fn new() -> Self {
Self {
storage: salsa::Storage::default(),
}
}
}
impl Default for CompilerDatabase {
fn default() -> Self {
Self::new()
}
}
#[salsa::db]
impl salsa::Database for CompilerDatabase {}
#[salsa::input]
pub struct SourceInput {
#[returns(ref)]
pub file_path: PathBuf,
#[returns(ref)]
pub source_text: String,
}
#[salsa::tracked]
pub fn tokenize<'db>(db: &'db dyn salsa::Database, source: SourceInput) -> TokenStream<'db> {
let text = source.source_text(db);
let mut lexer = lexer::Lexer::new(text);
let tokens = lexer.tokenize_with_locations();
TokenStream::new(db, tokens)
}
#[salsa::tracked]
pub fn parse_tokens<'db>(
db: &'db dyn salsa::Database,
token_stream: TokenStream<'db>,
) -> ParsedProgram<'db> {
let tokens = token_stream.tokens(db);
let parser = Box::leak(Box::new(parser::Parser::new(tokens.clone())));
let program = match parser.parse() {
Ok(prog) => prog,
Err(e) => {
eprintln!("Parse error: {}", e);
parser::Program { items: vec![] }
}
};
ParsedProgram::new(db, program)
}
#[salsa::tracked]
pub fn analyze_types<'db>(
db: &'db dyn salsa::Database,
parsed: ParsedProgram<'db>,
) -> TypedProgram<'db> {
let program = parsed.program(db);
TypedProgram::new(db, program.clone())
}
pub fn perform_analysis<'ast>(
program: &parser::Program<'ast>,
) -> Result<AnalysisResults<'ast>, String> {
use crate::analyzer::Analyzer;
let mut analyzer = Analyzer::new();
let (analyzed_functions, signatures, _analyzed_trait_methods) =
analyzer.analyze_program(program)?;
let inferred_bounds_map = crate::inference::collect_inferred_bounds(&program.items);
Ok(AnalysisResults {
analyzed_functions,
inferred_bounds: inferred_bounds_map,
signatures,
})
}
#[salsa::tracked]
pub fn optimize_program<'db>(
db: &'db dyn salsa::Database,
typed: TypedProgram<'db>,
) -> OptimizedProgram<'db> {
let program = typed.program(db);
OptimizedProgram::new(db, program.clone())
}
#[salsa::tracked]
pub fn generate_rust<'db>(
db: &'db dyn salsa::Database,
optimized: OptimizedProgram<'db>,
) -> RustCode<'db> {
let program = optimized.program(db);
let analysis = match perform_analysis(program) {
Ok(a) => a,
Err(e) => {
eprintln!("Analysis error during codegen: {}", e);
let signatures = crate::analyzer::SignatureRegistry::new();
let mut generator = crate::codegen::CodeGenerator::new_for_module(
signatures,
crate::CompilationTarget::Wasm,
);
let rust_code = generator.generate_program(program, &[]);
return RustCode::new(db, rust_code);
}
};
let mut generator = crate::codegen::CodeGenerator::new_for_module(
analysis.signatures,
crate::CompilationTarget::Wasm,
);
generator.set_inferred_bounds(analysis.inferred_bounds);
let rust_code = generator.generate_program(program, &analysis.analyzed_functions);
RustCode::new(db, rust_code)
}
#[salsa::tracked]
pub struct TokenStream<'db> {
#[returns(ref)]
pub tokens: Vec<lexer::TokenWithLocation>,
}
#[salsa::tracked]
pub struct ParsedProgram<'db> {
#[returns(ref)]
pub program: parser::Program<'static>,
}
pub struct AnalysisResults<'ast> {
pub analyzed_functions: Vec<analyzer::AnalyzedFunction<'ast>>,
pub inferred_bounds: std::collections::HashMap<String, inference::InferredBounds>,
pub signatures: analyzer::SignatureRegistry,
}
#[salsa::tracked]
pub struct TypedProgram<'db> {
#[returns(ref)]
pub program: parser::Program<'static>,
}
#[salsa::tracked]
pub struct OptimizedProgram<'db> {
#[returns(ref)]
pub program: parser::Program<'static>,
}
#[salsa::tracked]
pub struct RustCode<'db> {
#[returns(ref)]
pub code: String,
}
pub fn compile_file(
db: &dyn salsa::Database,
file_path: PathBuf,
source_text: String,
) -> Result<String, String> {
let source = SourceInput::new(db, file_path, source_text);
let tokens = tokenize(db, source);
let parsed = parse_tokens(db, tokens);
let typed = analyze_types(db, parsed);
let optimized = optimize_program(db, typed);
let rust_code = generate_rust(db, optimized);
Ok(rust_code.code(db).clone())
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub total_queries: usize,
pub cache_hits: usize,
pub cache_misses: usize,
}
impl CacheStats {
pub fn cache_hit_rate(&self) -> f64 {
if self.total_queries == 0 {
0.0
} else {
(self.cache_hits as f64 / self.total_queries as f64) * 100.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_simple_program() {
let db = CompilerDatabase::new();
let result = compile_file(
&db,
PathBuf::from("test.wj"),
"fn main() { println!(\"Hello\") }".to_string(), );
assert!(result.is_ok());
}
#[test]
fn test_incremental_compilation() {
let db = CompilerDatabase::new();
let source1 = SourceInput::new(
&db,
PathBuf::from("test.wj"),
"fn main() { println!(\"Hello\") }".to_string(), );
let tokens1 = tokenize(&db, source1);
let parsed1 = parse_tokens(&db, tokens1);
let source2 = SourceInput::new(
&db,
PathBuf::from("test.wj"),
"fn main() { println!(\"Hello\") }".to_string(), );
let tokens2 = tokenize(&db, source2);
let parsed2 = parse_tokens(&db, tokens2);
assert_eq!(
parsed1.program(&db).items.len(),
parsed2.program(&db).items.len()
);
}
#[test]
fn test_cache_invalidation() {
let db = CompilerDatabase::new();
let source1 = SourceInput::new(
&db,
PathBuf::from("test.wj"),
"fn main() { println!(\"Hello\") }".to_string(), );
let parsed1 = parse_tokens(&db, tokenize(&db, source1));
let source2 = SourceInput::new(
&db,
PathBuf::from("test.wj"),
"fn main() { println!(\"World\") }".to_string(), );
let parsed2 = parse_tokens(&db, tokenize(&db, source2));
assert!(!parsed1.program(&db).items.is_empty());
assert!(!parsed2.program(&db).items.is_empty());
}
}