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.
This commit is contained in:
Giulio Agostini
2025-10-03 19:07:12 +02:00
commit a7df45dc72
18 changed files with 2710 additions and 0 deletions
+44
View File
@@ -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<String, LiteralValue>,
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<LiteralValue> {
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);
}
}
+325
View File
@@ -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<LiteralValue>;
fn neg(self) -> Self::Output {
Ok(LiteralValue::Boolean(!self.is_truthy()))
}
}
impl Add for LiteralValue {
type Output = LoxResult<LiteralValue>;
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<LiteralValue>;
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<LiteralValue>;
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<LiteralValue>;
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<LiteralValue>;
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<std::cmp::Ordering> {
match (self, other) {
(LiteralValue::Number(a), LiteralValue::Number(b)) => a.partial_cmp(b),
_ => None,
}
}
}
impl From<LiteralValue> 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<LiteralValue> {
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<T> {
fn interpret(&mut self, stmt: T) -> LoxResult<LiteralValue>;
}
impl<'a, R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter<AstNode<R>>
for Interpreter<'a>
where
Interpreter<'a>: EvaluateInterpreter<R>,
{
fn interpret(&mut self, stmt: AstNode<R>) -> LoxResult<LiteralValue> {
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<Expr> for Interpreter<'a> {
fn interpret(&mut self, stmt: Expr) -> LoxResult<LiteralValue> {
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<Stmt> for Interpreter<'a> {
fn interpret(&mut self, stmt: Stmt) -> LoxResult<LiteralValue> {
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(),
}),
}
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod environment;
pub mod interpreter;
+304
View File
@@ -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<Expr>,
operator: TokenType,
right: Box<Expr>,
},
Unary {
operator: TokenType,
operand: Box<Expr>,
},
Grouping {
expression: Box<Expr>,
},
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<Expr>,
},
Print {
expression: Box<Expr>,
},
Stmt {
expression: Box<Expr>,
},
Var {
name: String,
initializer: Option<Box<Expr>>,
},
Assign {
name: String,
value: Box<Expr>,
},
Return {
expression: Box<Expr>,
},
Block {
statements: Box<Vec<Stmt>>,
},
If {
condition: Box<Expr>,
then_branch: Box<Stmt>,
elif_branch: Vec<(Box<Expr>, Box<Stmt>)>,
else_branch: Option<Box<Stmt>>,
},
}
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::<Vec<_>>()
.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::<Vec<_>>()
.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<T: AstNodeKind + Debug + Display> {
pub node: T,
pub source_slice: SourceSlice,
}
impl<T: AstNodeKind + Debug + Display> Display for AstNode<T> {
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<T: AstNodeKind + Debug + Display> Debug for AstNode<T> {
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<T: AstNodeKind + Debug + Display> AstNode<T> {
pub fn new(node: T, source_slice: SourceSlice) -> Self {
AstNode { node, source_slice }
}
}
+282
View File
@@ -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<TokenType> {
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<Vec<Token>> {
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<Option<Token>> {
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<Option<Token>> {
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<Option<Token>> {
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<Option<Token>> {
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()),
))),
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod ast;
pub mod lexer;
pub mod parser;
pub mod source_registry;
pub mod tokens;
+437
View File
@@ -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<Token>,
current: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, current: 0 }
}
pub fn parse(&mut self) -> LoxResult<Vec<AstNode<Stmt>>> {
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<AstNode<Stmt>> {
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<Stmt> {
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<Stmt> {
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<Stmt> {
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<Stmt> {
// 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<Stmt> {
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<Stmt> {
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<Stmt> {
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<Expr> {
self.or_and()
}
fn or_and(&mut self) -> LoxResult<Expr> {
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<Expr> {
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<Expr> {
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<Expr> {
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<Expr> {
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<Expr> {
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<Expr> {
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();
}
}
}
+201
View File
@@ -0,0 +1,201 @@
use std::{
fmt::{Debug, Display},
fs,
io::ErrorKind,
};
use crate::result::{LoxError, LoxResult};
pub struct SourceRegistry {
pub sources: Vec<SourceFile>,
}
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<String> {
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<SourceId> {
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<SourceId> {
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::<Vec<&str>>();
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
}
}
+184
View File
@@ -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<LiteralValue>,
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<LiteralValue>,
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
),
}
}
}
+581
View File
@@ -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<usize>,
/// 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<bool>, // 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<T: PrettyPrint>(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<T: PrettyPrint>(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<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrint for AstNode<T> {
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<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrintExt for AstNode<T> {
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<T: PrettyPrint>(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"));
}
}
View File
+1
View File
@@ -0,0 +1 @@
pub mod display_ast;
+109
View File
@@ -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<String> = 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<String> {
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
}
}
+29
View File
@@ -0,0 +1,29 @@
use std::io::{self, Write};
use crate::{frontend::source_registry::SourceId, result::LoxResult};
pub fn prompt_lines<T>(mut process_source: T) -> impl Iterator<Item = SourceId>
where
T: FnMut(String) -> LoxResult<SourceId>,
{
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
}
}
}
})
}
+153
View File
@@ -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<SourceSlice> {
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<String> {
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<T> = Result<T, LoxError>;