Switch interpreter to use new base value types
The commit changes the interpreter and related code to use the new `BaeValue` and `Number` types, moving away from direct floats. The main changes include: - Replace LiteralValue with BaeValue across the codebase - Add Number enum for type-safe numeric values - Move AST definitions to common module - Update operators to handle new numeric types - Add 'is' token type and parser support Switch to common base_value library for value types This commit represents a significant refactoring to switch the interpreter to use a new shared base value type system. The main changes include: 1. Move value types to a new common/base_value.rs module 2. Add richer numeric type support with promotion rules 3. Update interpreter to use new BaseValue enum 4. Move AST types to common module for sharing 5. Consolidate value operations like Add, Sub etc 6. Add proper test coverage for numeric operations The commit improves type safety and adds more robust numeric handling while keeping the interpreter's core logic clean.
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
use crate::{
|
||||
common::base_value::BaseValue,
|
||||
frontend::{source_registry::SourceSlice, tokens::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"|"dyn"|"mut")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration
|
||||
* function_declaration -> "::" "(" parameters? ")" ("->" IDENTIFIER)? block_statement
|
||||
* parameters -> ( IDENTIFIER (":" IDENTIFIER)? ("," IDENTIFIER (":" IDENTIFIER)? )* )
|
||||
* assignment_statement -> IDENTIFIER "=" expression ";"
|
||||
* block_statement -> "do" statement* "end"
|
||||
* if_statement -> "if" expression "then" statement ("elif" expression "then" statement)* ("else" statement)? "end"
|
||||
* while_statement -> "while" expression "do" statement "end"
|
||||
*
|
||||
* expression -> assignment
|
||||
* assignment -> IDENTIFIER "=" assignment | logical_or
|
||||
* logical_or -> logical_and (("or" logical_and)*
|
||||
* logical_and -> logical_is (("and" logical_is)*
|
||||
* logical_is -> equality (("is" equality)*
|
||||
* equality -> comparison (("==" | "!=") comparison)*
|
||||
* comparison -> term ((">" | ">=" | "<" | "<=") term)*
|
||||
* term -> factor (("+" | "-") factor)*
|
||||
* factor -> unary (("*" | "/") unary)*
|
||||
* unary -> ("!" | "-") unary | call
|
||||
* call -> primary ("(" arguments ")")*
|
||||
* arguments -> expression ("," expression)*
|
||||
* primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER
|
||||
*/
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Expr {
|
||||
Literal {
|
||||
value: BaseValue,
|
||||
},
|
||||
Binary {
|
||||
left: Box<AstNode<Expr>>,
|
||||
operator: TokenType,
|
||||
right: Box<AstNode<Expr>>,
|
||||
},
|
||||
Unary {
|
||||
operator: TokenType,
|
||||
operand: Box<AstNode<Expr>>,
|
||||
},
|
||||
Grouping {
|
||||
expression: Box<AstNode<Expr>>,
|
||||
},
|
||||
Identifier {
|
||||
name: String,
|
||||
},
|
||||
Call {
|
||||
callee: Box<AstNode<Expr>>,
|
||||
arguments: Vec<AstNode<Expr>>,
|
||||
},
|
||||
}
|
||||
|
||||
// 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.node, operator, right.node),
|
||||
Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand.node),
|
||||
Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node),
|
||||
Expr::Identifier { name } => write!(f, "Identifier (variable {})", name),
|
||||
Expr::Call { callee, arguments } => write!(
|
||||
f,
|
||||
"Call ({}({}))",
|
||||
callee.node,
|
||||
arguments
|
||||
.iter()
|
||||
.map(|arg| arg.node.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.node, right.node),
|
||||
Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand.node),
|
||||
Expr::Grouping { expression } => write!(f, "(group {:?})", expression.node),
|
||||
Expr::Identifier { name } => write!(f, "(variable {:?})", name),
|
||||
Expr::Call { callee, arguments } => write!(
|
||||
f,
|
||||
"Call ({}({}))",
|
||||
callee.node,
|
||||
arguments
|
||||
.iter()
|
||||
.map(|arg| arg.node.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Stmt {
|
||||
Expression {
|
||||
expression: Box<AstNode<Expr>>,
|
||||
},
|
||||
Print {
|
||||
expression: Box<AstNode<Expr>>,
|
||||
},
|
||||
Stmt {
|
||||
expression: Box<AstNode<Expr>>,
|
||||
},
|
||||
VarDeclaration {
|
||||
name: String,
|
||||
initializer: Option<Box<AstNode<Expr>>>,
|
||||
},
|
||||
VarAssigment {
|
||||
name: String,
|
||||
value: Box<AstNode<Expr>>,
|
||||
},
|
||||
Return {
|
||||
expression: Box<AstNode<Expr>>,
|
||||
},
|
||||
Block {
|
||||
statements: Box<Vec<AstNode<Stmt>>>,
|
||||
},
|
||||
If {
|
||||
condition: Box<AstNode<Expr>>,
|
||||
then_branch: Box<AstNode<Stmt>>,
|
||||
elif_branch: Vec<(Box<AstNode<Expr>>, Box<AstNode<Stmt>>)>,
|
||||
else_branch: Option<Box<AstNode<Stmt>>>,
|
||||
},
|
||||
While {
|
||||
condition: Box<AstNode<Expr>>,
|
||||
body: Box<AstNode<Stmt>>,
|
||||
},
|
||||
For {
|
||||
variable: Box<AstNode<Stmt>>,
|
||||
condition: Box<AstNode<Expr>>,
|
||||
increment: Box<AstNode<Stmt>>,
|
||||
body: Box<AstNode<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.node),
|
||||
Stmt::If {
|
||||
condition,
|
||||
then_branch,
|
||||
elif_branch,
|
||||
else_branch,
|
||||
} => {
|
||||
let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node);
|
||||
for (condition, branch) in elif_branch {
|
||||
result.push_str(&format!(
|
||||
" ELIF ({}) {{\n{}\n}}",
|
||||
condition.node, branch.node
|
||||
));
|
||||
}
|
||||
if let Some(else_branch) = else_branch {
|
||||
result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch.node));
|
||||
}
|
||||
write!(f, "IfStmt {}", result)
|
||||
}
|
||||
Stmt::Print { expression } => write!(f, "Print({});", expression.node),
|
||||
Stmt::Stmt { expression } => write!(f, "Stmt({});", expression.node),
|
||||
Stmt::VarDeclaration { name, initializer } => match initializer {
|
||||
Some(init) => write!(f, "Var({} = {});", name, init.node),
|
||||
None => write!(f, "Var({});", name),
|
||||
},
|
||||
Stmt::VarAssigment { name, value } => write!(f, "Assign({} = {});", name, value.node),
|
||||
Stmt::Return { expression } => write!(f, "Return({});", expression.node),
|
||||
Stmt::Block { statements } => write!(
|
||||
f,
|
||||
"Block([\n{}\n])",
|
||||
statements
|
||||
.iter()
|
||||
.map(|stmt| format!("\t \t{}", stmt.node))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
),
|
||||
Stmt::While { condition, body } => {
|
||||
write!(f, "While({}) {{\n{}\n}}", condition.node, body.node)
|
||||
}
|
||||
Stmt::For {
|
||||
variable,
|
||||
condition,
|
||||
increment,
|
||||
body,
|
||||
} => write!(
|
||||
f,
|
||||
"For({} = {} in {}) {{\n{}\n}}",
|
||||
variable.node, condition.node, increment.node, body.node
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Stmt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression.node),
|
||||
Stmt::If {
|
||||
condition,
|
||||
then_branch,
|
||||
elif_branch,
|
||||
else_branch,
|
||||
} => {
|
||||
let mut result =
|
||||
format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node);
|
||||
for (condition, branch) in elif_branch {
|
||||
result.push_str(&format!(
|
||||
" ELIF ({:?}) {{\n{:?}\n}}",
|
||||
condition.node, branch.node
|
||||
));
|
||||
}
|
||||
if let Some(else_branch) = else_branch {
|
||||
result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch.node));
|
||||
}
|
||||
write!(f, "IfStmt {:?}", result)
|
||||
}
|
||||
Stmt::Print { expression } => write!(f, "Print({:?});", expression.node),
|
||||
Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression.node),
|
||||
Stmt::VarDeclaration { name, initializer } => match initializer {
|
||||
Some(init) => write!(f, "Var({:?} = {:?});", name, init.node),
|
||||
None => write!(f, "Var({:?});", name),
|
||||
},
|
||||
Stmt::VarAssigment { name, value } => {
|
||||
write!(f, "Assign({:?} = {:?});", name, value.node)
|
||||
}
|
||||
Stmt::Return { expression } => write!(f, "Return({:?});", expression.node),
|
||||
Stmt::Block { statements } => write!(
|
||||
f,
|
||||
"Block([\n{:?}\n])",
|
||||
statements
|
||||
.iter()
|
||||
.map(|stmt| format!("{:?}", stmt.node))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\t\t\n")
|
||||
),
|
||||
Stmt::While { condition, body } => {
|
||||
write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node)
|
||||
}
|
||||
Stmt::For {
|
||||
variable,
|
||||
condition,
|
||||
increment,
|
||||
body,
|
||||
} => write!(
|
||||
f,
|
||||
"For({:?} = {:?} in {:?}) {{\n{:?}\n}}",
|
||||
variable.node, condition.node, increment.node, body.node
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 { .. } => "unary expression",
|
||||
Expr::Grouping { .. } => "grouping expression",
|
||||
Expr::Identifier { .. } => "variable expression",
|
||||
Expr::Call { .. } => "call expression",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AstNodeKind for Stmt {
|
||||
fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Stmt::Expression { .. } => "expression",
|
||||
Stmt::VarDeclaration { .. } => "variable declaration",
|
||||
Stmt::VarAssigment { .. } => "assignment",
|
||||
Stmt::Return { .. } => "return statement",
|
||||
Stmt::Block { .. } => "block statement",
|
||||
Stmt::If { .. } => "if statement",
|
||||
Stmt::Print { .. } => "print statement",
|
||||
Stmt::Stmt { .. } => "statement",
|
||||
Stmt::While { .. } => "while statement",
|
||||
Stmt::For { .. } => "for statement",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Default)]
|
||||
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,488 @@
|
||||
use core::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Add, Div, Mul, Not, Rem, Sub};
|
||||
|
||||
use crate::common::lox_result::{runtime_error, LoxResult};
|
||||
use crate::{
|
||||
backend::environment::EnvironmentStack,
|
||||
common::ast::{AstNode, Expr, Stmt},
|
||||
frontend::source_registry::SourceSlice,
|
||||
};
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Number {
|
||||
I16(i16),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
U8(u8),
|
||||
U16(u16),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
U128(u128),
|
||||
}
|
||||
|
||||
impl Display for Number {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Number::I16(n) => write!(f, "{}", n),
|
||||
Number::I32(n) => write!(f, "{}", n),
|
||||
Number::I64(n) => write!(f, "{}", n),
|
||||
Number::F32(n) => write!(f, "{}", n),
|
||||
Number::F64(n) => write!(f, "{}", n),
|
||||
Number::U8(n) => write!(f, "{}", n),
|
||||
Number::U16(n) => write!(f, "{}", n),
|
||||
Number::U32(n) => write!(f, "{}", n),
|
||||
Number::U64(n) => write!(f, "{}", n),
|
||||
Number::U128(n) => write!(f, "{}", n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Number {
|
||||
/// Promotes two numbers to their common type following the hierarchy:
|
||||
/// Unsigned -> Integer -> Float
|
||||
fn promote_to_common_type(a: Number, b: Number) -> (Number, Number) {
|
||||
use Number::*;
|
||||
match (&a, &b) {
|
||||
// If either is a float, both become F64
|
||||
(F32(_), _) | (F64(_), _) => {
|
||||
let a_as_f64 = match a {
|
||||
I16(n) => n as f64,
|
||||
I32(n) => n as f64,
|
||||
I64(n) => n as f64,
|
||||
F32(n) => n as f64,
|
||||
F64(n) => n,
|
||||
U8(n) => n as f64,
|
||||
U16(n) => n as f64,
|
||||
U32(n) => n as f64,
|
||||
U64(n) => n as f64,
|
||||
U128(n) => n as f64,
|
||||
};
|
||||
let b_as_f64 = match b {
|
||||
I16(n) => n as f64,
|
||||
I32(n) => n as f64,
|
||||
I64(n) => n as f64,
|
||||
F32(n) => n as f64,
|
||||
F64(n) => n,
|
||||
U8(n) => n as f64,
|
||||
U16(n) => n as f64,
|
||||
U32(n) => n as f64,
|
||||
U64(n) => n as f64,
|
||||
U128(n) => n as f64,
|
||||
};
|
||||
(F64(a_as_f64), F64(b_as_f64))
|
||||
}
|
||||
(_, F32(_)) | (_, F64(_)) => {
|
||||
// Swap and recurse to handle the float case
|
||||
let (b_promoted, a_promoted) = Self::promote_to_common_type(b.clone(), a.clone());
|
||||
(a_promoted, b_promoted)
|
||||
}
|
||||
// If either is signed integer, both become I64
|
||||
(I16(_), _) | (I32(_), _) | (I64(_), _) => {
|
||||
let a_as_i64 = match a {
|
||||
I16(n) => n as i64,
|
||||
I32(n) => n as i64,
|
||||
I64(n) => n,
|
||||
U8(n) => n as i64,
|
||||
U16(n) => n as i64,
|
||||
U32(n) => n as i64,
|
||||
U64(n) => n as i64,
|
||||
U128(n) => n as i64, // May overflow but keeping it simple
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let b_as_i64 = match b {
|
||||
I16(n) => n as i64,
|
||||
I32(n) => n as i64,
|
||||
I64(n) => n,
|
||||
U8(n) => n as i64,
|
||||
U16(n) => n as i64,
|
||||
U32(n) => n as i64,
|
||||
U64(n) => n as i64,
|
||||
U128(n) => n as i64, // May overflow but keeping it simple
|
||||
_ => unreachable!(),
|
||||
};
|
||||
(I64(a_as_i64), I64(b_as_i64))
|
||||
}
|
||||
(_, I16(_)) | (_, I32(_)) | (_, I64(_)) => {
|
||||
// Swap and recurse to handle the signed case
|
||||
let (b_promoted, a_promoted) = Self::promote_to_common_type(b.clone(), a.clone());
|
||||
(a_promoted, b_promoted)
|
||||
}
|
||||
// Both are unsigned, promote to U128
|
||||
_ => {
|
||||
let a_as_u128 = match a {
|
||||
U8(n) => n as u128,
|
||||
U16(n) => n as u128,
|
||||
U32(n) => n as u128,
|
||||
U64(n) => n as u128,
|
||||
U128(n) => n,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let b_as_u128 = match b {
|
||||
U8(n) => n as u128,
|
||||
U16(n) => n as u128,
|
||||
U32(n) => n as u128,
|
||||
U64(n) => n as u128,
|
||||
U128(n) => n,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
(U128(a_as_u128), U128(b_as_u128))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(self, other: Number) -> Number {
|
||||
let (a, b) = Self::promote_to_common_type(self, other);
|
||||
match (a, b) {
|
||||
(Number::F64(x), Number::F64(y)) => Number::F64(x + y),
|
||||
(Number::I64(x), Number::I64(y)) => Number::I64(x + y),
|
||||
(Number::U128(x), Number::U128(y)) => Number::U128(x + y),
|
||||
_ => unreachable!("promote_to_common_type should handle all cases"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub(self, other: Number) -> Number {
|
||||
let (a, b) = Self::promote_to_common_type(self, other);
|
||||
match (a, b) {
|
||||
(Number::F64(x), Number::F64(y)) => Number::F64(x - y),
|
||||
(Number::I64(x), Number::I64(y)) => Number::I64(x - y),
|
||||
(Number::U128(x), Number::U128(y)) => Number::U128(x - y),
|
||||
_ => unreachable!("promote_to_common_type should handle all cases"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul(self, other: Number) -> Number {
|
||||
let (a, b) = Self::promote_to_common_type(self, other);
|
||||
match (a, b) {
|
||||
(Number::F64(x), Number::F64(y)) => Number::F64(x * y),
|
||||
(Number::I64(x), Number::I64(y)) => Number::I64(x * y),
|
||||
(Number::U128(x), Number::U128(y)) => Number::U128(x * y),
|
||||
_ => unreachable!("promote_to_common_type should handle all cases"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div(self, other: Number) -> Option<Number> {
|
||||
let (a, b) = Self::promote_to_common_type(self, other);
|
||||
match (a, b) {
|
||||
(Number::F64(x), Number::F64(y)) => {
|
||||
if y == 0.0 {
|
||||
None
|
||||
} else {
|
||||
Some(Number::F64(x / y))
|
||||
}
|
||||
}
|
||||
(Number::I64(x), Number::I64(y)) => {
|
||||
if y == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Number::I64(x / y))
|
||||
}
|
||||
}
|
||||
(Number::U128(x), Number::U128(y)) => {
|
||||
if y == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Number::U128(x / y))
|
||||
}
|
||||
}
|
||||
_ => unreachable!("promote_to_common_type should handle all cases"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rem(self, other: Number) -> Option<Number> {
|
||||
let (a, b) = Self::promote_to_common_type(self, other);
|
||||
match (a, b) {
|
||||
(Number::F64(x), Number::F64(y)) => {
|
||||
if y == 0.0 {
|
||||
None
|
||||
} else {
|
||||
Some(Number::F64(x % y))
|
||||
}
|
||||
}
|
||||
(Number::I64(x), Number::I64(y)) => {
|
||||
if y == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Number::I64(x % y))
|
||||
}
|
||||
}
|
||||
(Number::U128(x), Number::U128(y)) => {
|
||||
if y == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Number::U128(x % y))
|
||||
}
|
||||
}
|
||||
_ => unreachable!("promote_to_common_type should handle all cases"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn neg(self) -> Number {
|
||||
match self {
|
||||
Number::I16(n) => Number::I16(-n),
|
||||
Number::I32(n) => Number::I32(-n),
|
||||
Number::I64(n) => Number::I64(-n),
|
||||
Number::F32(n) => Number::F32(-n),
|
||||
Number::F64(n) => Number::F64(-n),
|
||||
// Unsigned numbers become signed when negated
|
||||
Number::U8(n) => Number::I32(-(n as i32)),
|
||||
Number::U16(n) => Number::I32(-(n as i32)),
|
||||
Number::U32(n) => Number::I64(-(n as i64)),
|
||||
Number::U64(n) => Number::I64(-(n as i64)),
|
||||
Number::U128(n) => Number::I64(-(n as i64)), // May overflow
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
Number::I16(n) => *n == 0,
|
||||
Number::I32(n) => *n == 0,
|
||||
Number::I64(n) => *n == 0,
|
||||
Number::F32(n) => *n == 0.0,
|
||||
Number::F64(n) => *n == 0.0,
|
||||
Number::U8(n) => *n == 0,
|
||||
Number::U16(n) => *n == 0,
|
||||
Number::U32(n) => *n == 0,
|
||||
Number::U64(n) => *n == 0,
|
||||
Number::U128(n) => *n == 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Number {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
let (a, b) = Self::promote_to_common_type(self.clone(), other.clone());
|
||||
match (a, b) {
|
||||
(Number::F64(x), Number::F64(y)) => x.partial_cmp(&y),
|
||||
(Number::I64(x), Number::I64(y)) => x.partial_cmp(&y),
|
||||
(Number::U128(x), Number::U128(y)) => x.partial_cmp(&y),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BaseValue {
|
||||
Identifier(String),
|
||||
String(String),
|
||||
Number(Number),
|
||||
Boolean(bool),
|
||||
Nil,
|
||||
Function(LoxFunction),
|
||||
NativeFunction(NativeFunction),
|
||||
}
|
||||
|
||||
impl BaseValue {
|
||||
pub fn is_callable(&self) -> bool {
|
||||
match self {
|
||||
BaseValue::Function(..) | BaseValue::NativeFunction(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BaseValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BaseValue::Identifier(id) => write!(f, "{}", id),
|
||||
BaseValue::String(s) => write!(f, "{}", s),
|
||||
BaseValue::Number(n) => write!(f, "{}", n),
|
||||
BaseValue::Boolean(b) => write!(f, "{}", b),
|
||||
BaseValue::Nil => write!(f, "nil"),
|
||||
BaseValue::Function(..) => write!(f, "<fn lox function>"),
|
||||
BaseValue::NativeFunction(..) => write!(f, "<native native function>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LoxFunction {
|
||||
pub parameters: Vec<(String, String)>,
|
||||
pub body: AstNode<Stmt>,
|
||||
pub closure: Option<EnvironmentStack>,
|
||||
pub guard: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
impl LoxFunction {
|
||||
pub fn new(
|
||||
parameters: Vec<(String, String)>,
|
||||
body: AstNode<Stmt>,
|
||||
closure: Option<EnvironmentStack>,
|
||||
guard: Option<Box<Expr>>,
|
||||
) -> Self {
|
||||
LoxFunction {
|
||||
parameters,
|
||||
body,
|
||||
closure,
|
||||
guard,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn anonymous_function(parameters: Vec<(String, String)>, body: AstNode<Stmt>) -> Self {
|
||||
LoxFunction {
|
||||
parameters,
|
||||
body,
|
||||
closure: None,
|
||||
guard: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NativeFunction {
|
||||
pub parameters: Vec<(String, String)>,
|
||||
pub function: fn(&[BaseValue]) -> BaseValue,
|
||||
}
|
||||
|
||||
impl NativeFunction {
|
||||
pub fn new(parameters: Vec<(String, String)>, function: fn(&[BaseValue]) -> BaseValue) -> Self {
|
||||
NativeFunction {
|
||||
parameters,
|
||||
function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trait implementations for BaseValue
|
||||
|
||||
pub trait Truthy {
|
||||
fn is_truthy(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Truthy for BaseValue {
|
||||
fn is_truthy(&self) -> bool {
|
||||
match self {
|
||||
BaseValue::Boolean(b) => *b,
|
||||
BaseValue::Number(n) => !n.is_zero(),
|
||||
BaseValue::String(s) => !s.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for BaseValue {
|
||||
type Output = BaseValue;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
match self {
|
||||
BaseValue::Boolean(b) => BaseValue::Boolean(!b),
|
||||
BaseValue::Number(n) => BaseValue::Boolean(n.is_zero()),
|
||||
_ => BaseValue::Boolean(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for BaseValue {
|
||||
type Output = LoxResult<BaseValue>;
|
||||
|
||||
fn add(self, other: BaseValue) -> Self::Output {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.add(b))),
|
||||
(BaseValue::String(a), BaseValue::String(b)) => {
|
||||
Ok(BaseValue::String(format!("{}{}", a, b)))
|
||||
}
|
||||
_ => runtime_error(
|
||||
SourceSlice::default(),
|
||||
"Cannot add non-numeric values".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseValue {
|
||||
pub fn add_with_source(
|
||||
self,
|
||||
other: BaseValue,
|
||||
source_slice: SourceSlice,
|
||||
) -> LoxResult<BaseValue> {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.add(b))),
|
||||
(BaseValue::String(a), BaseValue::String(b)) => {
|
||||
Ok(BaseValue::String(format!("{}{}", a, b)))
|
||||
}
|
||||
_ => runtime_error(source_slice, "Cannot add non-numeric values".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for BaseValue {
|
||||
type Output = LoxResult<BaseValue>;
|
||||
|
||||
fn sub(self, other: BaseValue) -> Self::Output {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.sub(b))),
|
||||
_ => runtime_error(
|
||||
SourceSlice::default(),
|
||||
"Cannot subtract non-numeric values".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for BaseValue {
|
||||
type Output = LoxResult<BaseValue>;
|
||||
|
||||
fn div(self, other: BaseValue) -> Self::Output {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => match a.div(b) {
|
||||
Some(result) => Ok(BaseValue::Number(result)),
|
||||
None => runtime_error(SourceSlice::default(), "Division by zero".to_string()),
|
||||
},
|
||||
_ => runtime_error(
|
||||
SourceSlice::default(),
|
||||
"Cannot divide non-numeric values".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for BaseValue {
|
||||
type Output = LoxResult<BaseValue>;
|
||||
|
||||
fn mul(self, other: BaseValue) -> Self::Output {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.mul(b))),
|
||||
_ => runtime_error(
|
||||
SourceSlice::default(),
|
||||
"Cannot multiply non-numeric values".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for BaseValue {
|
||||
type Output = LoxResult<BaseValue>;
|
||||
|
||||
fn rem(self, other: BaseValue) -> Self::Output {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => match a.rem(b) {
|
||||
Some(result) => Ok(BaseValue::Number(result)),
|
||||
None => runtime_error(SourceSlice::default(), "Division by zero".to_string()),
|
||||
},
|
||||
_ => runtime_error(
|
||||
SourceSlice::default(),
|
||||
"Cannot divide non-numeric values".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for BaseValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match (self, other) {
|
||||
(BaseValue::Number(a), BaseValue::Number(b)) => a.partial_cmp(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BaseValue> for bool {
|
||||
fn from(value: BaseValue) -> Self {
|
||||
match value {
|
||||
BaseValue::Boolean(b) => b,
|
||||
BaseValue::Number(n) => !n.is_zero(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
use crate::frontend::source_registry::{SourceRegistry, SourceSlice};
|
||||
use std::fmt;
|
||||
|
||||
#[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,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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 error_kind(&self) -> &'static str {
|
||||
match self {
|
||||
LoxError::LexicalError { .. } => "lexical error",
|
||||
LoxError::RuntimeError { .. } => "runtime error",
|
||||
LoxError::ParseError { .. } => "parse error",
|
||||
LoxError::IoError { .. } => "io error",
|
||||
LoxError::TypeMismatch { .. } => "type error",
|
||||
}
|
||||
}
|
||||
|
||||
/// Display error with source code context using default configuration
|
||||
pub fn display_with_source(&self, source_registry: &SourceRegistry) -> String {
|
||||
self.display_with_source_and_config(source_registry, &ErrorDisplayConfig::default())
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
// Error header
|
||||
let header = if config.colored {
|
||||
format!(
|
||||
"\x1b[1;31merror\x1b[0m: [{}] {}",
|
||||
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 {
|
||||
format!("{:width$}", line_number, width = line_number_width)
|
||||
};
|
||||
|
||||
let separator = if config.colored {
|
||||
if line_idx == error_line {
|
||||
"\x1b[1;34m |\x1b[0m "
|
||||
} else {
|
||||
"\x1b[34m |\x1b[0m "
|
||||
}
|
||||
} else {
|
||||
" | "
|
||||
};
|
||||
|
||||
output.push_str(&format!(
|
||||
" {}{}{}\n",
|
||||
line_num_str, separator, line_content
|
||||
));
|
||||
} else {
|
||||
output.push_str(&format!(" {}\n", line_content));
|
||||
}
|
||||
|
||||
// Add error indicator on the error line
|
||||
if line_idx == error_line {
|
||||
let spaces_before =
|
||||
" ".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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 fmt::Display for LoxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LoxError::LexicalError {
|
||||
source_slice,
|
||||
message,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Lexical error at {}:{}: {}",
|
||||
source_slice.start_position.line + 1,
|
||||
source_slice.start_position.column + 1,
|
||||
message
|
||||
)
|
||||
}
|
||||
LoxError::RuntimeError {
|
||||
source_slice,
|
||||
message,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Runtime error at {}:{}: {}",
|
||||
source_slice.start_position.line + 1,
|
||||
source_slice.start_position.column + 1,
|
||||
message
|
||||
)
|
||||
}
|
||||
LoxError::ParseError {
|
||||
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::TypeMismatch {
|
||||
source_slice,
|
||||
expected,
|
||||
found,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Type mismatch at {}:{}: expected `{}`, found `{}`",
|
||||
source_slice.start_position.line + 1,
|
||||
source_slice.start_position.column + 1,
|
||||
expected,
|
||||
found
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LoxError {}
|
||||
|
||||
pub type LoxResult<T> = Result<T, LoxError>;
|
||||
|
||||
/// Helper function to create a lexical error
|
||||
#[allow(dead_code)]
|
||||
pub fn lexical_error<T>(source_slice: SourceSlice, message: impl Into<String>) -> LoxResult<T> {
|
||||
Err(LoxError::LexicalError {
|
||||
source_slice,
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to create a parse error
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_error<T>(source_slice: SourceSlice, message: impl Into<String>) -> LoxResult<T> {
|
||||
Err(LoxError::ParseError {
|
||||
source_slice,
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to create a runtime error
|
||||
pub fn runtime_error<T>(source_slice: SourceSlice, message: impl Into<String>) -> LoxResult<T> {
|
||||
Err(LoxError::RuntimeError {
|
||||
source_slice,
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to create a type mismatch error
|
||||
#[allow(dead_code)]
|
||||
pub fn type_mismatch_error<T>(
|
||||
source_slice: SourceSlice,
|
||||
expected: impl Into<String>,
|
||||
found: impl Into<String>,
|
||||
) -> LoxResult<T> {
|
||||
Err(LoxError::TypeMismatch {
|
||||
source_slice,
|
||||
expected: expected.into(),
|
||||
found: found.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to create an IO error
|
||||
#[allow(dead_code)]
|
||||
pub fn io_error<T>(message: impl Into<String>) -> LoxResult<T> {
|
||||
Err(LoxError::IoError {
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod ast;
|
||||
pub mod base_value;
|
||||
pub mod lox_result;
|
||||
Reference in New Issue
Block a user