Skip to main content

aver/interpreter/
eval.rs

1use super::lowered::{
2    self, ExprId, LoweredExpr, LoweredFunctionBody, LoweredMatchArm, LoweredStmt, LoweredStrPart,
3};
4use super::*;
5
6type SharedExprs = Rc<[ExprId]>;
7type SharedStrParts = Rc<[LoweredStrPart]>;
8type SharedMapEntries = Rc<[(ExprId, ExprId)]>;
9type SharedRecordFields = Rc<[(String, ExprId)]>;
10type SharedMatchArms = Rc<[LoweredMatchArm]>;
11
12#[derive(Debug)]
13enum EvalState {
14    Expr {
15        lowered: Rc<LoweredFunctionBody>,
16        expr: ExprId,
17    },
18    Body {
19        lowered: Rc<LoweredFunctionBody>,
20        idx: usize,
21        local_slots: Option<Rc<HashMap<String, u16>>>,
22        last: NanValue,
23    },
24    Apply(Result<NanValue, RuntimeError>),
25}
26
27#[derive(Debug, Clone)]
28enum EvalCont {
29    Attr(String),
30    Call {
31        lowered: Rc<LoweredFunctionBody>,
32        args: SharedExprs,
33        idx: usize,
34        fn_val: Option<NanValue>,
35        arg_vals: Vec<NanValue>,
36    },
37    BinOpLeft {
38        lowered: Rc<LoweredFunctionBody>,
39        op: BinOp,
40        right: ExprId,
41    },
42    BinOpRight {
43        op: BinOp,
44        left: NanValue,
45    },
46    Match {
47        lowered: Rc<LoweredFunctionBody>,
48        arms: SharedMatchArms,
49        line: usize,
50    },
51    Constructor(String),
52    ErrorProp,
53    InterpolatedStr {
54        lowered: Rc<LoweredFunctionBody>,
55        parts: SharedStrParts,
56        idx: usize,
57        result: String,
58    },
59    List {
60        lowered: Rc<LoweredFunctionBody>,
61        items: SharedExprs,
62        idx: usize,
63        values: Vec<NanValue>,
64    },
65    Tuple {
66        lowered: Rc<LoweredFunctionBody>,
67        items: SharedExprs,
68        idx: usize,
69        values: Vec<NanValue>,
70    },
71    MapKey {
72        lowered: Rc<LoweredFunctionBody>,
73        entries: SharedMapEntries,
74        idx: usize,
75        map: crate::nan_value::PersistentMap,
76    },
77    MapValue {
78        lowered: Rc<LoweredFunctionBody>,
79        entries: SharedMapEntries,
80        idx: usize,
81        map: crate::nan_value::PersistentMap,
82        key: NanValue,
83    },
84    RecordCreate(RecordCreateProgress),
85    RecordUpdateBase {
86        lowered: Rc<LoweredFunctionBody>,
87        type_name: String,
88        updates: SharedRecordFields,
89    },
90    RecordUpdateField(RecordUpdateProgress),
91    TailCallArgs {
92        lowered: Rc<LoweredFunctionBody>,
93        target: String,
94        args: SharedExprs,
95        idx: usize,
96        values: Vec<NanValue>,
97    },
98    BodyBinding {
99        name: String,
100        next_idx: usize,
101        lowered: Rc<LoweredFunctionBody>,
102        local_slots: Option<Rc<HashMap<String, u16>>>,
103    },
104    BodyExpr {
105        next_idx: usize,
106        lowered: Rc<LoweredFunctionBody>,
107        local_slots: Option<Rc<HashMap<String, u16>>>,
108    },
109    MatchScope,
110    FunctionReturn(FunctionFrame),
111}
112
113#[derive(Debug, Clone)]
114struct ActiveFunction {
115    function: Rc<crate::value::FunctionValue>,
116}
117
118#[derive(Debug, Clone)]
119struct FunctionFrame {
120    active: ActiveFunction,
121    prev_local_slots: Option<Rc<HashMap<String, u16>>>,
122    /// The env_base of the caller — restored on return so the caller's
123    /// frames become visible to lookup_ref again.
124    saved_base: usize,
125    prev_global: Option<EnvFrame>,
126    memo_key: Option<(u64, Vec<NanValue>)>,
127}
128
129#[derive(Debug, Clone)]
130struct RecordCreateProgress {
131    lowered: Rc<LoweredFunctionBody>,
132    type_name: String,
133    fields: SharedRecordFields,
134    idx: usize,
135    seen: HashSet<String>,
136    values: Vec<(String, NanValue)>,
137}
138
139#[derive(Debug, Clone)]
140struct RecordUpdateProgress {
141    lowered: Rc<LoweredFunctionBody>,
142    type_name: String,
143    base_type_id: u32,
144    base_fields: Vec<NanValue>,
145    base_field_names: Vec<String>,
146    updates: SharedRecordFields,
147    idx: usize,
148    update_vals: Vec<(String, NanValue)>,
149}
150
151enum CallDispatch {
152    Immediate(Result<NanValue, RuntimeError>),
153    EnterFunction {
154        frame: Box<FunctionFrame>,
155        state: EvalState,
156    },
157}
158
159impl Interpreter {
160    fn empty_slots_nv(local_count: u16) -> Vec<NanValue> {
161        vec![NanValue::UNIT; local_count as usize]
162    }
163
164    /// Public eval_expr — returns Value for external callers.
165    pub fn eval_expr(&mut self, expr: &Expr) -> Result<Value, RuntimeError> {
166        let nv = self.eval_expr_nv(expr)?;
167        Ok(nv.to_value(&self.arena))
168    }
169
170    /// Internal eval_expr — returns NanValue natively.
171    pub(super) fn eval_expr_nv(&mut self, expr: &Expr) -> Result<NanValue, RuntimeError> {
172        let (lowered, root) = lowered::lower_expr_root(expr);
173        self.eval_loop(
174            EvalState::Expr {
175                lowered,
176                expr: root,
177            },
178            Vec::new(),
179        )
180    }
181
182    fn eval_loop(
183        &mut self,
184        initial: EvalState,
185        mut conts: Vec<EvalCont>,
186    ) -> Result<NanValue, RuntimeError> {
187        let mut state = initial;
188
189        loop {
190            state = match state {
191                EvalState::Expr { lowered, expr } => self.step_expr(lowered, expr, &mut conts),
192                EvalState::Body {
193                    lowered,
194                    idx,
195                    local_slots,
196                    last,
197                } => self.step_body(lowered, idx, local_slots, last, &mut conts),
198                EvalState::Apply(result) => {
199                    let Some(cont) = conts.pop() else {
200                        return result;
201                    };
202                    self.apply_cont(cont, result, &mut conts)
203                }
204            };
205        }
206    }
207
208    fn step_expr(
209        &mut self,
210        lowered: Rc<LoweredFunctionBody>,
211        expr_id: ExprId,
212        conts: &mut Vec<EvalCont>,
213    ) -> EvalState {
214        match lowered.expr(expr_id) {
215            LoweredExpr::Literal(lit) => EvalState::Apply(Ok(self.eval_literal_nv(lit))),
216            &LoweredExpr::Resolved(slot) => EvalState::Apply(self.lookup_slot(slot)),
217            LoweredExpr::Ident(name) => EvalState::Apply(self.lookup_nv(name)),
218            LoweredExpr::Attr { obj, field } => {
219                let obj = *obj;
220                if let LoweredExpr::Ident(name) = lowered.expr(obj) {
221                    let nv = match self.lookup_nv(name) {
222                        Ok(nv) => nv,
223                        Err(err) => return EvalState::Apply(Err(err)),
224                    };
225                    let result = self.attr_access_nv(nv, field);
226                    return EvalState::Apply(result);
227                }
228
229                let field = field.clone();
230                conts.push(EvalCont::Attr(field));
231                EvalState::Expr { lowered, expr: obj }
232            }
233            LoweredExpr::FnCall { fn_expr, args } => {
234                let fn_expr = *fn_expr;
235                let args = Rc::clone(args);
236                conts.push(EvalCont::Call {
237                    lowered: Rc::clone(&lowered),
238                    idx: 0,
239                    fn_val: None,
240                    arg_vals: Vec::with_capacity(args.len()),
241                    args,
242                });
243                EvalState::Expr {
244                    lowered,
245                    expr: fn_expr,
246                }
247            }
248            &LoweredExpr::BinOp { op, left, right } => {
249                conts.push(EvalCont::BinOpLeft {
250                    lowered: Rc::clone(&lowered),
251                    op,
252                    right,
253                });
254                EvalState::Expr {
255                    lowered,
256                    expr: left,
257                }
258            }
259            LoweredExpr::Match {
260                subject,
261                arms,
262                line,
263            } => {
264                let subject = *subject;
265                let line = *line;
266                conts.push(EvalCont::Match {
267                    lowered: Rc::clone(&lowered),
268                    arms: Rc::clone(arms),
269                    line,
270                });
271                EvalState::Expr {
272                    lowered,
273                    expr: subject,
274                }
275            }
276            LoweredExpr::Constructor { name, arg } => match arg {
277                Some(inner) => {
278                    let inner = *inner;
279                    conts.push(EvalCont::Constructor(name.clone()));
280                    EvalState::Expr {
281                        lowered,
282                        expr: inner,
283                    }
284                }
285                None => EvalState::Apply(match name.as_str() {
286                    "None" => Ok(NanValue::NONE),
287                    "Ok" | "Err" | "Some" => Err(RuntimeError::Error(format!(
288                        "Constructor '{}' expects an argument",
289                        name
290                    ))),
291                    _ => Err(RuntimeError::Error(format!(
292                        "Unknown constructor: {}",
293                        name
294                    ))),
295                }),
296            },
297            &LoweredExpr::ErrorProp { inner } => {
298                conts.push(EvalCont::ErrorProp);
299                EvalState::Expr {
300                    lowered,
301                    expr: inner,
302                }
303            }
304            LoweredExpr::InterpolatedStr(parts) => {
305                let parts = Rc::clone(parts);
306                self.resume_interpolated_str(lowered, parts, 0, String::new(), conts)
307            }
308            LoweredExpr::List(items) => {
309                let items = Rc::clone(items);
310                self.resume_list(lowered, items, 0, Vec::new(), conts)
311            }
312            LoweredExpr::Tuple(items) => {
313                let cap = items.len();
314                let items = Rc::clone(items);
315                self.resume_tuple(lowered, items, 0, Vec::with_capacity(cap), conts)
316            }
317            LoweredExpr::MapLiteral(entries) => {
318                let entries = Rc::clone(entries);
319                self.resume_map(
320                    lowered,
321                    entries,
322                    0,
323                    crate::nan_value::PersistentMap::new(),
324                    conts,
325                )
326            }
327            LoweredExpr::RecordCreate { type_name, fields } => {
328                let type_name = type_name.clone();
329                let fields = Rc::clone(fields);
330                self.resume_record_create(
331                    RecordCreateProgress {
332                        lowered,
333                        type_name,
334                        fields,
335                        idx: 0,
336                        seen: HashSet::new(),
337                        values: Vec::new(),
338                    },
339                    conts,
340                )
341            }
342            LoweredExpr::RecordUpdate {
343                type_name,
344                base,
345                updates,
346            } => {
347                let base = *base;
348                conts.push(EvalCont::RecordUpdateBase {
349                    lowered: Rc::clone(&lowered),
350                    type_name: type_name.clone(),
351                    updates: Rc::clone(updates),
352                });
353                EvalState::Expr {
354                    lowered,
355                    expr: base,
356                }
357            }
358            LoweredExpr::TailCall { target, args } => {
359                let target = target.clone();
360                let args = Rc::clone(args);
361                self.resume_tail_call(lowered, target, args, 0, Vec::new(), conts)
362            }
363        }
364    }
365
366    fn step_body(
367        &mut self,
368        lowered: Rc<LoweredFunctionBody>,
369        idx: usize,
370        local_slots: Option<Rc<HashMap<String, u16>>>,
371        last: NanValue,
372        conts: &mut Vec<EvalCont>,
373    ) -> EvalState {
374        let Some(stmt) = lowered.stmt(idx) else {
375            return EvalState::Apply(Ok(last));
376        };
377
378        match stmt {
379            LoweredStmt::Binding(name, expr) => {
380                let expr = *expr;
381                conts.push(EvalCont::BodyBinding {
382                    name: name.clone(),
383                    next_idx: idx + 1,
384                    lowered: Rc::clone(&lowered),
385                    local_slots,
386                });
387                EvalState::Expr { lowered, expr }
388            }
389            &LoweredStmt::Expr(expr) => {
390                conts.push(EvalCont::BodyExpr {
391                    next_idx: idx + 1,
392                    lowered: Rc::clone(&lowered),
393                    local_slots,
394                });
395                EvalState::Expr { lowered, expr }
396            }
397        }
398    }
399
400    fn apply_cont(
401        &mut self,
402        cont: EvalCont,
403        result: Result<NanValue, RuntimeError>,
404        conts: &mut Vec<EvalCont>,
405    ) -> EvalState {
406        match cont {
407            EvalCont::Attr(field) => match result {
408                Ok(nv) => EvalState::Apply(self.attr_access_nv(nv, &field)),
409                Err(err) => EvalState::Apply(Err(err)),
410            },
411            EvalCont::Call {
412                lowered,
413                args,
414                mut idx,
415                mut fn_val,
416                mut arg_vals,
417            } => match result {
418                Ok(value) => {
419                    if fn_val.is_none() {
420                        fn_val = Some(value);
421                        if args.is_empty() {
422                            return self.dispatch_call(
423                                fn_val.expect("function value set before dispatch"),
424                                arg_vals,
425                                conts,
426                            );
427                        }
428                        conts.push(EvalCont::Call {
429                            lowered: Rc::clone(&lowered),
430                            args: Rc::clone(&args),
431                            idx,
432                            fn_val,
433                            arg_vals,
434                        });
435                        return EvalState::Expr {
436                            lowered,
437                            expr: args[idx],
438                        };
439                    }
440
441                    arg_vals.push(value);
442                    idx += 1;
443                    if idx < args.len() {
444                        conts.push(EvalCont::Call {
445                            lowered: Rc::clone(&lowered),
446                            args: Rc::clone(&args),
447                            idx,
448                            fn_val,
449                            arg_vals,
450                        });
451                        EvalState::Expr {
452                            lowered,
453                            expr: args[idx],
454                        }
455                    } else {
456                        self.dispatch_call(
457                            fn_val.expect("function value present when args are done"),
458                            arg_vals,
459                            conts,
460                        )
461                    }
462                }
463                Err(err) => EvalState::Apply(Err(err)),
464            },
465            EvalCont::BinOpLeft { lowered, op, right } => match result {
466                Ok(left) => {
467                    conts.push(EvalCont::BinOpRight { op, left });
468                    EvalState::Expr {
469                        lowered,
470                        expr: right,
471                    }
472                }
473                Err(err) => EvalState::Apply(Err(err)),
474            },
475            EvalCont::BinOpRight { op, left } => match result {
476                Ok(right) => EvalState::Apply(self.eval_binop_nv(&op, left, right)),
477                Err(err) => EvalState::Apply(Err(err)),
478            },
479            EvalCont::Match {
480                lowered,
481                arms,
482                line,
483            } => match result {
484                Ok(subject) => self.dispatch_match(lowered, subject, arms, line, conts),
485                Err(err) => EvalState::Apply(Err(err)),
486            },
487            EvalCont::Constructor(name) => match result {
488                Ok(inner) => EvalState::Apply(match name.as_str() {
489                    "Ok" => Ok(NanValue::new_ok_value(inner, &mut self.arena)),
490                    "Err" => Ok(NanValue::new_err_value(inner, &mut self.arena)),
491                    "Some" => Ok(NanValue::new_some_value(inner, &mut self.arena)),
492                    "None" => Err(RuntimeError::Error(
493                        "Constructor 'None' does not take an argument".to_string(),
494                    )),
495                    _ => Err(RuntimeError::Error(format!(
496                        "Unknown constructor: {}",
497                        name
498                    ))),
499                }),
500                Err(err) => EvalState::Apply(Err(err)),
501            },
502            EvalCont::ErrorProp => match result {
503                Ok(value) => EvalState::Apply(if value.is_ok() {
504                    let inner = value.wrapper_inner(&self.arena);
505                    Ok(inner)
506                } else if value.is_err() {
507                    let inner = value.wrapper_inner(&self.arena);
508                    Err(RuntimeError::ErrProp(inner))
509                } else {
510                    Err(RuntimeError::Error(
511                        "Operator '?' can only be applied to Result".to_string(),
512                    ))
513                }),
514                Err(err) => EvalState::Apply(Err(err)),
515            },
516            EvalCont::InterpolatedStr {
517                lowered,
518                parts,
519                idx,
520                result: mut text,
521            } => match result {
522                Ok(value) => {
523                    text.push_str(&value.repr(&self.arena));
524                    self.resume_interpolated_str(lowered, parts, idx, text, conts)
525                }
526                Err(err) => EvalState::Apply(Err(err)),
527            },
528            EvalCont::List {
529                lowered,
530                items,
531                idx,
532                mut values,
533            } => match result {
534                Ok(value) => {
535                    values.push(value);
536                    self.resume_list(lowered, items, idx, values, conts)
537                }
538                Err(err) => EvalState::Apply(Err(err)),
539            },
540            EvalCont::Tuple {
541                lowered,
542                items,
543                idx,
544                mut values,
545            } => match result {
546                Ok(value) => {
547                    values.push(value);
548                    self.resume_tuple(lowered, items, idx, values, conts)
549                }
550                Err(err) => EvalState::Apply(Err(err)),
551            },
552            EvalCont::MapKey {
553                lowered,
554                entries,
555                idx,
556                map,
557            } => match result {
558                Ok(key) => {
559                    if !Self::is_hashable_map_key_nv(key) {
560                        return EvalState::Apply(Err(RuntimeError::Error(
561                            "Map literal key must be Int, Float, String, or Bool".to_string(),
562                        )));
563                    }
564                    conts.push(EvalCont::MapValue {
565                        lowered: Rc::clone(&lowered),
566                        entries: Rc::clone(&entries),
567                        idx,
568                        map,
569                        key,
570                    });
571                    EvalState::Expr {
572                        lowered,
573                        expr: entries[idx].1,
574                    }
575                }
576                Err(err) => EvalState::Apply(Err(err)),
577            },
578            EvalCont::MapValue {
579                lowered,
580                entries,
581                idx,
582                map,
583                key,
584            } => match result {
585                Ok(value) => {
586                    let hash = key.map_key_hash(&self.arena);
587                    let map = map.insert(hash, (key, value));
588                    self.resume_map(lowered, entries, idx + 1, map, conts)
589                }
590                Err(err) => EvalState::Apply(Err(err)),
591            },
592            EvalCont::RecordCreate(mut progress) => match result {
593                Ok(value) => {
594                    let field_name = progress.fields[progress.idx].0.clone();
595                    if !progress.seen.insert(field_name.clone()) {
596                        return EvalState::Apply(Err(RuntimeError::Error(format!(
597                            "Record '{}' field '{}' provided more than once",
598                            progress.type_name, field_name
599                        ))));
600                    }
601                    progress.values.push((field_name, value));
602                    progress.idx += 1;
603                    self.resume_record_create(progress, conts)
604                }
605                Err(err) => EvalState::Apply(Err(err)),
606            },
607            EvalCont::RecordUpdateBase {
608                lowered,
609                type_name,
610                updates,
611            } => match result {
612                Ok(base_nv) => {
613                    if !base_nv.is_record() {
614                        return EvalState::Apply(Err(RuntimeError::Error(format!(
615                            "{}.update: base must be a {} record",
616                            type_name, type_name
617                        ))));
618                    }
619                    let (base_type_id, base_fields) = self.arena.get_record(base_nv.arena_index());
620                    let base_type_name = self.arena.get_type_name(base_type_id).to_string();
621                    if base_type_name != type_name {
622                        return EvalState::Apply(Err(RuntimeError::Error(format!(
623                            "{}.update: base is a {} record, expected {}",
624                            type_name, base_type_name, type_name
625                        ))));
626                    }
627                    let base_field_names: Vec<String> =
628                        self.arena.get_field_names(base_type_id).to_vec();
629                    let base_fields_vec: Vec<NanValue> = base_fields.to_vec();
630                    self.resume_record_update(
631                        RecordUpdateProgress {
632                            lowered,
633                            type_name,
634                            base_type_id,
635                            base_fields: base_fields_vec,
636                            base_field_names,
637                            updates,
638                            idx: 0,
639                            update_vals: Vec::new(),
640                        },
641                        conts,
642                    )
643                }
644                Err(err) => EvalState::Apply(Err(err)),
645            },
646            EvalCont::RecordUpdateField(mut progress) => match result {
647                Ok(value) => {
648                    progress
649                        .update_vals
650                        .push((progress.updates[progress.idx].0.clone(), value));
651                    progress.idx += 1;
652                    self.resume_record_update(progress, conts)
653                }
654                Err(err) => EvalState::Apply(Err(err)),
655            },
656            EvalCont::TailCallArgs {
657                lowered,
658                target,
659                args,
660                idx,
661                mut values,
662            } => match result {
663                Ok(value) => {
664                    values.push(value);
665                    self.resume_tail_call(lowered, target, args, idx + 1, values, conts)
666                }
667                Err(err) => EvalState::Apply(Err(err)),
668            },
669            EvalCont::BodyBinding {
670                name,
671                next_idx,
672                lowered,
673                local_slots,
674            } => match result {
675                Ok(value) => {
676                    if let Some(local_slots) = local_slots.as_ref()
677                        && let Some(&slot) = local_slots.get(&name)
678                    {
679                        self.define_slot(slot, value);
680                    } else {
681                        self.define_nv(name, value);
682                    }
683                    EvalState::Body {
684                        lowered,
685                        idx: next_idx,
686                        local_slots,
687                        last: NanValue::UNIT,
688                    }
689                }
690                Err(err) => EvalState::Apply(Err(err)),
691            },
692            EvalCont::BodyExpr {
693                next_idx,
694                lowered,
695                local_slots,
696            } => match result {
697                Ok(value) => EvalState::Body {
698                    lowered,
699                    idx: next_idx,
700                    local_slots,
701                    last: value,
702                },
703                Err(err) => EvalState::Apply(Err(err)),
704            },
705            EvalCont::MatchScope => {
706                self.pop_env();
707                EvalState::Apply(result)
708            }
709            EvalCont::FunctionReturn(frame) => self.finish_function_call(frame, result, conts),
710        }
711    }
712
713    /// Attribute access on a NanValue.
714    fn attr_access_nv(&self, nv: NanValue, field: &str) -> Result<NanValue, RuntimeError> {
715        if nv.is_record() {
716            let (tid, fields) = self.arena.get_record(nv.arena_index());
717            let field_names = self.arena.get_field_names(tid);
718            if let Some(pos) = field_names.iter().position(|n| n == field) {
719                return Ok(fields[pos]);
720            }
721            return Err(RuntimeError::Error(format!("Unknown field '{}'", field)));
722        }
723        if nv.is_namespace() {
724            let (name, members) = self.arena.get_namespace(nv.symbol_index());
725            if let Some((_, member_nv)) = members.iter().find(|(k, _)| k.as_ref() == field) {
726                return Ok(*member_nv);
727            }
728            return Err(RuntimeError::Error(format!(
729                "Unknown member '{}.{}'",
730                name, field
731            )));
732        }
733        Err(RuntimeError::Error(format!(
734            "Field access '{}' is not supported on this value",
735            field
736        )))
737    }
738
739    fn dispatch_match(
740        &mut self,
741        lowered: Rc<LoweredFunctionBody>,
742        subject: NanValue,
743        arms: SharedMatchArms,
744        line: usize,
745        conts: &mut Vec<EvalCont>,
746    ) -> EvalState {
747        let arm_count = arms.len();
748        for (arm_idx, arm) in arms.iter().enumerate() {
749            if let Some(bindings) = self.match_pattern_nv(&arm.pattern, subject) {
750                self.note_verify_match_arm(line, arm_count, arm_idx);
751                if bindings.is_empty() {
752                    return EvalState::Expr {
753                        lowered,
754                        expr: arm.body,
755                    };
756                }
757
758                if let Some(local_slots) = self.active_local_slots.clone() {
759                    let all_slotted = bindings
760                        .iter()
761                        .all(|(name, _)| local_slots.contains_key(name));
762                    if all_slotted {
763                        for (name, nv) in bindings {
764                            if let Some(&slot) = local_slots.get(&name) {
765                                self.define_slot(slot, nv);
766                            }
767                        }
768                        return EvalState::Expr {
769                            lowered,
770                            expr: arm.body,
771                        };
772                    }
773                }
774
775                let scope: HashMap<String, NanValue> = bindings.into_iter().collect();
776                self.push_env(EnvFrame::Owned(scope));
777                conts.push(EvalCont::MatchScope);
778                return EvalState::Expr {
779                    lowered,
780                    expr: arm.body,
781                };
782            }
783        }
784
785        EvalState::Apply(Err(RuntimeError::Error(format!(
786            "No match found for value {}",
787            subject.repr(&self.arena)
788        ))))
789    }
790
791    fn dispatch_call(
792        &mut self,
793        fn_val: NanValue,
794        args: Vec<NanValue>,
795        conts: &mut Vec<EvalCont>,
796    ) -> EvalState {
797        match self.start_call_nv(fn_val, args) {
798            Ok(CallDispatch::Immediate(result)) => EvalState::Apply(result),
799            Ok(CallDispatch::EnterFunction { frame, state }) => {
800                conts.push(EvalCont::FunctionReturn(*frame));
801                state
802            }
803            Err(err) => EvalState::Apply(Err(err)),
804        }
805    }
806
807    fn start_call_nv(
808        &mut self,
809        fn_val: NanValue,
810        args: Vec<NanValue>,
811    ) -> Result<CallDispatch, RuntimeError> {
812        if fn_val.is_builtin() {
813            let name = self.arena.get_builtin(fn_val.symbol_index()).to_string();
814            self.ensure_effects_allowed(&name, Self::builtin_effects(&name).iter().copied())?;
815            let result = self.call_builtin_nv(&name, &args);
816            return Ok(CallDispatch::Immediate(result));
817        }
818        if fn_val.is_fn() {
819            let function = Rc::clone(self.arena.get_fn_rc(fn_val.symbol_index()));
820            if args.len() != function.params.len() {
821                return Err(RuntimeError::Error(format!(
822                    "Function '{}' expects {} arguments, got {}",
823                    function.name,
824                    function.params.len(),
825                    args.len()
826                )));
827            }
828            self.ensure_effects_allowed(
829                function.name.as_str(),
830                function.effects.iter().map(String::as_str),
831            )?;
832
833            let memo_key = if function.memo_eligible {
834                let key = hash_memo_args_nv(&args, &self.arena);
835                let fn_name: &str = function.name.as_ref();
836                if let Some(cache) = self.memo_cache.get_mut(fn_name)
837                    && let Some(cached_val) = cache.get_nv_as_value(key, &args, &self.arena)
838                {
839                    let cached_nv = NanValue::from_value(&cached_val, &mut self.arena);
840                    return Ok(CallDispatch::Immediate(Ok(cached_nv)));
841                }
842                Some((key, args.clone()))
843            } else {
844                None
845            };
846
847            self.call_stack.push(CallFrame {
848                name: Rc::clone(&function.name),
849                effects: Rc::clone(&function.effects),
850            });
851
852            let prev_local_slots = self.active_local_slots.take();
853            let saved_base = self.env_base;
854            self.env_base = self.env.len();
855            let prev_global = if let Some(home) = function.home_globals.as_ref() {
856                let global = self
857                    .env
858                    .first_mut()
859                    .ok_or_else(|| RuntimeError::Error("No global scope".to_string()))?;
860                Some(std::mem::replace(global, EnvFrame::Shared(Rc::clone(home))))
861            } else {
862                None
863            };
864
865            let active = ActiveFunction { function };
866            let frame = FunctionFrame {
867                active,
868                prev_local_slots,
869                saved_base,
870                prev_global,
871                memo_key,
872            };
873            let state = self.enter_function_body_nv(&frame.active, args);
874            return Ok(CallDispatch::EnterFunction {
875                frame: Box::new(frame),
876                state,
877            });
878        }
879        Err(RuntimeError::Error(format!(
880            "Cannot call value: {}",
881            fn_val.repr(&self.arena)
882        )))
883    }
884
885    fn enter_function_body_nv(
886        &mut self,
887        active: &ActiveFunction,
888        args: Vec<NanValue>,
889    ) -> EvalState {
890        if let Some(resolution) = &active.function.resolution {
891            let local_slots = Rc::clone(&resolution.local_slots);
892            let mut slots = Self::empty_slots_nv(resolution.local_count);
893            for ((param_name, _), arg_nv) in active.function.params.iter().zip(args.into_iter()) {
894                if let Some(&slot) = resolution.local_slots.get(param_name) {
895                    slots[slot as usize] = arg_nv;
896                }
897            }
898            self.active_local_slots = Some(Rc::clone(&local_slots));
899            self.push_env(EnvFrame::Slots(slots));
900            EvalState::Body {
901                lowered: Rc::clone(&active.function.lowered_body),
902                idx: 0,
903                local_slots: Some(local_slots),
904                last: NanValue::UNIT,
905            }
906        } else {
907            let mut params_scope: HashMap<String, NanValue> = HashMap::new();
908            for ((param_name, _), arg_nv) in active.function.params.iter().zip(args.into_iter()) {
909                params_scope.insert(param_name.clone(), arg_nv);
910            }
911            self.push_env(EnvFrame::Owned(params_scope));
912            EvalState::Body {
913                lowered: Rc::clone(&active.function.lowered_body),
914                idx: 0,
915                local_slots: None,
916                last: NanValue::UNIT,
917            }
918        }
919    }
920
921    fn finish_function_call(
922        &mut self,
923        mut frame: FunctionFrame,
924        result: Result<NanValue, RuntimeError>,
925        conts: &mut Vec<EvalCont>,
926    ) -> EvalState {
927        self.pop_env();
928
929        match result {
930            Err(RuntimeError::TailCall(boxed)) => {
931                let (target, nv_args) = *boxed;
932                let next_active = if target == frame.active.function.name.as_str() {
933                    frame.active.clone()
934                } else {
935                    let next_function = match self.lookup_nv(&target) {
936                        Ok(nv) => {
937                            if !nv.is_fn() {
938                                return EvalState::Apply(Err(RuntimeError::Error(format!(
939                                    "TCO target '{}' is not a function: {}",
940                                    target,
941                                    nv.repr(&self.arena)
942                                ))));
943                            }
944                            Rc::clone(self.arena.get_fn_rc(nv.symbol_index()))
945                        }
946                        Err(err) => return EvalState::Apply(Err(err)),
947                    };
948
949                    if let Some(call_frame) = self.call_stack.last_mut() {
950                        call_frame.name = Rc::clone(&next_function.name);
951                        call_frame.effects = Rc::clone(&next_function.effects);
952                    }
953
954                    ActiveFunction {
955                        function: next_function,
956                    }
957                };
958
959                frame.active = next_active;
960                let state = self.enter_function_body_nv(&frame.active, nv_args);
961                conts.push(EvalCont::FunctionReturn(frame));
962                state
963            }
964            other => {
965                self.active_local_slots = frame.prev_local_slots;
966                if let Some(prev) = frame.prev_global
967                    && let Some(global) = self.env.first_mut()
968                {
969                    *global = prev;
970                }
971                self.env_base = frame.saved_base;
972                self.call_stack.pop();
973
974                let final_result = match other {
975                    Ok(value) => Ok(value),
976                    Err(RuntimeError::ErrProp(err_nv)) => {
977                        Ok(NanValue::new_err_value(err_nv, &mut self.arena))
978                    }
979                    Err(err) => Err(err),
980                };
981
982                if let (Some((key, memo_args)), Ok(value)) = (&frame.memo_key, &final_result) {
983                    let fn_name: &str = frame.active.function.name.as_ref();
984                    let cache = if let Some(c) = self.memo_cache.get_mut(fn_name) {
985                        c
986                    } else {
987                        self.memo_cache
988                            .entry(frame.active.function.name.as_ref().clone())
989                            .or_default()
990                    };
991                    cache.insert_nv(
992                        *key,
993                        memo_args.clone(),
994                        *value,
995                        &self.arena,
996                        MEMO_CACHE_CAP_PER_FN,
997                    );
998                }
999
1000                EvalState::Apply(final_result)
1001            }
1002        }
1003    }
1004
1005    fn resume_interpolated_str(
1006        &mut self,
1007        lowered: Rc<LoweredFunctionBody>,
1008        parts: SharedStrParts,
1009        mut idx: usize,
1010        mut result: String,
1011        conts: &mut Vec<EvalCont>,
1012    ) -> EvalState {
1013        while idx < parts.len() {
1014            match parts[idx].clone() {
1015                LoweredStrPart::Literal(text) => {
1016                    result.push_str(&text);
1017                    idx += 1;
1018                }
1019                LoweredStrPart::Parsed(expr) => {
1020                    conts.push(EvalCont::InterpolatedStr {
1021                        lowered: Rc::clone(&lowered),
1022                        parts: Rc::clone(&parts),
1023                        idx: idx + 1,
1024                        result,
1025                    });
1026                    return EvalState::Expr { lowered, expr };
1027                }
1028            }
1029        }
1030        EvalState::Apply(Ok(NanValue::new_string_value(&result, &mut self.arena)))
1031    }
1032
1033    fn resume_list(
1034        &mut self,
1035        lowered: Rc<LoweredFunctionBody>,
1036        items: SharedExprs,
1037        idx: usize,
1038        values: Vec<NanValue>,
1039        conts: &mut Vec<EvalCont>,
1040    ) -> EvalState {
1041        if idx >= items.len() {
1042            let list_idx = self.arena.push_list(values);
1043            return EvalState::Apply(Ok(NanValue::new_list(list_idx)));
1044        }
1045
1046        conts.push(EvalCont::List {
1047            lowered: Rc::clone(&lowered),
1048            items: Rc::clone(&items),
1049            idx: idx + 1,
1050            values,
1051        });
1052        EvalState::Expr {
1053            lowered,
1054            expr: items[idx],
1055        }
1056    }
1057
1058    fn resume_tuple(
1059        &mut self,
1060        lowered: Rc<LoweredFunctionBody>,
1061        items: SharedExprs,
1062        idx: usize,
1063        values: Vec<NanValue>,
1064        conts: &mut Vec<EvalCont>,
1065    ) -> EvalState {
1066        if idx >= items.len() {
1067            let tuple_idx = self.arena.push_tuple(values);
1068            return EvalState::Apply(Ok(NanValue::new_tuple(tuple_idx)));
1069        }
1070
1071        conts.push(EvalCont::Tuple {
1072            lowered: Rc::clone(&lowered),
1073            items: Rc::clone(&items),
1074            idx: idx + 1,
1075            values,
1076        });
1077        EvalState::Expr {
1078            lowered,
1079            expr: items[idx],
1080        }
1081    }
1082
1083    fn resume_map(
1084        &mut self,
1085        lowered: Rc<LoweredFunctionBody>,
1086        entries: SharedMapEntries,
1087        idx: usize,
1088        map: crate::nan_value::PersistentMap,
1089        conts: &mut Vec<EvalCont>,
1090    ) -> EvalState {
1091        if idx >= entries.len() {
1092            let map_idx = self.arena.push_map(map);
1093            return EvalState::Apply(Ok(NanValue::new_map(map_idx)));
1094        }
1095
1096        conts.push(EvalCont::MapKey {
1097            lowered: Rc::clone(&lowered),
1098            entries: Rc::clone(&entries),
1099            idx,
1100            map,
1101        });
1102        EvalState::Expr {
1103            lowered,
1104            expr: entries[idx].0,
1105        }
1106    }
1107
1108    fn resume_record_create(
1109        &mut self,
1110        progress: RecordCreateProgress,
1111        conts: &mut Vec<EvalCont>,
1112    ) -> EvalState {
1113        if progress.idx >= progress.fields.len() {
1114            return EvalState::Apply(
1115                self.build_record_create_nv(&progress.type_name, progress.values),
1116            );
1117        }
1118
1119        let lowered = Rc::clone(&progress.lowered);
1120        let expr = progress.fields[progress.idx].1;
1121        conts.push(EvalCont::RecordCreate(progress));
1122        EvalState::Expr { lowered, expr }
1123    }
1124
1125    fn resume_record_update(
1126        &mut self,
1127        progress: RecordUpdateProgress,
1128        conts: &mut Vec<EvalCont>,
1129    ) -> EvalState {
1130        if progress.idx >= progress.updates.len() {
1131            return EvalState::Apply(self.build_record_update_nv(
1132                &progress.type_name,
1133                progress.base_type_id,
1134                progress.base_fields,
1135                progress.base_field_names,
1136                progress.update_vals,
1137            ));
1138        }
1139
1140        let next_expr = progress.updates[progress.idx].1;
1141        let lowered = Rc::clone(&progress.lowered);
1142        conts.push(EvalCont::RecordUpdateField(progress));
1143        EvalState::Expr {
1144            lowered,
1145            expr: next_expr,
1146        }
1147    }
1148
1149    fn resume_tail_call(
1150        &mut self,
1151        lowered: Rc<LoweredFunctionBody>,
1152        target: String,
1153        args: SharedExprs,
1154        idx: usize,
1155        values: Vec<NanValue>,
1156        conts: &mut Vec<EvalCont>,
1157    ) -> EvalState {
1158        if idx >= args.len() {
1159            return EvalState::Apply(Err(RuntimeError::TailCall(Box::new((target, values)))));
1160        }
1161
1162        conts.push(EvalCont::TailCallArgs {
1163            lowered: Rc::clone(&lowered),
1164            target,
1165            args: Rc::clone(&args),
1166            idx,
1167            values,
1168        });
1169        EvalState::Expr {
1170            lowered,
1171            expr: args[idx],
1172        }
1173    }
1174
1175    fn build_record_create_nv(
1176        &mut self,
1177        type_name: &str,
1178        field_vals: Vec<(String, NanValue)>,
1179    ) -> Result<NanValue, RuntimeError> {
1180        if let Some(schema) = self.record_schemas.get(type_name) {
1181            // When we have a schema, register the type using schema field order
1182            // (which is the declaration order from the record definition).
1183            let schema_clone = schema.clone();
1184            let type_id = self.arena.find_type_id(type_name).unwrap_or_else(|| {
1185                self.arena
1186                    .register_record_type(type_name, schema_clone.clone())
1187            });
1188            let mut by_name = HashMap::with_capacity(field_vals.len());
1189            for (name, value) in field_vals {
1190                if by_name.insert(name.clone(), value).is_some() {
1191                    return Err(RuntimeError::Error(format!(
1192                        "Record '{}' field '{}' provided more than once",
1193                        type_name, name
1194                    )));
1195                }
1196            }
1197
1198            for provided in by_name.keys() {
1199                if !schema_clone.iter().any(|field| field == provided) {
1200                    return Err(RuntimeError::Error(format!(
1201                        "Record '{}' has no field '{}'",
1202                        type_name, provided
1203                    )));
1204                }
1205            }
1206
1207            let mut ordered = Vec::with_capacity(schema_clone.len());
1208            for required in &schema_clone {
1209                let value = by_name.remove(required).ok_or_else(|| {
1210                    RuntimeError::Error(format!(
1211                        "Record '{}' missing required field '{}'",
1212                        type_name, required
1213                    ))
1214                })?;
1215                ordered.push(value);
1216            }
1217
1218            let rec_idx = self.arena.push_record(type_id, ordered);
1219            return Ok(NanValue::new_record(rec_idx));
1220        }
1221
1222        // No schema — just use field order as-is
1223        let type_id = self.arena.find_type_id(type_name).unwrap_or_else(|| {
1224            let field_names: Vec<String> = field_vals.iter().map(|(n, _)| n.clone()).collect();
1225            self.arena.register_record_type(type_name, field_names)
1226        });
1227        let nv_fields: Vec<NanValue> = field_vals.iter().map(|(_, v)| *v).collect();
1228        let rec_idx = self.arena.push_record(type_id, nv_fields);
1229        Ok(NanValue::new_record(rec_idx))
1230    }
1231
1232    fn build_record_update_nv(
1233        &mut self,
1234        type_name: &str,
1235        base_type_id: u32,
1236        mut base_fields: Vec<NanValue>,
1237        base_field_names: Vec<String>,
1238        update_vals: Vec<(String, NanValue)>,
1239    ) -> Result<NanValue, RuntimeError> {
1240        if let Some(schema) = self.record_schemas.get(type_name) {
1241            for (field_name, _) in &update_vals {
1242                if !schema.iter().any(|field| field == field_name) {
1243                    return Err(RuntimeError::Error(format!(
1244                        "Record '{}' has no field '{}'",
1245                        type_name, field_name
1246                    )));
1247                }
1248            }
1249        }
1250
1251        for (update_name, update_val) in update_vals {
1252            if let Some(pos) = base_field_names.iter().position(|n| n == &update_name) {
1253                base_fields[pos] = update_val;
1254            } else {
1255                return Err(RuntimeError::Error(format!(
1256                    "Record '{}' has no field '{}'",
1257                    type_name, update_name
1258                )));
1259            }
1260        }
1261
1262        let rec_idx = self.arena.push_record(base_type_id, base_fields);
1263        Ok(NanValue::new_record(rec_idx))
1264    }
1265
1266    fn is_hashable_map_key_nv(value: NanValue) -> bool {
1267        value.is_int() || value.is_float() || value.is_string() || value.is_bool()
1268    }
1269
1270    pub(super) fn eval_literal_nv(&mut self, lit: &Literal) -> NanValue {
1271        match lit {
1272            Literal::Int(i) => NanValue::new_int(*i, &mut self.arena),
1273            Literal::Float(f) => NanValue::new_float(*f),
1274            Literal::Str(s) => NanValue::new_string_value(s, &mut self.arena),
1275            Literal::Bool(b) => NanValue::new_bool(*b),
1276            Literal::Unit => NanValue::UNIT,
1277        }
1278    }
1279
1280    #[allow(dead_code)]
1281    pub(super) fn eval_literal(&self, lit: &Literal) -> Value {
1282        match lit {
1283            Literal::Int(i) => Value::Int(*i),
1284            Literal::Float(f) => Value::Float(*f),
1285            Literal::Str(s) => Value::Str(s.clone()),
1286            Literal::Bool(b) => Value::Bool(*b),
1287            Literal::Unit => Value::Unit,
1288        }
1289    }
1290
1291    pub(super) fn call_value(
1292        &mut self,
1293        fn_val: Value,
1294        args: Vec<Value>,
1295    ) -> Result<Value, RuntimeError> {
1296        let fn_nv = NanValue::from_value(&fn_val, &mut self.arena);
1297        let nv_args: Vec<NanValue> = args
1298            .iter()
1299            .map(|v| NanValue::from_value(v, &mut self.arena))
1300            .collect();
1301        match self.start_call_nv(fn_nv, nv_args)? {
1302            CallDispatch::Immediate(result) => result.map(|nv| nv.to_value(&self.arena)),
1303            CallDispatch::EnterFunction { frame, state } => {
1304                let nv = self.eval_loop(state, vec![EvalCont::FunctionReturn(*frame)])?;
1305                Ok(nv.to_value(&self.arena))
1306            }
1307        }
1308    }
1309}
1310
1311/// Hash NanValue args for memo cache — uses arena for content-based hashing.
1312fn hash_memo_args_nv(args: &[NanValue], arena: &Arena) -> u64 {
1313    use std::hash::{Hash, Hasher};
1314    let mut hasher = std::collections::hash_map::DefaultHasher::new();
1315    args.len().hash(&mut hasher);
1316    for arg in args {
1317        arg.hash_in(&mut hasher, arena);
1318    }
1319    hasher.finish()
1320}