mod backend; mod common; mod frontend; mod logging; mod middleend; use crate::{ backend::interpreter::{EvaluateInterpreter, Interpreter}, common::{ ast::{AstNode, Stmt}, lox_result::{LoxError, LoxResult}, }, frontend::{ lexer::Lexer, parser::Parser, source_registry::{SourceId, SourceRegistry}, }, }; use std::env; use std::fs; #[derive(Debug, Clone, Copy, PartialEq)] enum ExecutionStage { Tokens, Ast, Full, } fn main() -> LoxResult<()> { let args: Vec = env::args().collect(); let (stage, file_path, debug) = parse_args(&args); let mut lox = LoxInterpreter::new(debug); let _ = match file_path { Some(path) => lox.run_file(&path, stage), None => lox.run_prompt(stage), }; Ok(()) } fn parse_args(args: &[String]) -> (ExecutionStage, Option, bool) { if args.len() == 1 { // Solo il nome del programma: modalità interattiva completa (ExecutionStage::Full, None, false) } else if args.len() == 2 { // Un argomento: potrebbe essere file o flag let arg = &args[1]; if arg == "--tokens" || arg == "--ast" || arg == "--full" || arg == "--debug" { // Flag senza file: modalità interattiva let stage = match arg.as_str() { "--tokens" => ExecutionStage::Tokens, "--ast" => ExecutionStage::Ast, "--full" => ExecutionStage::Full, "--debug" => ExecutionStage::Full, _ => ExecutionStage::Full, }; let debug = arg == "--debug"; (stage, None, debug) } else { // File senza flag: esecuzione completa del file (ExecutionStage::Full, Some(arg.clone()), false) } } else if args.len() == 3 { // Due argomenti: potrebbero essere flag + file o due flag let mut debug = false; let mut stage = ExecutionStage::Full; let mut file_path = None; for arg in &args[1..3] { if arg == "--debug" { debug = true; } else if arg == "--tokens" || arg == "--ast" || arg == "--full" { stage = match arg.as_str() { "--tokens" => ExecutionStage::Tokens, "--ast" => ExecutionStage::Ast, "--full" => ExecutionStage::Full, _ => ExecutionStage::Full, }; } else if !arg.starts_with("--") { file_path = Some(arg.clone()); } else { eprintln!( "Unknown flag: {}. Use --tokens, --ast, --full, or --debug", arg ); eprintln!( "Usage: {} [--tokens|--ast|--full] [--debug] [script]", args[0] ); std::process::exit(64); } } (stage, file_path, debug) } else if args.len() == 4 { // Tre argomenti: flag + flag + file let mut debug = false; let mut stage = ExecutionStage::Full; let mut file_path = None; for arg in &args[1..4] { if arg == "--debug" { debug = true; } else if arg == "--tokens" || arg == "--ast" || arg == "--full" { stage = match arg.as_str() { "--tokens" => ExecutionStage::Tokens, "--ast" => ExecutionStage::Ast, "--full" => ExecutionStage::Full, _ => ExecutionStage::Full, }; } else if !arg.starts_with("--") { file_path = Some(arg.clone()); } else { eprintln!( "Unknown flag: {}. Use --tokens, --ast, --full, or --debug", arg ); eprintln!( "Usage: {} [--tokens|--ast|--full] [--debug] [script]", args[0] ); std::process::exit(64); } } (stage, file_path, debug) } else { eprintln!( "Usage: {} [--tokens|--ast|--full] [--debug] [script]", args[0] ); std::process::exit(64); } } struct LoxInterpreter { source_registry: SourceRegistry, debug: bool, } impl LoxInterpreter { pub fn new(debug: bool) -> Self { LoxInterpreter { source_registry: SourceRegistry::new(), debug, } } // ✅ Pipeline funzionale per file fn run_file(&mut self, path: &str, stage: ExecutionStage) -> LoxResult<()> { // Fase 1: Lettura del file e creazione del source let source = fs::read_to_string(path).map_err(|e| LoxError::IoError { message: e.to_string(), })?; let source_id = self.source_registry.add_source_string(source)?; // Se debug è attivo, mostra sempre tokens e AST if self.debug { let tokens = self.tokenize(source_id)?; println!("=== TOKENS ===\n{}", format_tokens(&tokens)); println!(); } // Fase 2: Parsing completo - costruzione dell'AST let ast = self.parse_to_ast(source_id)?; if self.debug { println!("=== AST ===\n{}", format_ast(&ast)); println!(); } // Fase 3: Esecuzione (se richiesta) match stage { ExecutionStage::Tokens => { if !self.debug { let tokens = self.tokenize(source_id)?; println!("=== TOKENS ===\n{}", format_tokens(&tokens)); } } ExecutionStage::Ast => { if !self.debug { println!("=== AST ===\n{}", format_ast(&ast)); } } ExecutionStage::Full => { let result = self.execute_ast(ast, self.debug)?; println!("=== EXECUTION RESULT ===\n{}", result); } } Ok(()) } // ✅ Pipeline funzionale per prompt fn run_prompt(&mut self, stage: ExecutionStage) -> LoxResult<()> { use std::io::{self, Write}; println!("Lox REPL (Stage: {:?}) - Type 'exit' to quit", stage); loop { print!("> "); io::stdout().flush().ok(); let mut input = String::new(); match io::stdin().read_line(&mut input) { Ok(_) => { let line = input.trim(); if line == "exit" || line.is_empty() { break; } match self.source_registry.add_source_string(line.to_string()) { Ok(source_id) => { match self.process_source(source_id, stage, self.debug) { Ok(result) => match stage { ExecutionStage::Tokens => println!("Tokens: {}", result), ExecutionStage::Ast => println!("AST: {}", result), ExecutionStage::Full => println!("Result: {}", result), }, Err(_) => {} // Errore già stampato in process_source } } Err(e) => eprintln!("Error: {}", e), } } Err(e) => { eprintln!("Error reading input: {}", e); break; } } } Ok(()) } // Funzione per tokenizzare il codice fn tokenize(&self, source_id: SourceId) -> LoxResult> { let source_content = self.source_registry.get_by_id(source_id).content.clone(); let mut lexer = Lexer::new(source_content, source_id); lexer.scans_tokens().or_else(|err| { err.print_with_context(&self.source_registry); Err(err) }) } // Funzione per parsare fino all'AST fn parse_to_ast(&self, source_id: SourceId) -> LoxResult>> { let tokens = self.tokenize(source_id)?; Parser::new(tokens).parse().or_else(|err| { err.print_with_context(&self.source_registry); Err(err) }) } // Funzione per eseguire l'AST fn execute_ast(&self, ast: Vec>, debug: bool) -> LoxResult { let mut interpreter = Interpreter::new(); let mut result = None; for (index, stmt) in ast.iter().enumerate() { if debug { println!("Executing statement {}: {:?}", index, stmt); } result = Some(interpreter.evaluate(stmt.clone()).or_else(|err| { err.print_with_context(&self.source_registry); Err(err) })?); } match result { Some(res) => Ok(format!("{}", res)), None => Ok("No statements executed".to_string()), } } fn process_source( &self, source_id: SourceId, stage: ExecutionStage, debug: bool, ) -> LoxResult { let source_content = self.source_registry.get_by_id(source_id).content.clone(); let mut lexer = Lexer::new(source_content, source_id); // Stage 1: Tokenization let tokens = lexer.scans_tokens().or_else(|err| { err.print_with_context(&self.source_registry); Err(err) })?; // Se debug è attivo, mostra sempre i tokens if debug && stage != ExecutionStage::Tokens { println!("=== TOKENS ===\n{}", format_tokens(&tokens)); println!(); } if stage == ExecutionStage::Tokens { return Ok(format_tokens(&tokens)); } // Stage 2: Parsing let ast = Parser::new(tokens).parse().or_else(|err| { err.print_with_context(&self.source_registry); Err(err) })?; // Se debug è attivo, mostra sempre l'AST if debug && stage != ExecutionStage::Ast { println!("=== AST ===\n{}", format_ast(&ast)); println!(); } if stage == ExecutionStage::Ast { return Ok(format_ast(&ast)); } // Stage 3: Interpretation let mut interpreter = Interpreter::new(); let mut result = None; for (index, stmt) in ast.iter().enumerate() { if debug { println!("Executing statement {}: {:?}", index, stmt); } result = Some(interpreter.evaluate(stmt.clone()).or_else(|err| { err.print_with_context(&self.source_registry); Err(err) })?); } match result { Some(res) => Ok(format!("{}", res)), None => Ok("No statements executed".to_string()), } } } fn format_tokens(tokens: &[crate::frontend::tokens::Token]) -> String { tokens .iter() .enumerate() .map(|(i, token)| format!("{:3}: {:?}", i, token)) .collect::>() .join("\n") } fn format_ast(ast: &[AstNode]) -> String { use crate::logging::display_ast::{pretty_print_with_config, PrettyConfig}; let config = PrettyConfig { indent: " ".to_string(), max_depth: None, show_positions: false, ..Default::default() }; ast.iter() .enumerate() .map(|(i, stmt)| { format!( "Statement {}:\n{}", i, pretty_print_with_config(&stmt.node, &config) ) }) .collect::>() .join("\n\n") }