Files
lux/src/interpreter.rs
Brandon Lucas f2688072ac feat: add File.glob for file pattern matching (issue 15)
Add File.glob(pattern) effect operation that returns a list of file
paths matching a glob pattern (e.g., "src/**/*.lux"). Implemented
across interpreter (using glob crate), JS backend (handler-based),
and C backend (using POSIX glob.h).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:33:59 -05:00

5634 lines
222 KiB
Rust

//! 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,
ListSort,
ListSortBy,
// 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,
MathSin,
MathCos,
MathAtan2,
// 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,
IntToFloat,
FloatToString,
FloatToInt,
// JSON operations
JsonParse,
JsonStringify,
JsonPrettyPrint,
JsonGet,
JsonGetIndex,
JsonAsString,
JsonAsNumber,
JsonAsInt,
JsonAsBool,
JsonAsArray,
JsonIsNull,
JsonKeys,
JsonNull,
JsonBool,
JsonNumber,
JsonInt,
JsonString,
JsonArray,
JsonObject,
// Map operations
MapNew,
MapSet,
MapGet,
MapContains,
MapRemove,
MapKeys,
MapValues,
MapSize,
MapIsEmpty,
MapFromList,
MapToList,
MapMerge,
}
/// Runtime value
#[derive(Debug, Clone)]
pub enum Value {
Int(i64),
Float(f64),
Bool(bool),
String(String),
Char(char),
Unit,
List(Vec<Value>),
Tuple(Vec<Value>),
Record(HashMap<String, Value>),
Map(HashMap<String, Value>),
Function(Rc<Closure>),
Handler(Rc<HandlerValue>),
/// Built-in function
Builtin(BuiltinFn),
/// Constructor value (for ADTs)
Constructor {
name: String,
fields: Vec<Value>,
},
/// Versioned value (for schema evolution)
Versioned {
type_name: String,
version: u32,
value: Box<Value>,
},
/// 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::Map(_) => "Map",
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::Map(xs), Value::Map(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<Self>;
}
impl TryFromValue for i64 {
const TYPE_NAME: &'static str = "Int";
fn try_from_value(value: &Value) -> Option<Self> {
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<Self> {
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<Self> {
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<Self> {
match value {
Value::Bool(b) => Some(*b),
_ => None,
}
}
}
impl TryFromValue for Vec<Value> {
const TYPE_NAME: &'static str = "List";
fn try_from_value(value: &Value) -> Option<Self> {
match value {
Value::List(l) => Some(l.clone()),
_ => None,
}
}
}
impl TryFromValue for HashMap<String, Value> {
const TYPE_NAME: &'static str = "Map";
fn try_from_value(value: &Value) -> Option<Self> {
match value {
Value::Map(m) => Some(m.clone()),
_ => None,
}
}
}
impl TryFromValue for Value {
const TYPE_NAME: &'static str = "any";
fn try_from_value(value: &Value) -> Option<Self> {
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::Map(entries) => {
write!(f, "Map {{")?;
let mut sorted: Vec<_> = entries.iter().collect();
sorted.sort_by_key(|(k, _)| (*k).clone());
for (i, (key, value)) in sorted.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "\"{}\": {}", key, value)?;
}
write!(f, "}}")
}
Value::Function(_) => write!(f, "<function>"),
Value::Builtin(b) => write!(f, "<builtin:{:?}>", b),
Value::Handler(_) => write!(f, "<handler>"),
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<String>,
pub body: Expr,
pub env: Env,
}
/// Handler value
#[derive(Debug)]
pub struct HandlerValue {
pub effect: String,
pub implementations: HashMap<String, HandlerImpl>,
pub env: Env,
}
/// Environment (lexical scope)
#[derive(Debug, Clone, Default)]
pub struct Env {
bindings: Rc<RefCell<HashMap<String, Value>>>,
parent: Option<Box<Env>>,
}
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<String>, value: Value) {
self.bindings.borrow_mut().insert(name.into(), value);
}
pub fn get(&self, name: &str) -> Option<Value> {
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<Span>,
}
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<ErrorCode>, String, Vec<String>) {
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<Value>,
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<Value>,
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<Value>,
pub result: Option<Value>,
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<Rc<HandlerValue>>,
/// Evidence map for O(1) handler lookup (evidence passing optimization)
/// Maps effect name -> handler, updated when entering/exiting run blocks
evidence: HashMap<String, Rc<HandlerValue>>,
/// Stored continuations for resumption
continuations: HashMap<usize, Box<dyn FnOnce(Value) -> Result<EvalResult, RuntimeError>>>,
/// Effect tracing for debugging
pub trace_effects: bool,
/// Collected effect traces
pub effect_traces: Vec<EffectTrace>,
/// Start time for timestamps
start_time: std::time::Instant,
/// Migration registry: type_name -> (from_version -> to_version -> migration)
migrations: HashMap<String, HashMap<u32, StoredMigration>>,
/// Built-in State effect storage (uses RefCell for interior mutability)
builtin_state: RefCell<Value>,
/// Built-in Reader effect value (uses RefCell for interior mutability)
builtin_reader: RefCell<Value>,
/// 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<Mutex> for thread-safety with tiny_http)
http_server: Arc<Mutex<Option<tiny_http::Server>>>,
/// Current HTTP request being handled (stored for respond operation)
current_http_request: Arc<Mutex<Option<tiny_http::Request>>>,
/// Test results for the Test effect
test_results: RefCell<TestResults>,
/// SQL database connections (connection ID -> Connection)
sql_connections: RefCell<HashMap<i64, Connection>>,
/// Next SQL connection ID
next_sql_conn_id: RefCell<i64>,
/// PostgreSQL database connections (connection ID -> Client)
pg_connections: RefCell<HashMap<i64, PgClient>>,
/// Next PostgreSQL connection ID
next_pg_conn_id: RefCell<i64>,
/// Concurrent tasks: task_id -> (thunk_value, result_option, is_cancelled)
concurrent_tasks: RefCell<HashMap<i64, (Value, Option<Value>, bool)>>,
/// Next task ID
next_task_id: RefCell<i64>,
/// Channels: channel_id -> (queue, is_closed)
channels: RefCell<HashMap<i64, (Vec<Value>, bool)>>,
/// Next channel ID
next_channel_id: RefCell<i64>,
}
/// Results from running tests
#[derive(Debug, Clone, Default)]
pub struct TestResults {
pub passed: usize,
pub failed: usize,
pub failures: Vec<TestFailure>,
}
/// A single test failure
#[derive(Debug, Clone)]
pub struct TestFailure {
pub message: String,
pub expected: Option<String>,
pub actual: Option<String>,
}
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<String> = 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<String, std::collections::HashMap<u32, Expr>>,
) {
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<Value, RuntimeError> {
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(&current_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),
),
("sort".to_string(), Value::Builtin(BuiltinFn::ListSort)),
(
"sortBy".to_string(),
Value::Builtin(BuiltinFn::ListSortBy),
),
]));
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)),
("sin".to_string(), Value::Builtin(BuiltinFn::MathSin)),
("cos".to_string(), Value::Builtin(BuiltinFn::MathCos)),
("atan2".to_string(), Value::Builtin(BuiltinFn::MathAtan2)),
]));
env.define("Math", math_module);
// Int module
let int_module = Value::Record(HashMap::from([
("toString".to_string(), Value::Builtin(BuiltinFn::IntToString)),
("toFloat".to_string(), Value::Builtin(BuiltinFn::IntToFloat)),
]));
env.define("Int", int_module);
// Float module
let float_module = Value::Record(HashMap::from([
("toString".to_string(), Value::Builtin(BuiltinFn::FloatToString)),
("toInt".to_string(), Value::Builtin(BuiltinFn::FloatToInt)),
]));
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);
// Map module
let map_module = Value::Record(HashMap::from([
("new".to_string(), Value::Builtin(BuiltinFn::MapNew)),
("set".to_string(), Value::Builtin(BuiltinFn::MapSet)),
("get".to_string(), Value::Builtin(BuiltinFn::MapGet)),
("contains".to_string(), Value::Builtin(BuiltinFn::MapContains)),
("remove".to_string(), Value::Builtin(BuiltinFn::MapRemove)),
("keys".to_string(), Value::Builtin(BuiltinFn::MapKeys)),
("values".to_string(), Value::Builtin(BuiltinFn::MapValues)),
("size".to_string(), Value::Builtin(BuiltinFn::MapSize)),
("isEmpty".to_string(), Value::Builtin(BuiltinFn::MapIsEmpty)),
("fromList".to_string(), Value::Builtin(BuiltinFn::MapFromList)),
("toList".to_string(), Value::Builtin(BuiltinFn::MapToList)),
("merge".to_string(), Value::Builtin(BuiltinFn::MapMerge)),
]));
env.define("Map", map_module);
}
/// Execute a program
pub fn run(&mut self, program: &Program) -> Result<Value, RuntimeError> {
let mut last_value = Value::Unit;
let mut has_main_let = false;
for decl in &program.declarations {
// Track if there's a top-level `let main = ...`
if let Declaration::Let(let_decl) = decl {
if let_decl.name.name == "main" {
has_main_let = true;
}
}
last_value = self.eval_declaration(decl)?;
}
// Auto-invoke main if it was defined as a let binding with a function value
if has_main_let {
if let Some(main_val) = self.global_env.get("main") {
if let Value::Function(ref closure) = main_val {
if closure.params.is_empty() {
let span = Span { start: 0, end: 0 };
let mut result = self.eval_call(main_val.clone(), vec![], span)?;
// Trampoline loop
loop {
match result {
EvalResult::Value(v) => {
last_value = v;
break;
}
EvalResult::Effect(req) => {
last_value = self.handle_effect(req)?;
break;
}
EvalResult::TailCall { func, args, span } => {
result = self.eval_call(func, args, span)?;
}
EvalResult::Resume(v) => {
last_value = v;
break;
}
}
}
}
}
}
}
Ok(last_value)
}
/// Execute a program with module support
pub fn run_with_modules(
&mut self,
program: &Program,
loader: &crate::modules::ModuleLoader,
) -> Result<Value, RuntimeError> {
// 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<Value, RuntimeError> {
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<Value, RuntimeError> {
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<EvalResult, RuntimeError> {
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<EvalResult, RuntimeError> {
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<Value> = args
.iter()
.map(|a| self.eval_expr(a, env))
.collect::<Result<_, _>>()?;
// 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<Value> = args
.iter()
.map(|a| self.eval_expr(a, env))
.collect::<Result<_, _>>()?;
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<Value> = args
.iter()
.map(|a| self.eval_expr(a, env))
.collect::<Result<_, _>>()?;
// 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 {
spread, fields, ..
} => {
let mut record = HashMap::new();
// If there's a spread, evaluate it and start with its fields
if let Some(spread_expr) = spread {
let spread_val = self.eval_expr(spread_expr, env)?;
if let Value::Record(spread_fields) = spread_val {
record = spread_fields;
} else {
return Err(RuntimeError {
message: format!(
"Spread expression must evaluate to a record, got {}",
spread_val.type_name()
),
span: Some(expr.span()),
});
}
}
// Override with explicit fields
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<Value> = elements
.iter()
.map(|e| self.eval_expr(e, env))
.collect::<Result<_, _>>()?;
Ok(EvalResult::Value(Value::Tuple(vals)))
}
Expr::List { elements, .. } => {
let vals: Vec<Value> = elements
.iter()
.map(|e| self.eval_expr(e, env))
.collect::<Result<_, _>>()?;
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<Value, RuntimeError> {
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::Concat => match (left, right) {
(Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)),
(Value::List(a), Value::List(b)) => {
let mut result = a;
result.extend(b);
Ok(Value::List(result))
}
(l, r) => Err(RuntimeError {
message: format!("Cannot concatenate {} 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<Value, RuntimeError> {
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<Value>,
span: Span,
) -> Result<EvalResult, RuntimeError> {
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<Value>,
span: Span,
) -> Result<Value, RuntimeError> {
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<Value>,
span: Span,
) -> Result<EvalResult, RuntimeError> {
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::<Vec<Value>, 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::<Vec<Value>, 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::<Vec<Value>>(&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::<Vec<Value>>(&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<Value>, Vec<Value>>(&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::<Vec<Value>>(&args, "List.reverse", span)?;
list.reverse();
Ok(EvalResult::Value(Value::List(list)))
}
BuiltinFn::ListLength => {
let list = Self::expect_arg_1::<Vec<Value>>(&args, "List.length", span)?;
Ok(EvalResult::Value(Value::Int(list.len() as i64)))
}
BuiltinFn::ListGet => {
let (list, idx) = Self::expect_args_2::<Vec<Value>, 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::<i64, i64>(&args, "List.range", span)?;
let list: Vec<Value> = (start..end).map(Value::Int).collect();
Ok(EvalResult::Value(Value::List(list)))
}
// String operations
BuiltinFn::StringSplit => {
let (s, delim) =
Self::expect_args_2::<String, String>(&args, "String.split", span)?;
let parts: Vec<Value> = 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::<Vec<Value>, String>(&args, "String.join", span)?;
let strings: Result<Vec<String>, _> = 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::<String>(&args, "String.trim", span)?;
Ok(EvalResult::Value(Value::String(s.trim().to_string())))
}
BuiltinFn::StringContains => {
let (s, needle) =
Self::expect_args_2::<String, String>(&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::<String>(&args, "String.length", span)?;
Ok(EvalResult::Value(Value::Int(s.len() as i64)))
}
BuiltinFn::StringChars => {
let s = Self::expect_arg_1::<String>(&args, "String.chars", span)?;
let chars: Vec<Value> = s.chars().map(Value::Char).collect();
Ok(EvalResult::Value(Value::List(chars)))
}
BuiltinFn::StringLines => {
let s = Self::expect_arg_1::<String>(&args, "String.lines", span)?;
let lines: Vec<Value> = s.lines().map(|l| Value::String(l.to_string())).collect();
Ok(EvalResult::Value(Value::List(lines)))
}
BuiltinFn::StringParseInt => {
let s = Self::expect_arg_1::<String>(&args, "String.parseInt", span)?;
let trimmed = s.trim();
match trimmed.parse::<i64>() {
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::<String>(&args, "String.parseFloat", span)?;
let trimmed = s.trim();
match trimmed.parse::<f64>() {
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::<Value, Value>(&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::<Value, Value>(&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::<Value, Value>(&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::<Value>(&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::<Value>(&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::<Value, Value>(&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::<Value, Value>(&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::<Value, Value>(&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::<Value>(&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::<Value>(&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::IntToFloat => {
if args.len() != 1 {
return Err(err("Int.toFloat requires 1 argument"));
}
match &args[0] {
Value::Int(n) => Ok(EvalResult::Value(Value::Float(*n as f64))),
v => Err(err(&format!("Int.toFloat expects Int, got {}", v.type_name()))),
}
}
BuiltinFn::FloatToInt => {
if args.len() != 1 {
return Err(err("Float.toInt requires 1 argument"));
}
match &args[0] {
Value::Float(f) => Ok(EvalResult::Value(Value::Int(*f as i64))),
v => Err(err(&format!("Float.toInt expects Float, got {}", v.type_name()))),
}
}
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::<i64, i64>(&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()))),
}
}
BuiltinFn::MathSin => {
if args.len() != 1 {
return Err(err("Math.sin requires 1 argument"));
}
match &args[0] {
Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.sin()))),
Value::Int(n) => Ok(EvalResult::Value(Value::Float((*n as f64).sin()))),
v => Err(err(&format!("Math.sin expects number, got {}", v.type_name()))),
}
}
BuiltinFn::MathCos => {
if args.len() != 1 {
return Err(err("Math.cos requires 1 argument"));
}
match &args[0] {
Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.cos()))),
Value::Int(n) => Ok(EvalResult::Value(Value::Float((*n as f64).cos()))),
v => Err(err(&format!("Math.cos expects number, got {}", v.type_name()))),
}
}
BuiltinFn::MathAtan2 => {
if args.len() != 2 {
return Err(err("Math.atan2 requires 2 arguments: y, x"));
}
let y = match &args[0] {
Value::Float(n) => *n,
Value::Int(n) => *n as f64,
v => return Err(err(&format!("Math.atan2 expects number, got {}", v.type_name()))),
};
let x = match &args[1] {
Value::Float(n) => *n,
Value::Int(n) => *n as f64,
v => return Err(err(&format!("Math.atan2 expects number, got {}", v.type_name()))),
};
Ok(EvalResult::Value(Value::Float(y.atan2(x))))
}
// Additional List operations
BuiltinFn::ListIsEmpty => {
let list = Self::expect_arg_1::<Vec<Value>>(&args, "List.isEmpty", span)?;
Ok(EvalResult::Value(Value::Bool(list.is_empty())))
}
BuiltinFn::ListFind => {
let (list, func) = Self::expect_args_2::<Vec<Value>, 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::<Vec<Value>, 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::<Vec<Value>, 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::<Vec<Value>, i64>(&args, "List.take", span)?;
let n = n.max(0) as usize;
let result: Vec<Value> = list.into_iter().take(n).collect();
Ok(EvalResult::Value(Value::List(result)))
}
BuiltinFn::ListDrop => {
let (list, n) = Self::expect_args_2::<Vec<Value>, i64>(&args, "List.drop", span)?;
let n = n.max(0) as usize;
let result: Vec<Value> = 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::<Vec<Value>, 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))
}
BuiltinFn::ListSort => {
// List.sort(list) - sort using natural ordering (Int, Float, String, Bool)
let mut list =
Self::expect_arg_1::<Vec<Value>>(&args, "List.sort", span)?;
list.sort_by(|a, b| Self::compare_values(a, b));
Ok(EvalResult::Value(Value::List(list)))
}
BuiltinFn::ListSortBy => {
// List.sortBy(list, fn(a, b) => Int) - sort with custom comparator
// Comparator returns negative (a < b), 0 (a == b), or positive (a > b)
let (list, func) =
Self::expect_args_2::<Vec<Value>, Value>(&args, "List.sortBy", span)?;
let mut indexed: Vec<(usize, Value)> =
list.into_iter().enumerate().collect();
let mut err: Option<RuntimeError> = None;
let func_ref = &func;
let self_ptr = self as *mut Self;
indexed.sort_by(|a, b| {
if err.is_some() {
return std::cmp::Ordering::Equal;
}
// Safety: we're in a single-threaded context and the closure
// needs mutable access to call eval_call_to_value
let interp = unsafe { &mut *self_ptr };
match interp.eval_call_to_value(
func_ref.clone(),
vec![a.1.clone(), b.1.clone()],
span,
) {
Ok(Value::Int(n)) => {
if n < 0 {
std::cmp::Ordering::Less
} else if n > 0 {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
}
}
Ok(_) => {
err = Some(RuntimeError {
message: "List.sortBy comparator must return Int"
.to_string(),
span: Some(span),
});
std::cmp::Ordering::Equal
}
Err(e) => {
err = Some(e);
std::cmp::Ordering::Equal
}
}
});
if let Some(e) = err {
return Err(e);
}
let result: Vec<Value> =
indexed.into_iter().map(|(_, v)| v).collect();
Ok(EvalResult::Value(Value::List(result)))
}
// Additional String operations
BuiltinFn::StringStartsWith => {
let (s, prefix) = Self::expect_args_2::<String, String>(&args, "String.startsWith", span)?;
Ok(EvalResult::Value(Value::Bool(s.starts_with(&prefix))))
}
BuiltinFn::StringEndsWith => {
let (s, suffix) = Self::expect_args_2::<String, String>(&args, "String.endsWith", span)?;
Ok(EvalResult::Value(Value::Bool(s.ends_with(&suffix))))
}
BuiltinFn::StringToUpper => {
let s = Self::expect_arg_1::<String>(&args, "String.toUpper", span)?;
Ok(EvalResult::Value(Value::String(s.to_uppercase())))
}
BuiltinFn::StringToLower => {
let s = Self::expect_arg_1::<String>(&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<char> = 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<char> = 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::<String>(&args, "Json.parse", span)?;
match serde_json::from_str::<serde_json::Value>(&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<Json>
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<Json>
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<String>
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<Float>
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<Int>
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<Bool>
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<List<Json>>
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<Value> = 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<List<String>>
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<Value> = 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::<bool>(&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::<i64>(&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::<String>(&args, "Json.string", span)?;
Ok(EvalResult::Value(Value::Json(serde_json::Value::String(s))))
}
BuiltinFn::JsonArray => {
// Json.array(list: List<Json>) -> Json
let list = Self::expect_arg_1::<Vec<Value>>(&args, "Json.array", span)?;
let arr: Result<Vec<serde_json::Value>, RuntimeError> = list.into_iter().map(|v| {
match v {
Value::Json(j) => Ok(j),
_ => Err(err("Json.array expects List<Json>")),
}
}).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::<Vec<Value>>(&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))))
}
// Map operations
BuiltinFn::MapNew => {
Ok(EvalResult::Value(Value::Map(HashMap::new())))
}
BuiltinFn::MapSet => {
if args.len() != 3 {
return Err(err("Map.set requires 3 arguments: map, key, value"));
}
let mut map = match &args[0] {
Value::Map(m) => m.clone(),
v => return Err(err(&format!("Map.set expects Map as first argument, got {}", v.type_name()))),
};
let key = match &args[1] {
Value::String(s) => s.clone(),
v => return Err(err(&format!("Map.set expects String key, got {}", v.type_name()))),
};
map.insert(key, args[2].clone());
Ok(EvalResult::Value(Value::Map(map)))
}
BuiltinFn::MapGet => {
let (map, key) = Self::expect_args_2::<HashMap<String, Value>, String>(&args, "Map.get", span)?;
match map.get(&key) {
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::MapContains => {
let (map, key) = Self::expect_args_2::<HashMap<String, Value>, String>(&args, "Map.contains", span)?;
Ok(EvalResult::Value(Value::Bool(map.contains_key(&key))))
}
BuiltinFn::MapRemove => {
let (mut map, key) = Self::expect_args_2::<HashMap<String, Value>, String>(&args, "Map.remove", span)?;
map.remove(&key);
Ok(EvalResult::Value(Value::Map(map)))
}
BuiltinFn::MapKeys => {
let map = Self::expect_arg_1::<HashMap<String, Value>>(&args, "Map.keys", span)?;
let mut keys: Vec<String> = map.keys().cloned().collect();
keys.sort();
Ok(EvalResult::Value(Value::List(
keys.into_iter().map(Value::String).collect(),
)))
}
BuiltinFn::MapValues => {
let map = Self::expect_arg_1::<HashMap<String, Value>>(&args, "Map.values", span)?;
let mut entries: Vec<(String, Value)> = map.into_iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
Ok(EvalResult::Value(Value::List(
entries.into_iter().map(|(_, v)| v).collect(),
)))
}
BuiltinFn::MapSize => {
let map = Self::expect_arg_1::<HashMap<String, Value>>(&args, "Map.size", span)?;
Ok(EvalResult::Value(Value::Int(map.len() as i64)))
}
BuiltinFn::MapIsEmpty => {
let map = Self::expect_arg_1::<HashMap<String, Value>>(&args, "Map.isEmpty", span)?;
Ok(EvalResult::Value(Value::Bool(map.is_empty())))
}
BuiltinFn::MapFromList => {
let list = Self::expect_arg_1::<Vec<Value>>(&args, "Map.fromList", span)?;
let mut map = HashMap::new();
for item in list {
match item {
Value::Tuple(fields) if fields.len() == 2 => {
let key = match &fields[0] {
Value::String(s) => s.clone(),
v => return Err(err(&format!("Map.fromList expects (String, V) tuples, got {} key", v.type_name()))),
};
map.insert(key, fields[1].clone());
}
_ => return Err(err("Map.fromList expects List<(String, V)>")),
}
}
Ok(EvalResult::Value(Value::Map(map)))
}
BuiltinFn::MapToList => {
let map = Self::expect_arg_1::<HashMap<String, Value>>(&args, "Map.toList", span)?;
let mut entries: Vec<(String, Value)> = map.into_iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
Ok(EvalResult::Value(Value::List(
entries
.into_iter()
.map(|(k, v)| Value::Tuple(vec![Value::String(k), v]))
.collect(),
)))
}
BuiltinFn::MapMerge => {
if args.len() != 2 {
return Err(err("Map.merge requires 2 arguments: map1, map2"));
}
let mut map1 = match &args[0] {
Value::Map(m) => m.clone(),
v => return Err(err(&format!("Map.merge expects Map as first argument, got {}", v.type_name()))),
};
let map2 = match &args[1] {
Value::Map(m) => m.clone(),
v => return Err(err(&format!("Map.merge expects Map as second argument, got {}", v.type_name()))),
};
for (k, v) in map2 {
map1.insert(k, v);
}
Ok(EvalResult::Value(Value::Map(map1)))
}
}
}
// Helper functions for extracting typed arguments
fn expect_arg_1<T>(args: &[Value], name: &str, span: Span) -> Result<T, RuntimeError>
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<T, U>(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<EvalResult, RuntimeError> {
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),
})
}
/// Compare two values for natural ordering (used by List.sort)
fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
match (a, b) {
(Value::Int(x), Value::Int(y)) => x.cmp(y),
(Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal),
(Value::String(x), Value::String(y)) => x.cmp(y),
(Value::Bool(x), Value::Bool(y)) => x.cmp(y),
(Value::Char(x), Value::Char(y)) => x.cmp(y),
_ => std::cmp::Ordering::Equal,
}
}
fn match_pattern(&self, pattern: &Pattern, value: &Value) -> Option<Vec<(String, Value)>> {
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::Map(a), Value::Map(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<EvalResult, RuntimeError> {
// 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<Rc<HandlerValue>>)> = 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<Value, RuntimeError> {
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<Ident>)> = 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(&param.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<Value, RuntimeError> {
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::<i64>() {
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<Value> = 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,
}),
}
}
("File", "copy") => {
let source = match request.args.first() {
Some(Value::String(s)) => s.clone(),
_ => return Err(RuntimeError {
message: "File.copy requires a string source path".to_string(),
span: None,
}),
};
let dest = match request.args.get(1) {
Some(Value::String(s)) => s.clone(),
_ => return Err(RuntimeError {
message: "File.copy requires a string destination path".to_string(),
span: None,
}),
};
match std::fs::copy(&source, &dest) {
Ok(_) => Ok(Value::Unit),
Err(e) => Err(RuntimeError {
message: format!("Failed to copy '{}' to '{}': {}", source, dest, e),
span: None,
}),
}
}
("File", "glob") => {
let pattern = match request.args.first() {
Some(Value::String(s)) => s.clone(),
_ => return Err(RuntimeError {
message: "File.glob requires a string pattern".to_string(),
span: None,
}),
};
match glob::glob(&pattern) {
Ok(paths) => {
let entries: Vec<Value> = paths
.filter_map(|entry| entry.ok())
.map(|path| Value::String(path.to_string_lossy().to_string()))
.collect();
Ok(Value::List(entries))
}
Err(e) => Err(RuntimeError {
message: format!("Invalid glob pattern '{}': {}", pattern, 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<Value> = 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<Value> = 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<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
let column_count = column_names.len();
let rows: Result<Vec<Value>, _> = 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!("<blob {} bytes>", b.len()))
}
Err(_) => Value::String("<error>".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<String> = 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!("<blob {} bytes>", b.len()))
}
Err(_) => Value::String("<error>".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<i64> = row.get(i);
match v {
Some(n) => Value::Int(n),
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
}
}
"float4" | "float8" => {
let v: Option<f64> = row.get(i);
match v {
Some(n) => Value::Float(n),
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
}
}
"bool" => {
let v: Option<bool> = 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<String> = 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<String> = 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<i64> = row.get(i);
match v {
Some(n) => Value::Int(n),
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
}
}
"float4" | "float8" => {
let v: Option<f64> = row.get(i);
match v {
Some(n) => Value::Float(n),
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
}
}
"bool" => {
let v: Option<bool> = 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<String> = row.get(i);
match v {
Some(s) => Value::String(s),
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
}
}
_ => {
let v: Option<String> = 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<Value, RuntimeError> {
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<Value> = 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<Value, RuntimeError> {
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<Value> = 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 {
spread: None,
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");
}
}