commit a7df45dc72cf55f0a05922d48dcfe0b8ef51757c Author: Giulio Agostini Date: Fri Oct 3 19:07:12 2025 +0200 Initialize Rust implementation of Lox interpreter This initial commit sets up the basic structure for a Rust implementation of the Lox interpreter, including: - Lexer for tokenizing source code - Parser for building AST - Basic interpreter functionality - Environment for variable storage The core components include source tracking, error handling, and basic expression evaluation. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4495404 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +/target + +# Created by https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,rust-analyzer + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### rust-analyzer ### +# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules) +rust-project.json + + +# End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer +.zed/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3d27665 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rlox" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/example.lox b/example.lox new file mode 100644 index 0000000..0d3486a --- /dev/null +++ b/example.lox @@ -0,0 +1,19 @@ +// var Person :: struct { +// name: String, +// age: Int, +// address: String, +// } +// var func_name :: fn(param, param2) {param is Int} do +do + print "ciao"; + 1 + 2; + var cavallo = 1 + 2; + cavallo = cavallo + 1; + print cavallo; + cavallo +end +do + print "cavallo"; + 89 +end +99 diff --git a/src/backend/environment.rs b/src/backend/environment.rs new file mode 100644 index 0000000..2646de5 --- /dev/null +++ b/src/backend/environment.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use crate::{ + frontend::{source_registry::SourceSlice, tokens::LiteralValue}, + result::{LoxError, LoxResult}, +}; + +pub struct Environment<'a> { + values: HashMap, + parent: Option<&'a Environment<'a>>, +} + +impl<'a> Environment<'a> { + pub fn new() -> Self { + Environment { + values: HashMap::new(), + parent: None, + } + } + + pub fn new_with_parent(parent: &'a Environment<'a>) -> Self { + Environment { + values: HashMap::new(), + parent: Some(parent), + } + } + + pub fn get(&self, name: &str) -> LoxResult { + match self.values.get(name) { + Some(value) => Ok(value.clone()), + None => match self.parent { + Some(parent) => parent.get(name), + None => Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), // todo change this to the actual source slice + message: format!("Undefined variable '{}'", name), + }), + }, + } + } + + pub fn set(&mut self, name: String, value: LiteralValue) { + self.values.insert(name, value); + } +} diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs new file mode 100644 index 0000000..4423a01 --- /dev/null +++ b/src/backend/interpreter.rs @@ -0,0 +1,325 @@ +use crate::{ + backend::environment::Environment, + frontend::{ + ast::{AstNode, AstNodeKind, Expr, Stmt}, + source_registry::SourceSlice, + tokens::{LiteralValue, TokenType}, + }, + result::{LoxError, LoxResult}, +}; +use std::{ + fmt::{format, 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, + } +} + +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 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, + } + } +} + +pub struct Interpreter<'a> { + enviorment: &'a mut Environment<'a>, +} + +impl<'a> Interpreter<'a> { + pub fn new(env: &'a mut Environment<'a>) -> Self { + Self { enviorment: env } + } + + fn interpret_binary( + &mut self, + left: Expr, + operator: TokenType, + right: Expr, + ) -> LoxResult { + let left_value = self.interpret(left)?; + let right_value = self.interpret(right)?; + match operator { + TokenType::Minus => left_value - right_value, + TokenType::Plus => left_value + right_value, + TokenType::Slash => left_value / right_value, + TokenType::Star => left_value * right_value, + TokenType::EqualEqual => Ok(LiteralValue::Boolean(left_value == right_value)), + TokenType::BangEqual => Ok(LiteralValue::Boolean(left_value != right_value)), + TokenType::Greater => Ok(LiteralValue::Boolean(left_value > right_value)), + TokenType::GreaterEqual => Ok(LiteralValue::Boolean(left_value >= right_value)), + TokenType::Less => Ok(LiteralValue::Boolean(left_value < right_value)), + TokenType::LessEqual => Ok(LiteralValue::Boolean(left_value <= right_value)), + TokenType::Percent => left_value % right_value, + + TokenType::And => Ok(LiteralValue::Boolean( + left_value.is_truthy() && right_value.is_truthy(), + )), + TokenType::Or => Ok(LiteralValue::Boolean( + left_value.is_truthy() || right_value.is_truthy(), + )), + _ => Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), // todo change this to the actual source slice + message: format!("Unsupported binary operator {}", operator), + }), + } + } +} + +pub trait EvaluateInterpreter { + fn interpret(&mut self, stmt: T) -> LoxResult; +} + +impl<'a, R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter> + for Interpreter<'a> +where + Interpreter<'a>: EvaluateInterpreter, +{ + fn interpret(&mut self, stmt: AstNode) -> LoxResult { + match self.interpret(stmt.node.clone()) { + Ok(value) => Ok(value), + Err(err) => Err(LoxError::RuntimeError { + source_slice: stmt.source_slice, + message: err.get_message(), + }), + } + } +} + +impl<'a> EvaluateInterpreter for Interpreter<'a> { + fn interpret(&mut self, stmt: Expr) -> LoxResult { + match stmt { + Expr::Literal { value } => Ok(value.clone()), + Expr::Binary { + left, + operator, + right, + } => self.interpret_binary(*left, operator, *right), + Expr::Unary { operator, operand } => { + let right = self.interpret(*operand)?; + match operator { + TokenType::Minus => Ok((-right)?), + TokenType::Bang => Ok(!right), + _ => Err(error("Unsupported unary operator".to_string())), + } + } + Expr::Grouping { expression } => self.interpret(*expression), + Expr::Variable { name } => self.enviorment.get(&name), + } + } +} + +impl<'a> EvaluateInterpreter for Interpreter<'a> { + fn interpret(&mut self, stmt: Stmt) -> LoxResult { + match stmt { + Stmt::Expression { expression } => self.interpret(*expression), + Stmt::Print { expression } => { + let value = self.interpret(*expression)?; + println!("print interpreter: \t{}", value); + Ok(LiteralValue::Nil) + } + Stmt::Block { statements } => { + let (elements, final_expr) = match statements.split_last() { + Some((Stmt::Expression { expression }, body)) => (body, Some(expression)), + Some((Stmt::Return { expression }, body)) => (body, Some(expression)), + Some((_last_stmt, _body)) => { + (statements.as_slice(), None) // Non è un'Expression finale + } + None => { + (&[][..], None) // Blocco vuoto + } + }; + + // Ora elements è sempre disponibile + for statement in elements.iter() { + self.interpret((*statement).clone())?; + } + + // Gestisci l'espressione finale se presente + match final_expr { + Some(expr) => self.interpret((**expr).clone()), + None => Ok(LiteralValue::Nil), + } + } + Stmt::Stmt { expression } => { + let _ = self.interpret(*expression); + Ok(LiteralValue::Nil) + } + Stmt::Return { expression } => self.interpret(*expression), + Stmt::Var { name, initializer } => { + let value = if let Some(expr) = initializer { + self.interpret(*expr)? + } else { + LiteralValue::Nil + }; + self.enviorment.set(name.clone(), value); + return Ok(LiteralValue::Nil); + } + Stmt::Assign { name, value } => { + let value = self.interpret(*value)?; + self.enviorment.set(name.clone(), value); + Ok(LiteralValue::Nil) + } + Stmt::If { + condition, + then_branch, + elif_branch, + else_branch, + } => { + let condition = self.interpret(*condition)?; + match condition { + LiteralValue::Boolean(true) => self.interpret(*then_branch), + LiteralValue::Boolean(false) => { + for (elif_condition, elif_then_branch) in elif_branch { + let condition = self.interpret(*elif_condition)?; + match condition { + LiteralValue::Boolean(true) => { + return self.interpret(*elif_then_branch); + } + LiteralValue::Boolean(false) => continue, + _ => { + return Err(LoxError::TypeMismatch { + source_slice: SourceSlice::default(), // todo change this to the actual source slice + expected: "boolean".to_string(), + found: condition.to_string(), + }); + } + }; + } + if let Some(else_block) = else_branch { + self.interpret(*else_block) + } else { + Ok(LiteralValue::Nil) + } + } + _ => Err(LoxError::TypeMismatch { + source_slice: SourceSlice::default(), // todo change this to the actual source slice + expected: "boolean".to_string(), + found: condition.to_string(), + }), + } + } + } + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..ad68c27 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,2 @@ +pub mod environment; +pub mod interpreter; diff --git a/src/frontend/ast.rs b/src/frontend/ast.rs new file mode 100644 index 0000000..cc4e125 --- /dev/null +++ b/src/frontend/ast.rs @@ -0,0 +1,304 @@ +use crate::frontend::{ + source_registry::{SourceId, SourcePosition, SourceSlice}, + tokens::{LiteralValue, TokenType}, +}; +use std::fmt::{Debug, Display}; +/* + * grammar: + * program -> statement* EOF + * statement -> expression_statement + * | print_statement + * | var_statement + * | block_statement + * | assignment_statement + * | if_statement + * + * expression_statement -> expression ";" + * print_statement -> "print" expression ";" + * var_statement -> "var" IDENTIFIER ("=" expression)? ";" + * assignment_statement -> IDENTIFIER "=" expression ";" + * block_statement -> "do" statement* "end" + * if_statement -> "if" expression "then" statement ("elif" expression "then" statement)* ("else" statement)? "end" + * + * expression -> assignment + * assignment -> IDENTIFIER "=" assignment | logical_or + * logical_or -> logical_and (("or" logical_and)* + * logical_and -> equality (("and" equality)* + * equality -> comparison (("==" | "!=") comparison)* + * comparison -> term ((">" | ">=" | "<" | "<=") term)* + * term -> factor (("+" | "-") factor)* + * factor -> unary (("*" | "/") unary)* + * unary -> ("!" | "-") unary | primary + * primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER + */ +#[derive(Clone)] +pub enum Expr { + Literal { + value: LiteralValue, + }, + Binary { + left: Box, + operator: TokenType, + right: Box, + }, + Unary { + operator: TokenType, + operand: Box, + }, + Grouping { + expression: Box, + }, + Variable { + name: String, + }, +} + +pub trait ExprPrint { + fn print(&self) -> String; +} + +impl ExprPrint for Expr { + fn print(&self) -> String { + match self { + Expr::Literal { value } => format!("Literal {}", value), + Expr::Binary { + left, + operator, + right, + } => { + format!("Binary ({} {} {})", left, operator, right) + } + Expr::Unary { operator, operand } => { + format!("Unary ({} {})", operator, operand) + } + Expr::Grouping { expression } => { + format!("Grouping (group {})", expression) + } + Expr::Variable { name } => { + format!("Variable (variable {})", name) + } + } + } +} + +// Implementazione Display per Expr (per debugging) +impl std::fmt::Display for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expr::Literal { value } => write!(f, "Literal {}", value), + Expr::Binary { + left, + operator, + right, + } => write!(f, "Binary ({} {} {})", left, operator, right), + Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand), + Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression), + Expr::Variable { name } => write!(f, "Variable (variable {})", name), + } + } +} + +impl Debug for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expr::Literal { value } => write!(f, "{:?}", value), + Expr::Binary { + left, + operator, + right, + } => write!(f, "({:?} {:?} {:?})", operator, left, right), + Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand), + Expr::Grouping { expression } => write!(f, "(group {:?})", expression), + Expr::Variable { name } => write!(f, "(variable {:?})", name), + } + } +} + +#[derive(Clone)] +pub enum Stmt { + Expression { + expression: Box, + }, + Print { + expression: Box, + }, + Stmt { + expression: Box, + }, + Var { + name: String, + initializer: Option>, + }, + Assign { + name: String, + value: Box, + }, + Return { + expression: Box, + }, + Block { + statements: Box>, + }, + If { + condition: Box, + then_branch: Box, + elif_branch: Vec<(Box, Box)>, + else_branch: Option>, + }, +} + +impl Display for Stmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Stmt::Expression { expression } => write!(f, "Expression ({})", expression), + Stmt::If { + condition, + then_branch, + elif_branch, + else_branch, + } => { + let mut result = format!("IF ({}) {{\n{}\n}}", condition, then_branch); + for (condition, branch) in elif_branch { + result.push_str(&format!(" ELIF ({}) {{\n{}\n}}", condition, branch)); + } + if let Some(else_branch) = else_branch { + result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch)); + } + write!(f, "IfStmt {}", result) + } + Stmt::Print { expression } => write!(f, "Print({});", expression), + Stmt::Stmt { expression } => write!(f, "Stmt({});", expression), + Stmt::Var { name, initializer } => { + write!(f, "Var({} = {:?});", name, initializer) + } + Stmt::Assign { name, value } => write!(f, "Assign({} = {});", name, value), + Stmt::Return { expression } => write!(f, "Return({});", expression), + Stmt::Block { statements } => write!( + f, + "Block([\n{}\n])", + statements + .iter() + .map(|stmt| format!("\t \t{}", stmt)) + .collect::>() + .join("\n") + ), + } + } +} + +impl Debug for Stmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression), + Stmt::If { + condition, + then_branch, + elif_branch, + else_branch, + } => { + let mut result = format!("IF ({:?}) {{\n{:?}\n}}", condition, then_branch); + for (condition, branch) in elif_branch { + result.push_str(&format!(" ELIF ({:?}) {{\n{:?}\n}}", condition, branch)); + } + if let Some(else_branch) = else_branch { + result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch)); + } + write!(f, "IfStmt {:?}", result) + } + Stmt::Print { expression } => write!(f, "Print({:?});", expression), + Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression), + Stmt::Var { name, initializer } => { + write!(f, "Var({:?} = {:?});", name, initializer) + } + Stmt::Assign { name, value } => write!(f, "Assign({:?} = {:?});", name, value), + Stmt::Return { expression } => write!(f, "Return({:?});", expression), + Stmt::Block { statements } => write!( + f, + "Block([\n{:?}\n])", + statements + .iter() + .map(|stmt| format!("{:?}", stmt)) + .collect::>() + .join("\t\t\n") + ), + } + } +} + +pub trait AstNodeKind { + fn kind(&self) -> &'static str; +} + +impl AstNodeKind for Expr { + fn kind(&self) -> &'static str { + match self { + Expr::Literal { value: _ } => "literal", + Expr::Binary { + left: _, + operator: _, + right: _, + } => "binary expression", + Expr::Unary { + operator: _, + operand: _, + } => "unary expression", + Expr::Grouping { expression: _ } => "grouping expression", + Expr::Variable { name: _ } => "variable expression", + } + } +} + +impl AstNodeKind for Stmt { + fn kind(&self) -> &'static str { + match self { + Stmt::Expression { expression: _ } => "expression", + Stmt::Var { + name: _, + initializer: _, + } => "variable declaration", + Stmt::Assign { name: _, value: _ } => "assignment", + Stmt::Return { expression: _ } => "return statement", + Stmt::Block { statements: _ } => "block statement", + Stmt::If { + condition: _, + then_branch: _, + elif_branch: _, + else_branch: _, + } => "if statement", + Stmt::Print { expression: _ } => "print statement", + Stmt::Stmt { expression: _ } => "statement", + } + } +} + +#[derive(Clone, PartialEq)] +pub struct AstNode { + pub node: T, + pub source_slice: SourceSlice, +} + +impl Display for AstNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "AstNode \n{{ \n\tnode: {},\n\tsource_slice: {}, \n}}", + self.node, self.source_slice + ) + } +} + +impl Debug for AstNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "AstNode \n{{ \n\tnode: {:?},\n\tsource_slice: {:?}, \n}}", + self.node, self.source_slice + ) + } +} + +impl AstNode { + pub fn new(node: T, source_slice: SourceSlice) -> Self { + AstNode { node, source_slice } + } +} diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs new file mode 100644 index 0000000..a1fd71e --- /dev/null +++ b/src/frontend/lexer.rs @@ -0,0 +1,282 @@ +use crate::frontend::source_registry::{SourceId, SourcePosition, SourceSlice}; +use crate::frontend::tokens::{LiteralValue, Token, TokenType}; +use crate::result::{LoxError, LoxResult}; + +pub struct Lexer { + input: String, + start_char: usize, + current_char: usize, + start_pos: SourcePosition, + end_pos: SourcePosition, + source_id: SourceId, +} + +fn get_keyword_token(word: &str) -> Option { + match word { + "and" => Some(TokenType::And), + "class" => Some(TokenType::Class), + "do" => Some(TokenType::StartBlock), + "end" => Some(TokenType::EndBlock), + "false" => Some(TokenType::False), + "for" => Some(TokenType::For), + "fun" => Some(TokenType::Fun), + "if" => Some(TokenType::If), + "elif" => Some(TokenType::Elif), + "else" => Some(TokenType::Else), + "or" => Some(TokenType::Or), + "print" => Some(TokenType::Print), + "return" => Some(TokenType::Return), + "super" => Some(TokenType::Super), + "this" => Some(TokenType::This), + "true" => Some(TokenType::True), + "var" => Some(TokenType::Var), + "val" => Some(TokenType::Val), + "while" => Some(TokenType::While), + "True" => Some(TokenType::True), + "False" => Some(TokenType::False), + "Nil" => Some(TokenType::Nil), + _ => None, + } +} + +impl Lexer { + pub fn new(input: String, source_id: SourceId) -> Lexer { + Lexer { + input, + start_char: 0, + current_char: 0, + start_pos: SourcePosition::default(), + end_pos: SourcePosition::default(), + source_id, + } + } + + fn advance_column(&mut self) { + self.current_char += 1; + self.end_pos.column += 1; + } + + fn advance_line(&mut self) { + self.end_pos.line += 1; + self.end_pos.column = 0; + } + + pub fn scans_tokens(&mut self) -> LoxResult> { + let mut tokens = Vec::new(); + while !self.is_at_end() { + self.start_char = self.current_char; + self.start_pos = self.end_pos.clone(); + match self.scan_token() { + Ok(Some(token)) => tokens.push(token), + Ok(None) => {} + Err(err) => return Err(err), + } + } + tokens.push(self.make_token(TokenType::Eof)); + println!("Tokens:"); + tokens.iter().for_each(|val| println!("{}", val)); + Ok(tokens) + } + + fn is_at_end(&self) -> bool { + self.current_char >= self.input.len() + } + + fn advance(&mut self) -> char { + self.advance_column(); + self.input.chars().nth(self.current_char - 1).unwrap() + } + + fn peek(&self) -> char { + if self.is_at_end() { + '\0' + } else { + self.input.chars().nth(self.current_char).unwrap() + } + } + + fn peek_next(&self) -> char { + if self.current_char + 1 >= self.input.len() { + '\0' + } else { + self.input.chars().nth(self.current_char + 1).unwrap() + } + } + + fn make_token(&self, token_type: TokenType) -> Token { + let text = self.input[self.start_char..self.current_char].to_string(); + Token::new( + token_type, + text, + SourceSlice::from_positions( + self.source_id.clone(), + self.start_pos.clone(), + self.end_pos.clone(), + ), + ) + } + fn make_token_with_literal(&self, token_type: TokenType, literal: LiteralValue) -> Token { + let text = self.input[self.start_char..self.current_char].to_string(); + Token::new_complete( + token_type, + text, + Some(literal), + SourceSlice::from_positions( + self.source_id.clone(), + self.start_pos.clone(), + self.end_pos.clone(), + ), + ) + } + + fn scan_token(&mut self) -> LoxResult> { + let c = self.advance(); + match (c, self.peek()) { + ('(', _) => Ok(Some(self.make_token(TokenType::LeftParen))), + (')', _) => Ok(Some(self.make_token(TokenType::RightParen))), + ('{', _) => Ok(Some(self.make_token(TokenType::LeftBrace))), + ('}', _) => Ok(Some(self.make_token(TokenType::RightBrace))), + ('[', _) => Ok(Some(self.make_token(TokenType::LeftBracket))), + (']', _) => Ok(Some(self.make_token(TokenType::RightBracket))), + (',', _) => Ok(Some(self.make_token(TokenType::Comma))), + ('.', _) => Ok(Some(self.make_token(TokenType::Dot))), + ('-', _) => Ok(Some(self.make_token(TokenType::Minus))), + ('+', _) => Ok(Some(self.make_token(TokenType::Plus))), + (';', _) => Ok(Some(self.make_token(TokenType::Semicolon))), + ('*', _) => Ok(Some(self.make_token(TokenType::Star))), + ('!', '=') => { + self.advance(); // consuma il '=' + Ok(Some(self.make_token(TokenType::BangEqual))) + } + ('!', _) => Ok(Some(self.make_token(TokenType::Bang))), + ('=', '=') => { + self.advance(); // consuma il '=' + Ok(Some(self.make_token(TokenType::EqualEqual))) + } + ('=', _) => Ok(Some(self.make_token(TokenType::Equal))), + ('<', '=') => { + self.advance(); // consuma il '=' + Ok(Some(self.make_token(TokenType::LessEqual))) + } + ('<', _) => Ok(Some(self.make_token(TokenType::Less))), + ('>', '=') => { + self.advance(); // consuma il '=' + Ok(Some(self.make_token(TokenType::GreaterEqual))) + } + ('>', _) => Ok(Some(self.make_token(TokenType::Greater))), + ('/', '/') => { + // Commento single-line + while self.peek() != '\n' && !self.is_at_end() { + self.advance(); + } + Ok(None) + } + ('/', '*') => { + // Commento multi-line + while (self.peek() != '*' || self.peek_next() != '/') && !self.is_at_end() { + if self.peek() == '\n' { + self.advance_line(); + } + self.advance(); + } + if self.is_at_end() { + return Err(LoxError::LexicalError { + source_slice: SourceSlice::from_positions( + self.source_id.clone(), + self.start_pos.clone(), + self.end_pos.clone(), + ), + message: "Unterminated comment".to_string(), + }); + } else { + self.advance(); // consuma '*' + self.advance(); // consuma '/' + } + Ok(None) + } + ('/', _) => Ok(Some(self.make_token(TokenType::Slash))), + (' ', _) | ('\r', _) | ('\t', _) => Ok(None), + ('\n', _) => { + self.advance_line(); + Ok(None) + } + ('"', _) => self.string(), + (c, _) if c.is_digit(10) => self.number(), + (c, _) if c.is_alphanumeric() || c == '_' => self.identifier(), + _ => Err(LoxError::LexicalError { + source_slice: SourceSlice::from_positions( + self.source_id.clone(), + self.start_pos.clone(), + self.end_pos.clone(), + ), + message: format!("Unexpected character: {}", c), + }), + } + } + + fn string(&mut self) -> LoxResult> { + while self.peek() != '"' && !self.is_at_end() { + self.advance(); + } + if self.is_at_end() { + return Err(LoxError::LexicalError { + source_slice: SourceSlice::from_positions( + self.source_id.clone(), + self.start_pos.clone(), + self.end_pos.clone(), + ), + message: "Unterminated string".to_string(), + }); + } + self.advance(); + Ok(Some(self.make_token_with_literal( + TokenType::String, + LiteralValue::String(self.input[self.start_char..self.current_char].to_string()), + ))) + } + + fn number(&mut self) -> LoxResult> { + while self.peek().is_digit(10) { + self.advance(); + } + if self.peek() == '.' && self.peek_next().is_digit(10) { + self.advance(); + 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(), + ), + ), + )) + } + + fn identifier(&mut self) -> LoxResult> { + while self.peek().is_alphanumeric() { + self.advance(); + } + 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)), + )), + Some(TokenType::False) => Ok(Some( + self.make_token_with_literal(TokenType::False, LiteralValue::Boolean(false)), + )), + Some(TokenType::Nil) => Ok(Some( + self.make_token_with_literal(TokenType::Nil, LiteralValue::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()), + ))), + } + } +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs new file mode 100644 index 0000000..e2f449a --- /dev/null +++ b/src/frontend/mod.rs @@ -0,0 +1,5 @@ +pub mod ast; +pub mod lexer; +pub mod parser; +pub mod source_registry; +pub mod tokens; diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs new file mode 100644 index 0000000..aa099b9 --- /dev/null +++ b/src/frontend/parser.rs @@ -0,0 +1,437 @@ +use crate::{ + frontend::{ + ast::{AstNode, Expr, Stmt}, + tokens::{LiteralValue, Token, TokenType}, + }, + logging::display_ast::{pretty_print_with_config, PrettyConfig, PrettyPrint}, + result::{LoxError, LoxResult}, +}; + +pub struct Parser { + tokens: Vec, + current: usize, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { tokens, current: 0 } + } + + 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); + } + } + } + + if !errors.is_empty() { + return Err(errors.into_iter().next().unwrap()); + } + + Ok(statements) + } + + fn statement(&mut self) -> LoxResult> { + match (&self.peek().token_type, &self.peek_next().token_type) { + (TokenType::Var, _) => Ok(AstNode::new( + self.var_statement()?, + self.peek().source_slice.clone(), + )), + (TokenType::Print, _) => Ok(AstNode::new( + self.print_statement()?, + self.peek().source_slice.clone(), + )), + (TokenType::Return, _) => Ok(AstNode::new( + self.return_statement()?, + self.peek().source_slice.clone(), + )), + (TokenType::StartBlock, _) => Ok(AstNode::new( + self.block_statement()?, + self.peek().source_slice.clone(), + )), + (TokenType::Identifier, TokenType::Equal) => Ok(AstNode::new( + self.assignment_statement()?, + self.peek().source_slice.clone(), + )), + (TokenType::If, _) => Ok(AstNode::new( + self.if_statement()?, + self.peek().source_slice.clone(), + )), + _ => Ok(AstNode::new( + self.expression_statement()?, + self.peek().source_slice.clone(), + )), + } + } + + fn if_statement(&mut self) -> LoxResult { + self.advance(); + let condition = self.expression()?; + let then_branch = self.statement()?; + let mut elif_branches = Vec::new(); + while self.peek().token_type == TokenType::Elif { + self.advance(); + let condition = self.expression()?; + let then_branch = self.statement()?; + elif_branches.push((Box::new(condition), Box::new(then_branch.node))); + } + let else_branch = if self.peek().token_type == TokenType::Else { + self.advance(); + Some(Box::new(self.statement()?.node)) + } else { + None + }; + Ok(Stmt::If { + condition: Box::new(condition), + then_branch: Box::new(then_branch.node), + elif_branch: elif_branches, + else_branch: else_branch, + }) + } + + fn var_statement(&mut self) -> LoxResult { + self.advance(); + let name = self.consume(TokenType::Identifier, "Expect variable name.")?; + let name_lexeme = name.lexeme.clone(); + self.consume(TokenType::Equal, "Expect '=' after variable name.")?; + let value = self.expression()?; + self.consume( + TokenType::Semicolon, + "Expect ';' after variable declaration.", + )?; + Ok(Stmt::Var { + name: name_lexeme, + initializer: Some(Box::new(value)), + }) + } + + fn assignment_statement(&mut self) -> LoxResult { + let name = self.consume(TokenType::Identifier, "Expect variable name.")?; + let name_lexeme = name.lexeme.clone(); + self.consume(TokenType::Equal, "Expect '=' after variable name.")?; + let value = self.expression()?; + self.consume( + TokenType::Semicolon, + "Expect ';' after variable declaration.", + )?; + Ok(Stmt::Assign { + name: name_lexeme, + value: Box::new(value), + }) + } + + fn print_statement(&mut self) -> LoxResult { + // consume the print keyword + self.advance(); + let expr = self.expression()?; + self.consume(TokenType::Semicolon, "Expect ';' after value.")?; + Ok(Stmt::Print { + expression: Box::new(expr), + }) + } + + fn block_statement(&mut self) -> LoxResult { + self.advance(); + let mut statements = Vec::new(); + while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() { + let stmt = self.statement()?; + statements.push(stmt.node.clone()); + if let Stmt::Expression { .. } = stmt.node { + break; + } + } + self.consume(TokenType::EndBlock, "Expect 'end' after block.")?; + Ok(Stmt::Block { + statements: Box::new(statements), + }) + } + + fn expression_statement(&mut self) -> LoxResult { + let expr = self.expression()?; + if self.peek().token_type == TokenType::Semicolon { + self.advance(); + return Ok(Stmt::Stmt { + expression: Box::new(expr), + }); + } + Ok(Stmt::Expression { + expression: Box::new(expr), + }) + } + + fn return_statement(&mut self) -> LoxResult { + self.advance(); + let expr = self.expression()?; + self.consume(TokenType::Semicolon, "Expect ';' after return value.")?; + Ok(Stmt::Return { + expression: Box::new(expr), + }) + } + + fn expression(&mut self) -> LoxResult { + self.or_and() + } + + fn or_and(&mut self) -> LoxResult { + let mut expr = self.equality()?; + + while [TokenType::Or, TokenType::And].contains(&self.peek().token_type) { + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.equality()?; + expr = Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }; + } + + Ok(expr) + } + fn equality(&mut self) -> LoxResult { + let mut expr = self.comparison()?; + + while [TokenType::BangEqual, TokenType::EqualEqual].contains(&self.peek().token_type) { + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.comparison()?; + expr = Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }; + } + + Ok(expr) + } + + fn comparison(&mut self) -> LoxResult { + let mut expr = self.term()?; + + while [ + TokenType::Greater, + TokenType::GreaterEqual, + TokenType::Less, + TokenType::LessEqual, + ] + .contains(&self.peek().token_type) + { + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.term()?; + expr = Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }; + } + + Ok(expr) + } + + fn term(&mut self) -> LoxResult { + let mut expr = self.factor()?; + + while [TokenType::Plus, TokenType::Minus].contains(&self.peek().token_type) { + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.factor()?; + expr = Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }; + } + + Ok(expr) + } + + fn factor(&mut self) -> LoxResult { + let mut expr = self.unary()?; + + while [TokenType::Slash, TokenType::Star].contains(&self.peek().token_type) { + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.unary()?; + expr = Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }; + } + + Ok(expr) + } + + fn unary(&mut self) -> LoxResult { + if [TokenType::Bang, TokenType::Minus].contains(&self.peek().token_type) { + let operator = self.peek().token_type.clone(); + self.advance(); + let right = self.unary()?; + return Ok(Expr::Unary { + operator, + operand: Box::new(right), + }); + } + + self.primary() + } + + fn primary(&mut self) -> LoxResult { + match self.peek().token_type { + TokenType::False => { + self.advance(); + Ok(Expr::Literal { + value: LiteralValue::Boolean(false), + }) + } + TokenType::True => { + self.advance(); + Ok(Expr::Literal { + value: LiteralValue::Boolean(true), + }) + } + TokenType::Nil => { + self.advance(); + Ok(Expr::Literal { + value: LiteralValue::Nil, + }) + } + TokenType::Number => { + self.advance(); + let token = self.previous(); + if let Some(literal) = &token.literal { + Ok(Expr::Literal { + value: literal.clone(), + }) + } else { + Err(self.error(token, "Expected number literal").into()) + } + } + TokenType::String => { + self.advance(); + let token = self.previous(); + if let Some(literal) = &token.literal { + Ok(Expr::Literal { + value: literal.clone(), + }) + } else { + Err(self.error(token, "Expected string literal").into()) + } + } + TokenType::LeftParen => { + self.advance(); + let expr = self.expression()?; + self.consume(TokenType::RightParen, "Expect ')' after expression.")?; + Ok(Expr::Grouping { + expression: Box::new(expr), + }) + } + TokenType::Identifier => { + let name = self.peek().lexeme.clone(); + self.advance(); + Ok(Expr::Variable { name: name }) + } + _ => Err(self.error(self.peek(), "Expect expression.").into()), + } + } + + fn is_at_end(&self) -> bool { + if self.current >= self.tokens.len() { + true + } else { + self.tokens[self.current].token_type == TokenType::Eof + } + } + + fn advance(&mut self) -> &Token { + if !self.is_at_end() { + self.current += 1; + } + self.peek() + } + + fn peek(&self) -> &Token { + if self.is_at_end() { + &self.tokens.last().unwrap() + } else { + &self.tokens[self.current] + } + } + + fn peek_next(&self) -> &Token { + if self.is_at_end() { + &self.peek() + } else { + &self.tokens[self.current + 1] + } + } + + fn previous(&self) -> &Token { + &self.tokens[self.current - 1] + } + + fn consume(&mut self, token_type: TokenType, message: &str) -> LoxResult<&Token> { + if self.peek().token_type == token_type { + self.advance(); + Ok(self.previous()) + } else { + Err(self.error(self.peek(), message).into()) + } + } + + fn error(&self, token: &Token, message: &str) -> LoxError { + LoxError::ParseError { + source_slice: token.source_slice.clone(), + message: message.to_string(), + } + } + + fn synchronize(&mut self) { + self.advance(); + + while !self.is_at_end() { + if self.previous().token_type == TokenType::Semicolon { + return; + } + + match self.peek().token_type { + TokenType::Class + | TokenType::Fun + | TokenType::Var + | TokenType::For + | TokenType::If + | TokenType::While + | TokenType::Print + | TokenType::Return => return, + _ => {} + } + + self.advance(); + } + } +} diff --git a/src/frontend/source_registry.rs b/src/frontend/source_registry.rs new file mode 100644 index 0000000..a0543fa --- /dev/null +++ b/src/frontend/source_registry.rs @@ -0,0 +1,201 @@ +use std::{ + fmt::{Debug, Display}, + fs, + io::ErrorKind, +}; + +use crate::result::{LoxError, LoxResult}; + +pub struct SourceRegistry { + pub sources: Vec, +} + +pub type SourceId = usize; + +pub enum SourceType { + File, + String, +} + +pub struct SourceFile { + pub id: SourceId, + pub source_type: SourceType, + pub file_name: String, + pub content: String, +} + +impl SourceFile { + pub fn new(id: SourceId, source_type: SourceType, file_name: String, content: String) -> Self { + Self { + id, + source_type, + file_name, + content, + } + } + pub fn new_source_file(id: SourceId, file_name: String, content: String) -> Self { + Self { + id, + source_type: SourceType::File, + file_name, + content, + } + } + pub fn new_source_string(id: SourceId, content: String) -> Self { + Self { + id, + source_type: SourceType::String, + file_name: String::new(), + content, + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct SourcePosition { + pub line: usize, + pub column: usize, +} + +impl Display for SourcePosition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} + +#[derive(Clone, PartialEq, Default)] +pub struct SourceSlice { + pub source_id: SourceId, + pub start_position: SourcePosition, + pub end_position: SourcePosition, +} + +impl Debug for SourceSlice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "SourceSlice \n{{ \n\tsource_id: {:?},\n\tstart_position: {:?},\n\tend_position: {:?}, \n\tline: {:?} \n}}", + self.source_id, self.start_position, self.end_position, self.source_id + ) + } +} + +impl Display for SourceSlice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "SourceSlice \n{{ \n\tsource_id: {},\n\tstart_position: {},\n\tend_position: {}, \n\tline: {} \n}}", + self.source_id, self.start_position, self.end_position, self.source_id + ) + } +} + +impl SourceSlice { + pub fn from_positions( + source_id: SourceId, + start_position: SourcePosition, + end_position: SourcePosition, + ) -> Self { + Self { + source_id, + start_position, + end_position, + } + } + pub fn tree_point( + source_id: SourceId, + column_start: usize, + column_end: usize, + line: usize, + ) -> Self { + Self { + source_id, + start_position: SourcePosition { + line, + column: column_start, + }, + end_position: SourcePosition { + line, + column: column_end, + }, + } + } +} + +impl SourceRegistry { + pub fn new() -> Self { + Self { + sources: Vec::new(), + } + } + + fn read_file_detailed(path: &str) -> LoxResult { + match fs::read_to_string(path) { + Ok(content) => Ok(content), + Err(error) => { + let error_msg = match error.kind() { + ErrorKind::NotFound => { + format!("File '{}' not found", path) + } + ErrorKind::PermissionDenied => { + format!("Permission denied reading '{}'", path) + } + ErrorKind::InvalidData => { + format!("File '{}' contains invalid UTF-8", path) + } + _ => { + format!("Failed to read '{}': {}", path, error) + } + }; + Err(LoxError::IoError { message: error_msg }) + } + } + } + pub fn add_source_file(&mut self, filename: String) -> LoxResult { + let content = Self::read_file_detailed(&filename)?; + let source_id = self.sources.len(); + self.sources.push(SourceFile { + id: source_id, + source_type: SourceType::File, + file_name: filename, + content, + }); + Ok(source_id) + } + + pub fn add_source_string(&mut self, content: String) -> LoxResult { + let source_id = self.sources.len(); + self.sources.push(SourceFile { + id: source_id, + source_type: SourceType::String, + file_name: String::new(), + content, + }); + Ok(source_id) + } + + pub fn get_by_id(&self, id: SourceId) -> &SourceFile { + self.sources.get(id).unwrap() + } + + pub fn get_source_code(&self, source: &SourceSlice) -> String { + let lines = self + .sources + .get(source.source_id) + .unwrap() + .content + .split('\n') + .collect::>(); + + let mut line_of_code = String::new(); + lines + .get(source.start_position.line..source.end_position.line + 1) + .unwrap() + .iter() + .for_each(|line| { + line_of_code.push_str(line); + line_of_code.push('\n'); + }); + line_of_code + } +} diff --git a/src/frontend/tokens.rs b/src/frontend/tokens.rs new file mode 100644 index 0000000..6a7d81f --- /dev/null +++ b/src/frontend/tokens.rs @@ -0,0 +1,184 @@ +use std::fmt; + +use crate::frontend::source_registry::SourceSlice; + +#[derive(Debug, Clone, PartialEq)] +pub enum LiteralValue { + Identifier(String), + String(String), + Number(f64), + Boolean(bool), + Nil, +} + +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"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TokenType { + // Single character tokens + LeftParen, + RightParen, + LeftBrace, + RightBrace, + LeftBracket, + RightBracket, + Comma, + Dot, + Minus, + Plus, + Semicolon, + Slash, + Star, + Percent, + + // One or two character tokens + Bang, + BangEqual, + Equal, + EqualEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + + // Literals + Identifier, + String, + Number, + + // Keywords + And, + Class, + StartBlock, + EndBlock, + False, + True, + Fun, + For, + While, + If, + Elif, + Else, + Nil, + Or, + Print, + Return, + Super, + This, + Var, + Val, + + Eof, +} + +impl fmt::Display for TokenType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TokenType::Bang => write!(f, "!"), + TokenType::BangEqual => write!(f, "!="), + TokenType::Equal => write!(f, "="), + TokenType::EqualEqual => write!(f, "=="), + TokenType::Greater => write!(f, ">"), + TokenType::GreaterEqual => write!(f, ">="), + TokenType::Less => write!(f, "<"), + TokenType::LessEqual => write!(f, "<="), + TokenType::Identifier => write!(f, "IDENTIFIER"), + TokenType::String => write!(f, "STRING"), + TokenType::Number => write!(f, "NUMBER"), + TokenType::And => write!(f, "and"), + TokenType::Class => write!(f, "class"), + TokenType::Else => write!(f, "else"), + TokenType::False => write!(f, "false"), + TokenType::True => write!(f, "true"), + TokenType::Fun => write!(f, "fun"), + TokenType::For => write!(f, "for"), + TokenType::If => write!(f, "if"), + TokenType::Nil => write!(f, "nil"), + TokenType::Or => write!(f, "or"), + TokenType::Print => write!(f, "print"), + TokenType::Return => write!(f, "return"), + TokenType::Super => write!(f, "super"), + TokenType::This => write!(f, "this"), + TokenType::Var => write!(f, "var"), + TokenType::While => write!(f, "while"), + TokenType::Eof => write!(f, "EOF"), + TokenType::LeftParen => write!(f, "("), + TokenType::RightParen => write!(f, ")"), + TokenType::LeftBrace => write!(f, "{{"), + TokenType::RightBrace => write!(f, "}}"), + TokenType::LeftBracket => write!(f, "["), + TokenType::RightBracket => write!(f, "]"), + TokenType::Comma => write!(f, ","), + TokenType::Dot => write!(f, "."), + TokenType::Minus => write!(f, "-"), + TokenType::Plus => write!(f, "+"), + TokenType::Semicolon => write!(f, ";"), + TokenType::Slash => write!(f, "/"), + TokenType::Star => write!(f, "*"), + TokenType::StartBlock => write!(f, "do"), + TokenType::EndBlock => write!(f, "end"), + TokenType::Elif => write!(f, "elif"), + TokenType::Val => write!(f, "val"), + TokenType::Percent => write!(f, "%"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Token { + pub token_type: TokenType, + pub lexeme: String, + pub literal: Option, + pub source_slice: SourceSlice, +} + +impl Token { + pub fn new(token_type: TokenType, lexeme: String, source_slice: SourceSlice) -> Self { + Token { + token_type, + lexeme, + literal: None, + source_slice, + } + } + pub fn new_complete( + token_type: TokenType, + lexeme: String, + literal: Option, + source_slice: SourceSlice, + ) -> Self { + Token { + token_type, + lexeme, + literal, + source_slice, + } + } +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.literal { + Some(literal) => write!( + f, + "{:?} {} {:?} ref: {:?}", + self.token_type, self.lexeme, literal, self.source_slice + ), + None => write!( + f, + "{:?} {} None ref: {:?}", + self.token_type, self.lexeme, self.source_slice + ), + } + } +} diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs new file mode 100644 index 0000000..0c0e35b --- /dev/null +++ b/src/logging/display_ast.rs @@ -0,0 +1,581 @@ +//! Pretty printing module for AST nodes +//! +//! 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}, + source_registry::SourceSlice, +}; +use std::fmt::{self, Write}; + +/// Configuration for pretty printing output +#[derive(Clone, Debug)] +pub struct PrettyConfig { + /// Indentation string (default: " ") + pub indent: String, + /// Maximum depth before truncation (None = unlimited) + pub max_depth: Option, + /// Show source position information + pub show_positions: bool, + /// Use compact single-line format when possible + pub compact: bool, + /// Include type information + pub show_types: bool, + /// Use tree-style connectors + pub tree_style: bool, +} + +impl Default for PrettyConfig { + fn default() -> Self { + Self { + indent: " ".to_string(), + max_depth: None, + show_positions: true, + compact: false, + show_types: true, + tree_style: false, + } + } +} + +impl PrettyConfig { + /// Create a compact configuration + pub fn compact() -> Self { + Self { + compact: true, + show_positions: false, + ..Default::default() + } + } + + /// Create a tree-style configuration + pub fn tree() -> Self { + Self { + tree_style: true, + indent: "│ ".to_string(), + ..Default::default() + } + } + + /// Create a debug configuration with all details + pub fn debug() -> Self { + Self { + show_positions: true, + show_types: true, + compact: false, + max_depth: None, + ..Default::default() + } + } +} + +/// Context for pretty printing, tracks depth and styling +pub struct PrettyContext { + config: PrettyConfig, + depth: usize, + is_last: Vec, // For tree-style formatting +} + +impl PrettyContext { + pub fn new(config: PrettyConfig) -> Self { + Self { + config, + depth: 0, + is_last: Vec::new(), + } + } + + fn indent(&self) -> String { + if self.config.tree_style { + self.tree_indent() + } else { + self.config.indent.repeat(self.depth) + } + } + + fn tree_indent(&self) -> String { + let mut result = String::new(); + for (i, &is_last) in self.is_last.iter().enumerate() { + if i == self.is_last.len() - 1 { + result.push_str(if is_last { "└─ " } else { "├─ " }); + } else { + result.push_str(if is_last { " " } else { "│ " }); + } + } + result + } + + fn child_context(&self, is_last: bool) -> Self { + let mut new_is_last = self.is_last.clone(); + new_is_last.push(is_last); + + Self { + config: self.config.clone(), + depth: self.depth + 1, + is_last: new_is_last, + } + } + + fn should_truncate(&self) -> bool { + self.config.max_depth.map_or(false, |max| self.depth >= max) + } +} + +/// Trait for pretty printing AST nodes +pub trait PrettyPrint { + fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result; +} + +/// Convenience functions to get pretty printed strings +pub fn pretty_print_with_config(item: &T, config: &PrettyConfig) -> String { + let mut output = String::new(); + let ctx = PrettyContext::new(config.clone()); + write!(&mut output, "{}", PrettyWrapper { item, ctx: &ctx }).unwrap(); + output +} + +pub fn pretty_print(item: &T) -> String { + pretty_print_with_config(item, &PrettyConfig::default()) +} + +/// Wrapper for pretty printing with Display trait +struct PrettyWrapper<'a, T: PrettyPrint> { + item: &'a T, + ctx: &'a PrettyContext, +} + +impl<'a, T: PrettyPrint> fmt::Display for PrettyWrapper<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.item.pretty_print(self.ctx, f) + } +} + +impl PrettyPrint for Expr { + fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { + if ctx.should_truncate() { + return write!(f, "{}...", ctx.indent()); + } + + let indent = ctx.indent(); + + match self { + Expr::Literal { value } => { + if ctx.config.compact { + write!(f, "{:?}", value) + } else { + write!(f, "{}Literal {{ value: {:?} }}", indent, value) + } + } + + Expr::Binary { + left, + operator, + right, + } => { + if ctx.config.compact { + // For compact, we need to format recursively + let left_str = + pretty_print_with_config(left.as_ref(), &PrettyConfig::compact()); + let right_str = + pretty_print_with_config(right.as_ref(), &PrettyConfig::compact()); + write!(f, "({} {:?} {})", left_str, operator, right_str) + } else { + writeln!(f, "{}Binary {{", indent)?; + writeln!( + f, + "{}operator: {:?},", + ctx.child_context(false).indent(), + operator + )?; + write!(f, "{}left: ", ctx.child_context(false).indent())?; + left.pretty_print(&ctx.child_context(false), f)?; + writeln!(f)?; + write!(f, "{}right: ", ctx.child_context(true).indent())?; + right.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Expr::Unary { operator, operand } => { + if ctx.config.compact { + let operand_str = + pretty_print_with_config(operand.as_ref(), &PrettyConfig::compact()); + write!(f, "({:?} {})", operator, operand_str) + } else { + writeln!(f, "{}Unary {{", indent)?; + writeln!( + f, + "{}operator: {:?},", + ctx.child_context(false).indent(), + operator + )?; + write!(f, "{}operand: ", ctx.child_context(true).indent())?; + operand.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Expr::Grouping { expression } => { + if ctx.config.compact { + let expr_str = + pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); + write!(f, "({})", expr_str) + } else { + writeln!(f, "{}Grouping {{", indent)?; + write!(f, "{}expression: ", ctx.child_context(true).indent())?; + expression.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Expr::Variable { name } => { + if ctx.config.compact { + write!(f, "{}", name) + } else { + write!(f, "{}Variable {{ name: {:?} }}", indent, name) + } + } + } + } +} + +impl PrettyPrint for Stmt { + fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { + if ctx.should_truncate() { + return write!(f, "{}...", ctx.indent()); + } + + let indent = ctx.indent(); + + match self { + Stmt::Expression { expression } => { + if ctx.config.compact { + let expr_str = + pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); + write!(f, "{};", expr_str) + } else { + writeln!(f, "{}Expression {{", indent)?; + write!(f, "{}expression: ", ctx.child_context(true).indent())?; + expression.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Stmt::Print { expression } => { + if ctx.config.compact { + let expr_str = + pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); + write!(f, "print {};", expr_str) + } else { + writeln!(f, "{}Print {{", indent)?; + write!(f, "{}expression: ", ctx.child_context(true).indent())?; + expression.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Stmt::Var { name, initializer } => { + if ctx.config.compact { + match initializer { + Some(init) => { + let init_str = + pretty_print_with_config(init.as_ref(), &PrettyConfig::compact()); + write!(f, "var {} = {};", name, init_str) + } + None => write!(f, "var {};", name), + } + } else { + writeln!(f, "{}Var {{", indent)?; + writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?; + match initializer { + Some(init) => { + write!(f, "{}initializer: Some(", ctx.child_context(true).indent())?; + init.pretty_print(&ctx.child_context(true), f)?; + writeln!(f, "),")?; + } + None => { + writeln!(f, "{}initializer: None,", ctx.child_context(true).indent())? + } + } + write!(f, "{}}}", indent) + } + } + + Stmt::Assign { name, value } => { + if ctx.config.compact { + let value_str = + pretty_print_with_config(value.as_ref(), &PrettyConfig::compact()); + write!(f, "{} = {};", name, value_str) + } else { + writeln!(f, "{}Assign {{", indent)?; + writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?; + write!(f, "{}value: ", ctx.child_context(true).indent())?; + value.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Stmt::Return { expression } => { + if ctx.config.compact { + let expr_str = + pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); + write!(f, "return {};", expr_str) + } else { + writeln!(f, "{}Return {{", indent)?; + write!(f, "{}expression: ", ctx.child_context(true).indent())?; + expression.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } + + Stmt::Block { statements } => { + if ctx.config.compact { + write!(f, "{{ ... ({} statements) }}", statements.len()) + } else { + writeln!(f, "{}Block {{", indent)?; + writeln!(f, "{}statements: [", ctx.child_context(false).indent())?; + for (i, stmt) in statements.iter().enumerate() { + let is_last = i == statements.len() - 1; + stmt.pretty_print(&ctx.child_context(false).child_context(is_last), f)?; + if !is_last { + writeln!(f, ",")?; + } else { + writeln!(f)?; + } + } + writeln!(f, "{}]", ctx.child_context(false).indent())?; + write!(f, "{}}}", indent) + } + } + + Stmt::If { + condition, + then_branch, + elif_branch, + else_branch, + } => { + if ctx.config.compact { + let cond_str = + pretty_print_with_config(condition.as_ref(), &PrettyConfig::compact()); + write!(f, "if {} {{ ... }}", cond_str) + } else { + writeln!(f, "{}If {{", indent)?; + write!(f, "{}condition: ", ctx.child_context(false).indent())?; + condition.pretty_print(&ctx.child_context(false), f)?; + writeln!(f, ",")?; + write!(f, "{}then_branch: ", ctx.child_context(false).indent())?; + then_branch.pretty_print(&ctx.child_context(false), f)?; + writeln!(f, ",")?; + + if !elif_branch.is_empty() { + writeln!(f, "{}elif_branches: [", ctx.child_context(false).indent())?; + for (i, (cond, branch)) in elif_branch.iter().enumerate() { + let is_last = i == elif_branch.len() - 1; + let child_ctx = ctx.child_context(false).child_context(is_last); + writeln!(f, "{}(", child_ctx.indent())?; + write!(f, "{}condition: ", child_ctx.child_context(false).indent())?; + cond.pretty_print(&child_ctx.child_context(false), f)?; + writeln!(f, ",")?; + write!(f, "{}branch: ", child_ctx.child_context(true).indent())?; + branch.pretty_print(&child_ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{})", child_ctx.indent())?; + if !is_last { + writeln!(f, ",")?; + } else { + writeln!(f)?; + } + } + writeln!(f, "{}],", ctx.child_context(false).indent())?; + } + + match else_branch { + Some(else_stmt) => { + write!(f, "{}else_branch: Some(", ctx.child_context(true).indent())?; + else_stmt.pretty_print(&ctx.child_context(true), f)?; + writeln!(f, "),")?; + } + None => { + writeln!(f, "{}else_branch: None,", ctx.child_context(true).indent())? + } + } + write!(f, "{}}}", indent) + } + } + + Stmt::Stmt { expression } => expression.pretty_print(ctx, f), + } + } +} + +impl PrettyPrint for AstNode { + fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { + let indent = ctx.indent(); + + if ctx.config.compact { + self.node.pretty_print(ctx, f) + } else { + writeln!(f, "{}AstNode {{", indent)?; + + if ctx.config.show_types { + writeln!( + f, + "{}kind: {:?},", + ctx.child_context(false).indent(), + self.node.kind() + )?; + } + + write!( + f, + "{}node: ", + ctx.child_context(!ctx.config.show_positions).indent() + )?; + self.node + .pretty_print(&ctx.child_context(!ctx.config.show_positions), f)?; + writeln!(f, ",")?; + + if ctx.config.show_positions { + writeln!( + f, + "{}source_slice: {:?},", + ctx.child_context(true).indent(), + self.source_slice + )?; + } + + write!(f, "{}}}", indent) + } + } +} + +// Extension trait for convenience methods +pub trait PrettyPrintExt { + fn pretty(&self) -> String; + fn pretty_compact(&self) -> String; + fn pretty_tree(&self) -> String; + fn pretty_with_config(&self, config: &PrettyConfig) -> String; +} + +impl PrettyPrintExt for Expr { + fn pretty(&self) -> String { + pretty_print(self) + } + + fn pretty_compact(&self) -> String { + pretty_print_with_config(self, &PrettyConfig::compact()) + } + + fn pretty_tree(&self) -> String { + pretty_print_with_config(self, &PrettyConfig::tree()) + } + + fn pretty_with_config(&self, config: &PrettyConfig) -> String { + pretty_print_with_config(self, config) + } +} + +impl PrettyPrintExt for Stmt { + fn pretty(&self) -> String { + pretty_print(self) + } + + fn pretty_compact(&self) -> String { + pretty_print_with_config(self, &PrettyConfig::compact()) + } + + fn pretty_tree(&self) -> String { + pretty_print_with_config(self, &PrettyConfig::tree()) + } + + fn pretty_with_config(&self, config: &PrettyConfig) -> String { + pretty_print_with_config(self, config) + } +} + +impl PrettyPrintExt for AstNode { + fn pretty(&self) -> String { + pretty_print(self) + } + + fn pretty_compact(&self) -> String { + pretty_print_with_config(self, &PrettyConfig::compact()) + } + + fn pretty_tree(&self) -> String { + pretty_print_with_config(self, &PrettyConfig::tree()) + } + + fn pretty_with_config(&self, config: &PrettyConfig) -> String { + pretty_print_with_config(self, config) + } +} + +/// Convenience functions for quick pretty printing +pub fn pretty_expr(expr: &Expr) -> String { + pretty_print(expr) +} + +pub fn pretty_stmt(stmt: &Stmt) -> String { + pretty_print(stmt) +} + +pub fn pretty_expr_compact(expr: &Expr) -> String { + pretty_print_with_config(expr, &PrettyConfig::compact()) +} + +pub fn pretty_stmt_compact(stmt: &Stmt) -> String { + pretty_print_with_config(stmt, &PrettyConfig::compact()) +} + +pub fn pretty_tree(item: &T) -> String { + pretty_print_with_config(item, &PrettyConfig::tree()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::frontend::{ + source_registry::{SourceId, SourcePosition, SourceSlice}, + tokens::{LiteralValue, TokenType}, + }; + + #[test] + fn test_pretty_expr_literal() { + let expr = Expr::Literal { + value: LiteralValue::Number(42.0), + }; + + let compact = expr.pretty_compact(); + assert!(compact.contains("42")); + + let full = expr.pretty(); + assert!(full.contains("Literal")); + assert!(full.contains("42")); + } + + #[test] + fn test_pretty_expr_binary() { + let expr = Expr::Binary { + left: Box::new(Expr::Literal { + value: LiteralValue::Number(1.0), + }), + operator: TokenType::Plus, + right: Box::new(Expr::Literal { + value: LiteralValue::Number(2.0), + }), + }; + + let compact = expr.pretty_compact(); + assert!(compact.contains("1")); + assert!(compact.contains("2")); + } +} diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/logging/mod.rs b/src/logging/mod.rs new file mode 100644 index 0000000..2666c51 --- /dev/null +++ b/src/logging/mod.rs @@ -0,0 +1 @@ +pub mod display_ast; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6b526ed --- /dev/null +++ b/src/main.rs @@ -0,0 +1,109 @@ +mod backend; +mod frontend; +mod logging; +mod prompt; +mod result; + +use crate::{ + backend::{ + environment::Environment, + interpreter::{EvaluateInterpreter, Interpreter}, + }, + frontend::{ + lexer::Lexer, + parser::Parser, + source_registry::{SourceFile, SourceId, SourcePosition, SourceRegistry, SourceSlice}, + }, + prompt::prompt_lines, + result::{LoxError, LoxResult}, +}; +use std::fs; +use std::{env, error::Error}; + +fn main() -> LoxResult<()> { + let args: Vec = env::args().collect(); + let mut lox = LoxInterpreter::new(); + + if args.len() > 2 { + eprintln!("Usage: {} [script]", args[0]); + std::process::exit(64); + } else if args.len() == 2 { + // Esegui file + lox.run_file(&args[1])?; + } else { + // Modalità interattiva + lox.run_prompt()?; + } + + Ok(()) +} + +struct LoxInterpreter { + source_registry: SourceRegistry, +} + +impl LoxInterpreter { + pub fn new() -> Self { + LoxInterpreter { + source_registry: SourceRegistry::new(), + } + } + + // ✅ Pipeline funzionale per file + fn run_file(&mut self, path: &str) -> 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)) + .map(|result| println!("file {}", result)) + } + + // ✅ Pipeline funzionale per prompt + fn run_prompt(&mut self) -> LoxResult<()> { + println!("Lox REPL - Type 'exit' to quit"); + + prompt_lines(|source| self.source_registry.add_source_string(source)) + .for_each(|line| println!("hi: {}", line)); + + Ok(()) + } + + fn process_source(&self, source_id: SourceId) -> LoxResult { + let mut lexer = Lexer::new( + self.source_registry.get_by_id(source_id).content.clone(), + source_id, + ); + let res = lexer + .scans_tokens() + .and_then(|tokens| Parser::new(tokens).parse()) + .and_then(|asts| { + // println!("asts: {:?}", asts); + Ok(asts) + }) + .and_then(|stmts| { + let mut env = Environment::new(); + let mut interpreter = Interpreter::new(&mut env); + let mut result = None; + for stmt in stmts { + result = Some(interpreter.interpret(stmt)?); + } + match result { + Some(res) => Ok(res), + _ => Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), + message: "what?????".to_string(), + }), + } + }) + .and_then(|result| Ok(format!("{}", result))) + .or_else(|err| { + err.print_with_context(&self.source_registry); + Err(err) + }); + // println!("result {}", res); + + res + } +} diff --git a/src/prompt.rs b/src/prompt.rs new file mode 100644 index 0000000..88218b1 --- /dev/null +++ b/src/prompt.rs @@ -0,0 +1,29 @@ +use std::io::{self, Write}; + +use crate::{frontend::source_registry::SourceId, result::LoxResult}; + +pub fn prompt_lines(mut process_source: T) -> impl Iterator +where + T: FnMut(String) -> LoxResult, +{ + std::iter::from_fn(move || { + print!("> "); + io::stdout().flush().ok()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input).ok()?; + + let line = input.trim(); + if line == "exit" || line.is_empty() { + None + } else { + match process_source(line.to_string()) { + Ok(source_id) => Some(source_id), + Err(err) => { + eprintln!("Error: {}", err); + None + } + } + } + }) +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..0b62ee3 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,153 @@ +use crate::frontend::source_registry::{SourcePosition, SourceRegistry, SourceSlice}; + +#[derive(Debug, Clone)] +pub enum LoxError { + LexicalError { + source_slice: SourceSlice, + message: String, + }, + RuntimeError { + source_slice: SourceSlice, + message: String, + }, + ParseError { + source_slice: SourceSlice, + message: String, + }, + IoError { + message: String, + }, + TypeMismatch { + source_slice: SourceSlice, + expected: String, + found: String, + }, +} + +impl LoxError { + pub fn get_message(&self) -> String { + match self { + LoxError::LexicalError { message, .. } => message.clone(), + LoxError::RuntimeError { message, .. } => message.clone(), + LoxError::ParseError { message, .. } => message.clone(), + LoxError::IoError { message } => message.clone(), + LoxError::TypeMismatch { + expected, found, .. + } => { + format!("expected {}, found {}", expected, found) + } + } + } + + pub fn get_source_slice(&self) -> Option { + match self { + LoxError::LexicalError { source_slice, .. } => Some(source_slice.clone()), + LoxError::RuntimeError { source_slice, .. } => Some(source_slice.clone()), + LoxError::ParseError { source_slice, .. } => Some(source_slice.clone()), + LoxError::IoError { .. } => None, + LoxError::TypeMismatch { source_slice, .. } => Some(source_slice.clone()), + } + } + + pub fn print_with_context(&self, source_registry: &SourceRegistry) { + match self.get_source_slice() { + Some(source_slice) => { + let source_code = source_registry.get_source_code(&source_slice); + println!( + "\n =============================================== \n DATA:{} \n CODE \n {} | {}", + self, source_slice.start_position.line ,source_code + ); + } + None => println!("{}", self), + } + } +} + +fn fetch_source_code(path: &str, start: SourcePosition, end: SourcePosition) -> LoxResult { + let source_code = std::fs::read_to_string(path).map_err(|e| LoxError::IoError { + message: format!("Failed to read file: {}", e), + })?; + + let lines: Vec<&str> = source_code.split('\n').collect(); + + // Verifica bounds + if start.line >= lines.len() || end.line >= lines.len() { + return Err(LoxError::IoError { + message: "Line number out of bounds".to_string(), + }); + } + + let mut result = String::new(); + + if start.line == end.line { + // Caso speciale: stessa riga + let line = lines[start.line]; + if end.column <= line.len() && start.column <= end.column { + result.push_str(&line[start.column..end.column]); + } + } else { + // Caso generale: righe multiple + + // Prima riga: da start.column alla fine della riga + let first_line = lines[start.line]; + if start.column < first_line.len() { + result.push_str(&first_line[start.column..]); + } + result.push('\n'); + + // Righe intermedie: complete + for i in (start.line + 1)..end.line { + result.push_str(lines[i]); + result.push('\n'); + } + + // Ultima riga: dall'inizio fino a end.column + let last_line = lines[end.line]; + if end.column <= last_line.len() { + result.push_str(&last_line[..end.column]); + } + } + + Ok(result) +} + +impl std::fmt::Display for LoxError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LoxError::LexicalError { + source_slice, + message, + } => { + write!(f, "Lexical error on {:?}: \n {}", source_slice, message) + } + LoxError::RuntimeError { + source_slice, + message, + } => { + write!(f, "Runtime error on {:?}: \n{}", source_slice, message) + } + LoxError::ParseError { + source_slice, + message, + } => { + write!(f, "Parse error on {:?}: \n{}", source_slice, message) + } + LoxError::IoError { message } => write!(f, "IO error: {}", message), + LoxError::TypeMismatch { + source_slice, + expected, + found, + } => { + write!( + f, + "Type mismatch on {:?}: \n expected {}, found {}", + source_slice, expected, found + ) + } + } + } +} + +impl std::error::Error for LoxError {} + +pub type LoxResult = Result;