//! Tree-walking interpreter for the Lux language with algebraic effects #![allow(dead_code, unused_variables)] use crate::ast::*; use crate::diagnostics::{Diagnostic, Severity}; use std::cell::RefCell; use std::collections::HashMap; use std::fmt; use std::rc::Rc; /// Built-in function identifier #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BuiltinFn { // List operations ListMap, ListFilter, ListFold, ListHead, ListTail, ListConcat, ListReverse, ListLength, ListGet, ListRange, // String operations StringSplit, StringJoin, StringTrim, StringContains, StringReplace, StringLength, StringChars, StringLines, // Option operations OptionMap, OptionFlatMap, OptionGetOrElse, OptionIsSome, OptionIsNone, // Result operations ResultMap, ResultFlatMap, ResultGetOrElse, ResultIsOk, ResultIsErr, // Utility Print, ToString, TypeOf, } /// Runtime value #[derive(Debug, Clone)] pub enum Value { Int(i64), Float(f64), Bool(bool), String(String), Char(char), Unit, List(Vec), Tuple(Vec), Record(HashMap), Function(Rc), Handler(Rc), /// Built-in function Builtin(BuiltinFn), /// Constructor value (for ADTs) Constructor { name: String, fields: Vec, }, /// Versioned value (for schema evolution) Versioned { type_name: String, version: u32, value: Box, }, } impl Value { pub fn type_name(&self) -> &'static str { match self { Value::Int(_) => "Int", Value::Float(_) => "Float", Value::Bool(_) => "Bool", Value::String(_) => "String", Value::Char(_) => "Char", Value::Unit => "Unit", Value::List(_) => "List", Value::Tuple(_) => "Tuple", Value::Record(_) => "Record", Value::Function(_) => "Function", Value::Handler(_) => "Handler", Value::Builtin(_) => "Function", Value::Constructor { .. } => "Constructor", Value::Versioned { .. } => "Versioned", } } /// Unwrap a versioned value to get the inner value pub fn unwrap_versioned(&self) -> &Value { match self { Value::Versioned { value, .. } => value.unwrap_versioned(), other => other, } } /// Get version info if this is a versioned value pub fn version_info(&self) -> Option<(String, u32)> { match self { Value::Versioned { type_name, version, .. } => Some((type_name.clone(), *version)), _ => None, } } } /// Trait for extracting typed values from Value trait TryFromValue: Sized { const TYPE_NAME: &'static str; fn try_from_value(value: &Value) -> Option; } impl TryFromValue for i64 { const TYPE_NAME: &'static str = "Int"; fn try_from_value(value: &Value) -> Option { match value { Value::Int(n) => Some(*n), _ => None, } } } impl TryFromValue for f64 { const TYPE_NAME: &'static str = "Float"; fn try_from_value(value: &Value) -> Option { match value { Value::Float(n) => Some(*n), _ => None, } } } impl TryFromValue for String { const TYPE_NAME: &'static str = "String"; fn try_from_value(value: &Value) -> Option { match value { Value::String(s) => Some(s.clone()), _ => None, } } } impl TryFromValue for bool { const TYPE_NAME: &'static str = "Bool"; fn try_from_value(value: &Value) -> Option { match value { Value::Bool(b) => Some(*b), _ => None, } } } impl TryFromValue for Vec { const TYPE_NAME: &'static str = "List"; fn try_from_value(value: &Value) -> Option { match value { Value::List(l) => Some(l.clone()), _ => None, } } } impl TryFromValue for Value { const TYPE_NAME: &'static str = "any"; fn try_from_value(value: &Value) -> Option { Some(value.clone()) } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Value::Int(n) => write!(f, "{}", n), Value::Float(n) => write!(f, "{}", n), Value::Bool(b) => write!(f, "{}", b), Value::String(s) => write!(f, "\"{}\"", s), Value::Char(c) => write!(f, "'{}'", c), Value::Unit => write!(f, "()"), Value::List(elements) => { write!(f, "[")?; for (i, e) in elements.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", e)?; } write!(f, "]") } Value::Tuple(elements) => { write!(f, "(")?; for (i, e) in elements.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", e)?; } write!(f, ")") } Value::Record(fields) => { write!(f, "{{ ")?; for (i, (name, value)) in fields.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}: {}", name, value)?; } write!(f, " }}") } Value::Function(_) => write!(f, ""), Value::Builtin(b) => write!(f, "", b), Value::Handler(_) => write!(f, ""), Value::Constructor { name, fields } => { if fields.is_empty() { write!(f, "{}", name) } else { write!(f, "{}(", name)?; for (i, field) in fields.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", field)?; } write!(f, ")") } } Value::Versioned { type_name, version, value, } => { write!(f, "{} @v{}", value, version) } } } } /// Function closure #[derive(Debug)] pub struct Closure { pub params: Vec, pub body: Expr, pub env: Env, } /// Handler value #[derive(Debug)] pub struct HandlerValue { pub effect: String, pub implementations: HashMap, pub env: Env, } /// Environment (lexical scope) #[derive(Debug, Clone, Default)] pub struct Env { bindings: Rc>>, parent: Option>, } impl Env { pub fn new() -> Self { Self { bindings: Rc::new(RefCell::new(HashMap::new())), parent: None, } } pub fn extend(&self) -> Self { Self { bindings: Rc::new(RefCell::new(HashMap::new())), parent: Some(Box::new(self.clone())), } } pub fn define(&self, name: impl Into, value: Value) { self.bindings.borrow_mut().insert(name.into(), value); } pub fn get(&self, name: &str) -> Option { if let Some(value) = self.bindings.borrow().get(name) { return Some(value.clone()); } if let Some(ref parent) = self.parent { return parent.get(name); } None } } /// Runtime error #[derive(Debug, Clone)] pub struct RuntimeError { pub message: String, pub span: Option, } impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(span) = self.span { write!( f, "Runtime error at {}-{}: {}", span.start, span.end, self.message ) } else { write!(f, "Runtime error: {}", self.message) } } } impl std::error::Error for RuntimeError {} impl RuntimeError { /// Convert to a rich diagnostic for Elm-style error display pub fn to_diagnostic(&self) -> Diagnostic { let (title, hints) = categorize_runtime_error(&self.message); Diagnostic { severity: Severity::Error, title, message: self.message.clone(), span: self.span.unwrap_or_default(), hints, } } } /// Categorize runtime errors to provide better titles and hints fn categorize_runtime_error(message: &str) -> (String, Vec) { let message_lower = message.to_lowercase(); if message_lower.contains("undefined") || message_lower.contains("not found") { ( "Undefined Reference".to_string(), vec!["Make sure the name is defined and in scope.".to_string()], ) } else if message_lower.contains("division by zero") || message_lower.contains("divide by zero") { ( "Division by Zero".to_string(), vec![ "Check that the divisor is not zero before dividing.".to_string(), "Consider using a guard or match to handle this case.".to_string(), ], ) } else if message_lower.contains("type") && message_lower.contains("mismatch") { ( "Type Mismatch".to_string(), vec!["The value has a different type than expected.".to_string()], ) } else if message_lower.contains("effect") && message_lower.contains("unhandled") { ( "Unhandled Effect".to_string(), vec![ "This effect must be handled before the program can continue.".to_string(), "Wrap this code in a 'handle' expression.".to_string(), ], ) } else if message_lower.contains("pattern") && message_lower.contains("match") { ( "Non-exhaustive Pattern".to_string(), vec!["Add more patterns to cover all possible cases.".to_string()], ) } else if message_lower.contains("argument") { ( "Wrong Arguments".to_string(), vec!["Check the number and types of arguments provided.".to_string()], ) } else if message_lower.contains("index") || message_lower.contains("bounds") { ( "Index Out of Bounds".to_string(), vec![ "The index is outside the valid range.".to_string(), "Check the length of the collection before accessing.".to_string(), ], ) } else { ("Runtime Error".to_string(), vec![]) } } /// Effect operation request #[derive(Debug, Clone)] pub struct EffectRequest { pub effect: String, pub operation: String, pub args: Vec, pub continuation: Continuation, } /// Continuation (captured rest of computation) #[derive(Debug, Clone)] pub struct Continuation { // For simplicity, we'll use a callback-based approach // In a real implementation, this would capture the stack id: usize, } static NEXT_CONT_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); impl Continuation { fn new() -> Self { Self { id: NEXT_CONT_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), } } } /// Result of evaluation (either a value, effect request, or tail call) pub enum EvalResult { Value(Value), Effect(EffectRequest), /// Tail call optimization: instead of recursing, return the call to be trampolined TailCall { func: Value, args: Vec, span: Span, }, } /// Effect trace entry for debugging #[derive(Debug, Clone)] pub struct EffectTrace { pub effect: String, pub operation: String, pub args: Vec, pub result: Option, pub timestamp_us: u128, } /// The interpreter /// A stored migration function #[derive(Clone)] pub struct StoredMigration { /// The expression to evaluate for migration pub body: Expr, /// Environment captured when the migration was defined pub env: Env, } impl std::fmt::Debug for StoredMigration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("StoredMigration").finish() } } pub struct Interpreter { global_env: Env, /// Stack of active effect handlers handler_stack: Vec>, /// Stored continuations for resumption continuations: HashMap Result>>, /// Effect tracing for debugging pub trace_effects: bool, /// Collected effect traces pub effect_traces: Vec, /// Start time for timestamps start_time: std::time::Instant, /// Migration registry: type_name -> (from_version -> to_version -> migration) migrations: HashMap>, } impl Interpreter { pub fn new() -> Self { let global_env = Env::new(); // Add built-in functions Self::add_builtins(&global_env); Self { global_env, handler_stack: Vec::new(), continuations: HashMap::new(), trace_effects: false, effect_traces: Vec::new(), start_time: std::time::Instant::now(), migrations: HashMap::new(), } } /// Enable effect tracing for debugging pub fn enable_tracing(&mut self) { self.trace_effects = true; self.effect_traces.clear(); self.start_time = std::time::Instant::now(); } /// Get all effect traces pub fn get_traces(&self) -> &[EffectTrace] { &self.effect_traces } /// Print effect traces in a readable format pub fn print_traces(&self) { println!("\n── EFFECT TRACE ──────────────────────────────────────"); for trace in &self.effect_traces { let time_ms = trace.timestamp_us as f64 / 1000.0; let args_str: Vec = trace.args.iter().map(|a| format!("{}", a)).collect(); let result_str = trace .result .as_ref() .map(|r| format!(" → {}", r)) .unwrap_or_default(); println!( "[{:8.3}ms] {}.{}({}){}", time_ms, trace.effect, trace.operation, args_str.join(", "), result_str ); } println!("──────────────────────────────────────────────────────\n"); } /// Register a migration for a versioned type pub fn register_migration( &mut self, type_name: &str, from_version: u32, migration: StoredMigration, ) { self.migrations .entry(type_name.to_string()) .or_default() .insert(from_version, migration); } /// Create a versioned value pub fn create_versioned(&self, type_name: &str, version: u32, value: Value) -> Value { Value::Versioned { type_name: type_name.to_string(), version, value: Box::new(value), } } /// Migrate a versioned value to a target version pub fn migrate_value( &mut self, value: Value, target_version: u32, ) -> Result { let (type_name, current_version, inner_value) = match value { Value::Versioned { type_name, version, value, } => (type_name, version, *value), other => return Ok(other), // Non-versioned values don't need migration }; if current_version == target_version { return Ok(Value::Versioned { type_name, version: target_version, value: Box::new(inner_value), }); } if current_version > target_version { return Err(RuntimeError { message: format!( "Cannot downgrade {} from @v{} to @v{}", type_name, current_version, target_version ), span: None, }); } // Migrate step by step: v1 -> v2 -> v3 -> ... -> target let mut current_value = inner_value; let mut current_ver = current_version; while current_ver < target_version { let next_ver = current_ver + 1; // Look up the migration let migration = self .migrations .get(&type_name) .and_then(|m| m.get(¤t_ver)) .cloned(); match migration { Some(stored_migration) => { // Execute the migration let migration_env = stored_migration.env.clone(); migration_env.define("old", current_value.clone()); current_value = self.eval_expr(&stored_migration.body, &migration_env)?; current_ver = next_ver; } None => { // No explicit migration - try auto-migration (just pass through) // In a full implementation, we'd check compatibility here current_ver = next_ver; } } } Ok(Value::Versioned { type_name, version: target_version, value: Box::new(current_value), }) } fn add_builtins(env: &Env) { // Option constructors env.define( "None", Value::Constructor { name: "None".to_string(), fields: Vec::new(), }, ); env.define( "Some", Value::Constructor { name: "Some".to_string(), fields: Vec::new(), // Will accumulate args when called }, ); // Result constructors env.define( "Ok", Value::Constructor { name: "Ok".to_string(), fields: Vec::new(), }, ); env.define( "Err", Value::Constructor { name: "Err".to_string(), fields: Vec::new(), }, ); // List module (as a record with function fields) let list_module = Value::Record(HashMap::from([ ("map".to_string(), Value::Builtin(BuiltinFn::ListMap)), ("filter".to_string(), Value::Builtin(BuiltinFn::ListFilter)), ("fold".to_string(), Value::Builtin(BuiltinFn::ListFold)), ("head".to_string(), Value::Builtin(BuiltinFn::ListHead)), ("tail".to_string(), Value::Builtin(BuiltinFn::ListTail)), ("concat".to_string(), Value::Builtin(BuiltinFn::ListConcat)), ( "reverse".to_string(), Value::Builtin(BuiltinFn::ListReverse), ), ("length".to_string(), Value::Builtin(BuiltinFn::ListLength)), ("get".to_string(), Value::Builtin(BuiltinFn::ListGet)), ("range".to_string(), Value::Builtin(BuiltinFn::ListRange)), ])); env.define("List", list_module); // String module let string_module = Value::Record(HashMap::from([ ("split".to_string(), Value::Builtin(BuiltinFn::StringSplit)), ("join".to_string(), Value::Builtin(BuiltinFn::StringJoin)), ("trim".to_string(), Value::Builtin(BuiltinFn::StringTrim)), ( "contains".to_string(), Value::Builtin(BuiltinFn::StringContains), ), ( "replace".to_string(), Value::Builtin(BuiltinFn::StringReplace), ), ( "length".to_string(), Value::Builtin(BuiltinFn::StringLength), ), ("chars".to_string(), Value::Builtin(BuiltinFn::StringChars)), ("lines".to_string(), Value::Builtin(BuiltinFn::StringLines)), ])); env.define("String", string_module); // Option module (functions, not constructors) let option_module = Value::Record(HashMap::from([ ("map".to_string(), Value::Builtin(BuiltinFn::OptionMap)), ( "flatMap".to_string(), Value::Builtin(BuiltinFn::OptionFlatMap), ), ( "getOrElse".to_string(), Value::Builtin(BuiltinFn::OptionGetOrElse), ), ( "isSome".to_string(), Value::Builtin(BuiltinFn::OptionIsSome), ), ( "isNone".to_string(), Value::Builtin(BuiltinFn::OptionIsNone), ), ])); env.define("Option", option_module); // Result module let result_module = Value::Record(HashMap::from([ ("map".to_string(), Value::Builtin(BuiltinFn::ResultMap)), ( "flatMap".to_string(), Value::Builtin(BuiltinFn::ResultFlatMap), ), ( "getOrElse".to_string(), Value::Builtin(BuiltinFn::ResultGetOrElse), ), ("isOk".to_string(), Value::Builtin(BuiltinFn::ResultIsOk)), ("isErr".to_string(), Value::Builtin(BuiltinFn::ResultIsErr)), ])); env.define("Result", result_module); // Utility functions env.define("print", Value::Builtin(BuiltinFn::Print)); env.define("toString", Value::Builtin(BuiltinFn::ToString)); env.define("typeOf", Value::Builtin(BuiltinFn::TypeOf)); } /// Execute a program pub fn run(&mut self, program: &Program) -> Result { let mut last_value = Value::Unit; for decl in &program.declarations { last_value = self.eval_declaration(decl)?; } Ok(last_value) } /// Execute a program with module support pub fn run_with_modules( &mut self, program: &Program, loader: &crate::modules::ModuleLoader, ) -> Result { // Process imports first self.load_imports(&program.imports, loader)?; // Then run the declarations self.run(program) } /// Load imports into the environment pub fn load_imports( &mut self, imports: &[ImportDecl], loader: &crate::modules::ModuleLoader, ) -> Result<(), RuntimeError> { use crate::modules::ImportKind; let resolved = loader.resolve_imports(imports).map_err(|e| RuntimeError { message: e.message, span: None, })?; for (name, import) in resolved { match import.kind { ImportKind::Module => { // Import as a module object - create a record with all exports let module = loader .get_module(&import.module_path) .ok_or_else(|| RuntimeError { message: format!("Module '{}' not found", import.module_path), span: None, })?; // Create a temporary interpreter to evaluate the module // Clone the module.program to avoid borrow issues let program = module.program.clone(); let exports = module.exports.clone(); let mut module_interp = Interpreter::new(); module_interp.run_with_modules(&program, loader)?; // Collect all public values into a record let mut module_record = HashMap::new(); for export_name in &exports { if let Some(value) = module_interp.global_env.get(export_name) { module_record.insert(export_name.clone(), value); } } self.global_env.define(&name, Value::Record(module_record)); } ImportKind::Direct => { // Import a specific name directly let module = loader .get_module(&import.module_path) .ok_or_else(|| RuntimeError { message: format!("Module '{}' not found", import.module_path), span: None, })?; // Clone the module data to avoid borrow issues let program = module.program.clone(); let import_name = import.name.clone(); let module_path = import.module_path.clone(); // Evaluate the module to get the value let mut module_interp = Interpreter::new(); module_interp.run_with_modules(&program, loader)?; if let Some(value) = module_interp.global_env.get(&import_name) { self.global_env.define(&name, value); } else { return Err(RuntimeError { message: format!( "'{}' not found in module '{}'", import_name, module_path ), span: None, }); } } } } Ok(()) } /// Evaluate a declaration fn eval_declaration(&mut self, decl: &Declaration) -> Result { match decl { Declaration::Function(func) => { let closure = Closure { params: func.params.iter().map(|p| p.name.name.clone()).collect(), body: func.body.clone(), env: self.global_env.clone(), }; let value = Value::Function(Rc::new(closure)); self.global_env.define(&func.name.name, value.clone()); Ok(value) } Declaration::Let(let_decl) => { let value = self.eval_expr(&let_decl.value, &self.global_env.clone())?; self.global_env.define(&let_decl.name.name, value.clone()); Ok(value) } Declaration::Handler(handler) => { let mut implementations = HashMap::new(); for impl_ in &handler.implementations { implementations.insert(impl_.op_name.name.clone(), impl_.clone()); } let handler_value = HandlerValue { effect: handler.effect.name.clone(), implementations, env: self.global_env.clone(), }; let value = Value::Handler(Rc::new(handler_value)); self.global_env.define(&handler.name.name, value.clone()); Ok(value) } Declaration::Type(type_decl) => { // Register ADT constructors if this is an enum type if let crate::ast::TypeDef::Enum(variants) = &type_decl.definition { for variant in variants { let constructor = Value::Constructor { name: variant.name.name.clone(), fields: Vec::new(), }; self.global_env.define(&variant.name.name, constructor); } } Ok(Value::Unit) } Declaration::Effect(_) | Declaration::Trait(_) | Declaration::Impl(_) => { // These are compile-time only Ok(Value::Unit) } } } /// Evaluate an expression with tail call optimization (trampoline) fn eval_expr(&mut self, expr: &Expr, env: &Env) -> Result { let mut result = self.eval_expr_inner(expr, env)?; // Trampoline loop for tail call optimization loop { match result { EvalResult::Value(v) => return Ok(v), EvalResult::Effect(req) => { // Handle the effect return self.handle_effect(req); } EvalResult::TailCall { func, args, span } => { // Continue the tail call without growing the stack result = self.eval_call(func, args, span)?; } } } } fn eval_expr_inner(&mut self, expr: &Expr, env: &Env) -> Result { self.eval_expr_tail(expr, env, false) } /// Evaluate an expression, with tail position tracking for TCO fn eval_expr_tail(&mut self, expr: &Expr, env: &Env, tail: bool) -> Result { match expr { Expr::Literal(lit) => Ok(EvalResult::Value(self.eval_literal(lit))), Expr::Var(ident) => match env.get(&ident.name) { Some(value) => Ok(EvalResult::Value(value)), None => Err(RuntimeError { message: format!("Undefined variable: {}", ident.name), span: Some(ident.span), }), }, Expr::BinaryOp { op, left, right, span, } => { let left_val = self.eval_expr(left, env)?; let right_val = self.eval_expr(right, env)?; Ok(EvalResult::Value( self.eval_binary_op(*op, left_val, right_val, *span)?, )) } Expr::UnaryOp { op, operand, span } => { let val = self.eval_expr(operand, env)?; Ok(EvalResult::Value(self.eval_unary_op(*op, val, *span)?)) } Expr::Call { func, args, span } => { let func_val = self.eval_expr(func, env)?; let arg_vals: Vec = args .iter() .map(|a| self.eval_expr(a, env)) .collect::>()?; // If we're in tail position, return TailCall for trampoline if tail { Ok(EvalResult::TailCall { func: func_val, args: arg_vals, span: *span, }) } else { self.eval_call(func_val, arg_vals, *span) } } Expr::EffectOp { effect, operation, args, span, } => { // Check if this is a module call instead of an effect operation // This includes stdlib modules (List, String, etc.) and user-imported modules if let Some(module_val) = env.get(&effect.name) { if let Value::Record(fields) = module_val { if let Some(func) = fields.get(&operation.name) { let arg_vals: Vec = args .iter() .map(|a| self.eval_expr(a, env)) .collect::>()?; return self.eval_call(func.clone(), arg_vals, *span); } else { return Err(RuntimeError { message: format!( "Module '{}' has no member '{}'", effect.name, operation.name ), span: Some(*span), }); } } } let arg_vals: Vec = args .iter() .map(|a| self.eval_expr(a, env)) .collect::>()?; // Create effect request let request = EffectRequest { effect: effect.name.clone(), operation: operation.name.clone(), args: arg_vals, continuation: Continuation::new(), }; Ok(EvalResult::Effect(request)) } Expr::Field { object, field, span, } => { let obj_val = self.eval_expr(object, env)?; match obj_val { Value::Record(fields) => match fields.get(&field.name) { Some(v) => Ok(EvalResult::Value(v.clone())), None => Err(RuntimeError { message: format!("Record has no field '{}'", field.name), span: Some(*span), }), }, _ => Err(RuntimeError { message: format!("Cannot access field on {}", obj_val.type_name()), span: Some(*span), }), } } Expr::Lambda { params, body, .. } => { let closure = Closure { params: params.iter().map(|p| p.name.name.clone()).collect(), body: (**body).clone(), env: env.clone(), }; Ok(EvalResult::Value(Value::Function(Rc::new(closure)))) } Expr::Let { name, value, body, .. } => { let val = self.eval_expr(value, env)?; let new_env = env.extend(); new_env.define(&name.name, val); // Body of let is in tail position if the let itself is self.eval_expr_tail(body, &new_env, tail) } Expr::If { condition, then_branch, else_branch, span, } => { let cond_val = self.eval_expr(condition, env)?; match cond_val { // Branches are in tail position if the if itself is Value::Bool(true) => self.eval_expr_tail(then_branch, env, tail), Value::Bool(false) => self.eval_expr_tail(else_branch, env, tail), _ => Err(RuntimeError { message: format!("If condition must be Bool, got {}", cond_val.type_name()), span: Some(*span), }), } } Expr::Match { scrutinee, arms, span, } => { let val = self.eval_expr(scrutinee, env)?; // Match arms are in tail position if the match itself is self.eval_match(val, arms, env, *span, tail) } Expr::Block { statements, result, .. } => { let block_env = env.extend(); for stmt in statements { match stmt { Statement::Expr(e) => { self.eval_expr(e, &block_env)?; } Statement::Let { name, value, .. } => { let val = self.eval_expr(value, &block_env)?; block_env.define(&name.name, val); } } } // Block result is in tail position if the block itself is self.eval_expr_tail(result, &block_env, tail) } Expr::Record { fields, .. } => { let mut record = HashMap::new(); for (name, expr) in fields { let val = self.eval_expr(expr, env)?; record.insert(name.name.clone(), val); } Ok(EvalResult::Value(Value::Record(record))) } Expr::Tuple { elements, .. } => { let vals: Vec = elements .iter() .map(|e| self.eval_expr(e, env)) .collect::>()?; Ok(EvalResult::Value(Value::Tuple(vals))) } Expr::List { elements, .. } => { let vals: Vec = elements .iter() .map(|e| self.eval_expr(e, env)) .collect::>()?; Ok(EvalResult::Value(Value::List(vals))) } Expr::Run { expr, handlers, span, } => self.eval_run(expr, handlers, env, *span), Expr::Resume { value, span } => Err(RuntimeError { message: "Resume called outside of handler".to_string(), span: Some(*span), }), } } fn eval_literal(&self, lit: &Literal) -> Value { match &lit.kind { LiteralKind::Int(n) => Value::Int(*n), LiteralKind::Float(f) => Value::Float(*f), LiteralKind::String(s) => Value::String(s.clone()), LiteralKind::Char(c) => Value::Char(*c), LiteralKind::Bool(b) => Value::Bool(*b), LiteralKind::Unit => Value::Unit, } } fn eval_binary_op( &mut self, op: BinaryOp, left: Value, right: Value, span: Span, ) -> Result { match op { BinaryOp::Add => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a + b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)), (l, r) => Err(RuntimeError { message: format!("Cannot add {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Sub => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a - b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)), (l, r) => Err(RuntimeError { message: format!("Cannot subtract {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Mul => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a * b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)), (l, r) => Err(RuntimeError { message: format!("Cannot multiply {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Div => match (left, right) { (Value::Int(a), Value::Int(b)) => { if b == 0 { Err(RuntimeError { message: "Division by zero".to_string(), span: Some(span), }) } else { Ok(Value::Int(a / b)) } } (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a / b)), (l, r) => Err(RuntimeError { message: format!("Cannot divide {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Mod => match (left, right) { (Value::Int(a), Value::Int(b)) => { if b == 0 { Err(RuntimeError { message: "Modulo by zero".to_string(), span: Some(span), }) } else { Ok(Value::Int(a % b)) } } (l, r) => Err(RuntimeError { message: format!("Cannot modulo {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Eq => Ok(Value::Bool(self.values_equal(&left, &right))), BinaryOp::Ne => Ok(Value::Bool(!self.values_equal(&left, &right))), BinaryOp::Lt => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a < b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a < b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a < b)), (l, r) => Err(RuntimeError { message: format!("Cannot compare {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Le => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a <= b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a <= b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a <= b)), (l, r) => Err(RuntimeError { message: format!("Cannot compare {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Gt => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a > b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a > b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a > b)), (l, r) => Err(RuntimeError { message: format!("Cannot compare {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Ge => match (left, right) { (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a >= b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a >= b)), (Value::String(a), Value::String(b)) => Ok(Value::Bool(a >= b)), (l, r) => Err(RuntimeError { message: format!("Cannot compare {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::And => match (left, right) { (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)), (l, r) => Err(RuntimeError { message: format!("Cannot 'and' {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Or => match (left, right) { (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)), (l, r) => Err(RuntimeError { message: format!("Cannot 'or' {} and {}", l.type_name(), r.type_name()), span: Some(span), }), }, BinaryOp::Pipe => { // a |> f means f(a) self.eval_call_to_value(right, vec![left], span) } } } fn eval_unary_op(&self, op: UnaryOp, val: Value, span: Span) -> Result { match op { UnaryOp::Neg => match val { Value::Int(n) => Ok(Value::Int(-n)), Value::Float(f) => Ok(Value::Float(-f)), v => Err(RuntimeError { message: format!("Cannot negate {}", v.type_name()), span: Some(span), }), }, UnaryOp::Not => match val { Value::Bool(b) => Ok(Value::Bool(!b)), v => Err(RuntimeError { message: format!("Cannot negate {}", v.type_name()), span: Some(span), }), }, } } fn eval_call( &mut self, func: Value, args: Vec, span: Span, ) -> Result { match func { Value::Function(closure) => { if closure.params.len() != args.len() { return Err(RuntimeError { message: format!( "Function expects {} arguments, got {}", closure.params.len(), args.len() ), span: Some(span), }); } let call_env = closure.env.extend(); for (param, arg) in closure.params.iter().zip(args) { call_env.define(param, arg); } // Evaluate body in tail position for TCO self.eval_expr_tail(&closure.body, &call_env, true) } Value::Constructor { name, fields } => { // Constructor application let mut new_fields = fields; new_fields.extend(args); Ok(EvalResult::Value(Value::Constructor { name, fields: new_fields, })) } Value::Builtin(builtin) => self.eval_builtin(builtin, args, span), v => Err(RuntimeError { message: format!("Cannot call {}", v.type_name()), span: Some(span), }), } } /// Fully evaluate a call, handling any tail calls via trampoline. /// Used by builtins that need to call user functions and get a value back. fn eval_call_to_value( &mut self, func: Value, args: Vec, span: Span, ) -> Result { let mut result = self.eval_call(func, args, span)?; loop { match result { EvalResult::Value(v) => return Ok(v), EvalResult::Effect(_) => { return Err(RuntimeError { message: "Effect in callback not supported".to_string(), span: Some(span), }); } EvalResult::TailCall { func, args, span } => { result = self.eval_call(func, args, span)?; } } } } fn eval_builtin( &mut self, builtin: BuiltinFn, args: Vec, span: Span, ) -> Result { let err = |msg: &str| RuntimeError { message: msg.to_string(), span: Some(span), }; match builtin { // List operations BuiltinFn::ListMap => { let (list, func) = Self::expect_args_2::, Value>(&args, "List.map", span)?; let mut result = Vec::with_capacity(list.len()); for item in list { let v = self.eval_call_to_value(func.clone(), vec![item], span)?; result.push(v); } Ok(EvalResult::Value(Value::List(result))) } BuiltinFn::ListFilter => { let (list, func) = Self::expect_args_2::, Value>(&args, "List.filter", span)?; let mut result = Vec::new(); for item in list { let v = self.eval_call_to_value(func.clone(), vec![item.clone()], span)?; match v { Value::Bool(true) => result.push(item), Value::Bool(false) => {} _ => { return Err(err(&format!( "List.filter predicate must return Bool, got {}", v.type_name() ))) } } } Ok(EvalResult::Value(Value::List(result))) } BuiltinFn::ListFold => { // List.fold(list, initial, fn(acc, item) => ...) if args.len() != 3 { return Err(err( "List.fold requires 3 arguments: list, initial, reducer", )); } let list = match &args[0] { Value::List(l) => l.clone(), v => { return Err(err(&format!( "List.fold expects List as first argument, got {}", v.type_name() ))) } }; let mut acc = args[1].clone(); let func = args[2].clone(); for item in list { acc = self.eval_call_to_value(func.clone(), vec![acc, item], span)?; } Ok(EvalResult::Value(acc)) } BuiltinFn::ListHead => { let list = Self::expect_arg_1::>(&args, "List.head", span)?; match list.first() { Some(v) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![v.clone()], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::ListTail => { let list = Self::expect_arg_1::>(&args, "List.tail", span)?; if list.is_empty() { Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })) } else { Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::List(list[1..].to_vec())], })) } } BuiltinFn::ListConcat => { let (list1, list2) = Self::expect_args_2::, Vec>(&args, "List.concat", span)?; let mut result = list1; result.extend(list2); Ok(EvalResult::Value(Value::List(result))) } BuiltinFn::ListReverse => { let mut list = Self::expect_arg_1::>(&args, "List.reverse", span)?; list.reverse(); Ok(EvalResult::Value(Value::List(list))) } BuiltinFn::ListLength => { let list = Self::expect_arg_1::>(&args, "List.length", span)?; Ok(EvalResult::Value(Value::Int(list.len() as i64))) } BuiltinFn::ListGet => { let (list, idx) = Self::expect_args_2::, i64>(&args, "List.get", span)?; if idx < 0 || idx as usize >= list.len() { Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })) } else { Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![list[idx as usize].clone()], })) } } BuiltinFn::ListRange => { let (start, end) = Self::expect_args_2::(&args, "List.range", span)?; let list: Vec = (start..end).map(Value::Int).collect(); Ok(EvalResult::Value(Value::List(list))) } // String operations BuiltinFn::StringSplit => { let (s, delim) = Self::expect_args_2::(&args, "String.split", span)?; let parts: Vec = s .split(&delim) .map(|p| Value::String(p.to_string())) .collect(); Ok(EvalResult::Value(Value::List(parts))) } BuiltinFn::StringJoin => { let (list, sep) = Self::expect_args_2::, String>(&args, "String.join", span)?; let strings: Result, _> = list .iter() .map(|v| match v { Value::String(s) => Ok(s.clone()), _ => Err(err("String.join requires list of strings")), }) .collect(); Ok(EvalResult::Value(Value::String(strings?.join(&sep)))) } BuiltinFn::StringTrim => { let s = Self::expect_arg_1::(&args, "String.trim", span)?; Ok(EvalResult::Value(Value::String(s.trim().to_string()))) } BuiltinFn::StringContains => { let (s, needle) = Self::expect_args_2::(&args, "String.contains", span)?; Ok(EvalResult::Value(Value::Bool(s.contains(&needle)))) } BuiltinFn::StringReplace => { if args.len() != 3 { return Err(err("String.replace requires 3 arguments: string, from, to")); } let s = match &args[0] { Value::String(s) => s.clone(), v => { return Err(err(&format!( "String.replace expects String, got {}", v.type_name() ))) } }; let from = match &args[1] { Value::String(s) => s.clone(), v => { return Err(err(&format!( "String.replace expects String, got {}", v.type_name() ))) } }; let to = match &args[2] { Value::String(s) => s.clone(), v => { return Err(err(&format!( "String.replace expects String, got {}", v.type_name() ))) } }; Ok(EvalResult::Value(Value::String(s.replace(&from, &to)))) } BuiltinFn::StringLength => { let s = Self::expect_arg_1::(&args, "String.length", span)?; Ok(EvalResult::Value(Value::Int(s.len() as i64))) } BuiltinFn::StringChars => { let s = Self::expect_arg_1::(&args, "String.chars", span)?; let chars: Vec = s.chars().map(Value::Char).collect(); Ok(EvalResult::Value(Value::List(chars))) } BuiltinFn::StringLines => { let s = Self::expect_arg_1::(&args, "String.lines", span)?; let lines: Vec = s.lines().map(|l| Value::String(l.to_string())).collect(); Ok(EvalResult::Value(Value::List(lines))) } // Option operations BuiltinFn::OptionMap => { let (opt, func) = Self::expect_args_2::(&args, "Option.map", span)?; match opt { Value::Constructor { name, fields } if name == "Some" && !fields.is_empty() => { let v = self.eval_call_to_value(func, vec![fields[0].clone()], span)?; Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![v], })) } Value::Constructor { name, .. } if name == "None" => { Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })) } v => Err(err(&format!( "Option.map expects Option, got {}", v.type_name() ))), } } BuiltinFn::OptionFlatMap => { let (opt, func) = Self::expect_args_2::(&args, "Option.flatMap", span)?; match opt { Value::Constructor { name, fields } if name == "Some" && !fields.is_empty() => { let v = self.eval_call_to_value(func, vec![fields[0].clone()], span)?; Ok(EvalResult::Value(v)) } Value::Constructor { name, .. } if name == "None" => { Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })) } v => Err(err(&format!( "Option.flatMap expects Option, got {}", v.type_name() ))), } } BuiltinFn::OptionGetOrElse => { let (opt, default) = Self::expect_args_2::(&args, "Option.getOrElse", span)?; match opt { Value::Constructor { name, fields } if name == "Some" && !fields.is_empty() => { Ok(EvalResult::Value(fields[0].clone())) } Value::Constructor { name, .. } if name == "None" => { Ok(EvalResult::Value(default)) } v => Err(err(&format!( "Option.getOrElse expects Option, got {}", v.type_name() ))), } } BuiltinFn::OptionIsSome => { let opt = Self::expect_arg_1::(&args, "Option.isSome", span)?; match opt { Value::Constructor { name, .. } if name == "Some" => { Ok(EvalResult::Value(Value::Bool(true))) } Value::Constructor { name, .. } if name == "None" => { Ok(EvalResult::Value(Value::Bool(false))) } v => Err(err(&format!( "Option.isSome expects Option, got {}", v.type_name() ))), } } BuiltinFn::OptionIsNone => { let opt = Self::expect_arg_1::(&args, "Option.isNone", span)?; match opt { Value::Constructor { name, .. } if name == "None" => { Ok(EvalResult::Value(Value::Bool(true))) } Value::Constructor { name, .. } if name == "Some" => { Ok(EvalResult::Value(Value::Bool(false))) } v => Err(err(&format!( "Option.isNone expects Option, got {}", v.type_name() ))), } } // Result operations BuiltinFn::ResultMap => { let (res, func) = Self::expect_args_2::(&args, "Result.map", span)?; match res { Value::Constructor { name, fields } if name == "Ok" && !fields.is_empty() => { let v = self.eval_call_to_value(func, vec![fields[0].clone()], span)?; Ok(EvalResult::Value(Value::Constructor { name: "Ok".to_string(), fields: vec![v], })) } Value::Constructor { name, fields } if name == "Err" => { Ok(EvalResult::Value(Value::Constructor { name: "Err".to_string(), fields, })) } v => Err(err(&format!( "Result.map expects Result, got {}", v.type_name() ))), } } BuiltinFn::ResultFlatMap => { let (res, func) = Self::expect_args_2::(&args, "Result.flatMap", span)?; match res { Value::Constructor { name, fields } if name == "Ok" && !fields.is_empty() => { let v = self.eval_call_to_value(func, vec![fields[0].clone()], span)?; Ok(EvalResult::Value(v)) } Value::Constructor { name, fields } if name == "Err" => { Ok(EvalResult::Value(Value::Constructor { name: "Err".to_string(), fields, })) } v => Err(err(&format!( "Result.flatMap expects Result, got {}", v.type_name() ))), } } BuiltinFn::ResultGetOrElse => { let (res, default) = Self::expect_args_2::(&args, "Result.getOrElse", span)?; match res { Value::Constructor { name, fields } if name == "Ok" && !fields.is_empty() => { Ok(EvalResult::Value(fields[0].clone())) } Value::Constructor { name, .. } if name == "Err" => { Ok(EvalResult::Value(default)) } v => Err(err(&format!( "Result.getOrElse expects Result, got {}", v.type_name() ))), } } BuiltinFn::ResultIsOk => { let res = Self::expect_arg_1::(&args, "Result.isOk", span)?; match res { Value::Constructor { name, .. } if name == "Ok" => { Ok(EvalResult::Value(Value::Bool(true))) } Value::Constructor { name, .. } if name == "Err" => { Ok(EvalResult::Value(Value::Bool(false))) } v => Err(err(&format!( "Result.isOk expects Result, got {}", v.type_name() ))), } } BuiltinFn::ResultIsErr => { let res = Self::expect_arg_1::(&args, "Result.isErr", span)?; match res { Value::Constructor { name, .. } if name == "Err" => { Ok(EvalResult::Value(Value::Bool(true))) } Value::Constructor { name, .. } if name == "Ok" => { Ok(EvalResult::Value(Value::Bool(false))) } v => Err(err(&format!( "Result.isErr expects Result, got {}", v.type_name() ))), } } // Utility functions BuiltinFn::Print => { for arg in &args { match arg { Value::String(s) => print!("{}", s), v => print!("{}", v), } } println!(); Ok(EvalResult::Value(Value::Unit)) } BuiltinFn::ToString => { if args.len() != 1 { return Err(err("toString requires 1 argument")); } // For strings, return the string itself (no quotes) // For other values, use Display formatting let result = match &args[0] { Value::String(s) => s.clone(), v => format!("{}", v), }; Ok(EvalResult::Value(Value::String(result))) } BuiltinFn::TypeOf => { if args.len() != 1 { return Err(err("typeOf requires 1 argument")); } Ok(EvalResult::Value(Value::String( args[0].type_name().to_string(), ))) } } } // Helper functions for extracting typed arguments fn expect_arg_1(args: &[Value], name: &str, span: Span) -> Result where T: TryFromValue, { if args.len() != 1 { return Err(RuntimeError { message: format!("{} requires 1 argument, got {}", name, args.len()), span: Some(span), }); } T::try_from_value(&args[0]).ok_or_else(|| RuntimeError { message: format!("{} expects {} as argument", name, T::TYPE_NAME), span: Some(span), }) } fn expect_args_2(args: &[Value], name: &str, span: Span) -> Result<(T, U), RuntimeError> where T: TryFromValue, U: TryFromValue, { if args.len() != 2 { return Err(RuntimeError { message: format!("{} requires 2 arguments, got {}", name, args.len()), span: Some(span), }); } let a = T::try_from_value(&args[0]).ok_or_else(|| RuntimeError { message: format!("{} expects {} as first argument", name, T::TYPE_NAME), span: Some(span), })?; let b = U::try_from_value(&args[1]).ok_or_else(|| RuntimeError { message: format!("{} expects {} as second argument", name, U::TYPE_NAME), span: Some(span), })?; Ok((a, b)) } fn eval_match( &mut self, val: Value, arms: &[MatchArm], env: &Env, span: Span, tail: bool, ) -> Result { for arm in arms { if let Some(bindings) = self.match_pattern(&arm.pattern, &val) { let match_env = env.extend(); for (name, value) in bindings { match_env.define(name, value); } // Check guard if present if let Some(ref guard) = arm.guard { let guard_val = self.eval_expr(guard, &match_env)?; match guard_val { Value::Bool(true) => {} Value::Bool(false) => continue, _ => { return Err(RuntimeError { message: "Match guard must be Bool".to_string(), span: Some(arm.span), }); } } } // Match arm body is in tail position if the match itself is return self.eval_expr_tail(&arm.body, &match_env, tail); } } Err(RuntimeError { message: "No matching pattern".to_string(), span: Some(span), }) } fn match_pattern(&self, pattern: &Pattern, value: &Value) -> Option> { match pattern { Pattern::Wildcard(_) => Some(Vec::new()), Pattern::Var(ident) => Some(vec![(ident.name.clone(), value.clone())]), Pattern::Literal(lit) => { let lit_val = self.eval_literal(lit); if self.values_equal(&lit_val, value) { Some(Vec::new()) } else { None } } Pattern::Constructor { name, fields, .. } => match value { Value::Constructor { name: val_name, fields: val_fields, } => { if name.name != *val_name { return None; } if fields.len() != val_fields.len() { return None; } let mut bindings = Vec::new(); for (pat, val) in fields.iter().zip(val_fields) { bindings.extend(self.match_pattern(pat, val)?); } Some(bindings) } _ => None, }, Pattern::Tuple { elements, .. } => match value { Value::Tuple(vals) => { if elements.len() != vals.len() { return None; } let mut bindings = Vec::new(); for (pat, val) in elements.iter().zip(vals) { bindings.extend(self.match_pattern(pat, val)?); } Some(bindings) } _ => None, }, Pattern::Record { fields, .. } => match value { Value::Record(val_fields) => { let mut bindings = Vec::new(); for (name, pat) in fields { let val = val_fields.get(&name.name)?; bindings.extend(self.match_pattern(pat, val)?); } Some(bindings) } _ => None, }, } } fn values_equal(&self, a: &Value, b: &Value) -> bool { match (a, b) { (Value::Int(a), Value::Int(b)) => a == b, (Value::Float(a), Value::Float(b)) => a == b, (Value::Bool(a), Value::Bool(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Char(a), Value::Char(b)) => a == b, (Value::Unit, Value::Unit) => true, (Value::List(a), Value::List(b)) => { a.len() == b.len() && a.iter().zip(b).all(|(x, y)| self.values_equal(x, y)) } (Value::Tuple(a), Value::Tuple(b)) => { a.len() == b.len() && a.iter().zip(b).all(|(x, y)| self.values_equal(x, y)) } ( Value::Constructor { name: n1, fields: f1, }, Value::Constructor { name: n2, fields: f2, }, ) => { n1 == n2 && f1.len() == f2.len() && f1.iter().zip(f2).all(|(x, y)| self.values_equal(x, y)) } _ => false, } } fn eval_run( &mut self, expr: &Expr, handlers: &[(Ident, Expr)], env: &Env, span: Span, ) -> Result { // Evaluate handlers and push onto stack let mut handler_values = Vec::new(); for (effect_name, handler_expr) in handlers { let handler_val = self.eval_expr(handler_expr, env)?; match handler_val { Value::Handler(h) => { if h.effect != effect_name.name { return Err(RuntimeError { message: format!( "Handler for effect '{}' assigned to '{}'", h.effect, effect_name.name ), span: Some(span), }); } handler_values.push(h); } _ => { return Err(RuntimeError { message: format!( "Expected handler for effect '{}', got {}", effect_name.name, handler_val.type_name() ), span: Some(span), }); } } } // Push handlers for h in &handler_values { self.handler_stack.push(Rc::clone(h)); } // Evaluate expression let result = self.eval_expr_inner(expr, env); // Pop handlers for _ in &handler_values { self.handler_stack.pop(); } result } fn handle_effect(&mut self, request: EffectRequest) -> Result { let trace_enabled = self.trace_effects; let timestamp = if trace_enabled { self.start_time.elapsed().as_micros() } else { 0 }; // Find a handler for this effect - clone what we need to avoid borrow issues let handler_data: Option<(Env, crate::ast::Expr, Vec)> = self .handler_stack .iter() .rev() .find(|h| h.effect == request.effect) .and_then(|handler| { handler .implementations .get(&request.operation) .map(|impl_| { ( handler.env.clone(), impl_.body.clone(), impl_.params.clone(), ) }) }); let result = if let Some((handler_env, body, params)) = handler_data { let env = handler_env.extend(); for (i, param) in params.iter().enumerate() { if i < request.args.len() { env.define(¶m.name, request.args[i].clone()); } } self.eval_expr(&body, &env) } else { // No handler found - check for built-in effects self.handle_builtin_effect(&request) }; // Record trace if enabled if trace_enabled { self.effect_traces.push(EffectTrace { effect: request.effect.clone(), operation: request.operation.clone(), args: request.args.clone(), result: result.as_ref().ok().cloned(), timestamp_us: timestamp, }); } result } fn handle_builtin_effect(&self, request: &EffectRequest) -> Result { match (request.effect.as_str(), request.operation.as_str()) { ("Console", "print") => { if let Some(Value::String(s)) = request.args.first() { println!("{}", s); Ok(Value::Unit) } else if let Some(v) = request.args.first() { println!("{}", v); Ok(Value::Unit) } else { Ok(Value::Unit) } } ("Console", "read") => { let mut input = String::new(); std::io::stdin() .read_line(&mut input) .map_err(|e| RuntimeError { message: format!("Failed to read input: {}", e), span: None, })?; Ok(Value::String(input.trim().to_string())) } ("Fail", "fail") => { let msg = request .args .first() .map(|v| format!("{}", v)) .unwrap_or_else(|| "Unknown error".to_string()); Err(RuntimeError { message: msg, span: None, }) } _ => Err(RuntimeError { message: format!( "Unhandled effect operation: {}.{}", request.effect, request.operation ), span: None, }), } } } impl Default for Interpreter { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_versioned() { let interp = Interpreter::new(); let record = Value::Record( [("name".to_string(), Value::String("Alice".to_string()))] .into_iter() .collect(), ); let versioned = interp.create_versioned("User", 1, record); match versioned { Value::Versioned { type_name, version, value, } => { assert_eq!(type_name, "User"); assert_eq!(version, 1); match *value { Value::Record(fields) => match fields.get("name") { Some(Value::String(s)) => assert_eq!(s, "Alice"), _ => panic!("Expected name field with String value"), }, _ => panic!("Expected Record value"), } } _ => panic!("Expected Versioned value"), } } #[test] fn test_migrate_non_versioned_passthrough() { let mut interp = Interpreter::new(); let value = Value::Int(42); let result = interp.migrate_value(value, 2).unwrap(); match result { Value::Int(n) => assert_eq!(n, 42), _ => panic!("Expected Int value to pass through unchanged"), } } #[test] fn test_migrate_same_version() { let mut interp = Interpreter::new(); let versioned = Value::Versioned { type_name: "User".to_string(), version: 2, value: Box::new(Value::String("test".to_string())), }; let result = interp.migrate_value(versioned, 2).unwrap(); match result { Value::Versioned { version, .. } => assert_eq!(version, 2), _ => panic!("Expected Versioned value"), } } #[test] fn test_migrate_downgrade_error() { let mut interp = Interpreter::new(); let versioned = Value::Versioned { type_name: "User".to_string(), version: 3, value: Box::new(Value::String("test".to_string())), }; let result = interp.migrate_value(versioned, 2); assert!(result.is_err()); assert!(result.unwrap_err().message.contains("Cannot downgrade")); } #[test] fn test_migrate_with_auto_migration() { let mut interp = Interpreter::new(); // No explicit migration registered - should auto-migrate let versioned = Value::Versioned { type_name: "User".to_string(), version: 1, value: Box::new(Value::String("test".to_string())), }; let result = interp.migrate_value(versioned, 3).unwrap(); match result { Value::Versioned { version, value, .. } => { assert_eq!(version, 3); // Value should be unchanged for auto-migration match *value { Value::String(s) => assert_eq!(s, "test"), _ => panic!("Expected String value"), } } _ => panic!("Expected Versioned value"), } } #[test] fn test_register_and_execute_migration() { let mut interp = Interpreter::new(); // Create a simple migration that adds a field // Migration: old.name -> { name: old.name, email: "unknown" } let migration_body = Expr::Record { fields: vec![ ( Ident::new("name", Span::default()), Expr::Field { object: Box::new(Expr::Var(Ident::new("old", Span::default()))), field: Ident::new("name", Span::default()), span: Span::default(), }, ), ( Ident::new("email", Span::default()), Expr::Literal(Literal { kind: LiteralKind::String("unknown@example.com".to_string()), span: Span::default(), }), ), ], span: Span::default(), }; let stored_migration = StoredMigration { body: migration_body, env: Env::new(), }; interp.register_migration("User", 1, stored_migration); // Create a v1 value let v1_user = Value::Versioned { type_name: "User".to_string(), version: 1, value: Box::new(Value::Record( [("name".to_string(), Value::String("Alice".to_string()))] .into_iter() .collect(), )), }; // Migrate to v2 let result = interp.migrate_value(v1_user, 2).unwrap(); match result { Value::Versioned { type_name, version, value, } => { assert_eq!(type_name, "User"); assert_eq!(version, 2); match *value { Value::Record(fields) => { match fields.get("name") { Some(Value::String(s)) => assert_eq!(s, "Alice"), _ => panic!("Expected name field with String value"), } match fields.get("email") { Some(Value::String(s)) => assert_eq!(s, "unknown@example.com"), _ => panic!("Expected email field with String value"), } } _ => panic!("Expected Record value"), } } _ => panic!("Expected Versioned value"), } } }