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:
Giulio Agostini
2025-10-07 14:28:24 +02:00
parent fa2e9178fb
commit c1ecd7d1f7
17 changed files with 1074 additions and 398 deletions
+450
View File
@@ -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(),
})
}