Skip to main content

aver/interpreter/
eval.rs

1use super::lowered::{
2    self, ExprId, LoweredDirectCallTarget, LoweredExpr, LoweredForwardArg, LoweredFunctionBody,
3    LoweredLeafOp, LoweredMatchArm, LoweredStmt, LoweredStrPart, LoweredTailCallTarget,
4};
5use super::*;
6
7type SharedExprs = Rc<[ExprId]>;
8type SharedStrParts = Rc<[LoweredStrPart]>;
9type SharedMapEntries = Rc<[(ExprId, ExprId)]>;
10type SharedRecordFields = Rc<[(String, ExprId)]>;
11type SharedMatchArms = Rc<[LoweredMatchArm]>;
12
13#[derive(Debug)]
14enum EvalState {
15    Expr {
16        lowered: Rc<LoweredFunctionBody>,
17        expr: ExprId,
18    },
19    Body {
20        lowered: Rc<LoweredFunctionBody>,
21        idx: usize,
22        local_slots: Option<Rc<HashMap<String, u16>>>,
23        last: NanValue,
24    },
25    Apply(Result<NanValue, RuntimeError>),
26}
27
28impl EvalState {
29    /// Decorate an error in `Apply(Err(...))` with a source line.
30    fn map_err_line(self, line: usize) -> Self {
31        match self {
32            EvalState::Apply(Err(err)) => EvalState::Apply(Err(err.at_line(line))),
33            other => other,
34        }
35    }
36}
37
38#[derive(Debug, Clone)]
39enum EvalCont {
40    Attr(String, usize),
41    Call {
42        lowered: Rc<LoweredFunctionBody>,
43        args: SharedExprs,
44        idx: usize,
45        fn_val: Option<NanValue>,
46        arg_vals: Vec<NanValue>,
47        call_line: usize,
48    },
49    BinOpLeft {
50        lowered: Rc<LoweredFunctionBody>,
51        op: BinOp,
52        right: ExprId,
53        line: usize,
54    },
55    BinOpRight {
56        op: BinOp,
57        left: NanValue,
58        line: usize,
59    },
60    Match {
61        lowered: Rc<LoweredFunctionBody>,
62        arms: SharedMatchArms,
63        line: usize,
64    },
65    DirectCall {
66        lowered: Rc<LoweredFunctionBody>,
67        target: LoweredDirectCallTarget,
68        idx: usize,
69        args: SharedExprs,
70        arg_vals: Vec<NanValue>,
71        call_line: usize,
72    },
73    Leaf {
74        lowered: Rc<LoweredFunctionBody>,
75        leaf: LoweredLeafOp,
76        idx: usize,
77        values: Vec<NanValue>,
78    },
79    Constructor(String),
80    ErrorProp(usize),
81    InterpolatedStr {
82        lowered: Rc<LoweredFunctionBody>,
83        parts: SharedStrParts,
84        idx: usize,
85        result: String,
86    },
87    List {
88        lowered: Rc<LoweredFunctionBody>,
89        items: SharedExprs,
90        idx: usize,
91        values: Vec<NanValue>,
92    },
93    Tuple {
94        lowered: Rc<LoweredFunctionBody>,
95        items: SharedExprs,
96        idx: usize,
97        values: Vec<NanValue>,
98    },
99    IndependentProduct {
100        lowered: Rc<LoweredFunctionBody>,
101        items: SharedExprs,
102        idx: usize,
103        values: Vec<NanValue>,
104        unwrap: bool,
105    },
106    MapKey {
107        lowered: Rc<LoweredFunctionBody>,
108        entries: SharedMapEntries,
109        idx: usize,
110        map: crate::nan_value::PersistentMap,
111    },
112    MapValue {
113        lowered: Rc<LoweredFunctionBody>,
114        entries: SharedMapEntries,
115        idx: usize,
116        map: crate::nan_value::PersistentMap,
117        key: NanValue,
118    },
119    RecordCreate(RecordCreateProgress),
120    RecordUpdateBase {
121        lowered: Rc<LoweredFunctionBody>,
122        type_name: String,
123        updates: SharedRecordFields,
124    },
125    RecordUpdateField(RecordUpdateProgress),
126    TailCallArgs {
127        lowered: Rc<LoweredFunctionBody>,
128        target: LoweredTailCallTarget,
129        args: SharedExprs,
130        idx: usize,
131        values: Vec<NanValue>,
132    },
133    BodyBinding {
134        name: String,
135        next_idx: usize,
136        lowered: Rc<LoweredFunctionBody>,
137        local_slots: Option<Rc<HashMap<String, u16>>>,
138    },
139    BodyExpr {
140        next_idx: usize,
141        lowered: Rc<LoweredFunctionBody>,
142        local_slots: Option<Rc<HashMap<String, u16>>>,
143    },
144    MatchScope,
145    FunctionReturn(FunctionFrame),
146}
147
148#[derive(Debug, Clone)]
149struct ActiveFunction {
150    function: Rc<crate::value::FunctionValue>,
151}
152
153#[derive(Debug, Clone)]
154struct FunctionFrame {
155    active: ActiveFunction,
156    prev_local_slots: Option<Rc<HashMap<String, u16>>>,
157    /// The env_base of the caller — restored on return so the caller's
158    /// frames become visible to lookup_ref again.
159    saved_base: usize,
160    prev_global: Option<EnvFrame>,
161    memo_key: Option<(u64, Vec<NanValue>)>,
162}
163
164#[derive(Debug, Clone)]
165struct RecordCreateProgress {
166    lowered: Rc<LoweredFunctionBody>,
167    type_name: String,
168    fields: SharedRecordFields,
169    idx: usize,
170    seen: HashSet<String>,
171    values: Vec<(String, NanValue)>,
172}
173
174#[derive(Debug, Clone)]
175struct RecordUpdateProgress {
176    lowered: Rc<LoweredFunctionBody>,
177    type_name: String,
178    base_type_id: u32,
179    base_fields: Vec<NanValue>,
180    base_field_names: Vec<String>,
181    updates: SharedRecordFields,
182    idx: usize,
183    update_vals: Vec<(String, NanValue)>,
184}
185
186enum CallDispatch {
187    Immediate(Result<NanValue, RuntimeError>),
188    EnterFunction {
189        frame: Box<FunctionFrame>,
190        state: EvalState,
191    },
192}
193
194impl Interpreter {
195    fn empty_slots_nv(local_count: u16) -> Vec<NanValue> {
196        vec![NanValue::UNIT; local_count as usize]
197    }
198
199    /// Public eval_expr — returns Value for external callers.
200    pub fn eval_expr(&mut self, expr: &Spanned<Expr>) -> Result<Value, RuntimeError> {
201        let nv = self.eval_expr_nv(expr)?;
202        Ok(nv.to_value(&self.arena))
203    }
204
205    /// Internal eval_expr — returns NanValue natively.
206    pub(super) fn eval_expr_nv(&mut self, expr: &Spanned<Expr>) -> Result<NanValue, RuntimeError> {
207        let ctx = super::ir_bridge::InterpreterLowerCtx::new(self);
208        let (lowered, root) = lowered::lower_expr_root(expr, &ctx);
209        self.eval_loop(
210            EvalState::Expr {
211                lowered,
212                expr: root,
213            },
214            Vec::new(),
215        )
216    }
217
218    fn eval_loop(
219        &mut self,
220        initial: EvalState,
221        mut conts: Vec<EvalCont>,
222    ) -> Result<NanValue, RuntimeError> {
223        let mut state = initial;
224
225        loop {
226            state = match state {
227                EvalState::Expr { lowered, expr } => self.step_expr(lowered, expr, &mut conts),
228                EvalState::Body {
229                    lowered,
230                    idx,
231                    local_slots,
232                    last,
233                } => self.step_body(lowered, idx, local_slots, last, &mut conts),
234                EvalState::Apply(result) => {
235                    let Some(cont) = conts.pop() else {
236                        return result;
237                    };
238                    self.apply_cont(cont, result, &mut conts)
239                }
240            };
241        }
242    }
243
244    fn step_expr(
245        &mut self,
246        lowered: Rc<LoweredFunctionBody>,
247        expr_id: ExprId,
248        conts: &mut Vec<EvalCont>,
249    ) -> EvalState {
250        match lowered.expr(expr_id) {
251            LoweredExpr::Literal(lit) => EvalState::Apply(Ok(self.eval_literal_nv(lit))),
252            &LoweredExpr::Resolved(slot) => EvalState::Apply(self.lookup_slot(slot)),
253            LoweredExpr::Ident(name) => EvalState::Apply(self.lookup_nv(name)),
254            LoweredExpr::Attr { obj, field, line } => {
255                let obj = *obj;
256                let line = *line;
257                if let LoweredExpr::Ident(name) = lowered.expr(obj) {
258                    let nv = match self.lookup_nv(name) {
259                        Ok(nv) => nv,
260                        Err(err) => return EvalState::Apply(Err(err)),
261                    };
262                    let result = self.attr_access_nv(nv, field);
263                    return EvalState::Apply(result.map_err(|e| e.at_line(line)));
264                }
265
266                let field = field.clone();
267                conts.push(EvalCont::Attr(field, line));
268                EvalState::Expr { lowered, expr: obj }
269            }
270            LoweredExpr::FnCall {
271                fn_expr,
272                args,
273                call_line,
274            } => {
275                let fn_expr = *fn_expr;
276                let args = Rc::clone(args);
277                let call_line = *call_line;
278                conts.push(EvalCont::Call {
279                    lowered: Rc::clone(&lowered),
280                    idx: 0,
281                    fn_val: None,
282                    arg_vals: Vec::with_capacity(args.len()),
283                    args,
284                    call_line,
285                });
286                EvalState::Expr {
287                    lowered,
288                    expr: fn_expr,
289                }
290            }
291            LoweredExpr::DirectCall {
292                target,
293                args,
294                call_line,
295            } => self.resume_direct_call(
296                Rc::clone(&lowered),
297                target.clone(),
298                Rc::clone(args),
299                0,
300                Vec::with_capacity(args.len()),
301                *call_line,
302                conts,
303            ),
304            LoweredExpr::ForwardCall {
305                target,
306                args,
307                call_line,
308            } => self.dispatch_forward_call(target.clone(), args, *call_line, conts),
309            &LoweredExpr::BinOp {
310                op,
311                left,
312                right,
313                line,
314            } => {
315                conts.push(EvalCont::BinOpLeft {
316                    lowered: Rc::clone(&lowered),
317                    op,
318                    right,
319                    line,
320                });
321                EvalState::Expr {
322                    lowered,
323                    expr: left,
324                }
325            }
326            LoweredExpr::Match {
327                subject,
328                arms,
329                line,
330            } => {
331                let subject = *subject;
332                let line = *line;
333                conts.push(EvalCont::Match {
334                    lowered: Rc::clone(&lowered),
335                    arms: Rc::clone(arms),
336                    line,
337                });
338                EvalState::Expr {
339                    lowered,
340                    expr: subject,
341                }
342            }
343            LoweredExpr::Leaf(leaf) => {
344                let leaf = leaf.clone();
345                self.resume_leaf(
346                    Rc::clone(&lowered),
347                    leaf.clone(),
348                    0,
349                    Vec::with_capacity(leaf.arity()),
350                    conts,
351                )
352            }
353            LoweredExpr::Constructor { name, arg } => match arg {
354                Some(inner) => {
355                    let inner = *inner;
356                    conts.push(EvalCont::Constructor(name.clone()));
357                    EvalState::Expr {
358                        lowered,
359                        expr: inner,
360                    }
361                }
362                None => EvalState::Apply(self.apply_runtime_constructor_nv(name, None)),
363            },
364            &LoweredExpr::ErrorProp { inner, line } => {
365                conts.push(EvalCont::ErrorProp(line));
366                EvalState::Expr {
367                    lowered,
368                    expr: inner,
369                }
370            }
371            LoweredExpr::InterpolatedStr(parts) => {
372                let parts = Rc::clone(parts);
373                self.resume_interpolated_str(lowered, parts, 0, String::new(), conts)
374            }
375            LoweredExpr::List(items) => {
376                let items = Rc::clone(items);
377                self.resume_list(lowered, items, 0, Vec::new(), conts)
378            }
379            LoweredExpr::Tuple(items) => {
380                let cap = items.len();
381                let items = Rc::clone(items);
382                self.resume_tuple(lowered, items, 0, Vec::with_capacity(cap), conts)
383            }
384            LoweredExpr::IndependentProduct { items, unwrap } => {
385                let cap = items.len();
386                let items = Rc::clone(items);
387                let unwrap = *unwrap;
388                self.resume_independent_product(
389                    lowered,
390                    items,
391                    0,
392                    Vec::with_capacity(cap),
393                    unwrap,
394                    conts,
395                )
396            }
397            LoweredExpr::MapLiteral(entries) => {
398                let entries = Rc::clone(entries);
399                self.resume_map(
400                    lowered,
401                    entries,
402                    0,
403                    crate::nan_value::PersistentMap::new(),
404                    conts,
405                )
406            }
407            LoweredExpr::RecordCreate { type_name, fields } => {
408                let type_name = type_name.clone();
409                let fields = Rc::clone(fields);
410                self.resume_record_create(
411                    RecordCreateProgress {
412                        lowered,
413                        type_name,
414                        fields,
415                        idx: 0,
416                        seen: HashSet::new(),
417                        values: Vec::new(),
418                    },
419                    conts,
420                )
421            }
422            LoweredExpr::RecordUpdate {
423                type_name,
424                base,
425                updates,
426            } => {
427                let base = *base;
428                conts.push(EvalCont::RecordUpdateBase {
429                    lowered: Rc::clone(&lowered),
430                    type_name: type_name.clone(),
431                    updates: Rc::clone(updates),
432                });
433                EvalState::Expr {
434                    lowered,
435                    expr: base,
436                }
437            }
438            LoweredExpr::TailCall { target, args } => {
439                let target = target.clone();
440                let args = Rc::clone(args);
441                self.resume_tail_call(lowered, target, args, 0, Vec::new(), conts)
442            }
443        }
444    }
445
446    fn step_body(
447        &mut self,
448        lowered: Rc<LoweredFunctionBody>,
449        idx: usize,
450        local_slots: Option<Rc<HashMap<String, u16>>>,
451        last: NanValue,
452        conts: &mut Vec<EvalCont>,
453    ) -> EvalState {
454        let Some(stmt) = lowered.stmt(idx) else {
455            return EvalState::Apply(Ok(last));
456        };
457
458        match stmt {
459            LoweredStmt::Binding(name, expr) => {
460                let expr = *expr;
461                conts.push(EvalCont::BodyBinding {
462                    name: name.clone(),
463                    next_idx: idx + 1,
464                    lowered: Rc::clone(&lowered),
465                    local_slots,
466                });
467                EvalState::Expr { lowered, expr }
468            }
469            &LoweredStmt::Expr(expr) => {
470                conts.push(EvalCont::BodyExpr {
471                    next_idx: idx + 1,
472                    lowered: Rc::clone(&lowered),
473                    local_slots,
474                });
475                EvalState::Expr { lowered, expr }
476            }
477        }
478    }
479
480    fn apply_cont(
481        &mut self,
482        cont: EvalCont,
483        result: Result<NanValue, RuntimeError>,
484        conts: &mut Vec<EvalCont>,
485    ) -> EvalState {
486        match cont {
487            EvalCont::Attr(field, line) => match result {
488                Ok(nv) => {
489                    EvalState::Apply(self.attr_access_nv(nv, &field).map_err(|e| e.at_line(line)))
490                }
491                Err(err) => EvalState::Apply(Err(err)),
492            },
493            EvalCont::Call {
494                lowered,
495                args,
496                mut idx,
497                mut fn_val,
498                mut arg_vals,
499                call_line,
500            } => match result {
501                Ok(value) => {
502                    if fn_val.is_none() {
503                        fn_val = Some(value);
504                        if args.is_empty() {
505                            self.last_call_line = call_line;
506                            let state = self.dispatch_call(
507                                fn_val.expect("function value set before dispatch"),
508                                arg_vals,
509                                conts,
510                            );
511                            return state.map_err_line(call_line);
512                        }
513                        conts.push(EvalCont::Call {
514                            lowered: Rc::clone(&lowered),
515                            args: Rc::clone(&args),
516                            idx,
517                            fn_val,
518                            arg_vals,
519                            call_line,
520                        });
521                        return EvalState::Expr {
522                            lowered,
523                            expr: args[idx],
524                        };
525                    }
526
527                    arg_vals.push(value);
528                    idx += 1;
529                    if idx < args.len() {
530                        conts.push(EvalCont::Call {
531                            lowered: Rc::clone(&lowered),
532                            args: Rc::clone(&args),
533                            idx,
534                            fn_val,
535                            arg_vals,
536                            call_line,
537                        });
538                        EvalState::Expr {
539                            lowered,
540                            expr: args[idx],
541                        }
542                    } else {
543                        self.last_call_line = call_line;
544                        let state = self.dispatch_call(
545                            fn_val.expect("function value present when args are done"),
546                            arg_vals,
547                            conts,
548                        );
549                        state.map_err_line(call_line)
550                    }
551                }
552                Err(err) => EvalState::Apply(Err(err)),
553            },
554            EvalCont::BinOpLeft {
555                lowered,
556                op,
557                right,
558                line,
559            } => match result {
560                Ok(left) => {
561                    conts.push(EvalCont::BinOpRight { op, left, line });
562                    EvalState::Expr {
563                        lowered,
564                        expr: right,
565                    }
566                }
567                Err(err) => EvalState::Apply(Err(err)),
568            },
569            EvalCont::BinOpRight { op, left, line } => match result {
570                Ok(right) => EvalState::Apply(
571                    self.eval_binop_nv(&op, left, right)
572                        .map_err(|e| e.at_line(line)),
573                ),
574                Err(err) => EvalState::Apply(Err(err)),
575            },
576            EvalCont::Match {
577                lowered,
578                arms,
579                line,
580            } => match result {
581                Ok(subject) => self.dispatch_match(lowered, subject, arms, line, conts),
582                Err(err) => EvalState::Apply(Err(err)),
583            },
584            EvalCont::DirectCall {
585                lowered,
586                target,
587                idx,
588                args,
589                mut arg_vals,
590                call_line,
591            } => match result {
592                Ok(value) => {
593                    arg_vals.push(value);
594                    self.resume_direct_call(
595                        lowered,
596                        target,
597                        args,
598                        idx + 1,
599                        arg_vals,
600                        call_line,
601                        conts,
602                    )
603                }
604                Err(err) => EvalState::Apply(Err(err)),
605            },
606            EvalCont::Leaf {
607                lowered,
608                leaf,
609                idx,
610                mut values,
611            } => match result {
612                Ok(value) => {
613                    values.push(value);
614                    self.resume_leaf(lowered, leaf, idx + 1, values, conts)
615                }
616                Err(err) => EvalState::Apply(Err(err)),
617            },
618            EvalCont::Constructor(name) => match result {
619                Ok(inner) => {
620                    EvalState::Apply(self.apply_runtime_constructor_nv(&name, Some(inner)))
621                }
622                Err(err) => EvalState::Apply(Err(err)),
623            },
624            EvalCont::ErrorProp(line) => match result {
625                Ok(value) => EvalState::Apply(if value.is_ok() {
626                    let inner = value.wrapper_inner(&self.arena);
627                    Ok(inner)
628                } else if value.is_err() {
629                    let inner = value.wrapper_inner(&self.arena);
630                    Err(RuntimeError::ErrProp(inner))
631                } else {
632                    Err(RuntimeError::Error(
633                        "Operator '?' can only be applied to Result".to_string(),
634                    )
635                    .at_line(line))
636                }),
637                Err(err) => EvalState::Apply(Err(err)),
638            },
639            EvalCont::InterpolatedStr {
640                lowered,
641                parts,
642                idx,
643                result: mut text,
644            } => match result {
645                Ok(value) => {
646                    text.push_str(&value.repr(&self.arena));
647                    self.resume_interpolated_str(lowered, parts, idx, text, conts)
648                }
649                Err(err) => EvalState::Apply(Err(err)),
650            },
651            EvalCont::List {
652                lowered,
653                items,
654                idx,
655                mut values,
656            } => match result {
657                Ok(value) => {
658                    values.push(value);
659                    self.resume_list(lowered, items, idx, values, conts)
660                }
661                Err(err) => EvalState::Apply(Err(err)),
662            },
663            EvalCont::Tuple {
664                lowered,
665                items,
666                idx,
667                mut values,
668            } => match result {
669                Ok(value) => {
670                    values.push(value);
671                    self.resume_tuple(lowered, items, idx, values, conts)
672                }
673                Err(err) => EvalState::Apply(Err(err)),
674            },
675            EvalCont::IndependentProduct {
676                lowered,
677                items,
678                idx,
679                mut values,
680                unwrap,
681            } => match result {
682                Ok(value) => {
683                    values.push(value);
684                    self.resume_independent_product(lowered, items, idx, values, unwrap, conts)
685                }
686                Err(err) => EvalState::Apply(Err(err)),
687            },
688            EvalCont::MapKey {
689                lowered,
690                entries,
691                idx,
692                map,
693            } => match result {
694                Ok(key) => {
695                    if !Self::is_hashable_map_key_nv(key) {
696                        return EvalState::Apply(Err(RuntimeError::Error(
697                            "Map literal key must be Int, Float, String, or Bool".to_string(),
698                        )));
699                    }
700                    conts.push(EvalCont::MapValue {
701                        lowered: Rc::clone(&lowered),
702                        entries: Rc::clone(&entries),
703                        idx,
704                        map,
705                        key,
706                    });
707                    EvalState::Expr {
708                        lowered,
709                        expr: entries[idx].1,
710                    }
711                }
712                Err(err) => EvalState::Apply(Err(err)),
713            },
714            EvalCont::MapValue {
715                lowered,
716                entries,
717                idx,
718                map,
719                key,
720            } => match result {
721                Ok(value) => {
722                    let hash = key.map_key_hash(&self.arena);
723                    let map = map.insert(hash, (key, value));
724                    self.resume_map(lowered, entries, idx + 1, map, conts)
725                }
726                Err(err) => EvalState::Apply(Err(err)),
727            },
728            EvalCont::RecordCreate(mut progress) => match result {
729                Ok(value) => {
730                    let field_name = progress.fields[progress.idx].0.clone();
731                    if !progress.seen.insert(field_name.clone()) {
732                        return EvalState::Apply(Err(RuntimeError::Error(format!(
733                            "Record '{}' field '{}' provided more than once",
734                            progress.type_name, field_name
735                        ))));
736                    }
737                    progress.values.push((field_name, value));
738                    progress.idx += 1;
739                    self.resume_record_create(progress, conts)
740                }
741                Err(err) => EvalState::Apply(Err(err)),
742            },
743            EvalCont::RecordUpdateBase {
744                lowered,
745                type_name,
746                updates,
747            } => match result {
748                Ok(base_nv) => {
749                    if !base_nv.is_record() {
750                        return EvalState::Apply(Err(RuntimeError::Error(format!(
751                            "{}.update: base must be a {} record",
752                            type_name, type_name
753                        ))));
754                    }
755                    let (base_type_id, base_fields) = self.arena.get_record(base_nv.arena_index());
756                    let base_type_name = self.arena.get_type_name(base_type_id).to_string();
757                    if base_type_name != type_name {
758                        return EvalState::Apply(Err(RuntimeError::Error(format!(
759                            "{}.update: base is a {} record, expected {}",
760                            type_name, base_type_name, type_name
761                        ))));
762                    }
763                    let base_field_names: Vec<String> =
764                        self.arena.get_field_names(base_type_id).to_vec();
765                    let base_fields_vec: Vec<NanValue> = base_fields.to_vec();
766                    self.resume_record_update(
767                        RecordUpdateProgress {
768                            lowered,
769                            type_name,
770                            base_type_id,
771                            base_fields: base_fields_vec,
772                            base_field_names,
773                            updates,
774                            idx: 0,
775                            update_vals: Vec::new(),
776                        },
777                        conts,
778                    )
779                }
780                Err(err) => EvalState::Apply(Err(err)),
781            },
782            EvalCont::RecordUpdateField(mut progress) => match result {
783                Ok(value) => {
784                    progress
785                        .update_vals
786                        .push((progress.updates[progress.idx].0.clone(), value));
787                    progress.idx += 1;
788                    self.resume_record_update(progress, conts)
789                }
790                Err(err) => EvalState::Apply(Err(err)),
791            },
792            EvalCont::TailCallArgs {
793                lowered,
794                target,
795                args,
796                idx,
797                mut values,
798            } => match result {
799                Ok(value) => {
800                    values.push(value);
801                    self.resume_tail_call(lowered, target, args, idx + 1, values, conts)
802                }
803                Err(err) => EvalState::Apply(Err(err)),
804            },
805            EvalCont::BodyBinding {
806                name,
807                next_idx,
808                lowered,
809                local_slots,
810            } => match result {
811                Ok(value) => {
812                    if let Some(local_slots) = local_slots.as_ref()
813                        && let Some(&slot) = local_slots.get(&name)
814                    {
815                        self.define_slot(slot, value);
816                    } else {
817                        self.define_nv(name, value);
818                    }
819                    EvalState::Body {
820                        lowered,
821                        idx: next_idx,
822                        local_slots,
823                        last: NanValue::UNIT,
824                    }
825                }
826                Err(err) => EvalState::Apply(Err(err)),
827            },
828            EvalCont::BodyExpr {
829                next_idx,
830                lowered,
831                local_slots,
832            } => match result {
833                Ok(value) => EvalState::Body {
834                    lowered,
835                    idx: next_idx,
836                    local_slots,
837                    last: value,
838                },
839                Err(err) => EvalState::Apply(Err(err)),
840            },
841            EvalCont::MatchScope => {
842                self.pop_env();
843                EvalState::Apply(result)
844            }
845            EvalCont::FunctionReturn(frame) => self.finish_function_call(frame, result, conts),
846        }
847    }
848
849    /// Attribute access on a NanValue.
850    fn attr_access_nv(&self, nv: NanValue, field: &str) -> Result<NanValue, RuntimeError> {
851        if nv.is_record() {
852            let (tid, fields) = self.arena.get_record(nv.arena_index());
853            let field_names = self.arena.get_field_names(tid);
854            if let Some(pos) = field_names.iter().position(|n| n == field) {
855                return Ok(fields[pos]);
856            }
857            return Err(RuntimeError::Error(format!("Unknown field '{}'", field)));
858        }
859        if nv.is_namespace() {
860            let (name, members) = self.arena.get_namespace(nv.symbol_index());
861            if let Some((_, member_nv)) = members.iter().find(|(k, _)| k.as_ref() == field) {
862                return Ok(*member_nv);
863            }
864            return Err(RuntimeError::Error(format!(
865                "Unknown member '{}.{}'",
866                name, field
867            )));
868        }
869        Err(RuntimeError::Error(format!(
870            "Field access '{}' is not supported on this value",
871            field
872        )))
873    }
874
875    fn dispatch_match(
876        &mut self,
877        lowered: Rc<LoweredFunctionBody>,
878        subject: NanValue,
879        arms: SharedMatchArms,
880        line: usize,
881        conts: &mut Vec<EvalCont>,
882    ) -> EvalState {
883        if let Some((arm_idx, bindings)) = self.try_dispatch_match_plan_nv(subject, arms.as_ref()) {
884            let arm_count = arms.len();
885            let arm = &arms[arm_idx];
886            self.note_verify_match_arm(line, arm_count, arm_idx);
887            if bindings.is_empty() {
888                return EvalState::Expr {
889                    lowered,
890                    expr: arm.body,
891                };
892            }
893
894            if let Some(local_slots) = self.active_local_slots.clone() {
895                let all_slotted = bindings
896                    .iter()
897                    .all(|(name, _)| local_slots.contains_key(name));
898                if all_slotted {
899                    for (name, nv) in bindings {
900                        if let Some(&slot) = local_slots.get(&name) {
901                            self.define_slot(slot, nv);
902                        }
903                    }
904                    return EvalState::Expr {
905                        lowered,
906                        expr: arm.body,
907                    };
908                }
909            }
910
911            let scope: HashMap<String, NanValue> = bindings.into_iter().collect();
912            self.push_env(EnvFrame::Owned(scope));
913            conts.push(EvalCont::MatchScope);
914            return EvalState::Expr {
915                lowered,
916                expr: arm.body,
917            };
918        }
919
920        let arm_count = arms.len();
921        for (arm_idx, arm) in arms.iter().enumerate() {
922            if let Some(bindings) = self.match_pattern_nv(&arm.pattern, subject) {
923                self.note_verify_match_arm(line, arm_count, arm_idx);
924                if bindings.is_empty() {
925                    return EvalState::Expr {
926                        lowered,
927                        expr: arm.body,
928                    };
929                }
930
931                if let Some(local_slots) = self.active_local_slots.clone() {
932                    let all_slotted = bindings
933                        .iter()
934                        .all(|(name, _)| local_slots.contains_key(name));
935                    if all_slotted {
936                        for (name, nv) in bindings {
937                            if let Some(&slot) = local_slots.get(&name) {
938                                self.define_slot(slot, nv);
939                            }
940                        }
941                        return EvalState::Expr {
942                            lowered,
943                            expr: arm.body,
944                        };
945                    }
946                }
947
948                let scope: HashMap<String, NanValue> = bindings.into_iter().collect();
949                self.push_env(EnvFrame::Owned(scope));
950                conts.push(EvalCont::MatchScope);
951                return EvalState::Expr {
952                    lowered,
953                    expr: arm.body,
954                };
955            }
956        }
957
958        EvalState::Apply(Err(RuntimeError::Error(format!(
959            "No match found for value {}",
960            subject.repr(&self.arena)
961        ))))
962    }
963
964    fn dispatch_call(
965        &mut self,
966        fn_val: NanValue,
967        args: Vec<NanValue>,
968        conts: &mut Vec<EvalCont>,
969    ) -> EvalState {
970        match self.start_call_nv(fn_val, args) {
971            Ok(CallDispatch::Immediate(result)) => EvalState::Apply(result),
972            Ok(CallDispatch::EnterFunction { frame, state }) => {
973                conts.push(EvalCont::FunctionReturn(*frame));
974                state
975            }
976            Err(err) => EvalState::Apply(Err(err)),
977        }
978    }
979
980    fn start_call_nv(
981        &mut self,
982        fn_val: NanValue,
983        args: Vec<NanValue>,
984    ) -> Result<CallDispatch, RuntimeError> {
985        if fn_val.is_builtin() {
986            let name = self.arena.get_builtin(fn_val.symbol_index()).to_string();
987            self.ensure_effects_allowed(&name, Self::builtin_effects(&name).iter().copied())?;
988            let result = self.call_builtin_nv(&name, &args);
989            return Ok(CallDispatch::Immediate(result));
990        }
991        if fn_val.is_fn() {
992            let function = Rc::clone(self.arena.get_fn_rc(fn_val.symbol_index()));
993            if args.len() != function.params.len() {
994                return Err(RuntimeError::Error(format!(
995                    "Function '{}' expects {} arguments, got {}",
996                    function.name,
997                    function.params.len(),
998                    args.len()
999                )));
1000            }
1001            self.ensure_effects_allowed(
1002                function.name.as_str(),
1003                function.effects.iter().map(String::as_str),
1004            )?;
1005
1006            let memo_key = if function.memo_eligible {
1007                let key = hash_memo_args_nv(&args, &self.arena);
1008                let fn_name: &str = function.name.as_ref();
1009                if let Some(cache) = self.memo_cache.get_mut(fn_name)
1010                    && let Some(cached_val) = cache.get_nv_as_value(key, &args, &self.arena)
1011                {
1012                    let cached_nv = NanValue::from_value(&cached_val, &mut self.arena);
1013                    return Ok(CallDispatch::Immediate(Ok(cached_nv)));
1014                }
1015                Some((key, args.clone()))
1016            } else {
1017                None
1018            };
1019
1020            self.call_stack.push(CallFrame {
1021                name: Rc::clone(&function.name),
1022                effects: Rc::clone(&function.effects),
1023            });
1024
1025            let prev_local_slots = self.active_local_slots.take();
1026            let saved_base = self.env_base;
1027            self.env_base = self.env.len();
1028            let prev_global = if let Some(home) = function.home_globals.as_ref() {
1029                let global = self
1030                    .env
1031                    .first_mut()
1032                    .ok_or_else(|| RuntimeError::Error("No global scope".to_string()))?;
1033                Some(std::mem::replace(global, EnvFrame::Shared(Rc::clone(home))))
1034            } else {
1035                None
1036            };
1037
1038            let active = ActiveFunction { function };
1039            let frame = FunctionFrame {
1040                active,
1041                prev_local_slots,
1042                saved_base,
1043                prev_global,
1044                memo_key,
1045            };
1046            let state = self.enter_function_body_nv(&frame.active, args);
1047            return Ok(CallDispatch::EnterFunction {
1048                frame: Box::new(frame),
1049                state,
1050            });
1051        }
1052        Err(RuntimeError::Error(format!(
1053            "Cannot call value: {}",
1054            fn_val.repr(&self.arena)
1055        )))
1056    }
1057
1058    fn enter_function_body_nv(
1059        &mut self,
1060        active: &ActiveFunction,
1061        args: Vec<NanValue>,
1062    ) -> EvalState {
1063        if let Some(resolution) = &active.function.resolution {
1064            let local_slots = Rc::clone(&resolution.local_slots);
1065            let mut slots = Self::empty_slots_nv(resolution.local_count);
1066            for ((param_name, _), arg_nv) in active.function.params.iter().zip(args.into_iter()) {
1067                if let Some(&slot) = resolution.local_slots.get(param_name) {
1068                    slots[slot as usize] = arg_nv;
1069                }
1070            }
1071            self.active_local_slots = Some(Rc::clone(&local_slots));
1072            self.push_env(EnvFrame::Slots(slots));
1073            EvalState::Body {
1074                lowered: Rc::clone(&active.function.lowered_body),
1075                idx: 0,
1076                local_slots: Some(local_slots),
1077                last: NanValue::UNIT,
1078            }
1079        } else {
1080            let mut params_scope: HashMap<String, NanValue> = HashMap::new();
1081            for ((param_name, _), arg_nv) in active.function.params.iter().zip(args.into_iter()) {
1082                params_scope.insert(param_name.clone(), arg_nv);
1083            }
1084            self.push_env(EnvFrame::Owned(params_scope));
1085            EvalState::Body {
1086                lowered: Rc::clone(&active.function.lowered_body),
1087                idx: 0,
1088                local_slots: None,
1089                last: NanValue::UNIT,
1090            }
1091        }
1092    }
1093
1094    fn finish_function_call(
1095        &mut self,
1096        mut frame: FunctionFrame,
1097        result: Result<NanValue, RuntimeError>,
1098        conts: &mut Vec<EvalCont>,
1099    ) -> EvalState {
1100        self.pop_env();
1101
1102        match result {
1103            Err(RuntimeError::TailCall(boxed)) => {
1104                let (target, nv_args) = *boxed;
1105                let next_active = if target == frame.active.function.name.as_str() {
1106                    frame.active.clone()
1107                } else {
1108                    let next_function = match self.lookup_nv(&target) {
1109                        Ok(nv) => {
1110                            if !nv.is_fn() {
1111                                return EvalState::Apply(Err(RuntimeError::Error(format!(
1112                                    "TCO target '{}' is not a function: {}",
1113                                    target,
1114                                    nv.repr(&self.arena)
1115                                ))));
1116                            }
1117                            Rc::clone(self.arena.get_fn_rc(nv.symbol_index()))
1118                        }
1119                        Err(err) => return EvalState::Apply(Err(err)),
1120                    };
1121
1122                    if let Some(call_frame) = self.call_stack.last_mut() {
1123                        call_frame.name = Rc::clone(&next_function.name);
1124                        call_frame.effects = Rc::clone(&next_function.effects);
1125                    }
1126
1127                    ActiveFunction {
1128                        function: next_function,
1129                    }
1130                };
1131
1132                frame.active = next_active;
1133                let state = self.enter_function_body_nv(&frame.active, nv_args);
1134                conts.push(EvalCont::FunctionReturn(frame));
1135                state
1136            }
1137            other => {
1138                self.active_local_slots = frame.prev_local_slots;
1139                if let Some(prev) = frame.prev_global
1140                    && let Some(global) = self.env.first_mut()
1141                {
1142                    *global = prev;
1143                }
1144                self.env_base = frame.saved_base;
1145                self.call_stack.pop();
1146
1147                let final_result = match other {
1148                    Ok(value) => Ok(value),
1149                    Err(RuntimeError::ErrProp(err_nv)) => {
1150                        Ok(NanValue::new_err_value(err_nv, &mut self.arena))
1151                    }
1152                    Err(err) => Err(err),
1153                };
1154
1155                if let (Some((key, memo_args)), Ok(value)) = (&frame.memo_key, &final_result) {
1156                    let fn_name: &str = frame.active.function.name.as_ref();
1157                    let cache = if let Some(c) = self.memo_cache.get_mut(fn_name) {
1158                        c
1159                    } else {
1160                        self.memo_cache
1161                            .entry(frame.active.function.name.as_ref().clone())
1162                            .or_default()
1163                    };
1164                    cache.insert_nv(
1165                        *key,
1166                        memo_args.clone(),
1167                        *value,
1168                        &self.arena,
1169                        MEMO_CACHE_CAP_PER_FN,
1170                    );
1171                }
1172
1173                EvalState::Apply(final_result)
1174            }
1175        }
1176    }
1177
1178    fn resume_interpolated_str(
1179        &mut self,
1180        lowered: Rc<LoweredFunctionBody>,
1181        parts: SharedStrParts,
1182        mut idx: usize,
1183        mut result: String,
1184        conts: &mut Vec<EvalCont>,
1185    ) -> EvalState {
1186        while idx < parts.len() {
1187            match parts[idx].clone() {
1188                LoweredStrPart::Literal(text) => {
1189                    result.push_str(&text);
1190                    idx += 1;
1191                }
1192                LoweredStrPart::Parsed(expr) => {
1193                    conts.push(EvalCont::InterpolatedStr {
1194                        lowered: Rc::clone(&lowered),
1195                        parts: Rc::clone(&parts),
1196                        idx: idx + 1,
1197                        result,
1198                    });
1199                    return EvalState::Expr { lowered, expr };
1200                }
1201            }
1202        }
1203        EvalState::Apply(Ok(NanValue::new_string_value(&result, &mut self.arena)))
1204    }
1205
1206    #[allow(clippy::too_many_arguments)]
1207    fn resume_direct_call(
1208        &mut self,
1209        lowered: Rc<LoweredFunctionBody>,
1210        target: LoweredDirectCallTarget,
1211        args: SharedExprs,
1212        idx: usize,
1213        arg_vals: Vec<NanValue>,
1214        call_line: usize,
1215        conts: &mut Vec<EvalCont>,
1216    ) -> EvalState {
1217        if idx >= args.len() {
1218            self.last_call_line = call_line;
1219            return self
1220                .dispatch_direct_call(target, arg_vals, conts)
1221                .map_err_line(call_line);
1222        }
1223
1224        conts.push(EvalCont::DirectCall {
1225            lowered: Rc::clone(&lowered),
1226            target,
1227            idx,
1228            args: Rc::clone(&args),
1229            arg_vals,
1230            call_line,
1231        });
1232        EvalState::Expr {
1233            lowered,
1234            expr: args[idx],
1235        }
1236    }
1237
1238    fn resume_leaf(
1239        &mut self,
1240        lowered: Rc<LoweredFunctionBody>,
1241        leaf: LoweredLeafOp,
1242        idx: usize,
1243        values: Vec<NanValue>,
1244        conts: &mut Vec<EvalCont>,
1245    ) -> EvalState {
1246        if let Some(expr) = leaf.arg_at(idx) {
1247            conts.push(EvalCont::Leaf {
1248                lowered: Rc::clone(&lowered),
1249                leaf,
1250                idx,
1251                values,
1252            });
1253            EvalState::Expr { lowered, expr }
1254        } else {
1255            EvalState::Apply(self.apply_leaf_op_nv(&leaf, &values))
1256        }
1257    }
1258
1259    fn resume_list(
1260        &mut self,
1261        lowered: Rc<LoweredFunctionBody>,
1262        items: SharedExprs,
1263        idx: usize,
1264        values: Vec<NanValue>,
1265        conts: &mut Vec<EvalCont>,
1266    ) -> EvalState {
1267        if idx >= items.len() {
1268            let list_idx = self.arena.push_list(values);
1269            return EvalState::Apply(Ok(NanValue::new_list(list_idx)));
1270        }
1271
1272        conts.push(EvalCont::List {
1273            lowered: Rc::clone(&lowered),
1274            items: Rc::clone(&items),
1275            idx: idx + 1,
1276            values,
1277        });
1278        EvalState::Expr {
1279            lowered,
1280            expr: items[idx],
1281        }
1282    }
1283
1284    fn resume_tuple(
1285        &mut self,
1286        lowered: Rc<LoweredFunctionBody>,
1287        items: SharedExprs,
1288        idx: usize,
1289        values: Vec<NanValue>,
1290        conts: &mut Vec<EvalCont>,
1291    ) -> EvalState {
1292        if idx >= items.len() {
1293            let tuple_idx = self.arena.push_tuple(values);
1294            return EvalState::Apply(Ok(NanValue::new_tuple(tuple_idx)));
1295        }
1296
1297        conts.push(EvalCont::Tuple {
1298            lowered: Rc::clone(&lowered),
1299            items: Rc::clone(&items),
1300            idx: idx + 1,
1301            values,
1302        });
1303        EvalState::Expr {
1304            lowered,
1305            expr: items[idx],
1306        }
1307    }
1308
1309    fn resume_independent_product(
1310        &mut self,
1311        lowered: Rc<LoweredFunctionBody>,
1312        items: SharedExprs,
1313        idx: usize,
1314        values: Vec<NanValue>,
1315        unwrap: bool,
1316        conts: &mut Vec<EvalCont>,
1317    ) -> EvalState {
1318        // Enter replay group when starting the independent product
1319        if idx == 0 {
1320            self.replay_state.enter_group();
1321        }
1322
1323        if idx >= items.len() {
1324            // Exit replay group when all elements are evaluated
1325            self.replay_state.exit_group();
1326
1327            if unwrap {
1328                // Unwrap each Result: if any is Err, propagate it.
1329                let mut unwrapped = Vec::with_capacity(values.len());
1330                for v in &values {
1331                    if v.is_ok() {
1332                        unwrapped.push(v.wrapper_inner(&self.arena));
1333                    } else if v.is_err() {
1334                        let inner = v.wrapper_inner(&self.arena);
1335                        return EvalState::Apply(Err(RuntimeError::ErrProp(inner)));
1336                    } else {
1337                        return EvalState::Apply(Err(RuntimeError::Error(
1338                            "Independent product with '?' can only contain Result values"
1339                                .to_string(),
1340                        )));
1341                    }
1342                }
1343                let tuple_idx = self.arena.push_tuple(unwrapped);
1344                return EvalState::Apply(Ok(NanValue::new_tuple(tuple_idx)));
1345            } else {
1346                let tuple_idx = self.arena.push_tuple(values);
1347                return EvalState::Apply(Ok(NanValue::new_tuple(tuple_idx)));
1348            }
1349        }
1350
1351        self.replay_state.set_branch(idx as u32);
1352        conts.push(EvalCont::IndependentProduct {
1353            lowered: Rc::clone(&lowered),
1354            items: Rc::clone(&items),
1355            idx: idx + 1,
1356            values,
1357            unwrap,
1358        });
1359        EvalState::Expr {
1360            lowered,
1361            expr: items[idx],
1362        }
1363    }
1364
1365    fn resume_map(
1366        &mut self,
1367        lowered: Rc<LoweredFunctionBody>,
1368        entries: SharedMapEntries,
1369        idx: usize,
1370        map: crate::nan_value::PersistentMap,
1371        conts: &mut Vec<EvalCont>,
1372    ) -> EvalState {
1373        if idx >= entries.len() {
1374            let map_idx = self.arena.push_map(map);
1375            return EvalState::Apply(Ok(NanValue::new_map(map_idx)));
1376        }
1377
1378        conts.push(EvalCont::MapKey {
1379            lowered: Rc::clone(&lowered),
1380            entries: Rc::clone(&entries),
1381            idx,
1382            map,
1383        });
1384        EvalState::Expr {
1385            lowered,
1386            expr: entries[idx].0,
1387        }
1388    }
1389
1390    fn resume_record_create(
1391        &mut self,
1392        progress: RecordCreateProgress,
1393        conts: &mut Vec<EvalCont>,
1394    ) -> EvalState {
1395        if progress.idx >= progress.fields.len() {
1396            return EvalState::Apply(
1397                self.build_record_create_nv(&progress.type_name, progress.values),
1398            );
1399        }
1400
1401        let lowered = Rc::clone(&progress.lowered);
1402        let expr = progress.fields[progress.idx].1;
1403        conts.push(EvalCont::RecordCreate(progress));
1404        EvalState::Expr { lowered, expr }
1405    }
1406
1407    fn resume_record_update(
1408        &mut self,
1409        progress: RecordUpdateProgress,
1410        conts: &mut Vec<EvalCont>,
1411    ) -> EvalState {
1412        if progress.idx >= progress.updates.len() {
1413            return EvalState::Apply(self.build_record_update_nv(
1414                &progress.type_name,
1415                progress.base_type_id,
1416                progress.base_fields,
1417                progress.base_field_names,
1418                progress.update_vals,
1419            ));
1420        }
1421
1422        let next_expr = progress.updates[progress.idx].1;
1423        let lowered = Rc::clone(&progress.lowered);
1424        conts.push(EvalCont::RecordUpdateField(progress));
1425        EvalState::Expr {
1426            lowered,
1427            expr: next_expr,
1428        }
1429    }
1430
1431    fn resume_tail_call(
1432        &mut self,
1433        lowered: Rc<LoweredFunctionBody>,
1434        target: LoweredTailCallTarget,
1435        args: SharedExprs,
1436        idx: usize,
1437        values: Vec<NanValue>,
1438        conts: &mut Vec<EvalCont>,
1439    ) -> EvalState {
1440        if idx >= args.len() {
1441            let resolved_target = match target {
1442                LoweredTailCallTarget::SelfCall => self
1443                    .call_stack
1444                    .last()
1445                    .map(|frame| frame.name.as_ref().clone())
1446                    .unwrap_or_default(),
1447                LoweredTailCallTarget::KnownFunction(name)
1448                | LoweredTailCallTarget::Unknown(name) => name,
1449            };
1450            return EvalState::Apply(Err(RuntimeError::TailCall(Box::new((
1451                resolved_target,
1452                values,
1453            )))));
1454        }
1455
1456        conts.push(EvalCont::TailCallArgs {
1457            lowered: Rc::clone(&lowered),
1458            target,
1459            args: Rc::clone(&args),
1460            idx,
1461            values,
1462        });
1463        EvalState::Expr {
1464            lowered,
1465            expr: args[idx],
1466        }
1467    }
1468
1469    fn build_record_create_nv(
1470        &mut self,
1471        type_name: &str,
1472        field_vals: Vec<(String, NanValue)>,
1473    ) -> Result<NanValue, RuntimeError> {
1474        if let Some(schema) = self.record_schemas.get(type_name) {
1475            // When we have a schema, register the type using schema field order
1476            // (which is the declaration order from the record definition).
1477            let schema_clone = schema.clone();
1478            let type_id = self.arena.find_type_id(type_name).unwrap_or_else(|| {
1479                self.arena
1480                    .register_record_type(type_name, schema_clone.clone())
1481            });
1482            let mut by_name = HashMap::with_capacity(field_vals.len());
1483            for (name, value) in field_vals {
1484                if by_name.insert(name.clone(), value).is_some() {
1485                    return Err(RuntimeError::Error(format!(
1486                        "Record '{}' field '{}' provided more than once",
1487                        type_name, name
1488                    )));
1489                }
1490            }
1491
1492            for provided in by_name.keys() {
1493                if !schema_clone.iter().any(|field| field == provided) {
1494                    return Err(RuntimeError::Error(format!(
1495                        "Record '{}' has no field '{}'",
1496                        type_name, provided
1497                    )));
1498                }
1499            }
1500
1501            let mut ordered = Vec::with_capacity(schema_clone.len());
1502            for required in &schema_clone {
1503                let value = by_name.remove(required).ok_or_else(|| {
1504                    RuntimeError::Error(format!(
1505                        "Record '{}' missing required field '{}'",
1506                        type_name, required
1507                    ))
1508                })?;
1509                ordered.push(value);
1510            }
1511
1512            let rec_idx = self.arena.push_record(type_id, ordered);
1513            return Ok(NanValue::new_record(rec_idx));
1514        }
1515
1516        // No schema — just use field order as-is
1517        let type_id = self.arena.find_type_id(type_name).unwrap_or_else(|| {
1518            let field_names: Vec<String> = field_vals.iter().map(|(n, _)| n.clone()).collect();
1519            self.arena.register_record_type(type_name, field_names)
1520        });
1521        let nv_fields: Vec<NanValue> = field_vals.iter().map(|(_, v)| *v).collect();
1522        let rec_idx = self.arena.push_record(type_id, nv_fields);
1523        Ok(NanValue::new_record(rec_idx))
1524    }
1525
1526    fn apply_leaf_op_nv(
1527        &mut self,
1528        leaf: &LoweredLeafOp,
1529        args: &[NanValue],
1530    ) -> Result<NanValue, RuntimeError> {
1531        match leaf {
1532            LoweredLeafOp::MapGet { .. } => {
1533                map::call_nv("Map.get", args, &mut self.arena).expect("Map.get leaf owned by Map")
1534            }
1535            LoweredLeafOp::MapSet { .. } => {
1536                map::call_nv("Map.set", args, &mut self.arena).expect("Map.set leaf owned by Map")
1537            }
1538            LoweredLeafOp::VectorNew { .. } => vector::call_nv("Vector.new", args, &mut self.arena)
1539                .expect("Vector.new leaf owned by Vector"),
1540            LoweredLeafOp::VectorGetOrDefaultLiteral {
1541                default_literal, ..
1542            } => {
1543                let opt = vector::call_nv("Vector.get", args, &mut self.arena)
1544                    .expect("Vector.get leaf owned by Vector")?;
1545                if opt.is_some() {
1546                    Ok(opt.wrapper_inner(&self.arena))
1547                } else if opt.is_none() {
1548                    Ok(self.eval_literal_nv(default_literal))
1549                } else {
1550                    Err(RuntimeError::Error(
1551                        "Vector.get leaf expected Option result".to_string(),
1552                    ))
1553                }
1554            }
1555            LoweredLeafOp::IntModOrDefaultLiteral {
1556                default_literal, ..
1557            } => {
1558                let a = args[0].as_int(&self.arena);
1559                let b = args[1].as_int(&self.arena);
1560                if b == 0 {
1561                    Ok(self.eval_literal_nv(default_literal))
1562                } else {
1563                    Ok(NanValue::new_int(a.rem_euclid(b), &mut self.arena))
1564                }
1565            }
1566        }
1567    }
1568
1569    fn dispatch_direct_call(
1570        &mut self,
1571        target: LoweredDirectCallTarget,
1572        args: Vec<NanValue>,
1573        conts: &mut Vec<EvalCont>,
1574    ) -> EvalState {
1575        match target {
1576            LoweredDirectCallTarget::Builtin(name) | LoweredDirectCallTarget::Function(name) => {
1577                match self.lookup_path_nv(&name) {
1578                    Ok(fn_val) => self.dispatch_call(fn_val, args, conts),
1579                    Err(err) => EvalState::Apply(Err(err)),
1580                }
1581            }
1582            LoweredDirectCallTarget::Wrapper(kind) => match args.as_slice() {
1583                [inner] => {
1584                    let value = match kind {
1585                        crate::ir::WrapperKind::ResultOk => {
1586                            NanValue::new_ok_value(*inner, &mut self.arena)
1587                        }
1588                        crate::ir::WrapperKind::ResultErr => {
1589                            NanValue::new_err_value(*inner, &mut self.arena)
1590                        }
1591                        crate::ir::WrapperKind::OptionSome => {
1592                            NanValue::new_some_value(*inner, &mut self.arena)
1593                        }
1594                    };
1595                    EvalState::Apply(Ok(value))
1596                }
1597                _ => EvalState::Apply(Err(RuntimeError::Error(
1598                    "Wrapper constructor expects exactly 1 argument".to_string(),
1599                ))),
1600            },
1601            LoweredDirectCallTarget::NoneValue => {
1602                if args.is_empty() {
1603                    EvalState::Apply(Ok(NanValue::NONE))
1604                } else {
1605                    EvalState::Apply(Err(RuntimeError::Error(
1606                        "Option.None expects 0 arguments".to_string(),
1607                    )))
1608                }
1609            }
1610            LoweredDirectCallTarget::TypeConstructor {
1611                qualified_type_name,
1612                variant_name,
1613            } => {
1614                let name = format!("{qualified_type_name}.{variant_name}");
1615                EvalState::Apply(self.apply_runtime_constructor_args_nv(&name, &args))
1616            }
1617        }
1618    }
1619
1620    fn dispatch_forward_call(
1621        &mut self,
1622        target: LoweredDirectCallTarget,
1623        args: &[LoweredForwardArg],
1624        call_line: usize,
1625        conts: &mut Vec<EvalCont>,
1626    ) -> EvalState {
1627        let mut values = Vec::with_capacity(args.len());
1628        for arg in args {
1629            let value = match arg {
1630                LoweredForwardArg::Local(name) => match self.lookup_nv(name) {
1631                    Ok(value) => value,
1632                    Err(err) => return EvalState::Apply(Err(err)),
1633                },
1634                LoweredForwardArg::Slot(slot) => match self.lookup_slot(*slot) {
1635                    Ok(value) => value,
1636                    Err(err) => return EvalState::Apply(Err(err)),
1637                },
1638            };
1639            values.push(value);
1640        }
1641        self.last_call_line = call_line;
1642        self.dispatch_direct_call(target, values, conts)
1643            .map_err_line(call_line)
1644    }
1645
1646    fn build_record_update_nv(
1647        &mut self,
1648        type_name: &str,
1649        base_type_id: u32,
1650        mut base_fields: Vec<NanValue>,
1651        base_field_names: Vec<String>,
1652        update_vals: Vec<(String, NanValue)>,
1653    ) -> Result<NanValue, RuntimeError> {
1654        if let Some(schema) = self.record_schemas.get(type_name) {
1655            for (field_name, _) in &update_vals {
1656                if !schema.iter().any(|field| field == field_name) {
1657                    return Err(RuntimeError::Error(format!(
1658                        "Record '{}' has no field '{}'",
1659                        type_name, field_name
1660                    )));
1661                }
1662            }
1663        }
1664
1665        for (update_name, update_val) in update_vals {
1666            if let Some(pos) = base_field_names.iter().position(|n| n == &update_name) {
1667                base_fields[pos] = update_val;
1668            } else {
1669                return Err(RuntimeError::Error(format!(
1670                    "Record '{}' has no field '{}'",
1671                    type_name, update_name
1672                )));
1673            }
1674        }
1675
1676        let rec_idx = self.arena.push_record(base_type_id, base_fields);
1677        Ok(NanValue::new_record(rec_idx))
1678    }
1679
1680    fn is_hashable_map_key_nv(value: NanValue) -> bool {
1681        value.is_int() || value.is_float() || value.is_string() || value.is_bool()
1682    }
1683
1684    pub(super) fn eval_literal_nv(&mut self, lit: &Literal) -> NanValue {
1685        match lit {
1686            Literal::Int(i) => NanValue::new_int(*i, &mut self.arena),
1687            Literal::Float(f) => NanValue::new_float(*f),
1688            Literal::Str(s) => NanValue::new_string_value(s, &mut self.arena),
1689            Literal::Bool(b) => NanValue::new_bool(*b),
1690            Literal::Unit => NanValue::UNIT,
1691        }
1692    }
1693
1694    #[allow(dead_code)]
1695    pub(super) fn eval_literal(&self, lit: &Literal) -> Value {
1696        match lit {
1697            Literal::Int(i) => Value::Int(*i),
1698            Literal::Float(f) => Value::Float(*f),
1699            Literal::Str(s) => Value::Str(s.clone()),
1700            Literal::Bool(b) => Value::Bool(*b),
1701            Literal::Unit => Value::Unit,
1702        }
1703    }
1704
1705    pub(super) fn call_value(
1706        &mut self,
1707        fn_val: Value,
1708        args: Vec<Value>,
1709    ) -> Result<Value, RuntimeError> {
1710        let fn_nv = NanValue::from_value(&fn_val, &mut self.arena);
1711        let nv_args: Vec<NanValue> = args
1712            .iter()
1713            .map(|v| NanValue::from_value(v, &mut self.arena))
1714            .collect();
1715        match self.start_call_nv(fn_nv, nv_args)? {
1716            CallDispatch::Immediate(result) => result.map(|nv| nv.to_value(&self.arena)),
1717            CallDispatch::EnterFunction { frame, state } => {
1718                let nv = self.eval_loop(state, vec![EvalCont::FunctionReturn(*frame)])?;
1719                Ok(nv.to_value(&self.arena))
1720            }
1721        }
1722    }
1723}
1724
1725/// Hash NanValue args for memo cache — uses arena for content-based hashing.
1726fn hash_memo_args_nv(args: &[NanValue], arena: &Arena) -> u64 {
1727    use std::hash::{Hash, Hasher};
1728    let mut hasher = std::collections::hash_map::DefaultHasher::new();
1729    args.len().hash(&mut hasher);
1730    for arg in args {
1731        arg.hash_in(&mut hasher, arena);
1732    }
1733    hasher.finish()
1734}