Skip to main content

synoema_eval/
eval.rs

1//! Tree-walking evaluator for Synoema.
2//!
3//! Implements big-step operational semantics from the Language Reference §5.
4//! Strict (eager) evaluation — arguments are evaluated before substitution.
5
6use crate::value::{Value, Env};
7use synoema_parser::*;
8
9/// Evaluation error
10#[derive(Debug, Clone)]
11pub struct EvalError {
12    pub message: String,
13}
14
15impl EvalError {
16    pub fn new(msg: impl Into<String>) -> Self {
17        Self { message: msg.into() }
18    }
19}
20
21impl std::fmt::Display for EvalError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "Runtime error: {}", self.message)
24    }
25}
26
27type EResult<T> = Result<T, EvalError>;
28
29fn err(msg: impl Into<String>) -> EvalError { EvalError::new(msg) }
30
31/// The Synoema evaluator
32pub struct Evaluator {
33    /// Output buffer (for testing — captures print output)
34    pub output: Vec<String>,
35}
36
37impl Evaluator {
38    pub fn new() -> Self {
39        Evaluator { output: Vec::new() }
40    }
41
42    /// Evaluate a complete program, return the environment
43    pub fn eval_program(&mut self, program: &Program) -> EResult<Env> {
44        let mut env = self.builtin_env();
45
46        // First pass: register ADT constructors
47        for decl in &program.decls {
48            if let Decl::TypeDef { variants, .. } = decl {
49                for v in variants {
50                    let arity = v.fields.len();
51                    if arity == 0 {
52                        env.insert(v.name.clone(), Value::Con(v.name.clone(), vec![]));
53                    } else {
54                        env.insert(v.name.clone(), Value::Builtin(format!("ctor:{}", v.name), arity));
55                    }
56                }
57            }
58        }
59
60        // Collect impl method equations (more-specific first, to prepend to function equations)
61        let mut impl_eqs: std::collections::HashMap<String, Vec<Equation>> =
62            std::collections::HashMap::new();
63        for decl in &program.decls {
64            if let Decl::ImplDecl { methods, .. } = decl {
65                for (method_name, equations) in methods {
66                    impl_eqs.entry(method_name.clone())
67                        .or_default()
68                        .extend(equations.iter().cloned());
69                }
70            }
71        }
72
73        // Second pass: register all functions (to enable mutual recursion)
74        // Prepend impl equations where applicable
75        for decl in &program.decls {
76            if let Decl::Func { name, equations, .. } = decl {
77                let prepend = impl_eqs.remove(name).unwrap_or_default();
78                let mut all_eqs = prepend;
79                all_eqs.extend(equations.iter().cloned());
80                let func = Value::Func {
81                    name: name.clone(),
82                    equations: all_eqs,
83                    env: env.clone(),
84                };
85                env.insert(name.clone(), func);
86            }
87        }
88
89        // Register standalone impl methods not covered by any Func decl
90        for (method_name, equations) in impl_eqs {
91            let func = Value::Func {
92                name: method_name.clone(),
93                equations,
94                env: env.clone(),
95            };
96            env.insert(method_name, func);
97        }
98
99        // Update function closures to capture the complete environment
100        // (enables mutual recursion)
101        let snapshot = env.clone();
102        for decl in &program.decls {
103            if let Decl::Func { name, .. } = decl {
104                if let Some(Value::Func { equations, .. }) = snapshot.lookup(name) {
105                    let equations = equations.clone();
106                    let func = Value::Func {
107                        name: name.clone(),
108                        equations,
109                        env: snapshot.clone(),
110                    };
111                    env.insert(name.clone(), func);
112                }
113            }
114        }
115        // Also update standalone impl methods
116        for decl in &program.decls {
117            if let Decl::ImplDecl { methods, .. } = decl {
118                for (method_name, _) in methods {
119                    if let Some(Value::Func { equations, .. }) = snapshot.lookup(method_name) {
120                        let equations = equations.clone();
121                        let func = Value::Func {
122                            name: method_name.clone(),
123                            equations,
124                            env: snapshot.clone(),
125                        };
126                        env.insert(method_name.clone(), func);
127                    }
128                }
129            }
130        }
131
132        Ok(env)
133    }
134
135    /// Evaluate an expression in an environment
136    pub fn eval(&mut self, env: &Env, expr: &Expr) -> EResult<Value> {
137        match &expr.kind {
138            // ── Literals ────────────────────────────
139            ExprKind::Lit(lit) => Ok(self.eval_lit(lit)),
140
141            // ── Variable ────────────────────────────
142            ExprKind::Var(name) => {
143                let val = env.lookup(name)
144                    .cloned()
145                    .ok_or_else(|| err(format!("Undefined variable: {}", name)))?;
146                // Force 0-arity functions: `a = 10` is stored as
147                // Func { pats: [], body: Lit(10) } — evaluate the body
148                match &val {
149                    Value::Func { equations, env: fenv, name: fname }
150                        if equations.len() == 1 && equations[0].pats.is_empty() =>
151                    {
152                        let mut local = fenv.child();
153                        local.insert(fname.clone(), val.clone());
154                        self.eval(&local, &equations[0].body)
155                    }
156                    // 0-arity builtins (e.g. readline): execute immediately
157                    Value::Builtin(ref bname, 0) => {
158                        let n = bname.clone();
159                        self.call_builtin(&n, &[])
160                    }
161                    _ => Ok(val),
162                }
163            }
164
165            // ── Constructor (nullary) ───────────────
166            ExprKind::Con(name) => {
167                env.lookup(name)
168                    .cloned()
169                    .ok_or_else(|| err(format!("Undefined constructor: {}", name)))
170            }
171
172            // ── Application ─────────────────────────
173            ExprKind::App(func_e, arg_e) => {
174                let func_v = self.eval(env, func_e)?;
175                let arg_v = self.eval(env, arg_e)?;
176                self.apply(func_v, arg_v)
177            }
178
179            // ── Lambda ──────────────────────────────
180            ExprKind::Lam(pats, body) => {
181                Ok(Value::Closure {
182                    params: pats.clone(),
183                    body: *body.clone(),
184                    env: env.clone(),
185                })
186            }
187
188            // ── Binary operator ─────────────────────
189            ExprKind::BinOp(op, lhs, rhs) => {
190                // Short-circuit for && and ||
191                match op {
192                    BinOp::And => {
193                        let l = self.eval(env, lhs)?;
194                        return match l {
195                            Value::Bool(false) => Ok(Value::Bool(false)),
196                            Value::Bool(true) => self.eval(env, rhs),
197                            _ => Err(err("&& requires Bool operands")),
198                        };
199                    }
200                    BinOp::Or => {
201                        let l = self.eval(env, lhs)?;
202                        return match l {
203                            Value::Bool(true) => Ok(Value::Bool(true)),
204                            Value::Bool(false) => self.eval(env, rhs),
205                            _ => Err(err("|| requires Bool operands")),
206                        };
207                    }
208                    BinOp::Pipe => {
209                        // x |> f  =  f x
210                        let x = self.eval(env, lhs)?;
211                        let f = self.eval(env, rhs)?;
212                        return self.apply(f, x);
213                    }
214                    BinOp::Compose => {
215                        // f >> g  =  value that, when applied to x, returns g (f x)
216                        let f_val = self.eval(env, lhs)?;
217                        let g_val = self.eval(env, rhs)?;
218                        // Store f and g in a PartialBuiltin with 1 remaining arg
219                        return Ok(Value::PartialBuiltin("compose#".into(), 1, vec![f_val, g_val]));
220                    }
221                    BinOp::Seq => {
222                        // a ; b — evaluate a for side effects, return b
223                        self.eval(env, lhs)?;
224                        return self.eval(env, rhs);
225                    }
226                    _ => {}
227                }
228
229                let l = self.eval(env, lhs)?;
230                let r = self.eval(env, rhs)?;
231                self.eval_binop(*op, l, r)
232            }
233
234            // ── Negation ────────────────────────────
235            ExprKind::Neg(inner) => {
236                match self.eval(env, inner)? {
237                    Value::Int(n) => Ok(Value::Int(-n)),
238                    Value::Float(n) => Ok(Value::Float(-n)),
239                    _ => Err(err("Negation requires a number")),
240                }
241            }
242
243            // ── Conditional ─────────────────────────
244            ExprKind::Cond(guard, then_e, else_e) => {
245                match self.eval(env, guard)? {
246                    Value::Bool(true) => self.eval(env, then_e),
247                    Value::Bool(false) => self.eval(env, else_e),
248                    _ => Err(err("Condition must be Bool")),
249                }
250            }
251
252            // ── List literal ────────────────────────
253            ExprKind::List(elems) => {
254                let vals: Vec<Value> = elems.iter()
255                    .map(|e| self.eval(env, e))
256                    .collect::<EResult<_>>()?;
257                Ok(Value::List(vals))
258            }
259
260            // ── Range ───────────────────────────────
261            ExprKind::Range(from, to) => {
262                let f = self.eval(env, from)?;
263                let t = self.eval(env, to)?;
264                match (f, t) {
265                    (Value::Int(a), Value::Int(b)) => {
266                        Ok(Value::List((a..=b).map(Value::Int).collect()))
267                    }
268                    _ => Err(err("Range requires Int operands")),
269                }
270            }
271
272            // ── List comprehension ──────────────────
273            ExprKind::ListComp(body_expr, generators) => {
274                self.eval_list_comp(env, body_expr, generators, 0)
275            }
276
277            // ── Block ───────────────────────────────
278            ExprKind::Block(bindings, result) => {
279                let mut local = env.child();
280                for b in bindings {
281                    let val = self.eval(&local, &b.value)?;
282                    local.insert(b.name.clone(), val);
283                }
284                self.eval(&local, result)
285            }
286
287            // ── Record literal ───────────────────────
288            ExprKind::Record(fields) => {
289                let mut pairs = Vec::new();
290                for (name, expr) in fields {
291                    let val = self.eval(env, expr)?;
292                    pairs.push((name.clone(), val));
293                }
294                Ok(Value::Record(pairs))
295            }
296
297            // ── Field access ────────────────────────
298            ExprKind::Field(obj, field) => {
299                let obj_val = self.eval(env, obj)?;
300                match obj_val {
301                    Value::Record(fields) => {
302                        fields.into_iter()
303                            .find(|(name, _)| name == field)
304                            .map(|(_, val)| val)
305                            .ok_or_else(|| err(format!("Field '{}' not found in record", field)))
306                    }
307                    _ => Err(err(format!("Field access '{}' requires a record", field))),
308                }
309            }
310
311            // ── Parenthesized ───────────────────────
312            ExprKind::Paren(inner) => self.eval(env, inner),
313        }
314    }
315
316    // ── Apply a function value to an argument ────────
317
318    pub fn apply(&mut self, func: Value, arg: Value) -> EResult<Value> {
319        match func {
320            Value::Closure { params, body, env } => {
321                if params.len() == 1 {
322                    let mut local = env.child();
323                    self.bind_pattern(&params[0], &arg, &mut local)?;
324                    self.eval(&local, &body)
325                } else {
326                    // Multi-param lambda: apply first param, return closure for rest
327                    let mut local = env.child();
328                    self.bind_pattern(&params[0], &arg, &mut local)?;
329                    let rest = params[1..].to_vec();
330                    Ok(Value::Closure {
331                        params: rest,
332                        body,
333                        env: local,
334                    })
335                }
336            }
337
338            Value::Func { name, equations, env } => {
339                // Case 1: Any equation has 0 patterns → evaluate body first
340                // Handles: `f = \x -> x + a` where f is stored with 0 pats
341                if equations.iter().any(|eq| eq.pats.is_empty()) {
342                    // Find first 0-pattern equation and evaluate its body
343                    for eq in &equations {
344                        if eq.pats.is_empty() {
345                            let mut local = env.child();
346                            local.insert(name.clone(), Value::Func {
347                                name: name.clone(),
348                                equations: equations.clone(),
349                                env: env.clone(),
350                            });
351                            let body_val = self.eval(&local, &eq.body)?;
352                            return self.apply(body_val, arg);
353                        }
354                    }
355                }
356
357                // Case 2: All equations have >1 pattern → curry (bind first arg)
358                // Handles: `map f [] = ... / map f (x:xs) = ...`
359                let all_multi = equations.iter().all(|eq| eq.pats.len() > 1);
360
361                if all_multi {
362                    // Bind first pattern from each equation that matches,
363                    // build new Func with remaining patterns for all equations
364                    let mut local = env.child();
365                    // Only insert self-reference if not already in env.
366                    // The original full-arity function is in the parent env (set by eval_program).
367                    // Inserting the reduced-arity version would shadow it and break recursion.
368                    if env.lookup(&name).is_none() {
369                        local.insert(name.clone(), Value::Func {
370                            name: name.clone(),
371                            equations: equations.clone(),
372                            env: env.clone(),
373                        });
374                    }
375
376                    // For curried functions, the first pattern position must be
377                    // consistent (e.g., all Var, or matching constructors).
378                    // Bind using the first equation's first pattern:
379                    for eq in &equations {
380                        if self.try_bind_pattern(&eq.pats[0], &arg, &mut local) {
381                            break;
382                        }
383                    }
384
385                    // Build remaining equations with first pattern stripped
386                    let remaining: Vec<Equation> = equations.iter().map(|eq| {
387                        Equation {
388                            pats: eq.pats[1..].to_vec(),
389                            body: eq.body.clone(),
390                            span: eq.span,
391                        }
392                    }).collect();
393
394                    return Ok(Value::Func {
395                        name: name.clone(),
396                        equations: remaining,
397                        env: local,
398                    });
399                }
400
401                // Case 3: Single-pattern equations → match and dispatch
402                for eq in &equations {
403                    if eq.pats.is_empty() {
404                        continue;
405                    }
406                    let mut local = env.child();
407                    // Only add self-reference if not already in env
408                    // (after currying, env already has the original multi-arg version)
409                    if env.lookup(&name).is_none() {
410                        local.insert(name.clone(), Value::Func {
411                            name: name.clone(),
412                            equations: equations.clone(),
413                            env: env.clone(),
414                        });
415                    }
416
417                    if self.try_bind_pattern(&eq.pats[0], &arg, &mut local) {
418                        if eq.pats.len() == 1 {
419                            return self.eval(&local, &eq.body);
420                        } else {
421                            let remaining: Vec<Equation> = vec![Equation {
422                                pats: eq.pats[1..].to_vec(),
423                                body: eq.body.clone(),
424                                span: eq.span,
425                            }];
426                            return Ok(Value::Func {
427                                name: name.clone(),
428                                equations: remaining,
429                                env: local,
430                            });
431                        }
432                    }
433                }
434                Err(err(format!("No matching pattern in function '{}' for argument {}", name, arg)))
435            }
436
437            Value::Builtin(name, arity) => {
438                if arity == 1 {
439                    self.call_builtin(&name, &[arg])
440                } else {
441                    Ok(Value::PartialBuiltin(name, arity - 1, vec![arg]))
442                }
443            }
444
445            Value::PartialBuiltin(name, remaining, mut args) => {
446                args.push(arg);
447                if remaining == 1 {
448                    self.call_builtin(&name, &args)
449                } else {
450                    Ok(Value::PartialBuiltin(name, remaining - 1, args))
451                }
452            }
453
454            other => Err(err(format!("Cannot apply non-function: {}", other))),
455        }
456    }
457
458    // ── Pattern matching ────────────────────────────
459
460    fn bind_pattern(&self, pat: &Pat, val: &Value, env: &mut Env) -> EResult<()> {
461        if !self.try_bind_pattern(pat, val, env) {
462            Err(err(format!("Pattern match failed: {:?} vs {}", pat, val)))
463        } else {
464            Ok(())
465        }
466    }
467
468    fn try_bind_pattern(&self, pat: &Pat, val: &Value, env: &mut Env) -> bool {
469        match (pat, val) {
470            (Pat::Wildcard, _) => true,
471
472            (Pat::Var(name), _) => {
473                env.insert(name.clone(), val.clone());
474                true
475            }
476
477            (Pat::Lit(lit), val) => {
478                &self.eval_lit(lit) == val
479            }
480
481            (Pat::Con(pname, ppats), Value::Con(vname, vfields)) => {
482                if pname != vname || ppats.len() != vfields.len() {
483                    return false;
484                }
485                ppats.iter().zip(vfields.iter())
486                    .all(|(p, v)| self.try_bind_pattern(p, v, env))
487            }
488
489            // Empty list pattern: Con("[]", []) matches List([])
490            (Pat::Con(name, pats), Value::List(elems)) if name == "[]" || name == "Nil" => {
491                pats.is_empty() && elems.is_empty()
492            }
493
494            // Cons pattern: (x:xs) matches non-empty list
495            (Pat::Cons(head, tail), Value::List(elems)) => {
496                if elems.is_empty() {
497                    return false;
498                }
499                let h = &elems[0];
500                let t = Value::List(elems[1..].to_vec());
501                self.try_bind_pattern(head, h, env)
502                    && self.try_bind_pattern(tail, &t, env)
503            }
504
505            (Pat::Paren(inner), val) => self.try_bind_pattern(inner, val, env),
506
507            (Pat::Record(pat_fields), Value::Record(val_fields)) => {
508                for (pname, ppat) in pat_fields {
509                    if let Some((_, val)) = val_fields.iter().find(|(n, _)| n == pname) {
510                        if !self.try_bind_pattern(ppat, val, env) {
511                            return false;
512                        }
513                    } else {
514                        return false;
515                    }
516                }
517                true
518            }
519
520            _ => false,
521        }
522    }
523
524    // ── List comprehension ──────────────────────────
525
526    fn eval_list_comp(
527        &mut self,
528        env: &Env,
529        body: &Expr,
530        generators: &[Generator],
531        gen_idx: usize,
532    ) -> EResult<Value> {
533        if gen_idx >= generators.len() {
534            // All generators consumed — evaluate body
535            let val = self.eval(env, body)?;
536            return Ok(Value::List(vec![val]));
537        }
538
539        match &generators[gen_idx] {
540            Generator::Bind(name, source_expr) => {
541                let source = self.eval(env, source_expr)?;
542                let list = match source {
543                    Value::List(elems) => elems,
544                    _ => return Err(err("List comprehension source must be a list")),
545                };
546                let mut result = Vec::new();
547                for item in list {
548                    let mut local = env.child();
549                    local.insert(name.clone(), item);
550                    match self.eval_list_comp(&local, body, generators, gen_idx + 1)? {
551                        Value::List(vals) => result.extend(vals),
552                        _ => unreachable!(),
553                    }
554                }
555                Ok(Value::List(result))
556            }
557            Generator::Guard(guard_expr) => {
558                match self.eval(env, guard_expr)? {
559                    Value::Bool(true) => self.eval_list_comp(env, body, generators, gen_idx + 1),
560                    Value::Bool(false) => Ok(Value::List(vec![])),
561                    _ => Err(err("Guard must be Bool")),
562                }
563            }
564        }
565    }
566
567    // ── Binary operators ────────────────────────────
568
569    fn eval_binop(&self, op: BinOp, l: Value, r: Value) -> EResult<Value> {
570        match op {
571            // Arithmetic
572            BinOp::Add => num_op(l, r, |a, b| a + b, |a, b| a + b),
573            BinOp::Sub => num_op(l, r, |a, b| a - b, |a, b| a - b),
574            BinOp::Mul => num_op(l, r, |a, b| a * b, |a, b| a * b),
575            BinOp::Div => {
576                let is_zero = matches!(&r, Value::Int(0))
577                           || matches!(&r, Value::Float(f) if *f == 0.0);
578                if is_zero {
579                    Err(err("Division by zero"))
580                } else {
581                    num_op(l, r, |a, b| a / b, |a, b| a / b)
582                }
583            }
584            BinOp::Mod => {
585                match (&l, &r) {
586                    (Value::Int(a), Value::Int(b)) if *b != 0 => Ok(Value::Int(a % b)),
587                    (Value::Int(_), Value::Int(0)) => Err(err("Modulo by zero")),
588                    _ => Err(err("% requires Int operands")),
589                }
590            }
591            BinOp::Pow => {
592                match (&l, &r) {
593                    (Value::Int(a), Value::Int(b)) if *b >= 0 => {
594                        let exp = u32::try_from(*b)
595                            .map_err(|_| err("Exponent too large"))?;
596                        a.checked_pow(exp)
597                            .map(Value::Int)
598                            .ok_or_else(|| err("Integer overflow in **"))
599                    }
600                    (Value::Int(_), Value::Int(_)) => Err(err("** requires non-negative exponent for Int")),
601                    (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a.powf(*b))),
602                    (Value::Int(a), Value::Float(b)) => Ok(Value::Float((*a as f64).powf(*b))),
603                    (Value::Float(a), Value::Int(b)) => {
604                        if let Ok(exp) = i32::try_from(*b) {
605                            Ok(Value::Float(a.powi(exp)))
606                        } else {
607                            Ok(Value::Float(a.powf(*b as f64)))
608                        }
609                    }
610                    _ => Err(err("** requires numeric operands")),
611                }
612            }
613
614            // Comparison
615            BinOp::Eq => Ok(Value::Bool(l == r)),
616            BinOp::Neq => Ok(Value::Bool(l != r)),
617            BinOp::Lt => cmp_op(l, r, |o| o == std::cmp::Ordering::Less),
618            BinOp::Gt => cmp_op(l, r, |o| o == std::cmp::Ordering::Greater),
619            BinOp::Lte => cmp_op(l, r, |o| o != std::cmp::Ordering::Greater),
620            BinOp::Gte => cmp_op(l, r, |o| o != std::cmp::Ordering::Less),
621
622            // List
623            BinOp::Concat => {
624                match (l, r) {
625                    (Value::List(mut a), Value::List(b)) => { a.extend(b); Ok(Value::List(a)) }
626                    (Value::Str(a), Value::Str(b)) => Ok(Value::Str(format!("{}{}", a, b))),
627                    _ => Err(err("++ requires two lists or two strings")),
628                }
629            }
630            BinOp::Cons => {
631                match r {
632                    Value::List(mut elems) => { elems.insert(0, l); Ok(Value::List(elems)) }
633                    _ => Err(err(": requires a list on the right")),
634                }
635            }
636
637            BinOp::Compose => Err(err(">> composition should be desugared by parser")),
638            BinOp::Pipe => Err(err("|> pipe should be handled in eval")),
639            BinOp::And | BinOp::Or => Err(err("&&/|| should be short-circuited in eval")),
640            BinOp::Seq => Err(err("; sequence should be short-circuited in eval")),
641        }
642    }
643
644    // ── Literals ────────────────────────────────────
645
646    fn eval_lit(&self, lit: &Lit) -> Value {
647        match lit {
648            Lit::Int(n) => Value::Int(*n),
649            Lit::Float(n) => Value::Float(*n),
650            Lit::Bool(b) => Value::Bool(*b),
651            Lit::Str(s) => Value::Str(s.clone()),
652            Lit::Char(c) => Value::Char(*c),
653            Lit::Unit => Value::Unit,
654        }
655    }
656
657    // ── Builtins ────────────────────────────────────
658
659    fn builtin_env(&self) -> Env {
660        let mut env = Env::new();
661        // 0-arity: readline executes immediately on lookup (reads from stdin)
662        env.insert("readline".to_string(), Value::Builtin("readline".to_string(), 0));
663        for (name, arity) in &[
664            ("print", 1), ("show", 1), ("show_bool", 1), ("length", 1),
665            ("head", 1), ("tail", 1), ("even", 1), ("odd", 1),
666            ("not", 1), ("sum", 1), ("filter", 2), ("map", 2),
667            ("foldl", 3),
668            // Float math builtins
669            ("sqrt", 1), ("floor", 1), ("ceil", 1), ("round", 1), ("abs", 1),
670        ] {
671            env.insert(name.to_string(), Value::Builtin(name.to_string(), *arity));
672        }
673        // Nil / empty list constructors
674        env.insert("Nil".into(), Value::List(vec![]));
675        env.insert("None".into(), Value::Con("None".into(), vec![]));
676        env
677    }
678
679    fn call_builtin(&mut self, name: &str, args: &[Value]) -> EResult<Value> {
680        match name {
681            "readline" => {
682                use std::io::BufRead;
683                let stdin = std::io::stdin();
684                let mut line = String::new();
685                stdin.lock().read_line(&mut line)
686                    .map_err(|e| err(format!("readline error: {}", e)))?;
687                // Strip trailing newline
688                if line.ends_with('\n') { line.pop(); }
689                if line.ends_with('\r') { line.pop(); }
690                Ok(Value::Str(line))
691            }
692            "print" => {
693                let s = format!("{}", args[0]);
694                self.output.push(s);
695                Ok(Value::Unit)
696            }
697            "show" => Ok(Value::Str(format!("{}", args[0]))),
698            "show_bool" => {
699                let b = match &args[0] {
700                    Value::Bool(b) => *b,
701                    Value::Int(0) => false,
702                    _ => true,
703                };
704                Ok(Value::Str(if b { "true".into() } else { "false".into() }))
705            }
706            "length" => match &args[0] {
707                Value::List(l) => Ok(Value::Int(l.len() as i64)),
708                Value::Str(s) => Ok(Value::Int(s.len() as i64)),
709                _ => Err(err("length requires a list or string")),
710            },
711            "head" => match &args[0] {
712                Value::List(l) if !l.is_empty() => Ok(l[0].clone()),
713                Value::List(_) => Err(err("head of empty list")),
714                _ => Err(err("head requires a list")),
715            },
716            "tail" => match &args[0] {
717                Value::List(l) if !l.is_empty() => Ok(Value::List(l[1..].to_vec())),
718                Value::List(_) => Err(err("tail of empty list")),
719                _ => Err(err("tail requires a list")),
720            },
721            "even" => match &args[0] {
722                Value::Int(n) => Ok(Value::Bool(n % 2 == 0)),
723                _ => Err(err("even requires Int")),
724            },
725            "odd" => match &args[0] {
726                Value::Int(n) => Ok(Value::Bool(n % 2 != 0)),
727                _ => Err(err("odd requires Int")),
728            },
729            "not" => match &args[0] {
730                Value::Bool(b) => Ok(Value::Bool(!b)),
731                _ => Err(err("not requires Bool")),
732            },
733            "sum" => match &args[0] {
734                Value::List(l) => {
735                    let mut total = 0i64;
736                    for v in l {
737                        match v {
738                            Value::Int(n) => total += n,
739                            _ => return Err(err("sum requires a list of Int")),
740                        }
741                    }
742                    Ok(Value::Int(total))
743                }
744                _ => Err(err("sum requires a list")),
745            },
746            "filter" => {
747                let func = &args[0];
748                match &args[1] {
749                    Value::List(elems) => {
750                        let mut result = Vec::new();
751                        for e in elems {
752                            let r = self.apply(func.clone(), e.clone())?;
753                            if r == Value::Bool(true) {
754                                result.push(e.clone());
755                            }
756                        }
757                        Ok(Value::List(result))
758                    }
759                    _ => Err(err("filter requires a list")),
760                }
761            },
762            "map" => {
763                let func = &args[0];
764                match &args[1] {
765                    Value::List(elems) => {
766                        let mut result = Vec::new();
767                        for e in elems {
768                            result.push(self.apply(func.clone(), e.clone())?);
769                        }
770                        Ok(Value::List(result))
771                    }
772                    _ => Err(err("map requires a list")),
773                }
774            },
775            "foldl" => {
776                let func = &args[0];
777                let acc = &args[1];
778                match &args[2] {
779                    Value::List(elems) => {
780                        let mut current = acc.clone();
781                        for e in elems {
782                            let partial = self.apply(func.clone(), current)?;
783                            current = self.apply(partial, e.clone())?;
784                        }
785                        Ok(current)
786                    }
787                    _ => Err(err("foldl requires a list")),
788                }
789            },
790            "compose#" => {
791                // args[0]=f, args[1]=g, args[2]=x  →  g (f x)
792                let fx = self.apply(args[0].clone(), args[2].clone())?;
793                self.apply(args[1].clone(), fx)
794            }
795            "sqrt" => match &args[0] {
796                Value::Float(f) => Ok(Value::Float(f.sqrt())),
797                Value::Int(n) => Ok(Value::Float((*n as f64).sqrt())),
798                _ => Err(err("sqrt requires Float")),
799            },
800            "floor" => match &args[0] {
801                Value::Float(f) => Ok(Value::Float(f.floor())),
802                _ => Err(err("floor requires Float")),
803            },
804            "ceil" => match &args[0] {
805                Value::Float(f) => Ok(Value::Float(f.ceil())),
806                _ => Err(err("ceil requires Float")),
807            },
808            "round" => match &args[0] {
809                Value::Float(f) => Ok(Value::Float(f.round())),
810                _ => Err(err("round requires Float")),
811            },
812            "abs" => match &args[0] {
813                Value::Int(n) => Ok(Value::Int(n.abs())),
814                Value::Float(f) => Ok(Value::Float(f.abs())),
815                _ => Err(err("abs requires Int or Float")),
816            },
817            name if name.starts_with("ctor:") => {
818                let ctor_name = &name[5..];
819                Ok(Value::Con(ctor_name.to_string(), args.to_vec()))
820            },
821            _ => Err(err(format!("Unknown builtin: {}", name))),
822        }
823    }
824}
825
826// ── Helpers ──────────────────────────────────────────
827
828fn num_op(l: Value, r: Value, int_op: fn(i64, i64) -> i64, float_op: fn(f64, f64) -> f64) -> EResult<Value> {
829    match (l, r) {
830        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(int_op(a, b))),
831        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(float_op(a, b))),
832        (Value::Int(a), Value::Float(b)) => Ok(Value::Float(float_op(a as f64, b))),
833        (Value::Float(a), Value::Int(b)) => Ok(Value::Float(float_op(a, b as f64))),
834        _ => Err(err("Arithmetic requires numbers")),
835    }
836}
837
838fn cmp_op(l: Value, r: Value, pred: fn(std::cmp::Ordering) -> bool) -> EResult<Value> {
839    l.partial_cmp(&r)
840        .map(|o| Value::Bool(pred(o)))
841        .ok_or_else(|| err("Cannot compare these values"))
842}