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
+488
View File
@@ -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,
}
}
}