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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod environment;
|
||||
pub mod interpreter;
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod ast;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod source_registry;
|
||||
pub mod tokens;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod display_ast;
|
||||
+109
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>;
|
||||
Reference in New Issue
Block a user