Switch interpreter to use new base value types

The commit changes the interpreter and related code to use the new
`BaeValue` and `Number` types, moving away from direct floats. The main
changes include:

- Replace LiteralValue with BaeValue across the codebase - Add Number
enum for type-safe numeric values - Move AST definitions to common
module - Update operators to handle new numeric types - Add 'is' token
type and parser support Switch to common base_value library for value
types

This commit represents a significant refactoring to switch the
interpreter to use a new shared base value type system. The main changes
include:

1. Move value types to a new common/base_value.rs module 2. Add richer
numeric type support with promotion rules 3. Update interpreter to use
new BaseValue enum 4. Move AST types to common module for sharing 5.
Consolidate value operations like Add, Sub etc 6. Add proper test
coverage for numeric operations

The commit improves type safety and adds more robust numeric handling
while keeping the interpreter's core logic clean.
This commit is contained in:
Giulio Agostini
2025-10-07 14:28:24 +02:00
parent fa2e9178fb
commit c1ecd7d1f7
17 changed files with 1074 additions and 398 deletions
+189 -38
View File
@@ -1,16 +1,19 @@
mod backend;
mod common;
mod frontend;
mod logging;
mod result;
use crate::{
backend::interpreter::{EvaluateInterpreter, Interpreter},
common::{
ast::{AstNode, Stmt},
lox_result::{LoxError, LoxResult},
},
frontend::{
lexer::Lexer,
parser::Parser,
source_registry::{SourceId, SourceRegistry},
},
result::{LoxError, LoxResult},
};
use std::env;
use std::fs;
@@ -24,9 +27,9 @@ enum ExecutionStage {
fn main() -> LoxResult<()> {
let args: Vec<String> = env::args().collect();
let mut lox = LoxInterpreter::new();
let (stage, file_path) = parse_args(&args);
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),
@@ -36,71 +39,158 @@ fn main() -> LoxResult<()> {
Ok(())
}
fn parse_args(args: &[String]) -> (ExecutionStage, Option<String>) {
fn parse_args(args: &[String]) -> (ExecutionStage, Option<String>, bool) {
if args.len() == 1 {
// Solo il nome del programma: modalità interattiva completa
(ExecutionStage::Full, None)
(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" {
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,
};
(stage, None)
let debug = arg == "--debug";
(stage, None, debug)
} else {
// File senza flag: esecuzione completa del file
(ExecutionStage::Full, Some(arg.clone()))
(ExecutionStage::Full, Some(arg.clone()), false)
}
} else if args.len() == 3 {
// Due argomenti: flag + file
let flag = &args[1];
let file = &args[2];
let stage = match flag.as_str() {
"--tokens" => ExecutionStage::Tokens,
"--ast" => ExecutionStage::Ast,
"--full" => ExecutionStage::Full,
_ => {
eprintln!("Unknown flag: {}. Use --tokens, --ast, or --full", flag);
eprintln!("Usage: {} [--tokens|--ast|--full] [script]", args[0]);
// 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, Some(file.clone()))
}
(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] [script]", args[0]);
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() -> Self {
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<()> {
fs::read_to_string(path)
.map_err(|e| LoxError::IoError {
message: e.to_string(),
})
.and_then(|source| self.source_registry.add_source_string(source))
.and_then(|source_id| self.process_source(source_id, stage))
.map(|result| match stage {
ExecutionStage::Tokens => println!("=== TOKENS ===\n{}", result),
ExecutionStage::Ast => println!("=== AST ===\n{}", result),
ExecutionStage::Full => println!("=== EXECUTION RESULT ===\n{}", result),
})
// 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
@@ -123,7 +213,7 @@ impl LoxInterpreter {
match self.source_registry.add_source_string(line.to_string()) {
Ok(source_id) => {
match self.process_source(source_id, stage) {
match self.process_source(source_id, stage, self.debug) {
Ok(result) => match stage {
ExecutionStage::Tokens => println!("Tokens: {}", result),
ExecutionStage::Ast => println!("AST: {}", result),
@@ -145,7 +235,54 @@ impl LoxInterpreter {
Ok(())
}
fn process_source(&self, source_id: SourceId, stage: ExecutionStage) -> LoxResult<String> {
// 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);
@@ -155,6 +292,12 @@ impl LoxInterpreter {
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));
}
@@ -165,6 +308,12 @@ impl LoxInterpreter {
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));
}
@@ -174,7 +323,9 @@ impl LoxInterpreter {
let mut result = None;
for (index, stmt) in ast.iter().enumerate() {
println!("Executing statement {}: {:?}", index, stmt);
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)
@@ -197,7 +348,7 @@ fn format_tokens(tokens: &[crate::frontend::tokens::Token]) -> String {
.join("\n")
}
fn format_ast(ast: &[crate::frontend::ast::AstNode<crate::frontend::ast::Stmt>]) -> String {
fn format_ast(ast: &[AstNode<Stmt>]) -> String {
use crate::logging::display_ast::{pretty_print_with_config, PrettyConfig};
let config = PrettyConfig {