Files
rlox/src/main.rs
T
Giulio Agostini 07cedc27bf Add .idea to gitignore and prep return value types Add middleend module
for AST return value tracking

The changes introduce support for tracking return values and labels
across AST nodes. The key changes include:

- Adding ReturnValue type to BaseValue - Adding return_type field to
LoxFunction - Adding label field to Block statements - Creating
middleend crawler module for AST traversal Add return value types to AST

This accurately captures both changes: adding `.idea/` to gitignore and
adding return value types to the AST structure. The changes include
adding return value fields to statement nodes and adjusting the
interpreter logic accordingly.
2025-10-10 10:18:02 +02:00

374 lines
12 KiB
Rust

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<String> = 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<String>, 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<Vec<crate::frontend::tokens::Token>> {
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<Vec<AstNode<Stmt>>> {
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<AstNode<Stmt>>, debug: bool) -> LoxResult<String> {
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<String> {
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::<Vec<_>>()
.join("\n")
}
fn format_ast(ast: &[AstNode<Stmt>]) -> 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::<Vec<_>>()
.join("\n\n")
}