Add source location tracking to AST nodes
The commit expands the source location tracking through the AST by: 1. Switching from raw Expr/Stmt nodes to AstNode wrappers containing source slices 2. Updating interpreter and parser to preserve location info through node traversal 3. Enhancing error reporting with richer source context information
This commit is contained in:
+98
-28
@@ -19,6 +19,13 @@ fn error(message: String) -> LoxError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn error_at(source_slice: SourceSlice, message: String) -> LoxError {
|
||||||
|
LoxError::RuntimeError {
|
||||||
|
source_slice,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Not for LiteralValue {
|
impl Not for LiteralValue {
|
||||||
type Output = LiteralValue;
|
type Output = LiteralValue;
|
||||||
|
|
||||||
@@ -68,6 +75,25 @@ impl Add for LiteralValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LiteralValue {
|
||||||
|
pub fn add_with_source(
|
||||||
|
self,
|
||||||
|
other: LiteralValue,
|
||||||
|
source_slice: SourceSlice,
|
||||||
|
) -> LoxResult<LiteralValue> {
|
||||||
|
match (self, other) {
|
||||||
|
(LiteralValue::Number(a), LiteralValue::Number(b)) => Ok(LiteralValue::Number(a + b)),
|
||||||
|
(LiteralValue::String(a), LiteralValue::String(b)) => {
|
||||||
|
Ok(LiteralValue::String(format!("{}{}", a, b)))
|
||||||
|
}
|
||||||
|
_ => Err(error_at(
|
||||||
|
source_slice,
|
||||||
|
"Cannot add non-numeric values".to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Sub for LiteralValue {
|
impl Sub for LiteralValue {
|
||||||
type Output = LoxResult<LiteralValue>;
|
type Output = LoxResult<LiteralValue>;
|
||||||
|
|
||||||
@@ -154,15 +180,16 @@ impl<'a> Interpreter<'a> {
|
|||||||
|
|
||||||
fn interpret_binary(
|
fn interpret_binary(
|
||||||
&mut self,
|
&mut self,
|
||||||
left: Expr,
|
left: AstNode<Expr>,
|
||||||
operator: TokenType,
|
operator: TokenType,
|
||||||
right: Expr,
|
right: AstNode<Expr>,
|
||||||
|
source_slice: SourceSlice,
|
||||||
) -> LoxResult<LiteralValue> {
|
) -> LoxResult<LiteralValue> {
|
||||||
let left_value = self.interpret(left)?;
|
let left_value = self.interpret(left)?;
|
||||||
let right_value = self.interpret(right)?;
|
let right_value = self.interpret(right)?;
|
||||||
match operator {
|
match operator {
|
||||||
TokenType::Minus => left_value - right_value,
|
TokenType::Minus => left_value - right_value,
|
||||||
TokenType::Plus => left_value + right_value,
|
TokenType::Plus => left_value.add_with_source(right_value, source_slice.clone()),
|
||||||
TokenType::Slash => left_value / right_value,
|
TokenType::Slash => left_value / right_value,
|
||||||
TokenType::Star => left_value * right_value,
|
TokenType::Star => left_value * right_value,
|
||||||
TokenType::EqualEqual => Ok(LiteralValue::Boolean(left_value == right_value)),
|
TokenType::EqualEqual => Ok(LiteralValue::Boolean(left_value == right_value)),
|
||||||
@@ -207,31 +234,76 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct Expr evaluation to avoid infinite recursion
|
||||||
impl<'a> EvaluateInterpreter<Expr> for Interpreter<'a> {
|
impl<'a> EvaluateInterpreter<Expr> for Interpreter<'a> {
|
||||||
fn interpret(&mut self, stmt: Expr) -> LoxResult<LiteralValue> {
|
fn interpret(&mut self, expr: Expr) -> LoxResult<LiteralValue> {
|
||||||
match stmt {
|
match expr {
|
||||||
Expr::Literal { value } => Ok(value.clone()),
|
Expr::Literal { value } => Ok(value),
|
||||||
|
Expr::Variable { name } => self.enviorment.get(&name),
|
||||||
Expr::Binary {
|
Expr::Binary {
|
||||||
left,
|
left,
|
||||||
operator,
|
operator,
|
||||||
right,
|
right,
|
||||||
} => self.interpret_binary(*left, operator, *right),
|
} => {
|
||||||
Expr::Unary { operator, operand } => {
|
let left_val = self.interpret(*left)?;
|
||||||
let right = self.interpret(*operand)?;
|
let right_val = self.interpret(*right)?;
|
||||||
match operator {
|
self.evaluate_binary(left_val, operator, right_val)
|
||||||
TokenType::Minus => Ok((-right)?),
|
|
||||||
TokenType::Bang => Ok(!right),
|
|
||||||
_ => Err(error("Unsupported unary operator".to_string())),
|
|
||||||
}
|
}
|
||||||
|
Expr::Unary { operator, operand } => {
|
||||||
|
let operand_val = self.interpret(*operand)?;
|
||||||
|
self.evaluate_unary(operator, operand_val)
|
||||||
}
|
}
|
||||||
Expr::Grouping { expression } => self.interpret(*expression),
|
Expr::Grouping { expression } => self.interpret(*expression),
|
||||||
Expr::Variable { name } => self.enviorment.get(&name),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EvaluateInterpreter<Stmt> for Interpreter<'a> {
|
impl<'a> EvaluateInterpreter<AstNode<Stmt>> for Interpreter<'a> {
|
||||||
fn interpret(&mut self, stmt: Stmt) -> LoxResult<LiteralValue> {
|
fn interpret(&mut self, node: AstNode<Stmt>) -> LoxResult<LiteralValue> {
|
||||||
|
let stmt = node.node;
|
||||||
|
let _source_slice = node.source_slice;
|
||||||
|
self.interpret_stmt_inner(stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Interpreter<'a> {
|
||||||
|
fn evaluate_binary(
|
||||||
|
&mut self,
|
||||||
|
left: LiteralValue,
|
||||||
|
operator: TokenType,
|
||||||
|
right: LiteralValue,
|
||||||
|
) -> LoxResult<LiteralValue> {
|
||||||
|
match operator {
|
||||||
|
TokenType::Plus => left + right,
|
||||||
|
TokenType::Minus => left - right,
|
||||||
|
TokenType::Star => left * right,
|
||||||
|
TokenType::Slash => left / right,
|
||||||
|
TokenType::Greater => Ok(LiteralValue::Boolean(left > right)),
|
||||||
|
TokenType::GreaterEqual => Ok(LiteralValue::Boolean(left >= right)),
|
||||||
|
TokenType::Less => Ok(LiteralValue::Boolean(left < right)),
|
||||||
|
TokenType::LessEqual => Ok(LiteralValue::Boolean(left <= right)),
|
||||||
|
TokenType::EqualEqual => Ok(LiteralValue::Boolean(left == right)),
|
||||||
|
TokenType::BangEqual => Ok(LiteralValue::Boolean(left != right)),
|
||||||
|
_ => Err(error(format!(
|
||||||
|
"Unsupported binary operator: {:?}",
|
||||||
|
operator
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_unary(
|
||||||
|
&mut self,
|
||||||
|
operator: TokenType,
|
||||||
|
operand: LiteralValue,
|
||||||
|
) -> LoxResult<LiteralValue> {
|
||||||
|
match operator {
|
||||||
|
TokenType::Minus => -operand,
|
||||||
|
TokenType::Bang => Ok(!operand),
|
||||||
|
_ => Err(error(format!("Unsupported unary operator: {:?}", operator))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult<LiteralValue> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Expression { expression } => self.interpret(*expression),
|
Stmt::Expression { expression } => self.interpret(*expression),
|
||||||
Stmt::Print { expression } => {
|
Stmt::Print { expression } => {
|
||||||
@@ -241,11 +313,12 @@ impl<'a> EvaluateInterpreter<Stmt> for Interpreter<'a> {
|
|||||||
}
|
}
|
||||||
Stmt::Block { statements } => {
|
Stmt::Block { statements } => {
|
||||||
let (elements, final_expr) = match statements.split_last() {
|
let (elements, final_expr) = match statements.split_last() {
|
||||||
Some((Stmt::Expression { expression }, body)) => (body, Some(expression)),
|
Some((stmt, body)) => match &stmt.node {
|
||||||
Some((Stmt::Return { expression }, body)) => (body, Some(expression)),
|
Stmt::Expression { expression } => (body, Some(expression)),
|
||||||
Some((_last_stmt, _body)) => {
|
Stmt::Return { expression } => (body, Some(expression)),
|
||||||
(statements.as_slice(), None) // Non è un'Expression finale
|
_ => (statements.as_slice(), None),
|
||||||
}
|
},
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
(&[][..], None) // Blocco vuoto
|
(&[][..], None) // Blocco vuoto
|
||||||
}
|
}
|
||||||
@@ -258,14 +331,11 @@ impl<'a> EvaluateInterpreter<Stmt> for Interpreter<'a> {
|
|||||||
|
|
||||||
// Gestisci l'espressione finale se presente
|
// Gestisci l'espressione finale se presente
|
||||||
match final_expr {
|
match final_expr {
|
||||||
Some(expr) => self.interpret((**expr).clone()),
|
Some(expr) => self.interpret(*expr.clone()),
|
||||||
None => Ok(LiteralValue::Nil),
|
None => Ok(LiteralValue::Nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Stmt { expression } => {
|
Stmt::Stmt { expression } => self.interpret(*expression.clone()),
|
||||||
let _ = self.interpret(*expression);
|
|
||||||
Ok(LiteralValue::Nil)
|
|
||||||
}
|
|
||||||
Stmt::Return { expression } => self.interpret(*expression),
|
Stmt::Return { expression } => self.interpret(*expression),
|
||||||
Stmt::Var { name, initializer } => {
|
Stmt::Var { name, initializer } => {
|
||||||
let value = if let Some(expr) = initializer {
|
let value = if let Some(expr) = initializer {
|
||||||
@@ -277,8 +347,8 @@ impl<'a> EvaluateInterpreter<Stmt> for Interpreter<'a> {
|
|||||||
return Ok(LiteralValue::Nil);
|
return Ok(LiteralValue::Nil);
|
||||||
}
|
}
|
||||||
Stmt::Assign { name, value } => {
|
Stmt::Assign { name, value } => {
|
||||||
let value = self.interpret(*value)?;
|
let result = self.interpret(*value)?;
|
||||||
self.enviorment.set(name.clone(), value);
|
self.enviorment.set(name.clone(), result);
|
||||||
Ok(LiteralValue::Nil)
|
Ok(LiteralValue::Nil)
|
||||||
}
|
}
|
||||||
Stmt::If {
|
Stmt::If {
|
||||||
|
|||||||
+80
-59
@@ -38,16 +38,16 @@ pub enum Expr {
|
|||||||
value: LiteralValue,
|
value: LiteralValue,
|
||||||
},
|
},
|
||||||
Binary {
|
Binary {
|
||||||
left: Box<Expr>,
|
left: Box<AstNode<Expr>>,
|
||||||
operator: TokenType,
|
operator: TokenType,
|
||||||
right: Box<Expr>,
|
right: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Unary {
|
Unary {
|
||||||
operator: TokenType,
|
operator: TokenType,
|
||||||
operand: Box<Expr>,
|
operand: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Grouping {
|
Grouping {
|
||||||
expression: Box<Expr>,
|
expression: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Variable {
|
Variable {
|
||||||
name: String,
|
name: String,
|
||||||
@@ -67,13 +67,13 @@ impl ExprPrint for Expr {
|
|||||||
operator,
|
operator,
|
||||||
right,
|
right,
|
||||||
} => {
|
} => {
|
||||||
format!("Binary ({} {} {})", left, operator, right)
|
format!("Binary ({} {} {})", left.node, operator, right.node)
|
||||||
}
|
}
|
||||||
Expr::Unary { operator, operand } => {
|
Expr::Unary { operator, operand } => {
|
||||||
format!("Unary ({} {})", operator, operand)
|
format!("Unary ({} {})", operator, operand.node)
|
||||||
}
|
}
|
||||||
Expr::Grouping { expression } => {
|
Expr::Grouping { expression } => {
|
||||||
format!("Grouping (group {})", expression)
|
format!("Grouping (group {})", expression.node)
|
||||||
}
|
}
|
||||||
Expr::Variable { name } => {
|
Expr::Variable { name } => {
|
||||||
format!("Variable (variable {})", name)
|
format!("Variable (variable {})", name)
|
||||||
@@ -91,9 +91,9 @@ impl std::fmt::Display for Expr {
|
|||||||
left,
|
left,
|
||||||
operator,
|
operator,
|
||||||
right,
|
right,
|
||||||
} => write!(f, "Binary ({} {} {})", left, operator, right),
|
} => write!(f, "Binary ({} {} {})", left.node, operator, right.node),
|
||||||
Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand),
|
Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand.node),
|
||||||
Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression),
|
Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node),
|
||||||
Expr::Variable { name } => write!(f, "Variable (variable {})", name),
|
Expr::Variable { name } => write!(f, "Variable (variable {})", name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,9 +107,9 @@ impl Debug for Expr {
|
|||||||
left,
|
left,
|
||||||
operator,
|
operator,
|
||||||
right,
|
right,
|
||||||
} => write!(f, "({:?} {:?} {:?})", operator, left, right),
|
} => write!(f, "({:?} {:?} {:?})", operator, left.node, right.node),
|
||||||
Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand),
|
Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand.node),
|
||||||
Expr::Grouping { expression } => write!(f, "(group {:?})", expression),
|
Expr::Grouping { expression } => write!(f, "(group {:?})", expression.node),
|
||||||
Expr::Variable { name } => write!(f, "(variable {:?})", name),
|
Expr::Variable { name } => write!(f, "(variable {:?})", name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,86 +118,96 @@ impl Debug for Expr {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
Expression {
|
Expression {
|
||||||
expression: Box<Expr>,
|
expression: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Print {
|
Print {
|
||||||
expression: Box<Expr>,
|
expression: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Stmt {
|
Stmt {
|
||||||
expression: Box<Expr>,
|
expression: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Var {
|
Var {
|
||||||
name: String,
|
name: String,
|
||||||
initializer: Option<Box<Expr>>,
|
initializer: Option<Box<AstNode<Expr>>>,
|
||||||
},
|
},
|
||||||
Assign {
|
Assign {
|
||||||
name: String,
|
name: String,
|
||||||
value: Box<Expr>,
|
value: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Return {
|
Return {
|
||||||
expression: Box<Expr>,
|
expression: Box<AstNode<Expr>>,
|
||||||
},
|
},
|
||||||
Block {
|
Block {
|
||||||
statements: Box<Vec<Stmt>>,
|
statements: Box<Vec<AstNode<Stmt>>>,
|
||||||
},
|
},
|
||||||
If {
|
If {
|
||||||
condition: Box<Expr>,
|
condition: Box<AstNode<Expr>>,
|
||||||
then_branch: Box<Stmt>,
|
then_branch: Box<AstNode<Stmt>>,
|
||||||
elif_branch: Vec<(Box<Expr>, Box<Stmt>)>,
|
elif_branch: Vec<(Box<AstNode<Expr>>, Box<AstNode<Stmt>>)>,
|
||||||
else_branch: Option<Box<Stmt>>,
|
else_branch: Option<Box<AstNode<Stmt>>>,
|
||||||
},
|
},
|
||||||
While {
|
While {
|
||||||
condition: Box<Expr>,
|
condition: Box<AstNode<Expr>>,
|
||||||
body: Box<Stmt>,
|
body: Box<AstNode<Stmt>>,
|
||||||
},
|
},
|
||||||
For {
|
For {
|
||||||
variable: Box<Expr>,
|
variable: Box<AstNode<Expr>>,
|
||||||
iterable: Box<Expr>,
|
iterable: Box<AstNode<Expr>>,
|
||||||
body: Box<Stmt>,
|
body: Box<AstNode<Stmt>>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Stmt {
|
impl Display for Stmt {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Expression { expression } => write!(f, "Expression ({})", expression),
|
Stmt::Expression { expression } => write!(f, "Expression ({})", expression.node),
|
||||||
Stmt::If {
|
Stmt::If {
|
||||||
condition,
|
condition,
|
||||||
then_branch,
|
then_branch,
|
||||||
elif_branch,
|
elif_branch,
|
||||||
else_branch,
|
else_branch,
|
||||||
} => {
|
} => {
|
||||||
let mut result = format!("IF ({}) {{\n{}\n}}", condition, then_branch);
|
let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node);
|
||||||
for (condition, branch) in elif_branch {
|
for (condition, branch) in elif_branch {
|
||||||
result.push_str(&format!(" ELIF ({}) {{\n{}\n}}", condition, branch));
|
result.push_str(&format!(
|
||||||
|
" ELIF ({}) {{\n{}\n}}",
|
||||||
|
condition.node, branch.node
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if let Some(else_branch) = else_branch {
|
if let Some(else_branch) = else_branch {
|
||||||
result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch));
|
result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch.node));
|
||||||
}
|
}
|
||||||
write!(f, "IfStmt {}", result)
|
write!(f, "IfStmt {}", result)
|
||||||
}
|
}
|
||||||
Stmt::Print { expression } => write!(f, "Print({});", expression),
|
Stmt::Print { expression } => write!(f, "Print({});", expression.node),
|
||||||
Stmt::Stmt { expression } => write!(f, "Stmt({});", expression),
|
Stmt::Stmt { expression } => write!(f, "Stmt({});", expression.node),
|
||||||
Stmt::Var { name, initializer } => {
|
Stmt::Var { name, initializer } => match initializer {
|
||||||
write!(f, "Var({} = {:?});", name, initializer)
|
Some(init) => write!(f, "Var({} = {});", name, init.node),
|
||||||
}
|
None => write!(f, "Var({});", name),
|
||||||
Stmt::Assign { name, value } => write!(f, "Assign({} = {});", name, value),
|
},
|
||||||
Stmt::Return { expression } => write!(f, "Return({});", expression),
|
Stmt::Assign { name, value } => write!(f, "Assign({} = {});", name, value.node),
|
||||||
|
Stmt::Return { expression } => write!(f, "Return({});", expression.node),
|
||||||
Stmt::Block { statements } => write!(
|
Stmt::Block { statements } => write!(
|
||||||
f,
|
f,
|
||||||
"Block([\n{}\n])",
|
"Block([\n{}\n])",
|
||||||
statements
|
statements
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stmt| format!("\t \t{}", stmt))
|
.map(|stmt| format!("\t \t{}", stmt.node))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
),
|
),
|
||||||
Stmt::While { condition, body } => todo!(),
|
Stmt::While { condition, body } => {
|
||||||
|
write!(f, "While({}) {{\n{}\n}}", condition.node, body.node)
|
||||||
|
}
|
||||||
Stmt::For {
|
Stmt::For {
|
||||||
variable,
|
variable,
|
||||||
iterable,
|
iterable,
|
||||||
body,
|
body,
|
||||||
} => todo!(),
|
} => write!(
|
||||||
|
f,
|
||||||
|
"For({} in {}) {{\n{}\n}}",
|
||||||
|
variable.node, iterable.node, body.node
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,44 +215,55 @@ impl Display for Stmt {
|
|||||||
impl Debug for Stmt {
|
impl Debug for Stmt {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression),
|
Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression.node),
|
||||||
Stmt::If {
|
Stmt::If {
|
||||||
condition,
|
condition,
|
||||||
then_branch,
|
then_branch,
|
||||||
elif_branch,
|
elif_branch,
|
||||||
else_branch,
|
else_branch,
|
||||||
} => {
|
} => {
|
||||||
let mut result = format!("IF ({:?}) {{\n{:?}\n}}", condition, then_branch);
|
let mut result =
|
||||||
|
format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node);
|
||||||
for (condition, branch) in elif_branch {
|
for (condition, branch) in elif_branch {
|
||||||
result.push_str(&format!(" ELIF ({:?}) {{\n{:?}\n}}", condition, branch));
|
result.push_str(&format!(
|
||||||
|
" ELIF ({:?}) {{\n{:?}\n}}",
|
||||||
|
condition.node, branch.node
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if let Some(else_branch) = else_branch {
|
if let Some(else_branch) = else_branch {
|
||||||
result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch));
|
result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch.node));
|
||||||
}
|
}
|
||||||
write!(f, "IfStmt {:?}", result)
|
write!(f, "IfStmt {:?}", result)
|
||||||
}
|
}
|
||||||
Stmt::Print { expression } => write!(f, "Print({:?});", expression),
|
Stmt::Print { expression } => write!(f, "Print({:?});", expression.node),
|
||||||
Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression),
|
Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression.node),
|
||||||
Stmt::Var { name, initializer } => {
|
Stmt::Var { name, initializer } => match initializer {
|
||||||
write!(f, "Var({:?} = {:?});", name, initializer)
|
Some(init) => write!(f, "Var({:?} = {:?});", name, init.node),
|
||||||
}
|
None => write!(f, "Var({:?});", name),
|
||||||
Stmt::Assign { name, value } => write!(f, "Assign({:?} = {:?});", name, value),
|
},
|
||||||
Stmt::Return { expression } => write!(f, "Return({:?});", expression),
|
Stmt::Assign { name, value } => write!(f, "Assign({:?} = {:?});", name, value.node),
|
||||||
|
Stmt::Return { expression } => write!(f, "Return({:?});", expression.node),
|
||||||
Stmt::Block { statements } => write!(
|
Stmt::Block { statements } => write!(
|
||||||
f,
|
f,
|
||||||
"Block([\n{:?}\n])",
|
"Block([\n{:?}\n])",
|
||||||
statements
|
statements
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stmt| format!("{:?}", stmt))
|
.map(|stmt| format!("{:?}", stmt.node))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\t\t\n")
|
.join("\t\t\n")
|
||||||
),
|
),
|
||||||
Stmt::While { condition, body } => todo!(),
|
Stmt::While { condition, body } => {
|
||||||
|
write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node)
|
||||||
|
}
|
||||||
Stmt::For {
|
Stmt::For {
|
||||||
variable,
|
variable,
|
||||||
iterable,
|
iterable,
|
||||||
body,
|
body,
|
||||||
} => todo!(),
|
} => write!(
|
||||||
|
f,
|
||||||
|
"For({:?} in {:?}) {{\n{:?}\n}}",
|
||||||
|
variable.node, iterable.node, body.node
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,12 +310,12 @@ impl AstNodeKind for Stmt {
|
|||||||
} => "if statement",
|
} => "if statement",
|
||||||
Stmt::Print { expression: _ } => "print statement",
|
Stmt::Print { expression: _ } => "print statement",
|
||||||
Stmt::Stmt { expression: _ } => "statement",
|
Stmt::Stmt { expression: _ } => "statement",
|
||||||
Stmt::While { condition, body } => todo!(),
|
Stmt::While { condition, body } => "while statement",
|
||||||
Stmt::For {
|
Stmt::For {
|
||||||
variable,
|
variable,
|
||||||
iterable,
|
iterable,
|
||||||
body,
|
body,
|
||||||
} => todo!(),
|
} => "for statement",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ fn get_keyword_token(word: &str) -> Option<TokenType> {
|
|||||||
"for" => Some(TokenType::For),
|
"for" => Some(TokenType::For),
|
||||||
"fun" => Some(TokenType::Fun),
|
"fun" => Some(TokenType::Fun),
|
||||||
"if" => Some(TokenType::If),
|
"if" => Some(TokenType::If),
|
||||||
|
"then" => Some(TokenType::Then),
|
||||||
"elif" => Some(TokenType::Elif),
|
"elif" => Some(TokenType::Elif),
|
||||||
"else" => Some(TokenType::Else),
|
"else" => Some(TokenType::Else),
|
||||||
"or" => Some(TokenType::Or),
|
"or" => Some(TokenType::Or),
|
||||||
|
|||||||
+267
-118
@@ -1,9 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
frontend::{
|
frontend::{
|
||||||
ast::{AstNode, Expr, Stmt},
|
ast::{AstNode, Expr, Stmt},
|
||||||
|
source_registry::SourceSlice,
|
||||||
tokens::{LiteralValue, Token, TokenType},
|
tokens::{LiteralValue, Token, TokenType},
|
||||||
},
|
},
|
||||||
logging::display_ast::{pretty_print_with_config, PrettyConfig, PrettyPrint},
|
logging::display_ast::{pretty_print_with_config, PrettyConfig},
|
||||||
result::{LoxError, LoxResult},
|
result::{LoxError, LoxResult},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,193 +58,284 @@ impl Parser {
|
|||||||
|
|
||||||
fn statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
fn statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
match (&self.peek().token_type, &self.peek_next().token_type) {
|
match (&self.peek().token_type, &self.peek_next().token_type) {
|
||||||
(TokenType::Var, _) => Ok(AstNode::new(
|
(TokenType::Var, _) => self.var_statement(),
|
||||||
self.var_statement()?,
|
(TokenType::Print, _) => self.print_statement(),
|
||||||
self.peek().source_slice.clone(),
|
(TokenType::Return, _) => self.return_statement(),
|
||||||
)),
|
(TokenType::StartBlock, _) => self.block_statement(),
|
||||||
(TokenType::Print, _) => Ok(AstNode::new(
|
(TokenType::Identifier, TokenType::Equal) => self.assignment_statement(),
|
||||||
self.print_statement()?,
|
(TokenType::If, _) => self.if_statement(),
|
||||||
self.peek().source_slice.clone(),
|
(TokenType::While, _) => self.while_statement(),
|
||||||
)),
|
_ => self.expression_statement(),
|
||||||
(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(),
|
|
||||||
)),
|
|
||||||
(TokenType::While, _) => Ok(AstNode::new(
|
|
||||||
self.while_statement()?,
|
|
||||||
self.peek().source_slice.clone(),
|
|
||||||
)),
|
|
||||||
_ => Ok(AstNode::new(
|
|
||||||
self.expression_statement()?,
|
|
||||||
self.peek().source_slice.clone(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn while_statement(&mut self) -> LoxResult<Stmt> {
|
fn while_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let condition = self.expression()?;
|
let condition = self.expression()?;
|
||||||
let body = self.block_statement()?;
|
let body = self.statement()?;
|
||||||
Ok(Stmt::While {
|
let end_slice = body.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::While {
|
||||||
condition: Box::new(condition),
|
condition: Box::new(condition),
|
||||||
body: Box::new(body),
|
body: Box::new(body),
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn if_statement(&mut self) -> LoxResult<Stmt> {
|
fn if_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
self.advance();
|
let start_slice = self.peek().source_slice.clone();
|
||||||
|
self.advance(); // consume 'if'
|
||||||
let condition = self.expression()?;
|
let condition = self.expression()?;
|
||||||
|
self.consume(TokenType::Then, "Expect 'then' after if condition.")?;
|
||||||
let then_branch = self.statement()?;
|
let then_branch = self.statement()?;
|
||||||
let mut elif_branches = Vec::new();
|
let mut elif_branches = Vec::new();
|
||||||
|
let mut last_slice = then_branch.source_slice.clone();
|
||||||
|
|
||||||
while self.peek().token_type == TokenType::Elif {
|
while self.peek().token_type == TokenType::Elif {
|
||||||
self.advance();
|
self.advance(); // consume 'elif'
|
||||||
let condition = self.expression()?;
|
let elif_condition = self.expression()?;
|
||||||
let then_branch = self.statement()?;
|
self.consume(TokenType::Then, "Expect 'then' after elif condition.")?;
|
||||||
elif_branches.push((Box::new(condition), Box::new(then_branch.node)));
|
let elif_branch = self.statement()?;
|
||||||
|
last_slice = elif_branch.source_slice.clone();
|
||||||
|
elif_branches.push((Box::new(elif_condition), Box::new(elif_branch)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let else_branch = if self.peek().token_type == TokenType::Else {
|
let else_branch = if self.peek().token_type == TokenType::Else {
|
||||||
self.advance();
|
self.advance(); // consume 'else'
|
||||||
Some(Box::new(self.statement()?.node))
|
let else_stmt = self.statement()?;
|
||||||
|
last_slice = else_stmt.source_slice.clone();
|
||||||
|
Some(Box::new(else_stmt))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Ok(Stmt::If {
|
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
last_slice.end_position,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::If {
|
||||||
condition: Box::new(condition),
|
condition: Box::new(condition),
|
||||||
then_branch: Box::new(then_branch.node),
|
then_branch: Box::new(then_branch),
|
||||||
elif_branch: elif_branches,
|
elif_branch: elif_branches,
|
||||||
else_branch: else_branch,
|
else_branch: else_branch,
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn var_statement(&mut self) -> LoxResult<Stmt> {
|
fn var_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let name = self.consume(TokenType::Identifier, "Expect variable name.")?;
|
let name = self.consume(TokenType::Identifier, "Expect variable name.")?;
|
||||||
let name_lexeme = name.lexeme.clone();
|
let name_lexeme = name.lexeme.clone();
|
||||||
self.consume(TokenType::Equal, "Expect '=' after variable name.")?;
|
self.consume(TokenType::Equal, "Expect '=' after variable name.")?;
|
||||||
let value = self.expression()?;
|
let value = self.expression()?;
|
||||||
self.consume(
|
let semicolon = self.consume(
|
||||||
TokenType::Semicolon,
|
TokenType::Semicolon,
|
||||||
"Expect ';' after variable declaration.",
|
"Expect ';' after variable declaration.",
|
||||||
)?;
|
)?;
|
||||||
Ok(Stmt::Var {
|
let end_slice = semicolon.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::Var {
|
||||||
name: name_lexeme,
|
name: name_lexeme,
|
||||||
initializer: Some(Box::new(value)),
|
initializer: Some(Box::new(value)),
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assignment_statement(&mut self) -> LoxResult<Stmt> {
|
fn assignment_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
let name = self.consume(TokenType::Identifier, "Expect variable name.")?;
|
let name = self.consume(TokenType::Identifier, "Expect variable name.")?;
|
||||||
let name_lexeme = name.lexeme.clone();
|
let name_lexeme = name.lexeme.clone();
|
||||||
self.consume(TokenType::Equal, "Expect '=' after variable name.")?;
|
self.consume(TokenType::Equal, "Expect '=' after variable name.")?;
|
||||||
let value = self.expression()?;
|
let value = self.expression()?;
|
||||||
self.consume(
|
let semicolon = self.consume(
|
||||||
TokenType::Semicolon,
|
TokenType::Semicolon,
|
||||||
"Expect ';' after variable declaration.",
|
"Expect ';' after variable declaration.",
|
||||||
)?;
|
)?;
|
||||||
Ok(Stmt::Assign {
|
let end_slice = semicolon.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::Assign {
|
||||||
name: name_lexeme,
|
name: name_lexeme,
|
||||||
value: Box::new(value),
|
value: Box::new(value),
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_statement(&mut self) -> LoxResult<Stmt> {
|
fn print_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
// consume the print keyword
|
// consume the print keyword
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let expr = self.expression()?;
|
let expr = self.expression()?;
|
||||||
self.consume(TokenType::Semicolon, "Expect ';' after value.")?;
|
let semicolon = self.consume(TokenType::Semicolon, "Expect ';' after value.")?;
|
||||||
Ok(Stmt::Print {
|
let end_slice = semicolon.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::Print {
|
||||||
expression: Box::new(expr),
|
expression: Box::new(expr),
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_statement(&mut self) -> LoxResult<Stmt> {
|
fn block_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() {
|
while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() {
|
||||||
let stmt = self.statement()?;
|
let stmt = self.statement()?;
|
||||||
statements.push(stmt.node.clone());
|
let should_break = matches!(stmt.node, Stmt::Expression { .. });
|
||||||
if let Stmt::Expression { .. } = stmt.node {
|
statements.push(stmt);
|
||||||
|
if should_break {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.consume(TokenType::EndBlock, "Expect 'end' after block.")?;
|
let end_token = self.consume(TokenType::EndBlock, "Expect 'end' after block.")?;
|
||||||
Ok(Stmt::Block {
|
let end_slice = end_token.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::Block {
|
||||||
statements: Box::new(statements),
|
statements: Box::new(statements),
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expression_statement(&mut self) -> LoxResult<Stmt> {
|
fn expression_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
let expr = self.expression()?;
|
let expr = self.expression()?;
|
||||||
if self.peek().token_type == TokenType::Semicolon {
|
if self.peek().token_type == TokenType::Semicolon {
|
||||||
self.advance();
|
let semicolon = self.advance();
|
||||||
return Ok(Stmt::Stmt {
|
let end_slice = semicolon.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
return Ok(AstNode::new(
|
||||||
|
Stmt::Stmt {
|
||||||
expression: Box::new(expr),
|
expression: Box::new(expr),
|
||||||
});
|
},
|
||||||
|
combined_slice,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(Stmt::Expression {
|
// Use the expression's source slice for expression statements without semicolon
|
||||||
|
let expr_slice = expr.source_slice.clone();
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::Expression {
|
||||||
expression: Box::new(expr),
|
expression: Box::new(expr),
|
||||||
})
|
},
|
||||||
|
expr_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_statement(&mut self) -> LoxResult<Stmt> {
|
fn return_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
|
||||||
|
let start_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let expr = self.expression()?;
|
let expr = self.expression()?;
|
||||||
self.consume(TokenType::Semicolon, "Expect ';' after return value.")?;
|
let semicolon = self.consume(TokenType::Semicolon, "Expect ';' after return value.")?;
|
||||||
Ok(Stmt::Return {
|
let end_slice = semicolon.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
Ok(AstNode::new(
|
||||||
|
Stmt::Return {
|
||||||
expression: Box::new(expr),
|
expression: Box::new(expr),
|
||||||
})
|
},
|
||||||
|
combined_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expression(&mut self) -> LoxResult<Expr> {
|
fn expression(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
self.or_and()
|
self.or_and()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn or_and(&mut self) -> LoxResult<Expr> {
|
fn or_and(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
let mut expr = self.equality()?;
|
let mut expr = self.equality()?;
|
||||||
|
|
||||||
while [TokenType::Or, TokenType::And].contains(&self.peek().token_type) {
|
while [TokenType::Or, TokenType::And].contains(&self.peek().token_type) {
|
||||||
|
let start_slice = expr.source_slice.clone();
|
||||||
let operator = self.peek().token_type.clone();
|
let operator = self.peek().token_type.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let right = self.equality()?;
|
let right = self.equality()?;
|
||||||
expr = Expr::Binary {
|
let end_slice = right.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
expr = AstNode::new(
|
||||||
|
Expr::Binary {
|
||||||
left: Box::new(expr),
|
left: Box::new(expr),
|
||||||
operator,
|
operator,
|
||||||
right: Box::new(right),
|
right: Box::new(right),
|
||||||
};
|
},
|
||||||
|
combined_slice,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
fn equality(&mut self) -> LoxResult<Expr> {
|
fn equality(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
let mut expr = self.comparison()?;
|
let mut expr = self.comparison()?;
|
||||||
|
|
||||||
while [TokenType::BangEqual, TokenType::EqualEqual].contains(&self.peek().token_type) {
|
while [TokenType::BangEqual, TokenType::EqualEqual].contains(&self.peek().token_type) {
|
||||||
|
let start_slice = expr.source_slice.clone();
|
||||||
let operator = self.peek().token_type.clone();
|
let operator = self.peek().token_type.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let right = self.comparison()?;
|
let right = self.comparison()?;
|
||||||
expr = Expr::Binary {
|
let end_slice = right.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
expr = AstNode::new(
|
||||||
|
Expr::Binary {
|
||||||
left: Box::new(expr),
|
left: Box::new(expr),
|
||||||
operator,
|
operator,
|
||||||
right: Box::new(right),
|
right: Box::new(right),
|
||||||
};
|
},
|
||||||
|
combined_slice,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn comparison(&mut self) -> LoxResult<Expr> {
|
fn comparison(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
let mut expr = self.term()?;
|
let mut expr = self.term()?;
|
||||||
|
|
||||||
while [
|
while [
|
||||||
@@ -254,121 +346,174 @@ impl Parser {
|
|||||||
]
|
]
|
||||||
.contains(&self.peek().token_type)
|
.contains(&self.peek().token_type)
|
||||||
{
|
{
|
||||||
|
let start_slice = expr.source_slice.clone();
|
||||||
let operator = self.peek().token_type.clone();
|
let operator = self.peek().token_type.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let right = self.term()?;
|
let right = self.term()?;
|
||||||
expr = Expr::Binary {
|
let end_slice = right.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
expr = AstNode::new(
|
||||||
|
Expr::Binary {
|
||||||
left: Box::new(expr),
|
left: Box::new(expr),
|
||||||
operator,
|
operator,
|
||||||
right: Box::new(right),
|
right: Box::new(right),
|
||||||
};
|
},
|
||||||
|
combined_slice,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn term(&mut self) -> LoxResult<Expr> {
|
fn term(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
let mut expr = self.factor()?;
|
let mut expr = self.factor()?;
|
||||||
|
|
||||||
while [TokenType::Plus, TokenType::Minus].contains(&self.peek().token_type) {
|
while [TokenType::Plus, TokenType::Minus].contains(&self.peek().token_type) {
|
||||||
|
let start_slice = expr.source_slice.clone();
|
||||||
let operator = self.peek().token_type.clone();
|
let operator = self.peek().token_type.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let right = self.factor()?;
|
let right = self.factor()?;
|
||||||
expr = Expr::Binary {
|
let end_slice = right.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
expr = AstNode::new(
|
||||||
|
Expr::Binary {
|
||||||
left: Box::new(expr),
|
left: Box::new(expr),
|
||||||
operator,
|
operator,
|
||||||
right: Box::new(right),
|
right: Box::new(right),
|
||||||
};
|
},
|
||||||
|
combined_slice,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn factor(&mut self) -> LoxResult<Expr> {
|
fn factor(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
let mut expr = self.unary()?;
|
let mut expr = self.unary()?;
|
||||||
|
|
||||||
while [TokenType::Slash, TokenType::Star].contains(&self.peek().token_type) {
|
while [TokenType::Slash, TokenType::Star].contains(&self.peek().token_type) {
|
||||||
|
let start_slice = expr.source_slice.clone();
|
||||||
let operator = self.peek().token_type.clone();
|
let operator = self.peek().token_type.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let right = self.unary()?;
|
let right = self.unary()?;
|
||||||
expr = Expr::Binary {
|
let end_slice = right.source_slice.clone();
|
||||||
|
let combined_slice = SourceSlice::from_positions(
|
||||||
|
start_slice.source_id,
|
||||||
|
start_slice.start_position,
|
||||||
|
end_slice.end_position,
|
||||||
|
);
|
||||||
|
expr = AstNode::new(
|
||||||
|
Expr::Binary {
|
||||||
left: Box::new(expr),
|
left: Box::new(expr),
|
||||||
operator,
|
operator,
|
||||||
right: Box::new(right),
|
right: Box::new(right),
|
||||||
};
|
},
|
||||||
|
combined_slice,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unary(&mut self) -> LoxResult<Expr> {
|
fn unary(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
if [TokenType::Bang, TokenType::Minus].contains(&self.peek().token_type) {
|
if [TokenType::Bang, TokenType::Minus].contains(&self.peek().token_type) {
|
||||||
let operator = self.peek().token_type.clone();
|
let operator = self.peek().token_type.clone();
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let right = self.unary()?;
|
let right = self.unary()?;
|
||||||
return Ok(Expr::Unary {
|
return Ok(AstNode::new(
|
||||||
|
Expr::Unary {
|
||||||
operator,
|
operator,
|
||||||
operand: Box::new(right),
|
operand: Box::new(right),
|
||||||
});
|
},
|
||||||
|
source_slice,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.primary()
|
self.primary()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn primary(&mut self) -> LoxResult<Expr> {
|
fn primary(&mut self) -> LoxResult<AstNode<Expr>> {
|
||||||
match self.peek().token_type {
|
match self.peek().token_type {
|
||||||
TokenType::False => {
|
TokenType::False => {
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Expr::Literal {
|
Ok(AstNode::new(
|
||||||
|
Expr::Literal {
|
||||||
value: LiteralValue::Boolean(false),
|
value: LiteralValue::Boolean(false),
|
||||||
})
|
},
|
||||||
|
source_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
TokenType::True => {
|
TokenType::True => {
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Expr::Literal {
|
Ok(AstNode::new(
|
||||||
|
Expr::Literal {
|
||||||
value: LiteralValue::Boolean(true),
|
value: LiteralValue::Boolean(true),
|
||||||
})
|
},
|
||||||
|
source_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
TokenType::Nil => {
|
TokenType::Nil => {
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Expr::Literal {
|
Ok(AstNode::new(
|
||||||
|
Expr::Literal {
|
||||||
value: LiteralValue::Nil,
|
value: LiteralValue::Nil,
|
||||||
})
|
},
|
||||||
|
source_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
TokenType::Number => {
|
TokenType::Number => {
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
|
let literal = self.peek().literal.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let token = self.previous();
|
if let Some(literal) = literal {
|
||||||
if let Some(literal) = &token.literal {
|
Ok(AstNode::new(Expr::Literal { value: literal }, source_slice))
|
||||||
Ok(Expr::Literal {
|
|
||||||
value: literal.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Err(self.error(token, "Expected number literal").into())
|
Err(self
|
||||||
|
.error(self.previous(), "Expected number literal")
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenType::String => {
|
TokenType::String => {
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
|
let literal = self.peek().literal.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let token = self.previous();
|
if let Some(literal) = literal {
|
||||||
if let Some(literal) = &token.literal {
|
Ok(AstNode::new(Expr::Literal { value: literal }, source_slice))
|
||||||
Ok(Expr::Literal {
|
|
||||||
value: literal.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Err(self.error(token, "Expected string literal").into())
|
Err(self
|
||||||
|
.error(self.previous(), "Expected string literal")
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenType::LeftParen => {
|
TokenType::LeftParen => {
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
let expr = self.expression()?;
|
let expr = self.expression()?;
|
||||||
self.consume(TokenType::RightParen, "Expect ')' after expression.")?;
|
self.consume(TokenType::RightParen, "Expect ')' after expression.")?;
|
||||||
Ok(Expr::Grouping {
|
Ok(AstNode::new(
|
||||||
|
Expr::Grouping {
|
||||||
expression: Box::new(expr),
|
expression: Box::new(expr),
|
||||||
})
|
},
|
||||||
|
source_slice,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
TokenType::Identifier => {
|
TokenType::Identifier => {
|
||||||
let name = self.peek().lexeme.clone();
|
let name = self.peek().lexeme.clone();
|
||||||
|
let source_slice = self.peek().source_slice.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Expr::Variable { name: name })
|
Ok(AstNode::new(Expr::Variable { name }, source_slice))
|
||||||
}
|
}
|
||||||
_ => Err(self.error(self.peek(), "Expect expression.").into()),
|
_ => Err(self.error(self.peek(), "Expect expression.").into()),
|
||||||
}
|
}
|
||||||
@@ -386,7 +531,7 @@ impl Parser {
|
|||||||
if !self.is_at_end() {
|
if !self.is_at_end() {
|
||||||
self.current += 1;
|
self.current += 1;
|
||||||
}
|
}
|
||||||
self.peek()
|
self.previous()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek(&self) -> &Token {
|
fn peek(&self) -> &Token {
|
||||||
@@ -406,8 +551,12 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn previous(&self) -> &Token {
|
fn previous(&self) -> &Token {
|
||||||
|
if self.current == 0 {
|
||||||
|
&self.tokens[0]
|
||||||
|
} else {
|
||||||
&self.tokens[self.current - 1]
|
&self.tokens[self.current - 1]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn consume(&mut self, token_type: TokenType, message: &str) -> LoxResult<&Token> {
|
fn consume(&mut self, token_type: TokenType, message: &str) -> LoxResult<&Token> {
|
||||||
if self.peek().token_type == token_type {
|
if self.peek().token_type == token_type {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ pub enum TokenType {
|
|||||||
For,
|
For,
|
||||||
While,
|
While,
|
||||||
If,
|
If,
|
||||||
|
Then,
|
||||||
Elif,
|
Elif,
|
||||||
Else,
|
Else,
|
||||||
Nil,
|
Nil,
|
||||||
@@ -103,6 +104,7 @@ impl fmt::Display for TokenType {
|
|||||||
TokenType::Fun => write!(f, "fun"),
|
TokenType::Fun => write!(f, "fun"),
|
||||||
TokenType::For => write!(f, "for"),
|
TokenType::For => write!(f, "for"),
|
||||||
TokenType::If => write!(f, "if"),
|
TokenType::If => write!(f, "if"),
|
||||||
|
TokenType::Then => write!(f, "then"),
|
||||||
TokenType::Nil => write!(f, "nil"),
|
TokenType::Nil => write!(f, "nil"),
|
||||||
TokenType::Or => write!(f, "or"),
|
TokenType::Or => write!(f, "or"),
|
||||||
TokenType::Print => write!(f, "print"),
|
TokenType::Print => write!(f, "print"),
|
||||||
|
|||||||
@@ -552,43 +552,3 @@ pub fn pretty_stmt_compact(stmt: &Stmt) -> String {
|
|||||||
pub fn pretty_tree<T: PrettyPrint>(item: &T) -> String {
|
pub fn pretty_tree<T: PrettyPrint>(item: &T) -> String {
|
||||||
pretty_print_with_config(item, &PrettyConfig::tree())
|
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ impl Token {
|
|||||||
TokenType::For => "FOR",
|
TokenType::For => "FOR",
|
||||||
TokenType::While => "WHILE",
|
TokenType::While => "WHILE",
|
||||||
TokenType::If => "IF",
|
TokenType::If => "IF",
|
||||||
|
TokenType::Then => "THEN",
|
||||||
TokenType::Elif => "ELIF",
|
TokenType::Elif => "ELIF",
|
||||||
TokenType::Else => "ELSE",
|
TokenType::Else => "ELSE",
|
||||||
TokenType::Nil => "NIL",
|
TokenType::Nil => "NIL",
|
||||||
@@ -227,6 +228,7 @@ impl Token {
|
|||||||
| TokenType::For
|
| TokenType::For
|
||||||
| TokenType::While
|
| TokenType::While
|
||||||
| TokenType::If
|
| TokenType::If
|
||||||
|
| TokenType::Then
|
||||||
| TokenType::Elif
|
| TokenType::Elif
|
||||||
| TokenType::Else
|
| TokenType::Else
|
||||||
| TokenType::Nil
|
| TokenType::Nil
|
||||||
|
|||||||
+7
-6
@@ -24,16 +24,16 @@ fn main() -> LoxResult<()> {
|
|||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let mut lox = LoxInterpreter::new();
|
let mut lox = LoxInterpreter::new();
|
||||||
|
|
||||||
if args.len() > 2 {
|
let _ = if args.len() > 2 {
|
||||||
eprintln!("Usage: {} [script]", args[0]);
|
eprintln!("Usage: {} [script]", args[0]);
|
||||||
std::process::exit(64);
|
std::process::exit(64);
|
||||||
} else if args.len() == 2 {
|
} else if args.len() == 2 {
|
||||||
// Esegui file
|
// Esegui file
|
||||||
lox.run_file(&args[1])?;
|
lox.run_file(&args[1])
|
||||||
} else {
|
} else {
|
||||||
// Modalità interattiva
|
// Modalità interattiva
|
||||||
lox.run_prompt()?;
|
lox.run_prompt()
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -86,8 +86,9 @@ impl LoxInterpreter {
|
|||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
let mut interpreter = Interpreter::new(&mut env);
|
let mut interpreter = Interpreter::new(&mut env);
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
for stmt in stmts {
|
for (index, stmt) in stmts.iter().enumerate() {
|
||||||
result = Some(interpreter.interpret(stmt)?);
|
println!("executing stmt {}: {:?}", index, stmt);
|
||||||
|
result = Some(interpreter.interpret(stmt.clone())?);
|
||||||
}
|
}
|
||||||
match result {
|
match result {
|
||||||
Some(res) => Ok(res),
|
Some(res) => Ok(res),
|
||||||
|
|||||||
+345
-52
@@ -1,4 +1,5 @@
|
|||||||
use crate::frontend::source_registry::{SourcePosition, SourceRegistry, SourceSlice};
|
use crate::frontend::source_registry::{SourceRegistry, SourceSlice};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum LoxError {
|
pub enum LoxError {
|
||||||
@@ -24,6 +25,50 @@ pub enum LoxError {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for error display formatting
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ErrorDisplayConfig {
|
||||||
|
/// Show colored output (for terminals that support it)
|
||||||
|
pub colored: bool,
|
||||||
|
/// Number of context lines to show before and after the error
|
||||||
|
pub context_lines: usize,
|
||||||
|
/// Show line numbers
|
||||||
|
pub show_line_numbers: bool,
|
||||||
|
/// Use Unicode characters for arrows and decorations
|
||||||
|
pub use_unicode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ErrorDisplayConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
colored: true,
|
||||||
|
context_lines: 2,
|
||||||
|
show_line_numbers: true,
|
||||||
|
use_unicode: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorDisplayConfig {
|
||||||
|
pub fn simple() -> Self {
|
||||||
|
Self {
|
||||||
|
colored: false,
|
||||||
|
context_lines: 1,
|
||||||
|
show_line_numbers: false,
|
||||||
|
use_unicode: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verbose() -> Self {
|
||||||
|
Self {
|
||||||
|
colored: true,
|
||||||
|
context_lines: 3,
|
||||||
|
show_line_numbers: true,
|
||||||
|
use_unicode: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LoxError {
|
impl LoxError {
|
||||||
pub fn get_message(&self) -> String {
|
pub fn get_message(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
@@ -49,88 +94,289 @@ impl LoxError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_with_context(&self, source_registry: &SourceRegistry) {
|
pub fn error_kind(&self) -> &'static str {
|
||||||
match self.get_source_slice() {
|
match self {
|
||||||
Some(source_slice) => {
|
LoxError::LexicalError { .. } => "lexical error",
|
||||||
let source_code = source_registry.get_source_code(&source_slice);
|
LoxError::RuntimeError { .. } => "runtime error",
|
||||||
println!(
|
LoxError::ParseError { .. } => "parse error",
|
||||||
"\n =============================================== \n DATA:{} \n CODE \n {} | {}",
|
LoxError::IoError { .. } => "io error",
|
||||||
self, source_slice.start_position.line ,source_code
|
LoxError::TypeMismatch { .. } => "type error",
|
||||||
);
|
|
||||||
}
|
|
||||||
None => println!("{}", self),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_source_code(path: &str, start: SourcePosition, end: SourcePosition) -> LoxResult<String> {
|
/// Display error with source code context using default configuration
|
||||||
let source_code = std::fs::read_to_string(path).map_err(|e| LoxError::IoError {
|
pub fn display_with_source(&self, source_registry: &SourceRegistry) -> String {
|
||||||
message: format!("Failed to read file: {}", e),
|
self.display_with_source_and_config(source_registry, &ErrorDisplayConfig::default())
|
||||||
})?;
|
|
||||||
|
|
||||||
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();
|
/// Display error with source code context using custom configuration
|
||||||
|
pub fn display_with_source_and_config(
|
||||||
|
&self,
|
||||||
|
source_registry: &SourceRegistry,
|
||||||
|
config: &ErrorDisplayConfig,
|
||||||
|
) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
if start.line == end.line {
|
// Error header
|
||||||
// Caso speciale: stessa riga
|
let header = if config.colored {
|
||||||
let line = lines[start.line];
|
format!(
|
||||||
if end.column <= line.len() && start.column <= end.column {
|
"\x1b[1;31merror\x1b[0m: [{}] {}",
|
||||||
result.push_str(&line[start.column..end.column]);
|
self.error_kind(),
|
||||||
|
self.get_message()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("error: [{}] {}", self.error_kind(), self.get_message())
|
||||||
|
};
|
||||||
|
output.push_str(&header);
|
||||||
|
output.push('\n');
|
||||||
|
|
||||||
|
// If we have source location, show it
|
||||||
|
if let Some(source_slice) = self.get_source_slice() {
|
||||||
|
let source_info = source_registry.get_by_id(source_slice.source_id);
|
||||||
|
|
||||||
|
// File location line
|
||||||
|
let location = if config.colored {
|
||||||
|
format!(
|
||||||
|
" \x1b[34m-->\x1b[0m {}:{}:{}",
|
||||||
|
source_info.file_name,
|
||||||
|
source_slice.start_position.line + 1,
|
||||||
|
source_slice.start_position.column + 1
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
" --> {}:{}:{}",
|
||||||
|
source_info.file_name,
|
||||||
|
source_slice.start_position.line + 1,
|
||||||
|
source_slice.start_position.column + 1
|
||||||
|
)
|
||||||
|
};
|
||||||
|
output.push_str(&location);
|
||||||
|
output.push('\n');
|
||||||
|
|
||||||
|
// Source code context
|
||||||
|
if let Some(source_context) =
|
||||||
|
self.get_source_context(source_registry, &source_slice, config)
|
||||||
|
{
|
||||||
|
output.push_str(&source_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_source_context(
|
||||||
|
&self,
|
||||||
|
source_registry: &SourceRegistry,
|
||||||
|
source_slice: &SourceSlice,
|
||||||
|
config: &ErrorDisplayConfig,
|
||||||
|
) -> Option<String> {
|
||||||
|
let source_info = source_registry.get_by_id(source_slice.source_id);
|
||||||
|
let lines: Vec<&str> = source_info.content.lines().collect();
|
||||||
|
|
||||||
|
if lines.is_empty() || source_slice.start_position.line >= lines.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
let error_line = source_slice.start_position.line;
|
||||||
|
let start_line = error_line.saturating_sub(config.context_lines);
|
||||||
|
let end_line = (error_line + config.context_lines + 1).min(lines.len());
|
||||||
|
|
||||||
|
// Calculate the width needed for line numbers
|
||||||
|
let line_number_width = if config.show_line_numbers {
|
||||||
|
(end_line + 1).to_string().len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show context lines
|
||||||
|
for line_idx in start_line..end_line {
|
||||||
|
let line_content = lines[line_idx];
|
||||||
|
let line_number = line_idx + 1;
|
||||||
|
|
||||||
|
if config.show_line_numbers {
|
||||||
|
let line_num_str = if config.colored {
|
||||||
|
if line_idx == error_line {
|
||||||
|
format!(
|
||||||
|
"\x1b[1;34m{:width$}\x1b[0m",
|
||||||
|
line_number,
|
||||||
|
width = line_number_width
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"\x1b[34m{:width$}\x1b[0m",
|
||||||
|
line_number,
|
||||||
|
width = line_number_width
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Caso generale: righe multiple
|
format!("{:width$}", line_number, width = line_number_width)
|
||||||
|
};
|
||||||
|
|
||||||
// Prima riga: da start.column alla fine della riga
|
let separator = if config.colored {
|
||||||
let first_line = lines[start.line];
|
if line_idx == error_line {
|
||||||
if start.column < first_line.len() {
|
"\x1b[1;34m |\x1b[0m "
|
||||||
result.push_str(&first_line[start.column..]);
|
} else {
|
||||||
|
"\x1b[34m |\x1b[0m "
|
||||||
}
|
}
|
||||||
result.push('\n');
|
} else {
|
||||||
|
" | "
|
||||||
|
};
|
||||||
|
|
||||||
// Righe intermedie: complete
|
output.push_str(&format!(
|
||||||
for i in (start.line + 1)..end.line {
|
" {}{}{}\n",
|
||||||
result.push_str(lines[i]);
|
line_num_str, separator, line_content
|
||||||
result.push('\n');
|
));
|
||||||
|
} else {
|
||||||
|
output.push_str(&format!(" {}\n", line_content));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ultima riga: dall'inizio fino a end.column
|
// Add error indicator on the error line
|
||||||
let last_line = lines[end.line];
|
if line_idx == error_line {
|
||||||
if end.column <= last_line.len() {
|
let spaces_before =
|
||||||
result.push_str(&last_line[..end.column]);
|
" ".repeat(3 + line_number_width + 3 + source_slice.start_position.column);
|
||||||
|
|
||||||
|
let error_length = if source_slice.start_position.line
|
||||||
|
== source_slice.end_position.line
|
||||||
|
{
|
||||||
|
(source_slice.end_position.column - source_slice.start_position.column).max(1)
|
||||||
|
} else {
|
||||||
|
line_content.len() - source_slice.start_position.column
|
||||||
|
};
|
||||||
|
|
||||||
|
let indicator = if config.use_unicode {
|
||||||
|
"^".repeat(error_length)
|
||||||
|
} else {
|
||||||
|
"^".repeat(error_length)
|
||||||
|
};
|
||||||
|
|
||||||
|
let colored_indicator = if config.colored {
|
||||||
|
format!("\x1b[1;31m{}\x1b[0m", indicator)
|
||||||
|
} else {
|
||||||
|
indicator
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the indicator line
|
||||||
|
if config.show_line_numbers {
|
||||||
|
let empty_line_number = " ".repeat(line_number_width);
|
||||||
|
let separator = if config.colored {
|
||||||
|
"\x1b[34m |\x1b[0m "
|
||||||
|
} else {
|
||||||
|
" | "
|
||||||
|
};
|
||||||
|
output.push_str(&format!(
|
||||||
|
" {}{}{}{}\n",
|
||||||
|
empty_line_number,
|
||||||
|
separator,
|
||||||
|
spaces_before[3 + line_number_width + 3..].to_string(),
|
||||||
|
colored_indicator
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
output.push_str(&format!(
|
||||||
|
" {}{}\n",
|
||||||
|
spaces_before[3..].to_string(),
|
||||||
|
colored_indicator
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add error type and additional context
|
||||||
|
let error_note = match self {
|
||||||
|
LoxError::TypeMismatch {
|
||||||
|
expected, found, ..
|
||||||
|
} => Some(format!("expected `{}`, found `{}`", expected, found)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(note) = error_note {
|
||||||
|
let note_prefix = if config.show_line_numbers {
|
||||||
|
format!(
|
||||||
|
" {} {} ",
|
||||||
|
" ".repeat(line_number_width),
|
||||||
|
if config.colored {
|
||||||
|
"\x1b[34m|\x1b[0m"
|
||||||
|
} else {
|
||||||
|
"|"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
" ".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let colored_note = if config.colored {
|
||||||
|
format!("\x1b[1;36mnote\x1b[0m: {}", note)
|
||||||
|
} else {
|
||||||
|
format!("note: {}", note)
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push_str(&format!("{}{}\n", note_prefix, colored_note));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Some(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print error with source context to stdout
|
||||||
|
pub fn print_with_source(&self, source_registry: &SourceRegistry) {
|
||||||
|
println!("{}", self.display_with_source(source_registry));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print error with source context and custom config to stdout
|
||||||
|
pub fn print_with_source_and_config(
|
||||||
|
&self,
|
||||||
|
source_registry: &SourceRegistry,
|
||||||
|
config: &ErrorDisplayConfig,
|
||||||
|
) {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
self.display_with_source_and_config(source_registry, config)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Legacy method for backwards compatibility
|
||||||
|
pub fn print_with_context(&self, source_registry: &SourceRegistry) {
|
||||||
|
self.print_with_source(source_registry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for LoxError {
|
impl fmt::Display for LoxError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
LoxError::LexicalError {
|
LoxError::LexicalError {
|
||||||
source_slice,
|
source_slice,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "Lexical error on {:?}: \n {}", source_slice, message)
|
write!(
|
||||||
|
f,
|
||||||
|
"Lexical error at {}:{}: {}",
|
||||||
|
source_slice.start_position.line + 1,
|
||||||
|
source_slice.start_position.column + 1,
|
||||||
|
message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
LoxError::RuntimeError {
|
LoxError::RuntimeError {
|
||||||
source_slice,
|
source_slice,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "Runtime error on {:?}: \n{}", source_slice, message)
|
write!(
|
||||||
|
f,
|
||||||
|
"Runtime error at {}:{}: {}",
|
||||||
|
source_slice.start_position.line + 1,
|
||||||
|
source_slice.start_position.column + 1,
|
||||||
|
message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
LoxError::ParseError {
|
LoxError::ParseError {
|
||||||
source_slice,
|
source_slice,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "Parse error on {:?}: \n{}", source_slice, message)
|
write!(
|
||||||
|
f,
|
||||||
|
"Parse error at {}:{}: {}",
|
||||||
|
source_slice.start_position.line + 1,
|
||||||
|
source_slice.start_position.column + 1,
|
||||||
|
message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
LoxError::IoError { message } => write!(f, "IO error: {}", message),
|
LoxError::IoError { message } => write!(f, "IO error: {}", message),
|
||||||
LoxError::TypeMismatch {
|
LoxError::TypeMismatch {
|
||||||
@@ -140,8 +386,11 @@ impl std::fmt::Display for LoxError {
|
|||||||
} => {
|
} => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Type mismatch on {:?}: \n expected {}, found {}",
|
"Type mismatch at {}:{}: expected `{}`, found `{}`",
|
||||||
source_slice, expected, found
|
source_slice.start_position.line + 1,
|
||||||
|
source_slice.start_position.column + 1,
|
||||||
|
expected,
|
||||||
|
found
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,3 +400,47 @@ impl std::fmt::Display for LoxError {
|
|||||||
impl std::error::Error for LoxError {}
|
impl std::error::Error for LoxError {}
|
||||||
|
|
||||||
pub type LoxResult<T> = Result<T, LoxError>;
|
pub type LoxResult<T> = Result<T, LoxError>;
|
||||||
|
|
||||||
|
/// Helper function to create a lexical error
|
||||||
|
pub fn lexical_error(source_slice: SourceSlice, message: impl Into<String>) -> LoxError {
|
||||||
|
LoxError::LexicalError {
|
||||||
|
source_slice,
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a parse error
|
||||||
|
pub fn parse_error(source_slice: SourceSlice, message: impl Into<String>) -> LoxError {
|
||||||
|
LoxError::ParseError {
|
||||||
|
source_slice,
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a runtime error
|
||||||
|
pub fn runtime_error(source_slice: SourceSlice, message: impl Into<String>) -> LoxError {
|
||||||
|
LoxError::RuntimeError {
|
||||||
|
source_slice,
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a type mismatch error
|
||||||
|
pub fn type_mismatch_error(
|
||||||
|
source_slice: SourceSlice,
|
||||||
|
expected: impl Into<String>,
|
||||||
|
found: impl Into<String>,
|
||||||
|
) -> LoxError {
|
||||||
|
LoxError::TypeMismatch {
|
||||||
|
source_slice,
|
||||||
|
expected: expected.into(),
|
||||||
|
found: found.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create an IO error
|
||||||
|
pub fn io_error(message: impl Into<String>) -> LoxError {
|
||||||
|
LoxError::IoError {
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Test script to verify error positioning
|
||||||
|
var a = 5;
|
||||||
|
var b = "hello";
|
||||||
|
print 0;
|
||||||
|
var result = a + b; // This should cause a type error
|
||||||
|
print 1;
|
||||||
|
43 + "hello"; // This should cause a type error
|
||||||
|
print result;
|
||||||
|
print 46 + "world"; // This should cause a type error
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Test file for source slice tracking
|
||||||
|
var x = 42;
|
||||||
|
print x;
|
||||||
|
|
||||||
|
if x > 0 then do
|
||||||
|
print "positive";
|
||||||
|
var y = x + 1;
|
||||||
|
end
|
||||||
|
elif x == 0 then do
|
||||||
|
print "zero";
|
||||||
|
end
|
||||||
|
else do
|
||||||
|
print "negative";
|
||||||
|
end
|
||||||
|
|
||||||
|
while x > 0 do
|
||||||
|
x = x - 1;
|
||||||
|
print x;
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
var z = 10;
|
||||||
|
print z * 2;
|
||||||
|
end
|
||||||
|
|
||||||
|
return x + 5;
|
||||||
Reference in New Issue
Block a user