From c1ecd7d1f725320576e6121db3d58313eeb03878 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Tue, 7 Oct 2025 14:28:24 +0200 Subject: [PATCH] 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. --- examples/example.lox | 7 +- src/backend/environment.rs | 15 +- src/backend/interpreter.rs | 281 +++----------- src/{frontend => common}/ast.rs | 13 +- src/common/base_value.rs | 488 ++++++++++++++++++++++++ src/{result.rs => common/lox_result.rs} | 0 src/common/mod.rs | 3 + src/frontend/lexer.rs | 91 +++-- src/frontend/mod.rs | 1 - src/frontend/parser.rs | 78 ++-- src/frontend/source_registry.rs | 4 +- src/frontend/tokens.rs | 69 +--- src/lib.rs | 3 + src/logging/display_ast.rs | 3 +- src/logging/display_token.rs | 2 + src/main.rs | 227 +++++++++-- tests/number_operations.rs | 187 +++++++++ 17 files changed, 1074 insertions(+), 398 deletions(-) rename src/{frontend => common}/ast.rs (97%) create mode 100644 src/common/base_value.rs rename src/{result.rs => common/lox_result.rs} (100%) create mode 100644 src/common/mod.rs create mode 100644 src/lib.rs create mode 100644 tests/number_operations.rs diff --git a/examples/example.lox b/examples/example.lox index 828bb0c..b9a3c8b 100644 --- a/examples/example.lox +++ b/examples/example.lox @@ -12,7 +12,12 @@ func_name :: fn(param: Int, param2: String) do cavallo = cavallo + 1; print cavallo; end - + if True then do + print "this shoul be stop"; + return False; + end + print "this shoud be un other stop"; + return 42; for i := 11; i <= 21; i = i + 1; do print i; end diff --git a/src/backend/environment.rs b/src/backend/environment.rs index 91681f2..97166ec 100644 --- a/src/backend/environment.rs +++ b/src/backend/environment.rs @@ -1,13 +1,16 @@ use std::collections::HashMap; use crate::{ - frontend::{source_registry::SourceSlice, tokens::LiteralValue}, - result::{runtime_error, LoxResult}, + common::{ + base_value::BaseValue, + lox_result::{runtime_error, LoxResult}, + }, + frontend::source_registry::SourceSlice, }; #[derive(Debug, Clone, PartialEq)] pub struct EnvironmentStack { - stack: Vec>, + stack: Vec>, } impl EnvironmentStack { @@ -25,7 +28,7 @@ impl EnvironmentStack { self.stack.pop(); } - pub fn get(&self, name: &str) -> LoxResult { + pub fn get(&self, name: &str) -> LoxResult { let size = self.stack.len(); for i in (0..size).rev() { @@ -39,12 +42,12 @@ impl EnvironmentStack { ) } - pub fn declare(&mut self, name: String, value: LiteralValue) -> LoxResult { + pub fn declare(&mut self, name: String, value: BaseValue) -> LoxResult { self.stack.last_mut().unwrap().insert(name, value.clone()); Ok(value) } - pub fn set(&mut self, name: String, value: LiteralValue) -> LoxResult { + pub fn set(&mut self, name: String, value: BaseValue) -> LoxResult { let size = self.stack.len(); for i in (0..size).rev() { diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index a361a6a..235467f 100644 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -1,201 +1,37 @@ use crate::{ backend::environment::EnvironmentStack, - frontend::{ + common::{ ast::{AstNode, AstNodeKind, Expr, Stmt}, - source_registry::SourceSlice, - tokens::{LiteralValue, NativeFunction, TokenType}, + base_value::{BaseValue, NativeFunction, Number, Truthy}, + lox_result::{runtime_error, LoxError, LoxResult}, }, - logging::display_ast::PrettyPrintExt, - result::{runtime_error, LoxError, LoxResult}, + frontend::{source_registry::SourceSlice, tokens::TokenType}, }; -use std::{ - fmt::{Debug, Display}, - ops::{Add, Div, Mul, Neg, Not, Rem, Sub}, -}; - -fn error(message: String) -> LoxError { - LoxError::RuntimeError { - source_slice: SourceSlice::default(), // todo change this with the actual source slice - message: message, - } -} - -fn error_at(source_slice: SourceSlice, message: String) -> LoxError { - LoxError::RuntimeError { - source_slice, - message, - } -} - -impl Not for LiteralValue { - type Output = LiteralValue; - - fn not(self) -> Self::Output { - match self { - LiteralValue::Boolean(b) => LiteralValue::Boolean(!b), - LiteralValue::Number(n) => LiteralValue::Boolean(n == 0.0), - _ => LiteralValue::Boolean(false), - } - } -} - -pub trait Truthy { - fn is_truthy(&self) -> bool; -} - -impl Truthy for LiteralValue { - fn is_truthy(&self) -> bool { - match self { - LiteralValue::Boolean(b) => *b, - LiteralValue::Number(n) => *n != 0.0, - LiteralValue::String(s) => !s.is_empty(), - _ => false, - } - } -} - -impl Neg for LiteralValue { - type Output = LoxResult; - - fn neg(self) -> Self::Output { - Ok(LiteralValue::Boolean(!self.is_truthy())) - } -} - -impl Add for LiteralValue { - type Output = LoxResult; - - fn add(self, other: LiteralValue) -> Self::Output { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => Ok(LiteralValue::Number(a + b)), - (LiteralValue::String(a), LiteralValue::String(b)) => { - Ok(LiteralValue::String(format!("{}{}", a, b))) - } - _ => Err(error("Cannot add non-numeric values".to_string())), - } - } -} - -impl LiteralValue { - pub fn add_with_source( - self, - other: LiteralValue, - source_slice: SourceSlice, - ) -> LoxResult { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => Ok(LiteralValue::Number(a + b)), - (LiteralValue::String(a), LiteralValue::String(b)) => { - Ok(LiteralValue::String(format!("{}{}", a, b))) - } - _ => Err(error_at( - source_slice, - "Cannot add non-numeric values".to_string(), - )), - } - } -} - -impl Sub for LiteralValue { - type Output = LoxResult; - - fn sub(self, other: LiteralValue) -> Self::Output { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => Ok(LiteralValue::Number(a - b)), - _ => Err(error("Cannot subtract non-numeric values".to_string())), - } - } -} - -impl Div for LiteralValue { - type Output = LoxResult; - - fn div(self, other: LiteralValue) -> Self::Output { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => { - if b == 0.0 { - Err(error("Division by zero".to_string())) - } else { - Ok(LiteralValue::Number(a / b)) - } - } - _ => Err(error("Cannot divide non-numeric values".to_string())), - } - } -} - -impl Mul for LiteralValue { - type Output = LoxResult; - - fn mul(self, other: LiteralValue) -> Self::Output { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => Ok(LiteralValue::Number(a * b)), - _ => Err(error("Cannot multiply non-numeric values".to_string())), - } - } -} - -impl Rem for LiteralValue { - type Output = LoxResult; - - fn rem(self, other: LiteralValue) -> Self::Output { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => { - if b == 0.0 { - Err(error("Division by zero".to_string())) - } else { - Ok(LiteralValue::Number(a % b)) - } - } - _ => Err(error("Cannot divide non-numeric values".to_string())), - } - } -} - -impl PartialOrd for LiteralValue { - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (LiteralValue::Number(a), LiteralValue::Number(b)) => a.partial_cmp(b), - _ => None, - } - } -} - -impl From for bool { - fn from(value: LiteralValue) -> Self { - match value { - LiteralValue::Boolean(b) => b, - LiteralValue::Number(n) => n != 0.0, - _ => false, - } - } -} +use std::fmt::{Debug, Display}; pub struct Interpreter { enviorment: EnvironmentStack, } pub trait EvaluateInterpreter { - fn evaluate(&mut self, stmt: T) -> LoxResult; + fn evaluate(&mut self, stmt: T) -> LoxResult; } impl<'a, R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter> for Interpreter where Interpreter: EvaluateInterpreter, { - fn evaluate(&mut self, stmt: AstNode) -> LoxResult { + fn evaluate(&mut self, stmt: AstNode) -> LoxResult { match self.evaluate(stmt.node.clone()) { Ok(value) => Ok(value), - Err(err) => Err(LoxError::RuntimeError { - source_slice: stmt.source_slice, - message: err.get_message(), - }), + Err(err) => runtime_error(stmt.source_slice, err.get_message()), } } } // Direct Expr evaluation to avoid infinite recursion impl EvaluateInterpreter for Interpreter { - fn evaluate(&mut self, expr: Expr) -> LoxResult { + fn evaluate(&mut self, expr: Expr) -> LoxResult { match expr { Expr::Literal { value } => Ok(value), Expr::Identifier { name } => self.enviorment.get(&name), @@ -219,7 +55,7 @@ impl EvaluateInterpreter for Interpreter { } impl EvaluateInterpreter> for Interpreter { - fn evaluate(&mut self, node: AstNode) -> LoxResult { + fn evaluate(&mut self, node: AstNode) -> LoxResult { let stmt = node.node; let result = self.interpret_stmt_inner(stmt); match result { @@ -240,13 +76,13 @@ impl Interpreter { let mut env = EnvironmentStack::new(); let _ = env.declare( "clock".to_string(), - LiteralValue::NativeFunction(NativeFunction::new(0, |_args| { - LiteralValue::Number( + BaseValue::NativeFunction(NativeFunction::new(Vec::default(), |_args| { + BaseValue::Number(Number::U128( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() - .as_millis() as f64, - ) + .as_millis(), + )) })), ); Self { enviorment: env } @@ -255,7 +91,7 @@ impl Interpreter { &mut self, callee: Box>, arguments: Vec>, - ) -> LoxResult { + ) -> LoxResult { let source_slice = callee.source_slice.clone(); // Estrai il nome della variabile se il callee è un identificatore @@ -279,11 +115,11 @@ impl Interpreter { let evaluated_arguments = arguments .iter() .map(|arg| self.evaluate(arg.clone())) - .collect::, LoxError>>()?; + .collect::, LoxError>>()?; match function { - LiteralValue::NativeFunction(func) => Ok((func.function)(&evaluated_arguments)), - LiteralValue::Function(func) => { + BaseValue::NativeFunction(func) => Ok((func.function)(&evaluated_arguments)), + BaseValue::Function(func) => { func.parameters .iter() .enumerate() @@ -291,7 +127,7 @@ impl Interpreter { let value = evaluated_arguments.get(index).unwrap(); self.enviorment.declare(par.0.clone(), value.clone()) }) - .collect::>>()?; + .collect::>>()?; self.evaluate(func.body) } _ => runtime_error( @@ -303,49 +139,54 @@ impl Interpreter { fn evaluate_binary( &mut self, - left: LiteralValue, + left: BaseValue, operator: TokenType, - right: LiteralValue, - ) -> LoxResult { + right: BaseValue, + ) -> LoxResult { match operator { TokenType::Plus => left + right, TokenType::Minus => left - right, TokenType::Star => left * right, TokenType::Slash => left / right, - TokenType::Greater => Ok(LiteralValue::Boolean(left > right)), - TokenType::GreaterEqual => Ok(LiteralValue::Boolean(left >= right)), - TokenType::Less => Ok(LiteralValue::Boolean(left < right)), - TokenType::LessEqual => Ok(LiteralValue::Boolean(left <= right)), - TokenType::EqualEqual => Ok(LiteralValue::Boolean(left == right)), - TokenType::BangEqual => Ok(LiteralValue::Boolean(left != right)), - TokenType::And => Ok(LiteralValue::Boolean(left.is_truthy() && right.is_truthy())), - TokenType::Or => Ok(LiteralValue::Boolean(left.is_truthy() || right.is_truthy())), - _ => Err(error(format!( - "Unsupported binary operator: {:?}", - operator - ))), + TokenType::Greater => Ok(BaseValue::Boolean(left > right)), + TokenType::GreaterEqual => Ok(BaseValue::Boolean(left >= right)), + TokenType::Less => Ok(BaseValue::Boolean(left < right)), + TokenType::LessEqual => Ok(BaseValue::Boolean(left <= right)), + TokenType::EqualEqual => Ok(BaseValue::Boolean(left == right)), + TokenType::BangEqual => Ok(BaseValue::Boolean(left != right)), + TokenType::And => Ok(BaseValue::Boolean(left.is_truthy() && right.is_truthy())), + TokenType::Or => Ok(BaseValue::Boolean(left.is_truthy() || right.is_truthy())), + _ => Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), + message: format!("Unsupported binary operator: {:?}", operator), + }), } } - fn evaluate_unary( - &mut self, - operator: TokenType, - operand: LiteralValue, - ) -> LoxResult { + fn evaluate_unary(&mut self, operator: TokenType, operand: BaseValue) -> LoxResult { match operator { - TokenType::Minus => -operand, + TokenType::Minus => match operand { + BaseValue::Number(n) => Ok(BaseValue::Number(n.neg())), + _ => Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), + message: "Cannot negate non-numeric value".to_string(), + }), + }, TokenType::Bang => Ok(!operand), - _ => Err(error(format!("Unsupported unary operator: {:?}", operator))), + _ => Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), + message: format!("Unsupported unary operator: {:?}", operator), + }), } } - fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult { + fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult { match stmt { Stmt::Expression { expression } => self.evaluate(*expression), Stmt::Print { expression } => { let value = self.evaluate(*expression)?; - println!("print interpreter: \t{}", value); - Ok(LiteralValue::Nil) + println!("{}", value); + Ok(BaseValue::Nil) } Stmt::Block { statements } => self.evaluate_block(*statements), Stmt::Stmt { expression } => self.evaluate(*expression.clone()), @@ -362,7 +203,7 @@ impl Interpreter { } _ => self.evaluate(*expr_node)?, }, - None => LiteralValue::Nil, + None => BaseValue::Nil, }; self.enviorment.declare(name.clone(), value) } @@ -381,7 +222,7 @@ impl Interpreter { while self.evaluate(*condition.clone())?.is_truthy() { self.evaluate(*body.clone())?; } - Ok(LiteralValue::Nil) + Ok(BaseValue::Nil) } Stmt::For { variable, @@ -392,10 +233,10 @@ impl Interpreter { self.enviorment.push_new_scope(); let source_slice = variable.source_slice.clone(); let val = self.evaluate(*variable)?; - if !matches!(val, LiteralValue::Number(..)) { + if !matches!(val, BaseValue::Number(..)) { return runtime_error(source_slice, "Expected number literal"); } - let mut ret = LiteralValue::Nil; + let mut ret = BaseValue::Nil; while self.evaluate(*condition.clone())?.is_truthy() { ret = self.evaluate(*body.clone())?; self.evaluate(*increment.clone())?; @@ -411,18 +252,18 @@ impl Interpreter { then_branch: Box>, elif_branch: Vec<(Box>, Box>)>, else_branch: Option>>, - ) -> LoxResult { + ) -> LoxResult { let condition = self.evaluate(*condition)?; match condition { - LiteralValue::Boolean(true) => self.evaluate(*then_branch), - LiteralValue::Boolean(false) => { + BaseValue::Boolean(true) => self.evaluate(*then_branch), + BaseValue::Boolean(false) => { for (elif_condition, elif_then_branch) in elif_branch { let condition = self.evaluate(*elif_condition)?; match condition { - LiteralValue::Boolean(true) => { + BaseValue::Boolean(true) => { return self.evaluate(*elif_then_branch); } - LiteralValue::Boolean(false) => continue, + BaseValue::Boolean(false) => continue, _ => { return Err(LoxError::TypeMismatch { source_slice: SourceSlice::default(), // todo change this to the actual source slice @@ -435,7 +276,7 @@ impl Interpreter { if let Some(else_block) = else_branch { self.evaluate(*else_block) } else { - Ok(LiteralValue::Nil) + Ok(BaseValue::Nil) } } _ => Err(LoxError::TypeMismatch { @@ -446,7 +287,7 @@ impl Interpreter { } } - fn evaluate_block(&mut self, statements: Vec>) -> LoxResult { + fn evaluate_block(&mut self, statements: Vec>) -> LoxResult { self.enviorment.push_new_scope(); let (elements, final_expr) = match statements.split_last() { Some((stmt, body)) => match &stmt.node { @@ -474,7 +315,7 @@ impl Interpreter { } None => { self.enviorment.pop_scope(); - Ok(LiteralValue::Nil) + Ok(BaseValue::Nil) } } } diff --git a/src/frontend/ast.rs b/src/common/ast.rs similarity index 97% rename from src/frontend/ast.rs rename to src/common/ast.rs index 9131315..0127b79 100644 --- a/src/frontend/ast.rs +++ b/src/common/ast.rs @@ -1,6 +1,6 @@ -use crate::frontend::{ - source_registry::SourceSlice, - tokens::{LiteralValue, TokenType}, +use crate::{ + common::base_value::BaseValue, + frontend::{source_registry::SourceSlice, tokens::TokenType}, }; use std::fmt::{Debug, Display}; /* @@ -15,7 +15,7 @@ use std::fmt::{Debug, Display}; * * expression_statement -> expression ";" * print_statement -> "print" expression ";" - * var_statement -> ("var")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration + * var_statement -> ("var"|"dyn"|"mut")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration * function_declaration -> "::" "(" parameters? ")" ("->" IDENTIFIER)? block_statement * parameters -> ( IDENTIFIER (":" IDENTIFIER)? ("," IDENTIFIER (":" IDENTIFIER)? )* ) * assignment_statement -> IDENTIFIER "=" expression ";" @@ -26,7 +26,8 @@ use std::fmt::{Debug, Display}; * expression -> assignment * assignment -> IDENTIFIER "=" assignment | logical_or * logical_or -> logical_and (("or" logical_and)* - * logical_and -> equality (("and" equality)* + * logical_and -> logical_is (("and" logical_is)* + * logical_is -> equality (("is" equality)* * equality -> comparison (("==" | "!=") comparison)* * comparison -> term ((">" | ">=" | "<" | "<=") term)* * term -> factor (("+" | "-") factor)* @@ -39,7 +40,7 @@ use std::fmt::{Debug, Display}; #[derive(Clone, PartialEq)] pub enum Expr { Literal { - value: LiteralValue, + value: BaseValue, }, Binary { left: Box>, diff --git a/src/common/base_value.rs b/src/common/base_value.rs new file mode 100644 index 0000000..9ae8024 --- /dev/null +++ b/src/common/base_value.rs @@ -0,0 +1,488 @@ +use core::fmt; +use std::fmt::Display; +use std::ops::{Add, Div, Mul, Not, Rem, Sub}; + +use crate::common::lox_result::{runtime_error, LoxResult}; +use crate::{ + backend::environment::EnvironmentStack, + common::ast::{AstNode, Expr, Stmt}, + frontend::source_registry::SourceSlice, +}; +#[derive(Debug, Clone, PartialEq)] +pub enum Number { + I16(i16), + I32(i32), + I64(i64), + F32(f32), + F64(f64), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), +} + +impl Display for Number { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Number::I16(n) => write!(f, "{}", n), + Number::I32(n) => write!(f, "{}", n), + Number::I64(n) => write!(f, "{}", n), + Number::F32(n) => write!(f, "{}", n), + Number::F64(n) => write!(f, "{}", n), + Number::U8(n) => write!(f, "{}", n), + Number::U16(n) => write!(f, "{}", n), + Number::U32(n) => write!(f, "{}", n), + Number::U64(n) => write!(f, "{}", n), + Number::U128(n) => write!(f, "{}", n), + } + } +} + +impl Number { + /// Promotes two numbers to their common type following the hierarchy: + /// Unsigned -> Integer -> Float + fn promote_to_common_type(a: Number, b: Number) -> (Number, Number) { + use Number::*; + match (&a, &b) { + // If either is a float, both become F64 + (F32(_), _) | (F64(_), _) => { + let a_as_f64 = match a { + I16(n) => n as f64, + I32(n) => n as f64, + I64(n) => n as f64, + F32(n) => n as f64, + F64(n) => n, + U8(n) => n as f64, + U16(n) => n as f64, + U32(n) => n as f64, + U64(n) => n as f64, + U128(n) => n as f64, + }; + let b_as_f64 = match b { + I16(n) => n as f64, + I32(n) => n as f64, + I64(n) => n as f64, + F32(n) => n as f64, + F64(n) => n, + U8(n) => n as f64, + U16(n) => n as f64, + U32(n) => n as f64, + U64(n) => n as f64, + U128(n) => n as f64, + }; + (F64(a_as_f64), F64(b_as_f64)) + } + (_, F32(_)) | (_, F64(_)) => { + // Swap and recurse to handle the float case + let (b_promoted, a_promoted) = Self::promote_to_common_type(b.clone(), a.clone()); + (a_promoted, b_promoted) + } + // If either is signed integer, both become I64 + (I16(_), _) | (I32(_), _) | (I64(_), _) => { + let a_as_i64 = match a { + I16(n) => n as i64, + I32(n) => n as i64, + I64(n) => n, + U8(n) => n as i64, + U16(n) => n as i64, + U32(n) => n as i64, + U64(n) => n as i64, + U128(n) => n as i64, // May overflow but keeping it simple + _ => unreachable!(), + }; + let b_as_i64 = match b { + I16(n) => n as i64, + I32(n) => n as i64, + I64(n) => n, + U8(n) => n as i64, + U16(n) => n as i64, + U32(n) => n as i64, + U64(n) => n as i64, + U128(n) => n as i64, // May overflow but keeping it simple + _ => unreachable!(), + }; + (I64(a_as_i64), I64(b_as_i64)) + } + (_, I16(_)) | (_, I32(_)) | (_, I64(_)) => { + // Swap and recurse to handle the signed case + let (b_promoted, a_promoted) = Self::promote_to_common_type(b.clone(), a.clone()); + (a_promoted, b_promoted) + } + // Both are unsigned, promote to U128 + _ => { + let a_as_u128 = match a { + U8(n) => n as u128, + U16(n) => n as u128, + U32(n) => n as u128, + U64(n) => n as u128, + U128(n) => n, + _ => unreachable!(), + }; + let b_as_u128 = match b { + U8(n) => n as u128, + U16(n) => n as u128, + U32(n) => n as u128, + U64(n) => n as u128, + U128(n) => n, + _ => unreachable!(), + }; + (U128(a_as_u128), U128(b_as_u128)) + } + } + } + + pub fn add(self, other: Number) -> Number { + let (a, b) = Self::promote_to_common_type(self, other); + match (a, b) { + (Number::F64(x), Number::F64(y)) => Number::F64(x + y), + (Number::I64(x), Number::I64(y)) => Number::I64(x + y), + (Number::U128(x), Number::U128(y)) => Number::U128(x + y), + _ => unreachable!("promote_to_common_type should handle all cases"), + } + } + + pub fn sub(self, other: Number) -> Number { + let (a, b) = Self::promote_to_common_type(self, other); + match (a, b) { + (Number::F64(x), Number::F64(y)) => Number::F64(x - y), + (Number::I64(x), Number::I64(y)) => Number::I64(x - y), + (Number::U128(x), Number::U128(y)) => Number::U128(x - y), + _ => unreachable!("promote_to_common_type should handle all cases"), + } + } + + pub fn mul(self, other: Number) -> Number { + let (a, b) = Self::promote_to_common_type(self, other); + match (a, b) { + (Number::F64(x), Number::F64(y)) => Number::F64(x * y), + (Number::I64(x), Number::I64(y)) => Number::I64(x * y), + (Number::U128(x), Number::U128(y)) => Number::U128(x * y), + _ => unreachable!("promote_to_common_type should handle all cases"), + } + } + + pub fn div(self, other: Number) -> Option { + let (a, b) = Self::promote_to_common_type(self, other); + match (a, b) { + (Number::F64(x), Number::F64(y)) => { + if y == 0.0 { + None + } else { + Some(Number::F64(x / y)) + } + } + (Number::I64(x), Number::I64(y)) => { + if y == 0 { + None + } else { + Some(Number::I64(x / y)) + } + } + (Number::U128(x), Number::U128(y)) => { + if y == 0 { + None + } else { + Some(Number::U128(x / y)) + } + } + _ => unreachable!("promote_to_common_type should handle all cases"), + } + } + + pub fn rem(self, other: Number) -> Option { + let (a, b) = Self::promote_to_common_type(self, other); + match (a, b) { + (Number::F64(x), Number::F64(y)) => { + if y == 0.0 { + None + } else { + Some(Number::F64(x % y)) + } + } + (Number::I64(x), Number::I64(y)) => { + if y == 0 { + None + } else { + Some(Number::I64(x % y)) + } + } + (Number::U128(x), Number::U128(y)) => { + if y == 0 { + None + } else { + Some(Number::U128(x % y)) + } + } + _ => unreachable!("promote_to_common_type should handle all cases"), + } + } + + pub fn neg(self) -> Number { + match self { + Number::I16(n) => Number::I16(-n), + Number::I32(n) => Number::I32(-n), + Number::I64(n) => Number::I64(-n), + Number::F32(n) => Number::F32(-n), + Number::F64(n) => Number::F64(-n), + // Unsigned numbers become signed when negated + Number::U8(n) => Number::I32(-(n as i32)), + Number::U16(n) => Number::I32(-(n as i32)), + Number::U32(n) => Number::I64(-(n as i64)), + Number::U64(n) => Number::I64(-(n as i64)), + Number::U128(n) => Number::I64(-(n as i64)), // May overflow + } + } + + pub fn is_zero(&self) -> bool { + match self { + Number::I16(n) => *n == 0, + Number::I32(n) => *n == 0, + Number::I64(n) => *n == 0, + Number::F32(n) => *n == 0.0, + Number::F64(n) => *n == 0.0, + Number::U8(n) => *n == 0, + Number::U16(n) => *n == 0, + Number::U32(n) => *n == 0, + Number::U64(n) => *n == 0, + Number::U128(n) => *n == 0, + } + } +} + +impl PartialOrd for Number { + fn partial_cmp(&self, other: &Self) -> Option { + let (a, b) = Self::promote_to_common_type(self.clone(), other.clone()); + match (a, b) { + (Number::F64(x), Number::F64(y)) => x.partial_cmp(&y), + (Number::I64(x), Number::I64(y)) => x.partial_cmp(&y), + (Number::U128(x), Number::U128(y)) => x.partial_cmp(&y), + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BaseValue { + Identifier(String), + String(String), + Number(Number), + Boolean(bool), + Nil, + Function(LoxFunction), + NativeFunction(NativeFunction), +} + +impl BaseValue { + pub fn is_callable(&self) -> bool { + match self { + BaseValue::Function(..) | BaseValue::NativeFunction(..) => true, + _ => false, + } + } +} + +impl Display for BaseValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BaseValue::Identifier(id) => write!(f, "{}", id), + BaseValue::String(s) => write!(f, "{}", s), + BaseValue::Number(n) => write!(f, "{}", n), + BaseValue::Boolean(b) => write!(f, "{}", b), + BaseValue::Nil => write!(f, "nil"), + BaseValue::Function(..) => write!(f, ""), + BaseValue::NativeFunction(..) => write!(f, ""), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LoxFunction { + pub parameters: Vec<(String, String)>, + pub body: AstNode, + pub closure: Option, + pub guard: Option>, +} + +impl LoxFunction { + pub fn new( + parameters: Vec<(String, String)>, + body: AstNode, + closure: Option, + guard: Option>, + ) -> Self { + LoxFunction { + parameters, + body, + closure, + guard, + } + } + + pub fn anonymous_function(parameters: Vec<(String, String)>, body: AstNode) -> Self { + LoxFunction { + parameters, + body, + closure: None, + guard: None, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct NativeFunction { + pub parameters: Vec<(String, String)>, + pub function: fn(&[BaseValue]) -> BaseValue, +} + +impl NativeFunction { + pub fn new(parameters: Vec<(String, String)>, function: fn(&[BaseValue]) -> BaseValue) -> Self { + NativeFunction { + parameters, + function, + } + } +} + +// Trait implementations for BaseValue + +pub trait Truthy { + fn is_truthy(&self) -> bool; +} + +impl Truthy for BaseValue { + fn is_truthy(&self) -> bool { + match self { + BaseValue::Boolean(b) => *b, + BaseValue::Number(n) => !n.is_zero(), + BaseValue::String(s) => !s.is_empty(), + _ => false, + } + } +} + +impl Not for BaseValue { + type Output = BaseValue; + + fn not(self) -> Self::Output { + match self { + BaseValue::Boolean(b) => BaseValue::Boolean(!b), + BaseValue::Number(n) => BaseValue::Boolean(n.is_zero()), + _ => BaseValue::Boolean(false), + } + } +} + +impl Add for BaseValue { + type Output = LoxResult; + + fn add(self, other: BaseValue) -> Self::Output { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.add(b))), + (BaseValue::String(a), BaseValue::String(b)) => { + Ok(BaseValue::String(format!("{}{}", a, b))) + } + _ => runtime_error( + SourceSlice::default(), + "Cannot add non-numeric values".to_string(), + ), + } + } +} + +impl BaseValue { + pub fn add_with_source( + self, + other: BaseValue, + source_slice: SourceSlice, + ) -> LoxResult { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.add(b))), + (BaseValue::String(a), BaseValue::String(b)) => { + Ok(BaseValue::String(format!("{}{}", a, b))) + } + _ => runtime_error(source_slice, "Cannot add non-numeric values".to_string()), + } + } +} + +impl Sub for BaseValue { + type Output = LoxResult; + + fn sub(self, other: BaseValue) -> Self::Output { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.sub(b))), + _ => runtime_error( + SourceSlice::default(), + "Cannot subtract non-numeric values".to_string(), + ), + } + } +} + +impl Div for BaseValue { + type Output = LoxResult; + + fn div(self, other: BaseValue) -> Self::Output { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => match a.div(b) { + Some(result) => Ok(BaseValue::Number(result)), + None => runtime_error(SourceSlice::default(), "Division by zero".to_string()), + }, + _ => runtime_error( + SourceSlice::default(), + "Cannot divide non-numeric values".to_string(), + ), + } + } +} + +impl Mul for BaseValue { + type Output = LoxResult; + + fn mul(self, other: BaseValue) -> Self::Output { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.mul(b))), + _ => runtime_error( + SourceSlice::default(), + "Cannot multiply non-numeric values".to_string(), + ), + } + } +} + +impl Rem for BaseValue { + type Output = LoxResult; + + fn rem(self, other: BaseValue) -> Self::Output { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => match a.rem(b) { + Some(result) => Ok(BaseValue::Number(result)), + None => runtime_error(SourceSlice::default(), "Division by zero".to_string()), + }, + _ => runtime_error( + SourceSlice::default(), + "Cannot divide non-numeric values".to_string(), + ), + } + } +} + +impl PartialOrd for BaseValue { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (BaseValue::Number(a), BaseValue::Number(b)) => a.partial_cmp(b), + _ => None, + } + } +} + +impl From for bool { + fn from(value: BaseValue) -> Self { + match value { + BaseValue::Boolean(b) => b, + BaseValue::Number(n) => !n.is_zero(), + _ => false, + } + } +} diff --git a/src/result.rs b/src/common/lox_result.rs similarity index 100% rename from src/result.rs rename to src/common/lox_result.rs diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..736f74d --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,3 @@ +pub mod ast; +pub mod base_value; +pub mod lox_result; diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index 588c15a..b34d5ec 100644 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -1,7 +1,7 @@ +use crate::common::base_value::{BaseValue, Number}; +use crate::common::lox_result::{lexical_error, LoxError, LoxResult}; use crate::frontend::source_registry::{SourceId, SourcePosition, SourceSlice}; -use crate::frontend::tokens::{LiteralValue, Token, TokenType}; -use crate::logging::display_token::TokenPrettyPrintExt; -use crate::result::{LoxError, LoxResult}; +use crate::frontend::tokens::{Token, TokenType}; pub struct Lexer { input: String, @@ -77,8 +77,6 @@ impl Lexer { } } tokens.push(self.make_token(TokenType::Eof)); - println!("Tokens:"); - tokens.iter().for_each(|val| println!("{}", val.pretty())); Ok(tokens) } @@ -119,7 +117,7 @@ impl Lexer { ), ) } - fn make_token_with_literal(&self, token_type: TokenType, literal: LiteralValue) -> Token { + fn make_token_with_literal(&self, token_type: TokenType, literal: BaseValue) -> Token { let text = self.input[self.start_char..self.current_char].to_string(); Token::new_complete( token_type, @@ -186,14 +184,14 @@ impl Lexer { self.advance(); } if self.is_at_end() { - return Err(LoxError::LexicalError { - source_slice: SourceSlice::from_positions( + return lexical_error( + SourceSlice::from_positions( self.source_id.clone(), self.start_pos.clone(), self.end_pos.clone(), ), - message: "Unterminated comment".to_string(), - }); + "Unterminated comment".to_string(), + ); } else { self.advance(); // consuma '*' self.advance(); // consuma '/' @@ -237,30 +235,67 @@ impl Lexer { self.advance(); Ok(Some(self.make_token_with_literal( TokenType::String, - LiteralValue::String(self.input[self.start_char..self.current_char].to_string()), + BaseValue::String(self.input[self.start_char..self.current_char].to_string()), ))) } fn number(&mut self) -> LoxResult> { + // Leggi la parte intera while self.peek().is_digit(10) { self.advance(); } - if self.peek() == '.' && self.peek_next().is_digit(10) { - self.advance(); + + // Controlla se c'è una parte decimale + let has_decimal = if self.peek() == '.' && self.peek_next().is_digit(10) { + self.advance(); // consuma il '.' while self.peek().is_digit(10) { self.advance(); } - } - Ok(Some( - self.make_token_with_literal( - TokenType::Number, - LiteralValue::Number( - self.input[self.start_char..self.current_char] - .parse() - .unwrap(), - ), - ), - )) + true + } else { + false + }; + + // Controlla se c'è un suffisso (f o u) + let suffix = if self.peek() == 'f' || self.peek() == 'u' { + let s = self.peek(); + self.advance(); + Some(s) + } else { + None + }; + + let num_str = &self.input[self.start_char..self.current_char]; + let num_str_without_suffix = if suffix.is_some() { + &num_str[..num_str.len() - 1] + } else { + num_str + }; + + // Determina il tipo di numero basandosi sul contenuto e suffisso + let number_value = match suffix { + Some('f') => { + // Float esplicito con suffisso 'f' + Number::F64(num_str_without_suffix.parse().unwrap()) + } + Some('u') => { + // Unsigned esplicito con suffisso 'u' + Number::U128(num_str_without_suffix.parse().unwrap()) + } + _ if has_decimal => { + // Float implicito (contiene un punto decimale) + Number::F64(num_str.parse().unwrap()) + } + _ => { + // Intero con segno di default + Number::I32(num_str.parse().unwrap()) + } + }; + + Ok(Some(self.make_token_with_literal( + TokenType::Number, + BaseValue::Number(number_value), + ))) } fn identifier(&mut self) -> LoxResult> { @@ -270,18 +305,18 @@ impl Lexer { let text = self.input[self.start_char..self.current_char].to_string(); match get_keyword_token(&text) { Some(TokenType::True) => Ok(Some( - self.make_token_with_literal(TokenType::True, LiteralValue::Boolean(true)), + self.make_token_with_literal(TokenType::True, BaseValue::Boolean(true)), )), Some(TokenType::False) => Ok(Some( - self.make_token_with_literal(TokenType::False, LiteralValue::Boolean(false)), + self.make_token_with_literal(TokenType::False, BaseValue::Boolean(false)), )), Some(TokenType::Nil) => Ok(Some( - self.make_token_with_literal(TokenType::Nil, LiteralValue::Nil), + self.make_token_with_literal(TokenType::Nil, BaseValue::Nil), )), Some(token_type) => Ok(Some(self.make_token(token_type))), None => Ok(Some(self.make_token_with_literal( TokenType::Identifier, - LiteralValue::String(text.clone()), + BaseValue::String(text.clone()), ))), } } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index e2f449a..732ebff 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -1,4 +1,3 @@ -pub mod ast; pub mod lexer; pub mod parser; pub mod source_registry; diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index b1283b3..bb705f8 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -1,11 +1,13 @@ use crate::{ - frontend::{ + common::{ ast::{AstNode, Expr, Stmt}, - source_registry::{SourcePosition, SourceSlice}, - tokens::{LiteralValue, LoxFunction, Token, TokenType}, + base_value::{BaseValue, LoxFunction}, + lox_result::{parse_error, runtime_error, LoxResult}, + }, + frontend::{ + source_registry::SourceSlice, + tokens::{Token, TokenType}, }, - logging::display_ast::{pretty_print_with_config, PrettyConfig}, - result::{parse_error, runtime_error, LoxResult}, }; pub struct Parser { @@ -21,30 +23,14 @@ impl Parser { pub fn parse(&mut self) -> LoxResult>> { let mut statements = Vec::new(); let mut errors = Vec::new(); - println!("\n \n ++++++++++++++++++++++++++++++++++++\nStarting parsing"); - // Con configurazione personalizzata - let config = PrettyConfig { - indent: " ".to_string(), - max_depth: None, - show_positions: false, - ..Default::default() - }; while !self.is_at_end() { match self.statement() { Ok(stmt) => { - println!( - "Parsed Successfully: {}", - pretty_print_with_config(&stmt.node, &config) - ); - statements.push(stmt.clone()); } Err(err) => { - println!("Error: {:?}", err); errors.push(err); - let old_current = self.current; self.synchronize(); - println!("Synchronized from {} to {}", old_current, self.current); } } } @@ -217,7 +203,7 @@ impl Parser { let name = self.consume(TokenType::Identifier, "Expect variable name.")?; let name_lexeme = name.lexeme.clone(); - let mut _type_annotation = LiteralValue::Nil; + let mut _type_annotation = BaseValue::Nil; self.consume(TokenType::Colon, "Expect column")?; match self.peek().token_type { @@ -254,16 +240,18 @@ impl Parser { self.advance(); } } - self.advance(); + self.consume(TokenType::RightParen, "Expected ')' after parameters")?; let end_position = self.peek().source_slice.clone(); let combine_position = SourceSlice { source_id: start_slice.source_id, start_position: start_slice.start_position.clone(), end_position: end_position.end_position, }; + let mut guard: Option> = None; - if self.peek().token_type == TokenType::RightBrace { - self.advance(); + if self.peek().token_type == TokenType::LeftBrace { + self.consume(TokenType::LeftBrace, "Expected '{' after guard expression")?; + println!("ho trovato una guradia"); guard = Some(Box::new(self.expression()?.node.clone())); self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?; } @@ -271,7 +259,7 @@ impl Parser { let body = self.statement()?; let node = AstNode { node: Expr::Literal { - value: LiteralValue::Function(LoxFunction { + value: BaseValue::Function(LoxFunction { parameters, body, closure: None, @@ -303,7 +291,7 @@ impl Parser { } let mut value = AstNode { node: Expr::Literal { - value: LiteralValue::Nil, + value: BaseValue::Nil, }, source_slice: start_slice.clone(), }; @@ -453,9 +441,36 @@ impl Parser { } fn or_and(&mut self) -> LoxResult> { - let mut expr = self.equality()?; + let mut expr = self.logical_is()?; while [TokenType::Or, TokenType::And].contains(&self.peek().token_type) { + let start_slice = expr.source_slice.clone(); + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.logical_is()?; + let end_slice = right.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + expr = AstNode::new( + Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }, + combined_slice, + ); + } + + Ok(expr) + } + + fn logical_is(&mut self) -> LoxResult> { + let mut expr = self.equality()?; + + while self.peek().token_type == TokenType::Is { let start_slice = expr.source_slice.clone(); let operator = self.peek().token_type.clone(); self.advance(); @@ -478,6 +493,7 @@ impl Parser { Ok(expr) } + fn equality(&mut self) -> LoxResult> { let mut expr = self.comparison()?; @@ -667,7 +683,7 @@ impl Parser { self.advance(); Ok(AstNode::new( Expr::Literal { - value: LiteralValue::Boolean(false), + value: BaseValue::Boolean(false), }, source_slice, )) @@ -677,7 +693,7 @@ impl Parser { self.advance(); Ok(AstNode::new( Expr::Literal { - value: LiteralValue::Boolean(true), + value: BaseValue::Boolean(true), }, source_slice, )) @@ -687,7 +703,7 @@ impl Parser { self.advance(); Ok(AstNode::new( Expr::Literal { - value: LiteralValue::Nil, + value: BaseValue::Nil, }, source_slice, )) diff --git a/src/frontend/source_registry.rs b/src/frontend/source_registry.rs index a0543fa..3bdaa25 100644 --- a/src/frontend/source_registry.rs +++ b/src/frontend/source_registry.rs @@ -4,7 +4,7 @@ use std::{ io::ErrorKind, }; -use crate::result::{LoxError, LoxResult}; +use crate::common::lox_result::{io_error, LoxError, LoxResult}; pub struct SourceRegistry { pub sources: Vec, @@ -147,7 +147,7 @@ impl SourceRegistry { format!("Failed to read '{}': {}", path, error) } }; - Err(LoxError::IoError { message: error_msg }) + io_error(error_msg) } } } diff --git a/src/frontend/tokens.rs b/src/frontend/tokens.rs index a3345c3..cdad154 100644 --- a/src/frontend/tokens.rs +++ b/src/frontend/tokens.rs @@ -1,67 +1,6 @@ use std::fmt; -use crate::{ - backend::environment::EnvironmentStack, - frontend::{ - ast::{AstNode, Expr, Stmt}, - source_registry::SourceSlice, - }, - result::{runtime_error, LoxResult}, -}; - -#[derive(Debug, Clone, PartialEq)] -pub enum LiteralValue { - Identifier(String), - String(String), - Number(f64), - Boolean(bool), - Nil, - Function(LoxFunction), - NativeFunction(NativeFunction), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct LoxFunction { - pub parameters: Vec<(String, String)>, - pub body: AstNode, - pub closure: Option, - pub guard: Option>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct NativeFunction { - pub arity: usize, - pub function: fn(&[LiteralValue]) -> LiteralValue, -} - -impl NativeFunction { - pub fn new(arity: usize, function: fn(&[LiteralValue]) -> LiteralValue) -> Self { - NativeFunction { arity, function } - } -} - -impl LiteralValue { - pub fn is_callable(&self) -> bool { - match self { - LiteralValue::Function(..) | LiteralValue::NativeFunction(..) => true, - _ => false, - } - } -} - -impl fmt::Display for LiteralValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LiteralValue::Identifier(id) => write!(f, "{}", id), - LiteralValue::String(s) => write!(f, "{}", s), - LiteralValue::Number(n) => write!(f, "{}", n), - LiteralValue::Boolean(b) => write!(f, "{}", b), - LiteralValue::Nil => write!(f, "nil"), - LiteralValue::Function(..) => write!(f, ""), - LiteralValue::NativeFunction(..) => write!(f, ""), - } - } -} +use crate::{common::base_value::BaseValue, frontend::source_registry::SourceSlice}; #[derive(Debug, Clone, PartialEq)] pub enum TokenType { @@ -115,6 +54,7 @@ pub enum TokenType { Nil, Or, In, + Is, Print, Return, Super, @@ -178,6 +118,7 @@ impl fmt::Display for TokenType { TokenType::In => write!(f, "in"), TokenType::Colon => write!(f, ":"), TokenType::Fn => write!(f, "fn"), + TokenType::Is => write!(f, "is"), } } } @@ -186,7 +127,7 @@ impl fmt::Display for TokenType { pub struct Token { pub token_type: TokenType, pub lexeme: String, - pub literal: Option, + pub literal: Option, pub source_slice: SourceSlice, } @@ -202,7 +143,7 @@ impl Token { pub fn new_complete( token_type: TokenType, lexeme: String, - literal: Option, + literal: Option, source_slice: SourceSlice, ) -> Self { Token { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2d494f2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod backend; +pub mod common; +pub mod frontend; diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index c21f473..da837e1 100644 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -3,9 +3,10 @@ //! This module provides elegant and functional printing capabilities for AST objects, //! inspired by Rust's debug formatting but optimized for readability and analysis. -use crate::frontend::ast::{AstNode, AstNodeKind, Expr, Stmt}; use std::fmt::{self, Write}; +use crate::common::ast::{AstNode, AstNodeKind, Expr, Stmt}; + /// Configuration for pretty printing output #[derive(Clone, Debug)] pub struct PrettyConfig { diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs index df15511..f3b645f 100644 --- a/src/logging/display_token.rs +++ b/src/logging/display_token.rs @@ -198,6 +198,7 @@ impl Token { TokenType::In => "IN", TokenType::Colon => ":", TokenType::Fn => "FN", + TokenType::Is => "IS", } } @@ -235,6 +236,7 @@ impl Token { | TokenType::This | TokenType::Var | TokenType::Fn + | TokenType::Is | TokenType::Val => ("\x1b[34m", self.token_type_symbol()), TokenType::Identifier | TokenType::String | TokenType::Number => { ("\x1b[32m", self.token_type_symbol()) diff --git a/src/main.rs b/src/main.rs index 957bc57..98e2653 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 = 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) { +fn parse_args(args: &[String]) -> (ExecutionStage, Option, 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 { + // 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); @@ -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]) -> String { +fn format_ast(ast: &[AstNode]) -> String { use crate::logging::display_ast::{pretty_print_with_config, PrettyConfig}; let config = PrettyConfig { diff --git a/tests/number_operations.rs b/tests/number_operations.rs new file mode 100644 index 0000000..fb85f14 --- /dev/null +++ b/tests/number_operations.rs @@ -0,0 +1,187 @@ +use rlox::common::base_value::{BaseValue, Number}; + +#[test] +fn test_number_addition_with_type_promotion() { + // Test float + int -> both become F64 + let float_num = Number::F64(3.5); + let int_num = Number::I32(2); + let result = float_num.add(int_num); + match result { + Number::F64(val) => assert_eq!(val, 5.5), + _ => panic!("Expected F64 result from float + int"), + } + + // Test int + unsigned -> both become I64 + let int_num = Number::I32(-5); + let unsigned_num = Number::U32(10); + let result = int_num.add(unsigned_num); + match result { + Number::I64(val) => assert_eq!(val, 5), + _ => panic!("Expected I64 result from int + unsigned"), + } + + // Test float + unsigned -> both become F64 + let float_num = Number::F32(2.5); + let unsigned_num = Number::U64(3); + let result = float_num.add(unsigned_num); + match result { + Number::F64(val) => assert_eq!(val, 5.5), + _ => panic!("Expected F64 result from float + unsigned"), + } + + // Test unsigned + unsigned -> both stay U128 + let unsigned1 = Number::U32(100); + let unsigned2 = Number::U64(200); + let result = unsigned1.add(unsigned2); + match result { + Number::U128(val) => assert_eq!(val, 300), + _ => panic!("Expected U128 result from unsigned + unsigned"), + } +} + +#[test] +fn test_number_subtraction() { + let a = Number::F64(10.5); + let b = Number::I32(5); + let result = a.sub(b); + match result { + Number::F64(val) => assert_eq!(val, 5.5), + _ => panic!("Expected F64 result"), + } + + let a = Number::I32(10); + let b = Number::U32(3); + let result = a.sub(b); + match result { + Number::I64(val) => assert_eq!(val, 7), + _ => panic!("Expected I64 result"), + } +} + +#[test] +fn test_number_multiplication() { + let a = Number::F64(2.5); + let b = Number::I32(4); + let result = a.mul(b); + match result { + Number::F64(val) => assert_eq!(val, 10.0), + _ => panic!("Expected F64 result"), + } + + let a = Number::U32(5); + let b = Number::U32(6); + let result = a.mul(b); + match result { + Number::U128(val) => assert_eq!(val, 30), + _ => panic!("Expected U128 result"), + } +} + +#[test] +fn test_number_division() { + let a = Number::F64(10.0); + let b = Number::I32(4); + let result = a.div(b).unwrap(); + match result { + Number::F64(val) => assert_eq!(val, 2.5), + _ => panic!("Expected F64 result"), + } + + // Test division by zero + let a = Number::I32(10); + let b = Number::I32(0); + assert!(a.div(b).is_none()); + + let a = Number::F64(10.0); + let b = Number::F64(0.0); + assert!(a.div(b).is_none()); +} + +#[test] +fn test_number_remainder() { + let a = Number::I32(10); + let b = Number::I32(3); + let result = a.rem(b).unwrap(); + match result { + Number::I64(val) => assert_eq!(val, 1), + _ => panic!("Expected I64 result"), + } + + let a = Number::F64(10.5); + let b = Number::F64(3.0); + let result = a.rem(b).unwrap(); + match result { + Number::F64(val) => assert_eq!(val, 1.5), + _ => panic!("Expected F64 result"), + } +} + +#[test] +fn test_number_negation() { + let num = Number::I32(5); + let result = num.neg(); + match result { + Number::I32(val) => assert_eq!(val, -5), + _ => panic!("Expected I32 result"), + } + + let num = Number::U32(5); + let result = num.neg(); + match result { + Number::I64(val) => assert_eq!(val, -5), + _ => panic!("Expected I64 result for negated unsigned"), + } + + let num = Number::F64(3.5); + let result = num.neg(); + match result { + Number::F64(val) => assert_eq!(val, -3.5), + _ => panic!("Expected F64 result"), + } +} + +#[test] +fn test_number_comparison() { + let a = Number::I32(5); + let b = Number::F64(5.0); + assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Equal)); + + let a = Number::I32(5); + let b = Number::F64(6.0); + assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Less)); + + let a = Number::U32(10); + let b = Number::I32(5); + assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Greater)); +} + +#[test] +fn test_baevalue_operations() { + // Test BaseValue addition + let a = BaseValue::Number(Number::I32(5)); + let b = BaseValue::Number(Number::F64(3.5)); + let result = (a + b).unwrap(); + match result { + BaseValue::Number(Number::F64(val)) => assert_eq!(val, 8.5), + _ => panic!("Expected Number(F64) result"), + } + + // Test string concatenation + let a = BaseValue::String("Hello ".to_string()); + let b = BaseValue::String("World".to_string()); + let result = (a + b).unwrap(); + match result { + BaseValue::String(s) => assert_eq!(s, "Hello World"), + _ => panic!("Expected String result"), + } +} + +#[test] +fn test_is_zero() { + assert!(Number::I32(0).is_zero()); + assert!(Number::F64(0.0).is_zero()); + assert!(Number::U32(0).is_zero()); + assert!(!Number::I32(1).is_zero()); + assert!(!Number::F64(0.1).is_zero()); + assert!(!Number::U32(1).is_zero()); +}