//! Tree-walking interpreter for the Lux language with algebraic effects #![allow(dead_code, unused_variables)] use crate::ast::*; use crate::diagnostics::{Diagnostic, ErrorCode, Severity}; use rand::Rng; use postgres::{Client as PgClient, NoTls}; use rusqlite::Connection; use std::cell::RefCell; use std::collections::HashMap; use std::fmt; use std::rc::Rc; use std::sync::{Arc, Mutex}; /// 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, ListForEach, // String operations StringSplit, StringJoin, StringTrim, StringContains, StringReplace, StringLength, StringChars, StringLines, StringParseInt, StringParseFloat, // Option operations OptionMap, OptionFlatMap, OptionGetOrElse, OptionIsSome, OptionIsNone, // Result operations ResultMap, ResultFlatMap, ResultGetOrElse, ResultIsOk, ResultIsErr, // Utility Print, ToString, TypeOf, // Schema Evolution Versioned, // Create versioned value: versioned("TypeName", 1, value) Migrate, // Migrate to version: migrate(versionedValue, targetVersion) GetVersion, // Get version number: getVersion(versionedValue) // Math operations MathAbs, MathMin, MathMax, MathSqrt, MathPow, MathFloor, MathCeil, MathRound, // Additional List operations ListIsEmpty, ListFind, ListAny, ListAll, ListTake, ListDrop, // Additional String operations StringStartsWith, StringEndsWith, StringToUpper, StringToLower, StringSubstring, StringFromChar, StringCharAt, StringIndexOf, StringLastIndexOf, StringRepeat, // Int/Float operations IntToString, FloatToString, // JSON operations JsonParse, JsonStringify, JsonPrettyPrint, JsonGet, JsonGetIndex, JsonAsString, JsonAsNumber, JsonAsInt, JsonAsBool, JsonAsArray, JsonIsNull, JsonKeys, JsonNull, JsonBool, JsonNumber, JsonInt, JsonString, JsonArray, JsonObject, } /// 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, }, /// JSON value (for JSON parsing/manipulation) Json(serde_json::Value), } 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", Value::Json(_) => "Json", } } /// 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, } } /// Compare two values for equality (for testing) /// Returns true if the values are structurally equal pub fn values_equal(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Int(x), Value::Int(y)) => x == y, (Value::Float(x), Value::Float(y)) => (x - y).abs() < f64::EPSILON, (Value::Bool(x), Value::Bool(y)) => x == y, (Value::String(x), Value::String(y)) => x == y, (Value::Char(x), Value::Char(y)) => x == y, (Value::Unit, Value::Unit) => true, (Value::List(xs), Value::List(ys)) => { xs.len() == ys.len() && xs.iter().zip(ys.iter()).all(|(x, y)| Value::values_equal(x, y)) } (Value::Tuple(xs), Value::Tuple(ys)) => { xs.len() == ys.len() && xs.iter().zip(ys.iter()).all(|(x, y)| Value::values_equal(x, y)) } (Value::Record(xs), Value::Record(ys)) => { xs.len() == ys.len() && xs.iter().all(|(k, v)| { ys.get(k).map(|yv| Value::values_equal(v, yv)).unwrap_or(false) }) } (Value::Constructor { name: n1, fields: f1 }, Value::Constructor { name: n2, fields: f2 }) => { n1 == n2 && f1.len() == f2.len() && f1.iter().zip(f2.iter()).all(|(x, y)| Value::values_equal(x, y)) } (Value::Versioned { type_name: t1, version: v1, value: val1 }, Value::Versioned { type_name: t2, version: v2, value: val2 }) => { t1 == t2 && v1 == v2 && Value::values_equal(val1, val2) } (Value::Json(j1), Value::Json(j2)) => j1 == j2, // Functions and handlers cannot be compared for equality _ => false, } } } /// 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) } Value::Json(json) => write!(f, "{}", json), } } } /// 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 (code, title, hints) = categorize_runtime_error(&self.message); Diagnostic { severity: Severity::Error, code, title, message: self.message.clone(), span: self.span.unwrap_or_default(), hints, expected_type: None, actual_type: None, secondary_spans: Vec::new(), } } } /// Categorize runtime errors to provide better titles, hints, and error codes fn categorize_runtime_error(message: &str) -> (Option, String, Vec) { let message_lower = message.to_lowercase(); if message_lower.contains("undefined variable") { ( Some(ErrorCode::E0301), "Undefined Variable".to_string(), vec!["Make sure the variable is defined and in scope.".to_string()], ) } else if message_lower.contains("undefined function") || message_lower.contains("function not found") { ( Some(ErrorCode::E0302), "Undefined Function".to_string(), vec!["Make sure the function is defined and in scope.".to_string()], ) } else if message_lower.contains("undefined") || message_lower.contains("not found") { ( Some(ErrorCode::E0301), "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") { ( None, // Runtime error, not a type error "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") { ( Some(ErrorCode::E0201), "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") { ( Some(ErrorCode::E0401), "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") { ( Some(ErrorCode::E0501), "Non-exhaustive Pattern".to_string(), vec!["Add more patterns to cover all possible cases.".to_string()], ) } else if message_lower.contains("argument") { ( Some(ErrorCode::E0209), "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") { ( None, // Runtime error "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 { (None, "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, }, /// Resume from a handler - the value becomes the effect operation's return value Resume(Value), } /// 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 (kept for nested handler semantics) handler_stack: Vec>, /// Evidence map for O(1) handler lookup (evidence passing optimization) /// Maps effect name -> handler, updated when entering/exiting run blocks evidence: HashMap>, /// 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>, /// Built-in State effect storage (uses RefCell for interior mutability) builtin_state: RefCell, /// Built-in Reader effect value (uses RefCell for interior mutability) builtin_reader: RefCell, /// Depth of handler context (> 0 means we're inside a handler body where resume is valid) in_handler_depth: usize, /// HTTP server state (using Arc for thread-safety with tiny_http) http_server: Arc>>, /// Current HTTP request being handled (stored for respond operation) current_http_request: Arc>>, /// Test results for the Test effect test_results: RefCell, /// SQL database connections (connection ID -> Connection) sql_connections: RefCell>, /// Next SQL connection ID next_sql_conn_id: RefCell, /// PostgreSQL database connections (connection ID -> Client) pg_connections: RefCell>, /// Next PostgreSQL connection ID next_pg_conn_id: RefCell, /// Concurrent tasks: task_id -> (thunk_value, result_option, is_cancelled) concurrent_tasks: RefCell, bool)>>, /// Next task ID next_task_id: RefCell, /// Channels: channel_id -> (queue, is_closed) channels: RefCell, bool)>>, /// Next channel ID next_channel_id: RefCell, } /// Results from running tests #[derive(Debug, Clone, Default)] pub struct TestResults { pub passed: usize, pub failed: usize, pub failures: Vec, } /// A single test failure #[derive(Debug, Clone)] pub struct TestFailure { pub message: String, pub expected: Option, pub actual: Option, } 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(), evidence: HashMap::new(), continuations: HashMap::new(), trace_effects: false, effect_traces: Vec::new(), start_time: std::time::Instant::now(), migrations: HashMap::new(), builtin_state: RefCell::new(Value::Unit), builtin_reader: RefCell::new(Value::Unit), in_handler_depth: 0, http_server: Arc::new(Mutex::new(None)), current_http_request: Arc::new(Mutex::new(None)), test_results: RefCell::new(TestResults::default()), sql_connections: RefCell::new(HashMap::new()), next_sql_conn_id: RefCell::new(1), pg_connections: RefCell::new(HashMap::new()), next_pg_conn_id: RefCell::new(1), concurrent_tasks: RefCell::new(HashMap::new()), next_task_id: RefCell::new(1), channels: RefCell::new(HashMap::new()), next_channel_id: RefCell::new(1), } } /// Get the test results pub fn get_test_results(&self) -> TestResults { self.test_results.borrow().clone() } /// Reset test results for a new test run pub fn reset_test_results(&self) { *self.test_results.borrow_mut() = TestResults::default(); } /// Set the initial value for the built-in State effect pub fn set_state(&self, value: Value) { *self.builtin_state.borrow_mut() = value; } /// Get the current value of the built-in State effect pub fn get_state(&self) -> Value { self.builtin_state.borrow().clone() } /// Set the value for the built-in Reader effect pub fn set_reader(&self, value: Value) { *self.builtin_reader.borrow_mut() = value; } /// Get the current value of the built-in Reader effect pub fn get_reader(&self) -> Value { self.builtin_reader.borrow().clone() } /// 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); } /// Register auto-generated migrations from the typechecker /// These are migrations that were automatically generated for auto-migratable schema changes pub fn register_auto_migrations( &mut self, auto_migrations: &std::collections::HashMap>, ) { for (type_name, version_migrations) in auto_migrations { for (from_version, body) in version_migrations { // Only register if no migration already exists let already_has = self .migrations .get(type_name) .map(|m| m.contains_key(from_version)) .unwrap_or(false); if !already_has { let stored = StoredMigration { body: body.clone(), env: self.global_env.clone(), }; self.register_migration(type_name, *from_version, stored); } } } } /// 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)), ( "isEmpty".to_string(), Value::Builtin(BuiltinFn::ListIsEmpty), ), ("find".to_string(), Value::Builtin(BuiltinFn::ListFind)), ("any".to_string(), Value::Builtin(BuiltinFn::ListAny)), ("all".to_string(), Value::Builtin(BuiltinFn::ListAll)), ("take".to_string(), Value::Builtin(BuiltinFn::ListTake)), ("drop".to_string(), Value::Builtin(BuiltinFn::ListDrop)), ( "forEach".to_string(), Value::Builtin(BuiltinFn::ListForEach), ), ])); 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)), ( "startsWith".to_string(), Value::Builtin(BuiltinFn::StringStartsWith), ), ( "endsWith".to_string(), Value::Builtin(BuiltinFn::StringEndsWith), ), ( "toUpper".to_string(), Value::Builtin(BuiltinFn::StringToUpper), ), ( "toLower".to_string(), Value::Builtin(BuiltinFn::StringToLower), ), ( "substring".to_string(), Value::Builtin(BuiltinFn::StringSubstring), ), ( "fromChar".to_string(), Value::Builtin(BuiltinFn::StringFromChar), ), ( "parseInt".to_string(), Value::Builtin(BuiltinFn::StringParseInt), ), ( "parseFloat".to_string(), Value::Builtin(BuiltinFn::StringParseFloat), ), ( "charAt".to_string(), Value::Builtin(BuiltinFn::StringCharAt), ), ( "indexOf".to_string(), Value::Builtin(BuiltinFn::StringIndexOf), ), ( "lastIndexOf".to_string(), Value::Builtin(BuiltinFn::StringLastIndexOf), ), ( "repeat".to_string(), Value::Builtin(BuiltinFn::StringRepeat), ), ])); 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)); // Schema Evolution module let schema_module = Value::Record(HashMap::from([ ( "versioned".to_string(), Value::Builtin(BuiltinFn::Versioned), ), ("migrate".to_string(), Value::Builtin(BuiltinFn::Migrate)), ( "getVersion".to_string(), Value::Builtin(BuiltinFn::GetVersion), ), ])); env.define("Schema", schema_module); // Math module let math_module = Value::Record(HashMap::from([ ("abs".to_string(), Value::Builtin(BuiltinFn::MathAbs)), ("min".to_string(), Value::Builtin(BuiltinFn::MathMin)), ("max".to_string(), Value::Builtin(BuiltinFn::MathMax)), ("sqrt".to_string(), Value::Builtin(BuiltinFn::MathSqrt)), ("pow".to_string(), Value::Builtin(BuiltinFn::MathPow)), ("floor".to_string(), Value::Builtin(BuiltinFn::MathFloor)), ("ceil".to_string(), Value::Builtin(BuiltinFn::MathCeil)), ("round".to_string(), Value::Builtin(BuiltinFn::MathRound)), ])); env.define("Math", math_module); // Int module let int_module = Value::Record(HashMap::from([ ("toString".to_string(), Value::Builtin(BuiltinFn::IntToString)), ])); env.define("Int", int_module); // Float module let float_module = Value::Record(HashMap::from([ ("toString".to_string(), Value::Builtin(BuiltinFn::FloatToString)), ])); env.define("Float", float_module); // JSON module let json_module = Value::Record(HashMap::from([ ("parse".to_string(), Value::Builtin(BuiltinFn::JsonParse)), ("stringify".to_string(), Value::Builtin(BuiltinFn::JsonStringify)), ("prettyPrint".to_string(), Value::Builtin(BuiltinFn::JsonPrettyPrint)), ("get".to_string(), Value::Builtin(BuiltinFn::JsonGet)), ("getIndex".to_string(), Value::Builtin(BuiltinFn::JsonGetIndex)), ("asString".to_string(), Value::Builtin(BuiltinFn::JsonAsString)), ("asNumber".to_string(), Value::Builtin(BuiltinFn::JsonAsNumber)), ("asInt".to_string(), Value::Builtin(BuiltinFn::JsonAsInt)), ("asBool".to_string(), Value::Builtin(BuiltinFn::JsonAsBool)), ("asArray".to_string(), Value::Builtin(BuiltinFn::JsonAsArray)), ("isNull".to_string(), Value::Builtin(BuiltinFn::JsonIsNull)), ("keys".to_string(), Value::Builtin(BuiltinFn::JsonKeys)), ("null".to_string(), Value::Builtin(BuiltinFn::JsonNull)), ("bool".to_string(), Value::Builtin(BuiltinFn::JsonBool)), ("number".to_string(), Value::Builtin(BuiltinFn::JsonNumber)), ("int".to_string(), Value::Builtin(BuiltinFn::JsonInt)), ("string".to_string(), Value::Builtin(BuiltinFn::JsonString)), ("array".to_string(), Value::Builtin(BuiltinFn::JsonArray)), ("object".to_string(), Value::Builtin(BuiltinFn::JsonObject)), ])); env.define("Json", json_module); } /// 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); } } // Register migrations for versioned types for migration in &type_decl.migrations { let stored = StoredMigration { body: migration.body.clone(), env: self.global_env.clone(), }; self.register_migration( &type_decl.name.name, migration.from_version.number, stored, ); } 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)?; } EvalResult::Resume(v) => { // Resume propagates up - return the value return Ok(v); } } } } 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::TupleIndex { object, index, span, } => { let obj_val = self.eval_expr(object, env)?; match obj_val { Value::Tuple(elements) => { if *index < elements.len() { Ok(EvalResult::Value(elements[*index].clone())) } else { Err(RuntimeError { message: format!( "Tuple index {} out of bounds for tuple with {} elements", index, elements.len() ), span: Some(*span), }) } } _ => Err(RuntimeError { message: format!("Cannot use tuple index 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 } => { if self.in_handler_depth > 0 { // We're inside a handler body - evaluate the value and return Resume let val = self.eval_expr(value, env)?; Ok(EvalResult::Resume(val)) } else { 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)), (Value::Char(a), Value::Char(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)), (Value::Char(a), Value::Char(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)), (Value::Char(a), Value::Char(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)), (Value::Char(a), Value::Char(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(req) => { // Handle the effect and continue let handled = self.handle_effect(req)?; return Ok(handled); } EvalResult::TailCall { func, args, span } => { result = self.eval_call(func, args, span)?; } EvalResult::Resume(v) => return Ok(v), } } } 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))) } BuiltinFn::StringParseInt => { let s = Self::expect_arg_1::(&args, "String.parseInt", span)?; let trimmed = s.trim(); match trimmed.parse::() { Ok(n) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Int(n)], })), Err(_) => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::StringParseFloat => { let s = Self::expect_arg_1::(&args, "String.parseFloat", span)?; let trimmed = s.trim(); match trimmed.parse::() { Ok(f) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Float(f)], })), Err(_) => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } // 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::IntToString => { if args.len() != 1 { return Err(err("Int.toString requires 1 argument")); } match &args[0] { Value::Int(n) => Ok(EvalResult::Value(Value::String(format!("{}", n)))), v => Ok(EvalResult::Value(Value::String(format!("{}", v)))), } } BuiltinFn::FloatToString => { if args.len() != 1 { return Err(err("Float.toString requires 1 argument")); } match &args[0] { Value::Float(f) => Ok(EvalResult::Value(Value::String(format!("{}", f)))), v => Ok(EvalResult::Value(Value::String(format!("{}", v)))), } } BuiltinFn::TypeOf => { if args.len() != 1 { return Err(err("typeOf requires 1 argument")); } Ok(EvalResult::Value(Value::String( args[0].type_name().to_string(), ))) } // Schema Evolution BuiltinFn::Versioned => { // versioned(typeName: String, version: Int, value: Any) -> Versioned if args.len() != 3 { return Err(err("Schema.versioned requires 3 arguments: typeName, version, value")); } let type_name = match &args[0] { Value::String(s) => s.clone(), _ => return Err(err("Schema.versioned: first argument must be a String")), }; let version = match &args[1] { Value::Int(n) => *n as u32, _ => return Err(err("Schema.versioned: second argument must be an Int")), }; Ok(EvalResult::Value(Value::Versioned { type_name, version, value: Box::new(args[2].clone()), })) } BuiltinFn::Migrate => { // migrate(value: Versioned, targetVersion: Int) -> Versioned if args.len() != 2 { return Err(err("Schema.migrate requires 2 arguments: value, targetVersion")); } let target = match &args[1] { Value::Int(n) => *n as u32, _ => return Err(err("Schema.migrate: second argument must be an Int")), }; // Use migrate_value which executes registered migrations let migrated = self.migrate_value(args[0].clone(), target)?; Ok(EvalResult::Value(migrated)) } BuiltinFn::GetVersion => { // getVersion(value: Versioned) -> Int if args.len() != 1 { return Err(err("Schema.getVersion requires 1 argument")); } match &args[0] { Value::Versioned { version, .. } => { Ok(EvalResult::Value(Value::Int(*version as i64))) } _ => Err(err("Schema.getVersion: argument must be a Versioned value")), } } // Math operations BuiltinFn::MathAbs => { if args.len() != 1 { return Err(err("Math.abs requires 1 argument")); } match &args[0] { Value::Int(n) => Ok(EvalResult::Value(Value::Int(n.abs()))), Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.abs()))), v => Err(err(&format!("Math.abs expects number, got {}", v.type_name()))), } } BuiltinFn::MathMin => { let (a, b) = Self::expect_args_2::(&args, "Math.min", span) .or_else(|_| { // Try floats if args.len() == 2 { match (&args[0], &args[1]) { (Value::Float(a), Value::Float(b)) => { return Ok((0i64, 0i64)); // Placeholder - we'll handle below } _ => {} } } Err(err("Math.min requires 2 number arguments")) })?; // Check if they were floats match (&args[0], &args[1]) { (Value::Float(a), Value::Float(b)) => { Ok(EvalResult::Value(Value::Float(a.min(*b)))) } (Value::Int(a), Value::Int(b)) => { Ok(EvalResult::Value(Value::Int((*a).min(*b)))) } _ => Err(err("Math.min requires 2 number arguments")), } } BuiltinFn::MathMax => { match (&args[0], &args[1]) { (Value::Float(a), Value::Float(b)) => { Ok(EvalResult::Value(Value::Float(a.max(*b)))) } (Value::Int(a), Value::Int(b)) => { Ok(EvalResult::Value(Value::Int((*a).max(*b)))) } _ => Err(err("Math.max requires 2 number arguments")), } } BuiltinFn::MathSqrt => { if args.len() != 1 { return Err(err("Math.sqrt requires 1 argument")); } match &args[0] { Value::Int(n) => Ok(EvalResult::Value(Value::Float((*n as f64).sqrt()))), Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.sqrt()))), v => Err(err(&format!("Math.sqrt expects number, got {}", v.type_name()))), } } BuiltinFn::MathPow => { if args.len() != 2 { return Err(err("Math.pow requires 2 arguments: base, exponent")); } match (&args[0], &args[1]) { (Value::Int(base), Value::Int(exp)) => { if *exp >= 0 { Ok(EvalResult::Value(Value::Int(base.pow(*exp as u32)))) } else { Ok(EvalResult::Value(Value::Float((*base as f64).powi(*exp as i32)))) } } (Value::Float(base), Value::Int(exp)) => { Ok(EvalResult::Value(Value::Float(base.powi(*exp as i32)))) } (Value::Float(base), Value::Float(exp)) => { Ok(EvalResult::Value(Value::Float(base.powf(*exp)))) } (Value::Int(base), Value::Float(exp)) => { Ok(EvalResult::Value(Value::Float((*base as f64).powf(*exp)))) } _ => Err(err("Math.pow requires number arguments")), } } BuiltinFn::MathFloor => { if args.len() != 1 { return Err(err("Math.floor requires 1 argument")); } match &args[0] { Value::Float(n) => Ok(EvalResult::Value(Value::Int(n.floor() as i64))), Value::Int(n) => Ok(EvalResult::Value(Value::Int(*n))), v => Err(err(&format!("Math.floor expects number, got {}", v.type_name()))), } } BuiltinFn::MathCeil => { if args.len() != 1 { return Err(err("Math.ceil requires 1 argument")); } match &args[0] { Value::Float(n) => Ok(EvalResult::Value(Value::Int(n.ceil() as i64))), Value::Int(n) => Ok(EvalResult::Value(Value::Int(*n))), v => Err(err(&format!("Math.ceil expects number, got {}", v.type_name()))), } } BuiltinFn::MathRound => { if args.len() != 1 { return Err(err("Math.round requires 1 argument")); } match &args[0] { Value::Float(n) => Ok(EvalResult::Value(Value::Int(n.round() as i64))), Value::Int(n) => Ok(EvalResult::Value(Value::Int(*n))), v => Err(err(&format!("Math.round expects number, got {}", v.type_name()))), } } // Additional List operations BuiltinFn::ListIsEmpty => { let list = Self::expect_arg_1::>(&args, "List.isEmpty", span)?; Ok(EvalResult::Value(Value::Bool(list.is_empty()))) } BuiltinFn::ListFind => { let (list, func) = Self::expect_args_2::, Value>(&args, "List.find", span)?; for item in list { let v = self.eval_call_to_value(func.clone(), vec![item.clone()], span)?; match v { Value::Bool(true) => { return Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![item], })); } Value::Bool(false) => {} _ => return Err(err("List.find predicate must return Bool")), } } Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })) } BuiltinFn::ListAny => { let (list, func) = Self::expect_args_2::, Value>(&args, "List.any", span)?; for item in list { let v = self.eval_call_to_value(func.clone(), vec![item], span)?; match v { Value::Bool(true) => return Ok(EvalResult::Value(Value::Bool(true))), Value::Bool(false) => {} _ => return Err(err("List.any predicate must return Bool")), } } Ok(EvalResult::Value(Value::Bool(false))) } BuiltinFn::ListAll => { let (list, func) = Self::expect_args_2::, Value>(&args, "List.all", span)?; for item in list { let v = self.eval_call_to_value(func.clone(), vec![item], span)?; match v { Value::Bool(false) => return Ok(EvalResult::Value(Value::Bool(false))), Value::Bool(true) => {} _ => return Err(err("List.all predicate must return Bool")), } } Ok(EvalResult::Value(Value::Bool(true))) } BuiltinFn::ListTake => { let (list, n) = Self::expect_args_2::, i64>(&args, "List.take", span)?; let n = n.max(0) as usize; let result: Vec = list.into_iter().take(n).collect(); Ok(EvalResult::Value(Value::List(result))) } BuiltinFn::ListDrop => { let (list, n) = Self::expect_args_2::, i64>(&args, "List.drop", span)?; let n = n.max(0) as usize; let result: Vec = list.into_iter().skip(n).collect(); Ok(EvalResult::Value(Value::List(result))) } BuiltinFn::ListForEach => { // List.forEach(list, fn(item) => { effectful code }) // Unlike map, forEach doesn't collect results - it just runs effects let (list, func) = Self::expect_args_2::, Value>(&args, "List.forEach", span)?; for item in list { // Call the function for each item, ignoring the result self.eval_call_to_value(func.clone(), vec![item], span)?; } Ok(EvalResult::Value(Value::Unit)) } // Additional String operations BuiltinFn::StringStartsWith => { let (s, prefix) = Self::expect_args_2::(&args, "String.startsWith", span)?; Ok(EvalResult::Value(Value::Bool(s.starts_with(&prefix)))) } BuiltinFn::StringEndsWith => { let (s, suffix) = Self::expect_args_2::(&args, "String.endsWith", span)?; Ok(EvalResult::Value(Value::Bool(s.ends_with(&suffix)))) } BuiltinFn::StringToUpper => { let s = Self::expect_arg_1::(&args, "String.toUpper", span)?; Ok(EvalResult::Value(Value::String(s.to_uppercase()))) } BuiltinFn::StringToLower => { let s = Self::expect_arg_1::(&args, "String.toLower", span)?; Ok(EvalResult::Value(Value::String(s.to_lowercase()))) } BuiltinFn::StringSubstring => { // String.substring(s, start, end) - end is exclusive if args.len() != 3 { return Err(err("String.substring requires 3 arguments: string, start, end")); } let s = match &args[0] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.substring expects String, got {}", v.type_name()))), }; let start = match &args[1] { Value::Int(n) => (*n).max(0) as usize, v => return Err(err(&format!("String.substring expects Int for start, got {}", v.type_name()))), }; let end = match &args[2] { Value::Int(n) => (*n).max(0) as usize, v => return Err(err(&format!("String.substring expects Int for end, got {}", v.type_name()))), }; let chars: Vec = s.chars().collect(); let end = end.min(chars.len()); let start = start.min(end); let result: String = chars[start..end].iter().collect(); Ok(EvalResult::Value(Value::String(result))) } BuiltinFn::StringFromChar => { if args.len() != 1 { return Err(err("String.fromChar requires 1 argument: char")); } let c = match &args[0] { Value::Char(c) => *c, v => return Err(err(&format!("String.fromChar expects Char, got {}", v.type_name()))), }; Ok(EvalResult::Value(Value::String(c.to_string()))) } BuiltinFn::StringCharAt => { if args.len() != 2 { return Err(err("String.charAt requires 2 arguments: string, index")); } let s = match &args[0] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.charAt expects String, got {}", v.type_name()))), }; let idx = match &args[1] { Value::Int(n) => *n as usize, v => return Err(err(&format!("String.charAt expects Int for index, got {}", v.type_name()))), }; let chars: Vec = s.chars().collect(); if idx < chars.len() { Ok(EvalResult::Value(Value::String(chars[idx].to_string()))) } else { Ok(EvalResult::Value(Value::String(String::new()))) } } BuiltinFn::StringIndexOf => { if args.len() != 2 { return Err(err("String.indexOf requires 2 arguments: string, substring")); } let s = match &args[0] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.indexOf expects String, got {}", v.type_name()))), }; let sub = match &args[1] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.indexOf expects String for substring, got {}", v.type_name()))), }; match s.find(&sub) { Some(idx) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Int(idx as i64)], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::StringLastIndexOf => { if args.len() != 2 { return Err(err("String.lastIndexOf requires 2 arguments: string, substring")); } let s = match &args[0] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.lastIndexOf expects String, got {}", v.type_name()))), }; let sub = match &args[1] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.lastIndexOf expects String for substring, got {}", v.type_name()))), }; match s.rfind(&sub) { Some(idx) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Int(idx as i64)], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::StringRepeat => { if args.len() != 2 { return Err(err("String.repeat requires 2 arguments: string, count")); } let s = match &args[0] { Value::String(s) => s.clone(), v => return Err(err(&format!("String.repeat expects String, got {}", v.type_name()))), }; let count = match &args[1] { Value::Int(n) => (*n).max(0) as usize, v => return Err(err(&format!("String.repeat expects Int for count, got {}", v.type_name()))), }; Ok(EvalResult::Value(Value::String(s.repeat(count)))) } // JSON operations BuiltinFn::JsonParse => { let s = Self::expect_arg_1::(&args, "Json.parse", span)?; match serde_json::from_str::(&s) { Ok(json) => Ok(EvalResult::Value(Value::Constructor { name: "Ok".to_string(), fields: vec![Value::Json(json)], })), Err(e) => Ok(EvalResult::Value(Value::Constructor { name: "Err".to_string(), fields: vec![Value::String(e.to_string())], })), } } BuiltinFn::JsonStringify => { let json = match &args[0] { Value::Json(j) => j.clone(), v => return Err(err(&format!("Json.stringify expects Json, got {}", v.type_name()))), }; Ok(EvalResult::Value(Value::String(json.to_string()))) } BuiltinFn::JsonPrettyPrint => { let json = match &args[0] { Value::Json(j) => j.clone(), v => return Err(err(&format!("Json.prettyPrint expects Json, got {}", v.type_name()))), }; match serde_json::to_string_pretty(&json) { Ok(s) => Ok(EvalResult::Value(Value::String(s))), Err(e) => Err(err(&format!("Json.prettyPrint error: {}", e))), } } BuiltinFn::JsonGet => { // Json.get(json, key) -> Option if args.len() != 2 { return Err(err("Json.get requires 2 arguments: json, key")); } let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.get expects Json, got {}", v.type_name()))), }; let key = match &args[1] { Value::String(s) => s.clone(), v => return Err(err(&format!("Json.get expects String key, got {}", v.type_name()))), }; match json.get(&key) { Some(v) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Json(v.clone())], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonGetIndex => { // Json.getIndex(json, index) -> Option if args.len() != 2 { return Err(err("Json.getIndex requires 2 arguments: json, index")); } let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.getIndex expects Json, got {}", v.type_name()))), }; let idx = match &args[1] { Value::Int(n) => *n as usize, v => return Err(err(&format!("Json.getIndex expects Int index, got {}", v.type_name()))), }; match json.get(idx) { Some(v) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Json(v.clone())], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonAsString => { // Json.asString(json) -> Option let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.asString expects Json, got {}", v.type_name()))), }; match json.as_str() { Some(s) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::String(s.to_string())], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonAsNumber => { // Json.asNumber(json) -> Option let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.asNumber expects Json, got {}", v.type_name()))), }; match json.as_f64() { Some(n) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Float(n)], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonAsInt => { // Json.asInt(json) -> Option let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.asInt expects Json, got {}", v.type_name()))), }; match json.as_i64() { Some(n) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Int(n)], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonAsBool => { // Json.asBool(json) -> Option let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.asBool expects Json, got {}", v.type_name()))), }; match json.as_bool() { Some(b) => Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Bool(b)], })), None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonAsArray => { // Json.asArray(json) -> Option> let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.asArray expects Json, got {}", v.type_name()))), }; match json.as_array() { Some(arr) => { let items: Vec = arr.iter().map(|v| Value::Json(v.clone())).collect(); Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::List(items)], })) } None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } BuiltinFn::JsonIsNull => { // Json.isNull(json) -> Bool let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.isNull expects Json, got {}", v.type_name()))), }; Ok(EvalResult::Value(Value::Bool(json.is_null()))) } BuiltinFn::JsonKeys => { // Json.keys(json) -> Option> let json = match &args[0] { Value::Json(j) => j, v => return Err(err(&format!("Json.keys expects Json, got {}", v.type_name()))), }; match json.as_object() { Some(obj) => { let keys: Vec = obj.keys().map(|k| Value::String(k.clone())).collect(); Ok(EvalResult::Value(Value::Constructor { name: "Some".to_string(), fields: vec![Value::List(keys)], })) } None => Ok(EvalResult::Value(Value::Constructor { name: "None".to_string(), fields: vec![], })), } } // JSON constructors BuiltinFn::JsonNull => { Ok(EvalResult::Value(Value::Json(serde_json::Value::Null))) } BuiltinFn::JsonBool => { let b = Self::expect_arg_1::(&args, "Json.bool", span)?; Ok(EvalResult::Value(Value::Json(serde_json::Value::Bool(b)))) } BuiltinFn::JsonNumber => { let n = match &args[0] { Value::Float(f) => serde_json::Number::from_f64(*f) .ok_or_else(|| err("Invalid float for JSON"))?, Value::Int(i) => serde_json::Number::from(*i), v => return Err(err(&format!("Json.number expects Float or Int, got {}", v.type_name()))), }; Ok(EvalResult::Value(Value::Json(serde_json::Value::Number(n)))) } BuiltinFn::JsonInt => { let n = Self::expect_arg_1::(&args, "Json.int", span)?; Ok(EvalResult::Value(Value::Json(serde_json::Value::Number(serde_json::Number::from(n))))) } BuiltinFn::JsonString => { let s = Self::expect_arg_1::(&args, "Json.string", span)?; Ok(EvalResult::Value(Value::Json(serde_json::Value::String(s)))) } BuiltinFn::JsonArray => { // Json.array(list: List) -> Json let list = Self::expect_arg_1::>(&args, "Json.array", span)?; let arr: Result, RuntimeError> = list.into_iter().map(|v| { match v { Value::Json(j) => Ok(j), _ => Err(err("Json.array expects List")), } }).collect(); Ok(EvalResult::Value(Value::Json(serde_json::Value::Array(arr?)))) } BuiltinFn::JsonObject => { // Json.object(entries: List<(String, Json)>) -> Json let list = Self::expect_arg_1::>(&args, "Json.object", span)?; let mut map = serde_json::Map::new(); for item in list { match item { Value::Tuple(fields) if fields.len() == 2 => { let key = match &fields[0] { Value::String(s) => s.clone(), _ => return Err(err("Json.object expects (String, Json) tuples")), }; let value = match &fields[1] { Value::Json(j) => j.clone(), _ => return Err(err("Json.object expects (String, Json) tuples")), }; map.insert(key, value); } _ => return Err(err("Json.object expects List<(String, Json)>")), } } Ok(EvalResult::Value(Value::Json(serde_json::Value::Object(map)))) } } } // 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::Record(a), Value::Record(b)) => { a.len() == b.len() && a.iter().all(|(k, v)| { b.get(k).map(|bv| self.values_equal(v, bv)).unwrap_or(false) }) } ( 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)) } (Value::Json(a), Value::Json(b)) => a == b, _ => 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 onto stack (for nested handler semantics) for h in &handler_values { self.handler_stack.push(Rc::clone(h)); } // Update evidence map for O(1) lookup (evidence passing optimization) // Save previous evidence values for restoration let mut previous_evidence: Vec<(String, Option>)> = Vec::new(); for h in &handler_values { let effect_name = h.effect.clone(); let prev = self.evidence.insert(effect_name.clone(), Rc::clone(h)); previous_evidence.push((effect_name, prev)); } // Evaluate expression let result = self.eval_expr_inner(expr, env); // Restore previous evidence (for proper nested handler support) for (effect_name, prev) in previous_evidence.into_iter().rev() { match prev { Some(h) => { self.evidence.insert(effect_name, h); } None => { self.evidence.remove(&effect_name); } } } // Pop handlers from stack 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 using evidence map (O(1) lookup via evidence passing) // This replaces the previous O(n) handler_stack search let handler_data: Option<(Env, crate::ast::Expr, Vec)> = self .evidence .get(&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()); } } // Enter handler context (enables resume) self.in_handler_depth += 1; let eval_result = self.eval_expr_inner(&body, &env); self.in_handler_depth -= 1; // Handle Resume result - use the resumed value as the effect's return value match eval_result { Ok(EvalResult::Resume(value)) => Ok(value), Ok(EvalResult::Value(value)) => Ok(value), Ok(EvalResult::Effect(req)) => { // Handler body can perform effects - handle them self.handle_effect(req) } Ok(EvalResult::TailCall { func, args, span }) => { // Tail call in handler - evaluate it self.eval_call_to_value(func, args, span) } Err(e) => Err(e), } } 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") | ("Console", "readLine") => { 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())) } ("Console", "readInt") => { 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, })?; let trimmed = input.trim(); match trimmed.parse::() { Ok(n) => Ok(Value::Int(n)), Err(_) => Err(RuntimeError { message: format!("Invalid integer: '{}'", trimmed), span: None, }), } } ("Fail", "fail") => { let msg = request .args .first() .map(|v| format!("{}", v)) .unwrap_or_else(|| "Unknown error".to_string()); Err(RuntimeError { message: msg, span: None, }) } ("State", "get") => { Ok(self.builtin_state.borrow().clone()) } ("State", "put") => { if let Some(value) = request.args.first() { *self.builtin_state.borrow_mut() = value.clone(); Ok(Value::Unit) } else { Err(RuntimeError { message: "State.put requires a value argument".to_string(), span: None, }) } } ("Reader", "ask") => { Ok(self.builtin_reader.borrow().clone()) } ("Random", "int") => { let min = match request.args.first() { Some(Value::Int(n)) => *n, _ => 0, }; let max = match request.args.get(1) { Some(Value::Int(n)) => *n, _ => i64::MAX, }; let mut rng = rand::thread_rng(); let value = rng.gen_range(min..=max); Ok(Value::Int(value)) } ("Random", "float") => { let mut rng = rand::thread_rng(); let value: f64 = rng.gen(); Ok(Value::Float(value)) } ("Random", "bool") => { let mut rng = rand::thread_rng(); let value: bool = rng.gen(); Ok(Value::Bool(value)) } ("Time", "now") => { use std::time::{SystemTime, UNIX_EPOCH}; let duration = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default(); let millis = duration.as_millis() as i64; Ok(Value::Int(millis)) } ("Time", "sleep") => { use std::thread; use std::time::Duration; let ms = match request.args.first() { Some(Value::Int(n)) => *n as u64, _ => 0, }; thread::sleep(Duration::from_millis(ms)); Ok(Value::Unit) } // ===== File Effect ===== ("File", "read") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.read requires a string path".to_string(), span: None, }), }; match std::fs::read_to_string(&path) { Ok(content) => Ok(Value::String(content)), Err(e) => Err(RuntimeError { message: format!("Failed to read file '{}': {}", path, e), span: None, }), } } ("File", "write") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.write requires a string path".to_string(), span: None, }), }; let content = match request.args.get(1) { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.write requires string content".to_string(), span: None, }), }; match std::fs::write(&path, &content) { Ok(()) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Failed to write file '{}': {}", path, e), span: None, }), } } ("File", "append") => { use std::fs::OpenOptions; use std::io::Write; let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.append requires a string path".to_string(), span: None, }), }; let content = match request.args.get(1) { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.append requires string content".to_string(), span: None, }), }; match OpenOptions::new().create(true).append(true).open(&path) { Ok(mut file) => { file.write_all(content.as_bytes()).map_err(|e| RuntimeError { message: format!("Failed to append to file '{}': {}", path, e), span: None, })?; Ok(Value::Unit) } Err(e) => Err(RuntimeError { message: format!("Failed to open file '{}': {}", path, e), span: None, }), } } ("File", "exists") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.exists requires a string path".to_string(), span: None, }), }; Ok(Value::Bool(std::path::Path::new(&path).exists())) } ("File", "delete") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.delete requires a string path".to_string(), span: None, }), }; match std::fs::remove_file(&path) { Ok(()) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Failed to delete file '{}': {}", path, e), span: None, }), } } ("File", "readDir") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.readDir requires a string path".to_string(), span: None, }), }; match std::fs::read_dir(&path) { Ok(entries) => { let files: Vec = entries .filter_map(|e| e.ok()) .map(|e| Value::String(e.file_name().to_string_lossy().to_string())) .collect(); Ok(Value::List(files)) } Err(e) => Err(RuntimeError { message: format!("Failed to read directory '{}': {}", path, e), span: None, }), } } ("File", "isDir") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.isDir requires a string path".to_string(), span: None, }), }; Ok(Value::Bool(std::path::Path::new(&path).is_dir())) } ("File", "mkdir") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "File.mkdir requires a string path".to_string(), span: None, }), }; match std::fs::create_dir_all(&path) { Ok(()) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Failed to create directory '{}': {}", path, e), span: None, }), } } // ===== Process Effect ===== ("Process", "exec") => { use std::process::Command; let cmd = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Process.exec requires a string command".to_string(), span: None, }), }; match Command::new("sh").arg("-c").arg(&cmd).output() { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout).to_string(); Ok(Value::String(stdout)) } Err(e) => Err(RuntimeError { message: format!("Failed to execute command: {}", e), span: None, }), } } ("Process", "execStatus") => { use std::process::Command; let cmd = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Process.execStatus requires a string command".to_string(), span: None, }), }; match Command::new("sh").arg("-c").arg(&cmd).status() { Ok(status) => { let code = status.code().unwrap_or(-1) as i64; Ok(Value::Int(code)) } Err(e) => Err(RuntimeError { message: format!("Failed to execute command: {}", e), span: None, }), } } ("Process", "env") => { let var_name = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Process.env requires a string name".to_string(), span: None, }), }; match std::env::var(&var_name) { Ok(value) => Ok(Value::Constructor { name: "Some".to_string(), fields: vec![Value::String(value)], }), Err(_) => Ok(Value::Constructor { name: "None".to_string(), fields: vec![], }), } } ("Process", "args") => { let args: Vec = std::env::args() .skip(1) // Skip the program name .map(Value::String) .collect(); Ok(Value::List(args)) } ("Process", "exit") => { let code = match request.args.first() { Some(Value::Int(n)) => *n as i32, _ => 0, }; std::process::exit(code); } ("Process", "cwd") => { match std::env::current_dir() { Ok(path) => Ok(Value::String(path.to_string_lossy().to_string())), Err(e) => Err(RuntimeError { message: format!("Failed to get current directory: {}", e), span: None, }), } } ("Process", "setCwd") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Process.setCwd requires a string path".to_string(), span: None, }), }; match std::env::set_current_dir(&path) { Ok(()) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Failed to change directory to '{}': {}", path, e), span: None, }), } } // ===== Http Effect ===== ("Http", "get") => { let url = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Http.get requires a URL string".to_string(), span: None, }), }; self.http_request("GET", &url, None) } ("Http", "post") => { let (url, body) = match (request.args.get(0), request.args.get(1)) { (Some(Value::String(u)), Some(Value::String(b))) => (u.clone(), b.clone()), _ => return Err(RuntimeError { message: "Http.post requires URL and body strings".to_string(), span: None, }), }; self.http_request("POST", &url, Some(&body)) } ("Http", "postJson") => { let (url, json) = match (request.args.get(0), request.args.get(1)) { (Some(Value::String(u)), Some(Value::Json(j))) => (u.clone(), j.clone()), _ => return Err(RuntimeError { message: "Http.postJson requires URL string and Json value".to_string(), span: None, }), }; self.http_request_json("POST", &url, &json) } ("Http", "put") => { let (url, body) = match (request.args.get(0), request.args.get(1)) { (Some(Value::String(u)), Some(Value::String(b))) => (u.clone(), b.clone()), _ => return Err(RuntimeError { message: "Http.put requires URL and body strings".to_string(), span: None, }), }; self.http_request("PUT", &url, Some(&body)) } ("Http", "delete") => { let url = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Http.delete requires a URL string".to_string(), span: None, }), }; self.http_request("DELETE", &url, None) } ("Http", "setHeader") => { // Headers would need to be stored in interpreter state // For now, this is a no-op placeholder Ok(Value::Unit) } ("Http", "setTimeout") => { // Timeout would need to be stored in interpreter state // For now, this is a no-op placeholder Ok(Value::Unit) } // ===== HttpServer Effect ===== ("HttpServer", "listen") => { let port = match request.args.first() { Some(Value::Int(p)) => *p as u16, _ => return Err(RuntimeError { message: "HttpServer.listen requires an integer port".to_string(), span: None, }), }; let addr = format!("0.0.0.0:{}", port); match tiny_http::Server::http(&addr) { Ok(server) => { *self.http_server.lock().unwrap() = Some(server); Ok(Value::Unit) } Err(e) => Err(RuntimeError { message: format!("Failed to start HTTP server on port {}: {}", port, e), span: None, }), } } ("HttpServer", "accept") => { let server_guard = self.http_server.lock().unwrap(); let server = match server_guard.as_ref() { Some(s) => s, None => return Err(RuntimeError { message: "HttpServer.accept: No server is listening. Call HttpServer.listen first.".to_string(), span: None, }), }; // Block until a request arrives match server.recv() { Ok(mut request) => { // Extract request info let method = request.method().to_string(); let path = request.url().to_string(); // Read body let mut body = String::new(); let _ = std::io::Read::read_to_string(&mut request.as_reader(), &mut body); // Extract headers let headers: Vec = request.headers() .iter() .map(|h| Value::Tuple(vec![ Value::String(h.field.as_str().to_string()), Value::String(h.value.as_str().to_string()), ])) .collect(); // Store the request for respond operation drop(server_guard); // Release lock before storing request *self.current_http_request.lock().unwrap() = Some(request); // Return request as a record Ok(Value::Record(HashMap::from([ ("method".to_string(), Value::String(method)), ("path".to_string(), Value::String(path)), ("body".to_string(), Value::String(body)), ("headers".to_string(), Value::List(headers)), ]))) } Err(e) => Err(RuntimeError { message: format!("HttpServer.accept failed: {}", e), span: None, }), } } ("HttpServer", "respond") => { let (status, body) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(s)), Some(Value::String(b))) => (*s as u16, b.clone()), _ => return Err(RuntimeError { message: "HttpServer.respond requires status (Int) and body (String)".to_string(), span: None, }), }; let mut req_guard = self.current_http_request.lock().unwrap(); match req_guard.take() { Some(http_request) => { let status_code = tiny_http::StatusCode(status); let response = tiny_http::Response::from_string(body) .with_status_code(status_code); if let Err(e) = http_request.respond(response) { return Err(RuntimeError { message: format!("Failed to send HTTP response: {}", e), span: None, }); } Ok(Value::Unit) } None => Err(RuntimeError { message: "HttpServer.respond: No pending request to respond to".to_string(), span: None, }), } } ("HttpServer", "respondWithHeaders") => { let (status, body, headers) = match (request.args.get(0), request.args.get(1), request.args.get(2)) { (Some(Value::Int(s)), Some(Value::String(b)), Some(Value::List(h))) => { (*s as u16, b.clone(), h.clone()) } _ => return Err(RuntimeError { message: "HttpServer.respondWithHeaders requires status (Int), body (String), and headers (List)".to_string(), span: None, }), }; let mut req_guard = self.current_http_request.lock().unwrap(); match req_guard.take() { Some(http_request) => { let status_code = tiny_http::StatusCode(status); let mut response = tiny_http::Response::from_string(body) .with_status_code(status_code); // Add custom headers for header_val in headers { if let Value::Tuple(pair) = header_val { if let (Some(Value::String(name)), Some(Value::String(value))) = (pair.get(0), pair.get(1)) { if let Ok(header) = tiny_http::Header::from_bytes( name.as_bytes(), value.as_bytes(), ) { response = response.with_header(header); } } } } if let Err(e) = http_request.respond(response) { return Err(RuntimeError { message: format!("Failed to send HTTP response: {}", e), span: None, }); } Ok(Value::Unit) } None => Err(RuntimeError { message: "HttpServer.respondWithHeaders: No pending request to respond to".to_string(), span: None, }), } } ("HttpServer", "stop") => { // Drop the server to stop listening *self.http_server.lock().unwrap() = None; *self.current_http_request.lock().unwrap() = None; Ok(Value::Unit) } // Test effect for testing framework ("Test", "assert") => { let condition = match request.args.first() { Some(Value::Bool(b)) => *b, _ => false, }; let message = match request.args.get(1) { Some(Value::String(s)) => s.clone(), _ => "Assertion failed".to_string(), }; if condition { self.test_results.borrow_mut().passed += 1; } else { self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message, expected: None, actual: None, }); } Ok(Value::Unit) } ("Test", "assertEqual") => { let expected = request.args.first().cloned().unwrap_or(Value::Unit); let actual = request.args.get(1).cloned().unwrap_or(Value::Unit); if Value::values_equal(&expected, &actual) { self.test_results.borrow_mut().passed += 1; } else { self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message: "Values not equal".to_string(), expected: Some(format!("{}", expected)), actual: Some(format!("{}", actual)), }); } Ok(Value::Unit) } ("Test", "assertEqualMsg") => { let expected = request.args.first().cloned().unwrap_or(Value::Unit); let actual = request.args.get(1).cloned().unwrap_or(Value::Unit); let label = match request.args.get(2) { Some(Value::String(s)) => s.clone(), _ => "Values not equal".to_string(), }; if Value::values_equal(&expected, &actual) { self.test_results.borrow_mut().passed += 1; } else { self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message: label, expected: Some(format!("{}", expected)), actual: Some(format!("{}", actual)), }); } Ok(Value::Unit) } ("Test", "assertNotEqual") => { let a = request.args.first().cloned().unwrap_or(Value::Unit); let b = request.args.get(1).cloned().unwrap_or(Value::Unit); if !Value::values_equal(&a, &b) { self.test_results.borrow_mut().passed += 1; } else { self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message: "Values should not be equal".to_string(), expected: Some(format!("not {}", a)), actual: Some(format!("{}", b)), }); } Ok(Value::Unit) } ("Test", "assertTrue") => { let condition = match request.args.first() { Some(Value::Bool(b)) => *b, _ => false, }; if condition { self.test_results.borrow_mut().passed += 1; } else { self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message: "Expected true".to_string(), expected: Some("true".to_string()), actual: Some("false".to_string()), }); } Ok(Value::Unit) } ("Test", "assertFalse") => { let condition = match request.args.first() { Some(Value::Bool(b)) => *b, _ => true, }; if !condition { self.test_results.borrow_mut().passed += 1; } else { self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message: "Expected false".to_string(), expected: Some("false".to_string()), actual: Some("true".to_string()), }); } Ok(Value::Unit) } ("Test", "fail") => { let message = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => "Test failed".to_string(), }; self.test_results.borrow_mut().failed += 1; self.test_results.borrow_mut().failures.push(TestFailure { message, expected: None, actual: None, }); Ok(Value::Unit) } // ===== Sql Effect ===== ("Sql", "open") => { let path = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Sql.open requires a path string".to_string(), span: None, }), }; match Connection::open(&path) { Ok(conn) => { let id = *self.next_sql_conn_id.borrow(); *self.next_sql_conn_id.borrow_mut() += 1; self.sql_connections.borrow_mut().insert(id, conn); Ok(Value::Int(id)) } Err(e) => Err(RuntimeError { message: format!("Sql.open failed: {}", e), span: None, }), } } ("Sql", "openMemory") => { match Connection::open_in_memory() { Ok(conn) => { let id = *self.next_sql_conn_id.borrow(); *self.next_sql_conn_id.borrow_mut() += 1; self.sql_connections.borrow_mut().insert(id, conn); Ok(Value::Int(id)) } Err(e) => Err(RuntimeError { message: format!("Sql.openMemory failed: {}", e), span: None, }), } } ("Sql", "close") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Sql.close requires a connection ID".to_string(), span: None, }), }; if self.sql_connections.borrow_mut().remove(&conn_id).is_some() { Ok(Value::Unit) } else { Err(RuntimeError { message: format!("Sql.close: invalid connection ID {}", conn_id), span: None, }) } } ("Sql", "execute") => { let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()), _ => return Err(RuntimeError { message: "Sql.execute requires connection ID and SQL string".to_string(), span: None, }), }; let conns = self.sql_connections.borrow(); let conn = match conns.get(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Sql.execute: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute(&sql, []) { Ok(rows_affected) => Ok(Value::Int(rows_affected as i64)), Err(e) => Err(RuntimeError { message: format!("Sql.execute failed: {}", e), span: None, }), } } ("Sql", "query") => { let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()), _ => return Err(RuntimeError { message: "Sql.query requires connection ID and SQL string".to_string(), span: None, }), }; let conns = self.sql_connections.borrow(); let conn = match conns.get(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Sql.query: invalid connection ID {}", conn_id), span: None, }), }; let mut stmt = match conn.prepare(&sql) { Ok(s) => s, Err(e) => return Err(RuntimeError { message: format!("Sql.query prepare failed: {}", e), span: None, }), }; let column_names: Vec = stmt.column_names().iter().map(|s| s.to_string()).collect(); let column_count = column_names.len(); let rows: Result, _> = stmt.query_map([], |row| { let mut record = HashMap::new(); for (i, col_name) in column_names.iter().enumerate() { let value = match row.get_ref(i) { Ok(rusqlite::types::ValueRef::Null) => Value::Constructor { name: "None".to_string(), fields: vec![], }, Ok(rusqlite::types::ValueRef::Integer(n)) => Value::Int(n), Ok(rusqlite::types::ValueRef::Real(f)) => Value::Float(f), Ok(rusqlite::types::ValueRef::Text(s)) => { Value::String(String::from_utf8_lossy(s).to_string()) } Ok(rusqlite::types::ValueRef::Blob(b)) => { Value::String(format!("", b.len())) } Err(_) => Value::String("".to_string()), }; record.insert(col_name.clone(), value); } Ok(Value::Record(record)) }).and_then(|rows| rows.collect()); match rows { Ok(r) => Ok(Value::List(r)), Err(e) => Err(RuntimeError { message: format!("Sql.query failed: {}", e), span: None, }), } } ("Sql", "queryOne") => { let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()), _ => return Err(RuntimeError { message: "Sql.queryOne requires connection ID and SQL string".to_string(), span: None, }), }; let conns = self.sql_connections.borrow(); let conn = match conns.get(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Sql.queryOne: invalid connection ID {}", conn_id), span: None, }), }; let mut stmt = match conn.prepare(&sql) { Ok(s) => s, Err(e) => return Err(RuntimeError { message: format!("Sql.queryOne prepare failed: {}", e), span: None, }), }; let column_names: Vec = stmt.column_names().iter().map(|s| s.to_string()).collect(); let result = stmt.query_row([], |row| { let mut record = HashMap::new(); for (i, col_name) in column_names.iter().enumerate() { let value = match row.get_ref(i) { Ok(rusqlite::types::ValueRef::Null) => Value::Constructor { name: "None".to_string(), fields: vec![], }, Ok(rusqlite::types::ValueRef::Integer(n)) => Value::Int(n), Ok(rusqlite::types::ValueRef::Real(f)) => Value::Float(f), Ok(rusqlite::types::ValueRef::Text(s)) => { Value::String(String::from_utf8_lossy(s).to_string()) } Ok(rusqlite::types::ValueRef::Blob(b)) => { Value::String(format!("", b.len())) } Err(_) => Value::String("".to_string()), }; record.insert(col_name.clone(), value); } Ok(Value::Record(record)) }); match result { Ok(row) => Ok(Value::Constructor { name: "Some".to_string(), fields: vec![row], }), Err(rusqlite::Error::QueryReturnedNoRows) => Ok(Value::Constructor { name: "None".to_string(), fields: vec![], }), Err(e) => Err(RuntimeError { message: format!("Sql.queryOne failed: {}", e), span: None, }), } } ("Sql", "beginTx") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Sql.beginTx requires a connection ID".to_string(), span: None, }), }; let conns = self.sql_connections.borrow(); let conn = match conns.get(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Sql.beginTx: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute("BEGIN TRANSACTION", []) { Ok(_) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Sql.beginTx failed: {}", e), span: None, }), } } ("Sql", "commit") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Sql.commit requires a connection ID".to_string(), span: None, }), }; let conns = self.sql_connections.borrow(); let conn = match conns.get(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Sql.commit: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute("COMMIT", []) { Ok(_) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Sql.commit failed: {}", e), span: None, }), } } ("Sql", "rollback") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Sql.rollback requires a connection ID".to_string(), span: None, }), }; let conns = self.sql_connections.borrow(); let conn = match conns.get(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Sql.rollback: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute("ROLLBACK", []) { Ok(_) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Sql.rollback failed: {}", e), span: None, }), } } // ============================================================ // PostgreSQL Effect // ============================================================ ("Postgres", "connect") => { let conn_str = match request.args.first() { Some(Value::String(s)) => s.clone(), _ => return Err(RuntimeError { message: "Postgres.connect requires a connection string".to_string(), span: None, }), }; match PgClient::connect(&conn_str, NoTls) { Ok(client) => { let id = *self.next_pg_conn_id.borrow(); *self.next_pg_conn_id.borrow_mut() += 1; self.pg_connections.borrow_mut().insert(id, client); Ok(Value::Int(id)) } Err(e) => Err(RuntimeError { message: format!("Postgres.connect failed: {}", e), span: None, }), } } ("Postgres", "close") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Postgres.close requires a connection ID".to_string(), span: None, }), }; if self.pg_connections.borrow_mut().remove(&conn_id).is_some() { Ok(Value::Unit) } else { Err(RuntimeError { message: format!("Postgres.close: invalid connection ID {}", conn_id), span: None, }) } } ("Postgres", "execute") => { let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()), _ => return Err(RuntimeError { message: "Postgres.execute requires connection ID and SQL string".to_string(), span: None, }), }; let mut conns = self.pg_connections.borrow_mut(); let conn = match conns.get_mut(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Postgres.execute: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute(&sql, &[]) { Ok(rows) => Ok(Value::Int(rows as i64)), Err(e) => Err(RuntimeError { message: format!("Postgres.execute failed: {}", e), span: None, }), } } ("Postgres", "query") => { let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()), _ => return Err(RuntimeError { message: "Postgres.query requires connection ID and SQL string".to_string(), span: None, }), }; let mut conns = self.pg_connections.borrow_mut(); let conn = match conns.get_mut(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Postgres.query: invalid connection ID {}", conn_id), span: None, }), }; match conn.query(&sql, &[]) { Ok(rows) => { let mut results = Vec::new(); for row in rows { let mut record = HashMap::new(); for (i, col) in row.columns().iter().enumerate() { let col_name = col.name().to_string(); let value: Value = match col.type_().name() { "int4" | "int8" | "int2" => { let v: Option = row.get(i); match v { Some(n) => Value::Int(n), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } "float4" | "float8" => { let v: Option = row.get(i); match v { Some(n) => Value::Float(n), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } "bool" => { let v: Option = row.get(i); match v { Some(b) => Value::Bool(b), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } "text" | "varchar" | "char" | "bpchar" | "name" => { let v: Option = row.get(i); match v { Some(s) => Value::String(s), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } _ => { // Try to get as string for other types let v: Option = row.try_get(i).ok().flatten(); match v { Some(s) => Value::String(s), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } }; record.insert(col_name, value); } results.push(Value::Record(record)); } Ok(Value::List(results)) } Err(e) => Err(RuntimeError { message: format!("Postgres.query failed: {}", e), span: None, }), } } ("Postgres", "queryOne") => { let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) { (Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()), _ => return Err(RuntimeError { message: "Postgres.queryOne requires connection ID and SQL string".to_string(), span: None, }), }; let mut conns = self.pg_connections.borrow_mut(); let conn = match conns.get_mut(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Postgres.queryOne: invalid connection ID {}", conn_id), span: None, }), }; match conn.query_opt(&sql, &[]) { Ok(Some(row)) => { let mut record = HashMap::new(); for (i, col) in row.columns().iter().enumerate() { let col_name = col.name().to_string(); let value: Value = match col.type_().name() { "int4" | "int8" | "int2" => { let v: Option = row.get(i); match v { Some(n) => Value::Int(n), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } "float4" | "float8" => { let v: Option = row.get(i); match v { Some(n) => Value::Float(n), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } "bool" => { let v: Option = row.get(i); match v { Some(b) => Value::Bool(b), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } "text" | "varchar" | "char" | "bpchar" | "name" => { let v: Option = row.get(i); match v { Some(s) => Value::String(s), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } _ => { let v: Option = row.try_get(i).ok().flatten(); match v { Some(s) => Value::String(s), None => Value::Constructor { name: "None".to_string(), fields: vec![] }, } } }; record.insert(col_name, value); } Ok(Value::Constructor { name: "Some".to_string(), fields: vec![Value::Record(record)], }) } Ok(None) => Ok(Value::Constructor { name: "None".to_string(), fields: vec![], }), Err(e) => Err(RuntimeError { message: format!("Postgres.queryOne failed: {}", e), span: None, }), } } ("Postgres", "beginTx") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Postgres.beginTx requires a connection ID".to_string(), span: None, }), }; let mut conns = self.pg_connections.borrow_mut(); let conn = match conns.get_mut(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Postgres.beginTx: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute("BEGIN", &[]) { Ok(_) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Postgres.beginTx failed: {}", e), span: None, }), } } ("Postgres", "commit") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Postgres.commit requires a connection ID".to_string(), span: None, }), }; let mut conns = self.pg_connections.borrow_mut(); let conn = match conns.get_mut(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Postgres.commit: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute("COMMIT", &[]) { Ok(_) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Postgres.commit failed: {}", e), span: None, }), } } ("Postgres", "rollback") => { let conn_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Postgres.rollback requires a connection ID".to_string(), span: None, }), }; let mut conns = self.pg_connections.borrow_mut(); let conn = match conns.get_mut(&conn_id) { Some(c) => c, None => return Err(RuntimeError { message: format!("Postgres.rollback: invalid connection ID {}", conn_id), span: None, }), }; match conn.execute("ROLLBACK", &[]) { Ok(_) => Ok(Value::Unit), Err(e) => Err(RuntimeError { message: format!("Postgres.rollback failed: {}", e), span: None, }), } } // ===== Concurrent Effect ===== ("Concurrent", "spawn") => { // For now, spawn just stores the thunk - it will be evaluated on await // In a real implementation, this would start a thread/fiber let thunk = match request.args.first() { Some(v) => v.clone(), _ => return Err(RuntimeError { message: "Concurrent.spawn requires a thunk argument".to_string(), span: None, }), }; let task_id = *self.next_task_id.borrow(); *self.next_task_id.borrow_mut() += 1; // Store task: (thunk, None for result, not cancelled) self.concurrent_tasks.borrow_mut().insert(task_id, (thunk, None, false)); Ok(Value::Int(task_id)) } ("Concurrent", "await") => { let task_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Concurrent.await requires a task ID".to_string(), span: None, }), }; // Check if already computed or cancelled let task_info = { let tasks = self.concurrent_tasks.borrow(); tasks.get(&task_id).cloned() }; match task_info { Some((_, Some(result), _)) => Ok(result), Some((_, _, true)) => Err(RuntimeError { message: format!("Task {} was cancelled", task_id), span: None, }), Some((thunk, None, false)) => { // For cooperative concurrency, we just need to signal // that we're waiting on this task // Return the thunk to be evaluated by the caller // This is a simplification - real async would use fibers Ok(thunk) } None => Err(RuntimeError { message: format!("Unknown task ID: {}", task_id), span: None, }), } } ("Concurrent", "yield") => { // In cooperative concurrency, yield allows other tasks to run // For now, this is a no-op in our single-threaded model Ok(Value::Unit) } ("Concurrent", "sleep") => { // Non-blocking sleep (delegates to thread sleep for now) use std::thread; use std::time::Duration; let ms = match request.args.first() { Some(Value::Int(n)) => *n as u64, _ => 0, }; thread::sleep(Duration::from_millis(ms)); Ok(Value::Unit) } ("Concurrent", "cancel") => { let task_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Concurrent.cancel requires a task ID".to_string(), span: None, }), }; let mut tasks = self.concurrent_tasks.borrow_mut(); if let Some((thunk, result, _)) = tasks.get(&task_id).cloned() { tasks.insert(task_id, (thunk, result, true)); Ok(Value::Bool(true)) } else { Ok(Value::Bool(false)) } } ("Concurrent", "isRunning") => { let task_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Concurrent.isRunning requires a task ID".to_string(), span: None, }), }; let tasks = self.concurrent_tasks.borrow(); let is_running = match tasks.get(&task_id) { Some((_, None, false)) => true, // Not completed and not cancelled _ => false, }; Ok(Value::Bool(is_running)) } ("Concurrent", "taskCount") => { let tasks = self.concurrent_tasks.borrow(); let count = tasks.iter() .filter(|(_, (_, result, cancelled))| result.is_none() && !cancelled) .count(); Ok(Value::Int(count as i64)) } // ===== Channel Effect ===== ("Channel", "create") => { let channel_id = *self.next_channel_id.borrow(); *self.next_channel_id.borrow_mut() += 1; // Create empty channel queue, not closed self.channels.borrow_mut().insert(channel_id, (Vec::new(), false)); Ok(Value::Int(channel_id)) } ("Channel", "send") => { let channel_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Channel.send requires a channel ID".to_string(), span: None, }), }; let value = match request.args.get(1) { Some(v) => v.clone(), _ => return Err(RuntimeError { message: "Channel.send requires a value".to_string(), span: None, }), }; let mut channels = self.channels.borrow_mut(); match channels.get_mut(&channel_id) { Some((queue, false)) => { queue.push(value); Ok(Value::Unit) } Some((_, true)) => Err(RuntimeError { message: format!("Channel {} is closed", channel_id), span: None, }), None => Err(RuntimeError { message: format!("Unknown channel ID: {}", channel_id), span: None, }), } } ("Channel", "receive") => { let channel_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Channel.receive requires a channel ID".to_string(), span: None, }), }; let mut channels = self.channels.borrow_mut(); match channels.get_mut(&channel_id) { Some((queue, _)) if !queue.is_empty() => { Ok(queue.remove(0)) } Some((_, true)) => Err(RuntimeError { message: format!("Channel {} is closed and empty", channel_id), span: None, }), Some((_, false)) => Err(RuntimeError { message: format!("Channel {} is empty (blocking receive not supported yet)", channel_id), span: None, }), None => Err(RuntimeError { message: format!("Unknown channel ID: {}", channel_id), span: None, }), } } ("Channel", "tryReceive") => { let channel_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Channel.tryReceive requires a channel ID".to_string(), span: None, }), }; let mut channels = self.channels.borrow_mut(); match channels.get_mut(&channel_id) { Some((queue, _)) if !queue.is_empty() => { Ok(Value::Constructor { name: "Some".to_string(), fields: vec![queue.remove(0)], }) } Some(_) => { Ok(Value::Constructor { name: "None".to_string(), fields: vec![], }) } None => Err(RuntimeError { message: format!("Unknown channel ID: {}", channel_id), span: None, }), } } ("Channel", "close") => { let channel_id = match request.args.first() { Some(Value::Int(id)) => *id, _ => return Err(RuntimeError { message: "Channel.close requires a channel ID".to_string(), span: None, }), }; let mut channels = self.channels.borrow_mut(); if let Some((queue, closed)) = channels.get_mut(&channel_id) { *closed = true; Ok(Value::Unit) } else { Err(RuntimeError { message: format!("Unknown channel ID: {}", channel_id), span: None, }) } } _ => Err(RuntimeError { message: format!( "Unhandled effect operation: {}.{}", request.effect, request.operation ), span: None, }), } } /// Helper for HTTP requests fn http_request(&self, method: &str, url: &str, body: Option<&str>) -> Result { use reqwest::blocking::Client; let client = Client::new(); let request = match method { "GET" => client.get(url), "POST" => { let req = client.post(url); if let Some(b) = body { req.header("Content-Type", "text/plain").body(b.to_string()) } else { req } } "PUT" => { let req = client.put(url); if let Some(b) = body { req.header("Content-Type", "text/plain").body(b.to_string()) } else { req } } "DELETE" => client.delete(url), _ => return Err(RuntimeError { message: format!("Unsupported HTTP method: {}", method), span: None, }), }; match request.send() { Ok(response) => { let status = response.status().as_u16() as i64; let headers: Vec = response.headers() .iter() .map(|(name, value)| { Value::Tuple(vec![ Value::String(name.to_string()), Value::String(value.to_str().unwrap_or("").to_string()), ]) }) .collect(); let body = response.text().unwrap_or_default(); let response_record = Value::Record(HashMap::from([ ("status".to_string(), Value::Int(status)), ("body".to_string(), Value::String(body)), ("headers".to_string(), Value::List(headers)), ])); Ok(Value::Constructor { name: "Ok".to_string(), fields: vec![response_record], }) } Err(e) => { Ok(Value::Constructor { name: "Err".to_string(), fields: vec![Value::String(e.to_string())], }) } } } /// Helper for HTTP requests with JSON body fn http_request_json(&self, method: &str, url: &str, json: &serde_json::Value) -> Result { use reqwest::blocking::Client; let client = Client::new(); let request = match method { "POST" => client.post(url).json(json), "PUT" => client.put(url).json(json), _ => return Err(RuntimeError { message: format!("Unsupported HTTP method for JSON: {}", method), span: None, }), }; match request.send() { Ok(response) => { let status = response.status().as_u16() as i64; let headers: Vec = response.headers() .iter() .map(|(name, value)| { Value::Tuple(vec![ Value::String(name.to_string()), Value::String(value.to_str().unwrap_or("").to_string()), ]) }) .collect(); let body = response.text().unwrap_or_default(); let response_record = Value::Record(HashMap::from([ ("status".to_string(), Value::Int(status)), ("body".to_string(), Value::String(body)), ("headers".to_string(), Value::List(headers)), ])); Ok(Value::Constructor { name: "Ok".to_string(), fields: vec![response_record], }) } Err(e) => { Ok(Value::Constructor { name: "Err".to_string(), fields: vec![Value::String(e.to_string())], }) } } } } 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"), } } #[test] fn test_migration_from_type_declaration() { use crate::parser::Parser; // Test that migrations defined in type declarations are registered and executed let source = r#" type User @v2 { name: String, email: String, from @v1 = { name: old.name, email: "default@example.com" } } // Create a v1 user using Schema.versioned let v1_user = Schema.versioned("User", 1, { name: "Alice" }) // Migrate to v2 - should use the declared migration let v2_user = Schema.migrate(v1_user, 2) // Get the migrated value let version = Schema.getVersion(v2_user) "#; let program = Parser::parse_source(source).expect("parse failed"); let mut interp = Interpreter::new(); let result = interp.run(&program); assert!(result.is_ok(), "Interpreter failed: {:?}", result); let result_value = result.unwrap(); // The last expression should be the version number assert_eq!(format!("{}", result_value), "2"); } #[test] fn test_migration_chain() { use crate::parser::Parser; // Test that migrations chain correctly: v1 -> v2 -> v3 let source = r#" type Config @v3 { host: String, port: Int, secure: Bool, from @v2 = { host: old.host, port: old.port, secure: true }, from @v1 = { host: old.host, port: 8080 } } // Create v1 config let v1_config = Schema.versioned("Config", 1, { host: "localhost" }) // Migrate directly to v3 - should go v1 -> v2 -> v3 let v3_config = Schema.migrate(v1_config, 3) let version = Schema.getVersion(v3_config) "#; let program = Parser::parse_source(source).expect("parse failed"); let mut interp = Interpreter::new(); let result = interp.run(&program); assert!(result.is_ok(), "Interpreter failed: {:?}", result); assert_eq!(format!("{}", result.unwrap()), "3"); } }