1use crate::nan_value::{Arena, NanValue};
2use std::collections::{HashMap, HashSet};
3use std::path::{Path, PathBuf};
4use std::sync::Arc as Rc;
5
6use crate::ast::*;
7use crate::replay::{
8 EffectRecord, EffectReplayMode, EffectReplayState, JsonValue, RecordedOutcome,
9 SessionRecording, session_recording_to_string_pretty, value_to_json, values_to_json_lossy,
10};
11#[cfg(feature = "terminal")]
12use crate::services::terminal;
13use crate::services::{args, console, disk, env, http, http_server, random, tcp, time};
14use crate::source::{
15 canonicalize_path, find_module_file, parse_source, require_module_declaration,
16};
17use crate::types::{bool, byte, char, float, int, list, map, option, result, string, vector};
18pub use crate::value::{Env, EnvFrame, RuntimeError, Value, aver_display, aver_repr};
20use crate::value::{list_len, list_view};
21
22#[derive(Debug, Clone)]
23struct CallFrame {
24 name: Rc<String>,
25 effects: Rc<Vec<String>>,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ExecutionMode {
30 Normal,
31 Record,
32 Replay,
33}
34
35const MEMO_CACHE_CAP_PER_FN: usize = 4096;
36
37#[derive(Debug, Clone)]
38struct MemoEntry {
39 id: u64,
40 args: Vec<Value>,
41 result: Value,
42}
43
44#[derive(Debug, Clone)]
45struct RecordingSink {
46 path: PathBuf,
47 request_id: String,
48 timestamp: String,
49 program_file: String,
50 module_root: String,
51 entry_fn: String,
52 input: JsonValue,
53}
54
55type MatchSiteKey = (usize, usize); #[derive(Debug, Clone)]
58struct VerifyMatchCoverageTracker {
59 target_fn: String,
60 expected_arms: std::collections::BTreeMap<MatchSiteKey, usize>,
61 visited_arms: HashMap<MatchSiteKey, HashSet<usize>>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct VerifyMatchCoverageMiss {
66 pub line: usize,
67 pub total_arms: usize,
68 pub missing_arms: Vec<usize>, }
70
71#[derive(Debug, Clone)]
72pub struct RecordingConfig {
73 pub path: PathBuf,
74 pub request_id: String,
75 pub timestamp: String,
76 pub program_file: String,
77 pub module_root: String,
78 pub entry_fn: String,
79 pub input: JsonValue,
80}
81
82#[derive(Debug, Default, Clone)]
84struct FnMemoCache {
85 buckets: HashMap<u64, Vec<MemoEntry>>,
87 positions: HashMap<u64, (u64, usize)>,
89 links: HashMap<u64, (Option<u64>, Option<u64>)>,
91 lru_head: Option<u64>,
92 lru_tail: Option<u64>,
93 next_id: u64,
94 len: usize,
95}
96
97impl FnMemoCache {
98 fn get(&mut self, hash: u64, args: &[Value]) -> Option<Value> {
99 let found = self
100 .buckets
101 .get_mut(&hash)
102 .and_then(|entries| entries.iter_mut().find(|entry| entry.args == args))
103 .map(|entry| (entry.id, entry.result.clone()));
104
105 if let Some((id, value)) = found {
106 self.touch(id);
107 Some(value)
108 } else {
109 None
110 }
111 }
112
113 fn insert(&mut self, hash: u64, args: Vec<Value>, result: Value, cap: usize) {
114 let update_hit = self
115 .buckets
116 .get_mut(&hash)
117 .and_then(|entries| entries.iter_mut().find(|entry| entry.args == args))
118 .map(|entry| {
119 entry.result = result.clone();
120 entry.id
121 });
122
123 if let Some(id) = update_hit {
124 self.touch(id);
125 return;
126 }
127
128 if self.len >= cap {
129 self.evict_lru();
130 }
131
132 let id = self.alloc_id();
133 let entry = MemoEntry { id, args, result };
134 let idx = self.buckets.entry(hash).or_default().len();
135 self.buckets.entry(hash).or_default().push(entry);
136 self.positions.insert(id, (hash, idx));
137 self.append_tail(id);
138 self.len += 1;
139 }
140
141 fn alloc_id(&mut self) -> u64 {
142 let id = self.next_id;
143 self.next_id = self.next_id.wrapping_add(1);
144 id
145 }
146
147 fn evict_lru(&mut self) {
148 if let Some(id) = self.lru_head {
149 self.remove_entry(id);
150 }
151 }
152
153 fn touch(&mut self, id: u64) {
154 if self.lru_tail == Some(id) {
155 return;
156 }
157 self.detach(id);
158 self.append_tail(id);
159 }
160
161 fn append_tail(&mut self, id: u64) {
162 let prev = self.lru_tail;
163 self.links.insert(id, (prev, None));
164 if let Some(tail) = prev {
165 if let Some((_, next)) = self.links.get_mut(&tail) {
166 *next = Some(id);
167 }
168 } else {
169 self.lru_head = Some(id);
170 }
171 self.lru_tail = Some(id);
172 }
173
174 fn detach(&mut self, id: u64) {
175 let Some((prev, next)) = self.links.get(&id).copied() else {
176 return;
177 };
178
179 if let Some(p) = prev {
180 if let Some((_, p_next)) = self.links.get_mut(&p) {
181 *p_next = next;
182 }
183 } else {
184 self.lru_head = next;
185 }
186
187 if let Some(n) = next {
188 if let Some((n_prev, _)) = self.links.get_mut(&n) {
189 *n_prev = prev;
190 }
191 } else {
192 self.lru_tail = prev;
193 }
194
195 if let Some(link) = self.links.get_mut(&id) {
196 *link = (None, None);
197 }
198 }
199
200 fn remove_entry(&mut self, id: u64) {
201 let Some((hash, idx)) = self.positions.remove(&id) else {
202 return;
203 };
204 self.detach(id);
205 self.links.remove(&id);
206
207 let mut remove_bucket = false;
208 if let Some(entries) = self.buckets.get_mut(&hash) {
209 entries.swap_remove(idx);
210 if idx < entries.len() {
211 let moved_id = entries[idx].id;
212 self.positions.insert(moved_id, (hash, idx));
213 }
214 remove_bucket = entries.is_empty();
215 }
216 if remove_bucket {
217 self.buckets.remove(&hash);
218 }
219 self.len = self.len.saturating_sub(1);
220 }
221
222 fn get_nv_as_value(&mut self, hash: u64, nv_args: &[NanValue], arena: &Arena) -> Option<Value> {
225 let args: Vec<Value> = nv_args.iter().map(|nv| nv.to_value(arena)).collect();
226 self.get(hash, &args)
227 }
228
229 fn insert_nv(
231 &mut self,
232 hash: u64,
233 nv_args: Vec<NanValue>,
234 nv_result: NanValue,
235 arena: &Arena,
236 cap: usize,
237 ) {
238 let args: Vec<Value> = nv_args.iter().map(|nv| nv.to_value(arena)).collect();
239 let result = nv_result.to_value(arena);
240 self.insert(hash, args, result, cap);
241 }
242}
243
244pub struct Interpreter {
245 pub env: Env,
246 env_base: usize,
250 pub arena: Arena,
252 module_cache: HashMap<String, Value>,
253 mounted_module_paths: HashSet<String>,
254 record_schemas: HashMap<String, Vec<String>>,
257 call_stack: Vec<CallFrame>,
258 active_local_slots: Option<Rc<HashMap<String, u16>>>,
261 memo_fns: HashSet<String>,
263 memo_cache: HashMap<String, FnMemoCache>,
265 replay_state: EffectReplayState,
266 recording_sink: Option<RecordingSink>,
267 verify_match_coverage: Option<VerifyMatchCoverageTracker>,
268 runtime_policy: Option<crate::config::ProjectConfig>,
270 cli_args: Vec<String>,
272 pub(crate) last_call_line: usize,
275}
276
277mod api;
278mod builtins;
279mod core;
280mod effects;
281mod eval;
282mod exec;
283mod ir_bridge;
284pub(crate) mod lowered;
285mod ops;
286mod patterns;
287
288#[cfg(test)]
289mod memo_cache_tests {
290 use super::*;
291
292 #[test]
293 fn collision_bucket_is_exact_match_on_args() {
294 let mut cache = FnMemoCache::default();
295 cache.insert(1, vec![Value::Int(1)], Value::Int(10), 8);
296 cache.insert(1, vec![Value::Int(2)], Value::Int(20), 8);
297
298 assert_eq!(cache.get(1, &[Value::Int(1)]), Some(Value::Int(10)));
299 assert_eq!(cache.get(1, &[Value::Int(2)]), Some(Value::Int(20)));
300 assert_eq!(cache.get(1, &[Value::Int(3)]), None);
301 }
302
303 #[test]
304 fn lru_evicts_least_recently_used() {
305 let mut cache = FnMemoCache::default();
306 cache.insert(11, vec![Value::Int(1)], Value::Int(10), 2);
307 cache.insert(22, vec![Value::Int(2)], Value::Int(20), 2);
308
309 assert_eq!(cache.get(11, &[Value::Int(1)]), Some(Value::Int(10)));
311 cache.insert(33, vec![Value::Int(3)], Value::Int(30), 2);
312
313 assert_eq!(cache.get(11, &[Value::Int(1)]), Some(Value::Int(10)));
314 assert_eq!(cache.get(22, &[Value::Int(2)]), None);
315 assert_eq!(cache.get(33, &[Value::Int(3)]), Some(Value::Int(30)));
316 }
317}
318
319#[cfg(test)]
320mod ir_bridge_tests {
321 use aver_rt::AverVector;
322
323 use crate::ir::CallLowerCtx;
324
325 use super::ir_bridge::InterpreterLowerCtx;
326 use super::lowered::{
327 self, ExprId, LoweredDirectCallTarget, LoweredExpr, LoweredForwardArg, LoweredLeafOp,
328 LoweredMatchArm, LoweredTailCallTarget,
329 };
330 use super::*;
331
332 fn sb(expr: Expr) -> Spanned<Expr> {
334 Spanned::bare(expr)
335 }
336
337 fn sbb(expr: Expr) -> Box<Spanned<Expr>> {
339 Box::new(Spanned::bare(expr))
340 }
341
342 fn register_task_event_type(interpreter: &mut Interpreter) {
343 interpreter.register_type_def(&TypeDef::Sum {
344 name: "TaskEvent".to_string(),
345 variants: vec![
346 TypeVariant {
347 name: "TaskCreated".to_string(),
348 fields: vec!["String".to_string()],
349 },
350 TypeVariant {
351 name: "TaskMoved".to_string(),
352 fields: vec!["String".to_string(), "Int".to_string()],
353 },
354 ],
355 line: 1,
356 });
357
358 let mut members = HashMap::new();
359 members.insert(
360 "TaskEvent".to_string(),
361 interpreter
362 .lookup("TaskEvent")
363 .expect("TaskEvent namespace should be defined"),
364 );
365 interpreter
366 .define_module_path(
367 "Domain.Types",
368 Value::Namespace {
369 name: "Types".to_string(),
370 members,
371 },
372 )
373 .expect("module path should be mountable");
374 }
375
376 fn register_expr_type(interpreter: &mut Interpreter) {
377 interpreter.register_type_def(&TypeDef::Sum {
378 name: "Expr".to_string(),
379 variants: vec![TypeVariant {
380 name: "ExprInt".to_string(),
381 fields: vec!["Int".to_string()],
382 }],
383 line: 1,
384 });
385 }
386
387 fn register_token_type(interpreter: &mut Interpreter) {
388 interpreter.register_type_def(&TypeDef::Sum {
389 name: "Token".to_string(),
390 variants: vec![
391 TypeVariant {
392 name: "TkInt".to_string(),
393 fields: vec!["Int".to_string()],
394 },
395 TypeVariant {
396 name: "TkQuestion".to_string(),
397 fields: vec![],
398 },
399 ],
400 line: 1,
401 });
402
403 let mut members = HashMap::new();
404 members.insert(
405 "Token".to_string(),
406 interpreter
407 .lookup("Token")
408 .expect("Token type namespace should be defined"),
409 );
410 interpreter
411 .define_module_path(
412 "Domain.Token",
413 Value::Namespace {
414 name: "Domain.Token".to_string(),
415 members,
416 },
417 )
418 .expect("module path should be mountable");
419 }
420
421 #[test]
422 fn eval_constructor_uses_shared_semantics_for_wrappers_and_qualified_variants() {
423 let mut interpreter = Interpreter::new();
424 register_task_event_type(&mut interpreter);
425
426 let ok_expr = sb(Expr::Constructor(
427 "Ok".to_string(),
428 Some(sbb(Expr::Literal(Literal::Int(7)))),
429 ));
430 let created_expr = sb(Expr::Constructor(
431 "Domain.Types.TaskEvent.TaskCreated".to_string(),
432 Some(sbb(Expr::Literal(Literal::Str("now".to_string())))),
433 ));
434
435 assert_eq!(
436 interpreter
437 .eval_expr(&ok_expr)
438 .expect("Ok constructor should evaluate"),
439 Value::Ok(Box::new(Value::Int(7)))
440 );
441
442 match interpreter
443 .eval_expr(&created_expr)
444 .expect("qualified constructor should build a variant")
445 {
446 Value::Variant {
447 type_name,
448 variant,
449 fields,
450 } => {
451 assert_eq!(type_name, "TaskEvent");
452 assert_eq!(variant, "TaskCreated");
453 assert_eq!(fields.as_ref(), &[Value::Str("now".to_string())]);
454 }
455 other => panic!("expected variant, got {other:?}"),
456 }
457 }
458
459 #[test]
460 fn qualified_module_function_call_prefers_deepest_module_prefix_over_type_namespace() {
461 let mut interpreter = Interpreter::new();
462 register_expr_type(&mut interpreter);
463
464 let parse_expr = FnDef {
465 name: "parseExpr".to_string(),
466 line: 1,
467 params: vec![
468 ("tokens".to_string(), "List<Int>".to_string()),
469 ("pos".to_string(), "Int".to_string()),
470 ],
471 return_type: "Int".to_string(),
472 effects: vec![],
473 desc: None,
474 body: Rc::new(FnBody::from_expr(sb(Expr::Literal(Literal::Int(7))))),
475 resolution: None,
476 };
477 interpreter
478 .exec_fn_def(&parse_expr)
479 .expect("parseExpr function should register");
480
481 let mut members = HashMap::new();
482 members.insert(
483 "parseExpr".to_string(),
484 interpreter
485 .lookup("parseExpr")
486 .expect("parseExpr function should be available"),
487 );
488 interpreter
489 .define_module_path(
490 "Domain.Parser.Expr",
491 Value::Namespace {
492 name: "Domain.Parser.Expr".to_string(),
493 members,
494 },
495 )
496 .expect("module path should be mountable");
497
498 let ctx = InterpreterLowerCtx::new(&interpreter);
499 assert_eq!(
500 ctx.resolve_module_call("Domain.Parser.Expr.parseExpr"),
501 Some(("Domain.Parser.Expr", "parseExpr"))
502 );
503
504 let call = sb(Expr::FnCall(
505 sbb(Expr::Attr(
506 sbb(Expr::Attr(
507 sbb(Expr::Attr(
508 sbb(Expr::Ident("Domain".to_string())),
509 "Parser".to_string(),
510 )),
511 "Expr".to_string(),
512 )),
513 "parseExpr".to_string(),
514 )),
515 vec![
516 sb(Expr::List(vec![sb(Expr::Literal(Literal::Int(1)))])),
517 sb(Expr::Literal(Literal::Int(0))),
518 ],
519 ));
520
521 assert_eq!(
522 interpreter
523 .eval_expr(&call)
524 .expect("qualified module function call should run"),
525 Value::Int(7)
526 );
527 }
528
529 #[test]
530 fn qualified_constructor_call_keeps_outer_module_prefix_when_type_namespace_exists() {
531 let mut interpreter = Interpreter::new();
532 register_expr_type(&mut interpreter);
533
534 let mut members = HashMap::new();
535 members.insert(
536 "Expr".to_string(),
537 interpreter
538 .lookup("Expr")
539 .expect("Expr type namespace should be available"),
540 );
541 interpreter
542 .define_module_path(
543 "Domain.Ast",
544 Value::Namespace {
545 name: "Domain.Ast".to_string(),
546 members,
547 },
548 )
549 .expect("module path should be mountable");
550
551 let ctx = InterpreterLowerCtx::new(&interpreter);
552 assert_eq!(
553 ctx.resolve_module_call("Domain.Ast.Expr.ExprInt"),
554 Some(("Domain.Ast", "Expr.ExprInt"))
555 );
556
557 let ctor_call = sb(Expr::FnCall(
558 sbb(Expr::Attr(
559 sbb(Expr::Attr(
560 sbb(Expr::Attr(
561 sbb(Expr::Ident("Domain".to_string())),
562 "Ast".to_string(),
563 )),
564 "Expr".to_string(),
565 )),
566 "ExprInt".to_string(),
567 )),
568 vec![sb(Expr::Literal(Literal::Int(7)))],
569 ));
570
571 match interpreter
572 .eval_expr(&ctor_call)
573 .expect("qualified constructor call should run")
574 {
575 Value::Variant {
576 type_name,
577 variant,
578 fields,
579 } => {
580 assert_eq!(type_name, "Expr");
581 assert_eq!(variant, "ExprInt");
582 assert_eq!(fields.as_ref(), &[Value::Int(7)]);
583 }
584 other => panic!("expected variant, got {other:?}"),
585 }
586 }
587
588 #[test]
589 fn short_type_aliases_are_not_misclassified_as_module_prefixes() {
590 let mut interpreter = Interpreter::new();
591 register_token_type(&mut interpreter);
592
593 let ctx = InterpreterLowerCtx::new(&interpreter);
594 assert_eq!(ctx.resolve_module_call("Token.TkQuestion"), None);
595
596 let pattern = Pattern::Constructor("Token.TkInt".to_string(), vec!["n".to_string()]);
597 let value = Value::Variant {
598 type_name: "Token".to_string(),
599 variant: "TkInt".to_string(),
600 fields: vec![Value::Int(1)].into(),
601 };
602
603 assert_eq!(
604 interpreter.match_pattern(&pattern, &value),
605 Some(vec![("n".to_string(), Value::Int(1))])
606 );
607
608 let nv = NanValue::from_value(&value, &mut interpreter.arena);
609 let bindings = interpreter
610 .match_pattern_nv(&pattern, nv)
611 .expect("short type alias constructor pattern should match");
612 assert_eq!(bindings.len(), 1);
613 assert_eq!(bindings[0].0, "n");
614 assert_eq!(bindings[0].1.to_value(&interpreter.arena), Value::Int(1));
615 }
616
617 #[test]
618 fn qualified_constructor_patterns_use_shared_semantics_in_both_match_paths() {
619 let mut interpreter = Interpreter::new();
620 register_task_event_type(&mut interpreter);
621
622 let pattern = Pattern::Constructor(
623 "Domain.Types.TaskEvent.TaskCreated".to_string(),
624 vec!["at".to_string()],
625 );
626 let value = Value::Variant {
627 type_name: "TaskEvent".to_string(),
628 variant: "TaskCreated".to_string(),
629 fields: vec![Value::Str("now".to_string())].into(),
630 };
631
632 assert_eq!(
633 interpreter.match_pattern(&pattern, &value),
634 Some(vec![("at".to_string(), Value::Str("now".to_string()))])
635 );
636
637 let nv = NanValue::from_value(&value, &mut interpreter.arena);
638 let bindings = interpreter
639 .match_pattern_nv(&pattern, nv)
640 .expect("nan-value pattern path should match");
641 assert_eq!(bindings.len(), 1);
642 assert_eq!(bindings[0].0, "at");
643 assert_eq!(
644 bindings[0].1.to_value(&interpreter.arena),
645 Value::Str("now".to_string())
646 );
647 }
648
649 #[test]
650 fn constructor_patterns_match_inline_single_field_variants_in_nan_path() {
651 let mut interpreter = Interpreter::new();
652 interpreter.register_type_def(&TypeDef::Sum {
653 name: "Expr".to_string(),
654 variants: vec![
655 TypeVariant {
656 name: "Int".to_string(),
657 fields: vec!["Int".to_string()],
658 },
659 TypeVariant {
660 name: "Text".to_string(),
661 fields: vec!["String".to_string()],
662 },
663 ],
664 line: 1,
665 });
666
667 let pattern = Pattern::Constructor("Expr.Int".to_string(), vec!["n".to_string()]);
668 let value = Value::Variant {
669 type_name: "Expr".to_string(),
670 variant: "Int".to_string(),
671 fields: vec![Value::Int(7)].into(),
672 };
673
674 let nv = NanValue::from_value(&value, &mut interpreter.arena);
675 let bindings = interpreter
676 .match_pattern_nv(&pattern, nv)
677 .expect("inline one-field variant should match in nan path");
678 assert_eq!(bindings.len(), 1);
679 assert_eq!(bindings[0].0, "n");
680 assert_eq!(bindings[0].1.to_value(&interpreter.arena), Value::Int(7));
681 }
682
683 #[test]
684 fn runtime_match_dispatch_plan_selects_bool_list_and_wrapper_arms() {
685 let mut interpreter = Interpreter::new();
686
687 let bool_arms = vec![
688 LoweredMatchArm {
689 pattern: Pattern::Literal(Literal::Bool(true)),
690 body: ExprId(0),
691 },
692 LoweredMatchArm {
693 pattern: Pattern::Ident("other".to_string()),
694 body: ExprId(1),
695 },
696 ];
697 let (bool_arm, bool_bindings) = interpreter
698 .try_dispatch_match_plan_nv(NanValue::FALSE, &bool_arms)
699 .expect("bool match plan should dispatch");
700 assert_eq!(bool_arm, 1);
701 assert_eq!(bool_bindings.len(), 1);
702 assert_eq!(bool_bindings[0].0, "other");
703 assert_eq!(bool_bindings[0].1.bits(), NanValue::FALSE.bits());
704
705 let non_empty_list = NanValue::new_list(interpreter.arena.push_list(vec![NanValue::TRUE]));
706 let list_arms = vec![
707 LoweredMatchArm {
708 pattern: Pattern::EmptyList,
709 body: ExprId(0),
710 },
711 LoweredMatchArm {
712 pattern: Pattern::Cons("head".to_string(), "tail".to_string()),
713 body: ExprId(1),
714 },
715 ];
716 let (list_arm, list_bindings) = interpreter
717 .try_dispatch_match_plan_nv(non_empty_list, &list_arms)
718 .expect("list match plan should dispatch");
719 assert_eq!(list_arm, 1);
720 assert_eq!(list_bindings.len(), 2);
721 assert_eq!(list_bindings[0].0, "head");
722 assert_eq!(list_bindings[0].1.bits(), NanValue::TRUE.bits());
723
724 let wrapper_arms = vec![
725 LoweredMatchArm {
726 pattern: Pattern::Constructor("Option.None".to_string(), vec![]),
727 body: ExprId(0),
728 },
729 LoweredMatchArm {
730 pattern: Pattern::Constructor("Option.Some".to_string(), vec!["x".to_string()]),
731 body: ExprId(1),
732 },
733 LoweredMatchArm {
734 pattern: Pattern::Ident("fallback".to_string()),
735 body: ExprId(2),
736 },
737 ];
738 let some_subject = NanValue::new_some_value(
739 NanValue::new_int(7, &mut interpreter.arena),
740 &mut interpreter.arena,
741 );
742 let (wrapper_arm, wrapper_bindings) = interpreter
743 .try_dispatch_match_plan_nv(some_subject, &wrapper_arms)
744 .expect("wrapper match plan should dispatch");
745 assert_eq!(wrapper_arm, 1);
746 assert_eq!(wrapper_bindings.len(), 1);
747 assert_eq!(wrapper_bindings[0].0, "x");
748 assert_eq!(wrapper_bindings[0].1.as_int(&interpreter.arena), 7);
749
750 let (default_arm, default_bindings) = interpreter
751 .try_dispatch_match_plan_nv(NanValue::TRUE, &wrapper_arms)
752 .expect("dispatch table default arm should match");
753 assert_eq!(default_arm, 2);
754 assert_eq!(default_bindings.len(), 1);
755 assert_eq!(default_bindings[0].0, "fallback");
756 assert_eq!(default_bindings[0].1.bits(), NanValue::TRUE.bits());
757 }
758
759 #[test]
760 fn lowered_roots_classify_shared_builtin_leaf_ops() {
761 let interpreter = Interpreter::new();
762 let ctx = InterpreterLowerCtx::new(&interpreter);
763
764 let map_get = sb(Expr::FnCall(
765 sbb(Expr::Attr(
766 sbb(Expr::Ident("Map".to_string())),
767 "get".to_string(),
768 )),
769 vec![
770 sb(Expr::Ident("m".to_string())),
771 sb(Expr::Literal(Literal::Str("k".to_string()))),
772 ],
773 ));
774 let (lowered_map_get, map_get_root) = lowered::lower_expr_root(&map_get, &ctx);
775 assert!(matches!(
776 lowered_map_get.expr(map_get_root),
777 LoweredExpr::Leaf(LoweredLeafOp::MapGet { .. })
778 ));
779
780 let vec_default = sb(Expr::FnCall(
781 sbb(Expr::Attr(
782 sbb(Expr::Ident("Option".to_string())),
783 "withDefault".to_string(),
784 )),
785 vec![
786 sb(Expr::FnCall(
787 sbb(Expr::Attr(
788 sbb(Expr::Ident("Vector".to_string())),
789 "get".to_string(),
790 )),
791 vec![
792 sb(Expr::Ident("v".to_string())),
793 sb(Expr::Ident("idx".to_string())),
794 ],
795 )),
796 sb(Expr::Literal(Literal::Int(0))),
797 ],
798 ));
799 let (lowered_vec_default, vec_default_root) = lowered::lower_expr_root(&vec_default, &ctx);
800 assert!(matches!(
801 lowered_vec_default.expr(vec_default_root),
802 LoweredExpr::Leaf(LoweredLeafOp::VectorGetOrDefaultLiteral {
803 default_literal: Literal::Int(0),
804 ..
805 })
806 ));
807 }
808
809 #[test]
810 fn runtime_executes_shared_leaf_ops_in_host_interpreter() {
811 let mut interpreter = Interpreter::new();
812
813 let mut map_value = HashMap::new();
814 map_value.insert(Value::Str("k".to_string()), Value::Int(7));
815 interpreter.define("m".to_string(), Value::Map(map_value));
816 interpreter.define(
817 "v".to_string(),
818 Value::Vector(AverVector::from_vec(vec![Value::Int(10), Value::Int(20)])),
819 );
820 interpreter.define("idx".to_string(), Value::Int(1));
821 interpreter.define("miss".to_string(), Value::Int(5));
822
823 let map_get = sb(Expr::FnCall(
824 sbb(Expr::Attr(
825 sbb(Expr::Ident("Map".to_string())),
826 "get".to_string(),
827 )),
828 vec![
829 sb(Expr::Ident("m".to_string())),
830 sb(Expr::Literal(Literal::Str("k".to_string()))),
831 ],
832 ));
833 assert_eq!(
834 interpreter
835 .eval_expr(&map_get)
836 .expect("Map.get leaf should run"),
837 Value::Some(Box::new(Value::Int(7)))
838 );
839
840 let vec_hit = sb(Expr::FnCall(
841 sbb(Expr::Attr(
842 sbb(Expr::Ident("Option".to_string())),
843 "withDefault".to_string(),
844 )),
845 vec![
846 sb(Expr::FnCall(
847 sbb(Expr::Attr(
848 sbb(Expr::Ident("Vector".to_string())),
849 "get".to_string(),
850 )),
851 vec![
852 sb(Expr::Ident("v".to_string())),
853 sb(Expr::Ident("idx".to_string())),
854 ],
855 )),
856 sb(Expr::Literal(Literal::Int(0))),
857 ],
858 ));
859 assert_eq!(
860 interpreter
861 .eval_expr(&vec_hit)
862 .expect("Vector.get default leaf should return hit"),
863 Value::Int(20)
864 );
865
866 let vec_miss = sb(Expr::FnCall(
867 sbb(Expr::Attr(
868 sbb(Expr::Ident("Option".to_string())),
869 "withDefault".to_string(),
870 )),
871 vec![
872 sb(Expr::FnCall(
873 sbb(Expr::Attr(
874 sbb(Expr::Ident("Vector".to_string())),
875 "get".to_string(),
876 )),
877 vec![
878 sb(Expr::Ident("v".to_string())),
879 sb(Expr::Ident("miss".to_string())),
880 ],
881 )),
882 sb(Expr::Literal(Literal::Int(0))),
883 ],
884 ));
885 assert_eq!(
886 interpreter
887 .eval_expr(&vec_miss)
888 .expect("Vector.get default leaf should return fallback"),
889 Value::Int(0)
890 );
891 }
892
893 #[test]
894 fn lowered_roots_classify_shared_call_plans_for_builtin_and_function_calls() {
895 let mut interpreter = Interpreter::new();
896 register_task_event_type(&mut interpreter);
897 let ctx = InterpreterLowerCtx::new(&interpreter);
898
899 let list_len = sb(Expr::FnCall(
900 sbb(Expr::Attr(
901 sbb(Expr::Ident("List".to_string())),
902 "len".to_string(),
903 )),
904 vec![sb(Expr::Ident("xs".to_string()))],
905 ));
906 let (lowered_builtin, builtin_root) = lowered::lower_expr_root(&list_len, &ctx);
907 assert!(matches!(
908 lowered_builtin.expr(builtin_root),
909 LoweredExpr::DirectCall {
910 target: LoweredDirectCallTarget::Builtin(name),
911 ..
912 } if name == "List.len"
913 ));
914
915 let identity_call = sb(Expr::FnCall(
916 sbb(Expr::Ident("identity".to_string())),
917 vec![sb(Expr::Literal(Literal::Int(7)))],
918 ));
919 let (lowered_fn, fn_root) = lowered::lower_expr_root(&identity_call, &ctx);
920 assert!(matches!(
921 lowered_fn.expr(fn_root),
922 LoweredExpr::DirectCall {
923 target: LoweredDirectCallTarget::Function(name),
924 ..
925 } if name == "identity"
926 ));
927
928 let wrapper_call = sb(Expr::FnCall(
929 sbb(Expr::Attr(
930 sbb(Expr::Ident("Result".to_string())),
931 "Ok".to_string(),
932 )),
933 vec![sb(Expr::Literal(Literal::Int(1)))],
934 ));
935 let (lowered_wrapper, wrapper_root) = lowered::lower_expr_root(&wrapper_call, &ctx);
936 assert!(matches!(
937 lowered_wrapper.expr(wrapper_root),
938 LoweredExpr::DirectCall {
939 target: LoweredDirectCallTarget::Wrapper(crate::ir::WrapperKind::ResultOk),
940 ..
941 }
942 ));
943
944 let none_call = sb(Expr::FnCall(
945 sbb(Expr::Attr(
946 sbb(Expr::Ident("Option".to_string())),
947 "None".to_string(),
948 )),
949 vec![],
950 ));
951 let (lowered_none, none_root) = lowered::lower_expr_root(&none_call, &ctx);
952 assert!(matches!(
953 lowered_none.expr(none_root),
954 LoweredExpr::DirectCall {
955 target: LoweredDirectCallTarget::NoneValue,
956 ..
957 }
958 ));
959
960 let ctor_call = sb(Expr::FnCall(
961 sbb(Expr::Attr(
962 sbb(Expr::Attr(
963 sbb(Expr::Attr(
964 sbb(Expr::Ident("Domain".to_string())),
965 "Types".to_string(),
966 )),
967 "TaskEvent".to_string(),
968 )),
969 "TaskCreated".to_string(),
970 )),
971 vec![sb(Expr::Literal(Literal::Str("now".to_string())))],
972 ));
973 let (lowered_ctor, ctor_root) = lowered::lower_expr_root(&ctor_call, &ctx);
974 assert!(matches!(
975 lowered_ctor.expr(ctor_root),
976 LoweredExpr::DirectCall {
977 target: LoweredDirectCallTarget::TypeConstructor {
978 qualified_type_name,
979 variant_name,
980 },
981 ..
982 } if qualified_type_name == "Domain.Types.TaskEvent" && variant_name == "TaskCreated"
983 ));
984 }
985
986 #[test]
987 fn runtime_executes_shared_direct_calls_in_host_interpreter() {
988 let mut interpreter = Interpreter::new();
989 register_task_event_type(&mut interpreter);
990
991 let identity = FnDef {
992 name: "identity".to_string(),
993 line: 1,
994 params: vec![("x".to_string(), "Int".to_string())],
995 return_type: "Int".to_string(),
996 effects: vec![],
997 desc: None,
998 body: Rc::new(FnBody::from_expr(sb(Expr::Resolved(0)))),
999 resolution: Some(FnResolution {
1000 local_slots: Rc::new(HashMap::from([(String::from("x"), 0u16)])),
1001 local_count: 1,
1002 }),
1003 };
1004 interpreter
1005 .exec_fn_def(&identity)
1006 .expect("identity function should register");
1007
1008 let list_len = sb(Expr::FnCall(
1009 sbb(Expr::Attr(
1010 sbb(Expr::Ident("List".to_string())),
1011 "len".to_string(),
1012 )),
1013 vec![sb(Expr::List(vec![
1014 sb(Expr::Literal(Literal::Int(1))),
1015 sb(Expr::Literal(Literal::Int(2))),
1016 ]))],
1017 ));
1018 assert_eq!(
1019 interpreter
1020 .eval_expr(&list_len)
1021 .expect("direct builtin call should run"),
1022 Value::Int(2)
1023 );
1024
1025 let identity_call = sb(Expr::FnCall(
1026 sbb(Expr::Ident("identity".to_string())),
1027 vec![sb(Expr::Literal(Literal::Int(9)))],
1028 ));
1029 assert_eq!(
1030 interpreter
1031 .eval_expr(&identity_call)
1032 .expect("direct function call should run"),
1033 Value::Int(9)
1034 );
1035
1036 let wrapper_call = sb(Expr::FnCall(
1037 sbb(Expr::Attr(
1038 sbb(Expr::Ident("Result".to_string())),
1039 "Ok".to_string(),
1040 )),
1041 vec![sb(Expr::Literal(Literal::Int(5)))],
1042 ));
1043 assert_eq!(
1044 interpreter
1045 .eval_expr(&wrapper_call)
1046 .expect("direct wrapper call should run"),
1047 Value::Ok(Box::new(Value::Int(5)))
1048 );
1049
1050 let none_call = sb(Expr::FnCall(
1051 sbb(Expr::Attr(
1052 sbb(Expr::Ident("Option".to_string())),
1053 "None".to_string(),
1054 )),
1055 vec![],
1056 ));
1057 assert_eq!(
1058 interpreter
1059 .eval_expr(&none_call)
1060 .expect("direct none call should run"),
1061 Value::None
1062 );
1063
1064 let ctor_call = sb(Expr::FnCall(
1065 sbb(Expr::Attr(
1066 sbb(Expr::Attr(
1067 sbb(Expr::Attr(
1068 sbb(Expr::Ident("Domain".to_string())),
1069 "Types".to_string(),
1070 )),
1071 "TaskEvent".to_string(),
1072 )),
1073 "TaskCreated".to_string(),
1074 )),
1075 vec![sb(Expr::Literal(Literal::Str("now".to_string())))],
1076 ));
1077 match interpreter
1078 .eval_expr(&ctor_call)
1079 .expect("direct qualified ctor call should run")
1080 {
1081 Value::Variant {
1082 type_name,
1083 variant,
1084 fields,
1085 } => {
1086 assert_eq!(type_name, "TaskEvent");
1087 assert_eq!(variant, "TaskCreated");
1088 assert_eq!(fields.as_ref(), &[Value::Str("now".to_string())]);
1089 }
1090 other => panic!("expected variant, got {other:?}"),
1091 }
1092
1093 let multi_ctor_call = sb(Expr::FnCall(
1094 sbb(Expr::Attr(
1095 sbb(Expr::Attr(
1096 sbb(Expr::Attr(
1097 sbb(Expr::Ident("Domain".to_string())),
1098 "Types".to_string(),
1099 )),
1100 "TaskEvent".to_string(),
1101 )),
1102 "TaskMoved".to_string(),
1103 )),
1104 vec![
1105 sb(Expr::Literal(Literal::Str("later".to_string()))),
1106 sb(Expr::Literal(Literal::Int(3))),
1107 ],
1108 ));
1109 match interpreter
1110 .eval_expr(&multi_ctor_call)
1111 .expect("direct qualified multi-field ctor call should run")
1112 {
1113 Value::Variant {
1114 type_name,
1115 variant,
1116 fields,
1117 } => {
1118 assert_eq!(type_name, "TaskEvent");
1119 assert_eq!(variant, "TaskMoved");
1120 assert_eq!(
1121 fields.as_ref(),
1122 &[Value::Str("later".to_string()), Value::Int(3)]
1123 );
1124 }
1125 other => panic!("expected variant, got {other:?}"),
1126 }
1127 }
1128
1129 #[test]
1130 fn lowered_fn_bodies_classify_forward_calls_through_shared_ir() {
1131 let interpreter = Interpreter::new();
1132 let ctx = InterpreterLowerCtx::new(&interpreter);
1133 let body = FnBody::from_expr(sb(Expr::FnCall(
1134 sbb(Expr::Ident("first".to_string())),
1135 vec![sb(Expr::Resolved(1)), sb(Expr::Resolved(0))],
1136 )));
1137 let lowered = lowered::lower_fn_body(&body, &ctx, "swap");
1138
1139 assert!(matches!(
1140 lowered.expr(ExprId(0)),
1141 LoweredExpr::ForwardCall {
1142 target: LoweredDirectCallTarget::Function(name),
1143 args,
1144 ..
1145 } if name == "first"
1146 && matches!(
1147 args.as_ref(),
1148 [LoweredForwardArg::Slot(1), LoweredForwardArg::Slot(0)]
1149 )
1150 ));
1151 }
1152
1153 #[test]
1154 fn runtime_executes_forward_calls_without_evaling_arg_exprs() {
1155 let mut interpreter = Interpreter::new();
1156
1157 let first = FnDef {
1158 name: "first".to_string(),
1159 line: 1,
1160 params: vec![
1161 ("x".to_string(), "Int".to_string()),
1162 ("y".to_string(), "Int".to_string()),
1163 ],
1164 return_type: "Int".to_string(),
1165 effects: vec![],
1166 desc: None,
1167 body: Rc::new(FnBody::from_expr(sb(Expr::Resolved(0)))),
1168 resolution: Some(FnResolution {
1169 local_slots: Rc::new(HashMap::from([
1170 (String::from("x"), 0u16),
1171 (String::from("y"), 1u16),
1172 ])),
1173 local_count: 2,
1174 }),
1175 };
1176 interpreter
1177 .exec_fn_def(&first)
1178 .expect("first function should register");
1179
1180 let swap = FnDef {
1181 name: "swap".to_string(),
1182 line: 2,
1183 params: vec![
1184 ("a".to_string(), "Int".to_string()),
1185 ("b".to_string(), "Int".to_string()),
1186 ],
1187 return_type: "Int".to_string(),
1188 effects: vec![],
1189 desc: None,
1190 body: Rc::new(FnBody::from_expr(sb(Expr::FnCall(
1191 sbb(Expr::Ident("first".to_string())),
1192 vec![sb(Expr::Resolved(1)), sb(Expr::Resolved(0))],
1193 )))),
1194 resolution: Some(FnResolution {
1195 local_slots: Rc::new(HashMap::from([
1196 (String::from("a"), 0u16),
1197 (String::from("b"), 1u16),
1198 ])),
1199 local_count: 2,
1200 }),
1201 };
1202 interpreter
1203 .exec_fn_def(&swap)
1204 .expect("swap function should register");
1205
1206 let swap_call = sb(Expr::FnCall(
1207 sbb(Expr::Ident("swap".to_string())),
1208 vec![
1209 sb(Expr::Literal(Literal::Int(3))),
1210 sb(Expr::Literal(Literal::Int(7))),
1211 ],
1212 ));
1213 assert_eq!(
1214 interpreter
1215 .eval_expr(&swap_call)
1216 .expect("forward call should run"),
1217 Value::Int(7)
1218 );
1219 }
1220
1221 #[test]
1222 fn lowered_fn_bodies_classify_tail_calls_through_shared_ir() {
1223 let interpreter = Interpreter::new();
1224 let ctx = InterpreterLowerCtx::new(&interpreter);
1225
1226 let self_body = FnBody::from_expr(sb(Expr::TailCall(Box::new((
1227 "loop".to_string(),
1228 vec![sb(Expr::Literal(Literal::Int(1)))],
1229 )))));
1230 let lowered_self = lowered::lower_fn_body(&self_body, &ctx, "loop");
1231 assert!(matches!(
1232 lowered_self.expr(ExprId(1)),
1233 LoweredExpr::TailCall {
1234 target: LoweredTailCallTarget::SelfCall,
1235 ..
1236 }
1237 ));
1238
1239 let known_body = FnBody::from_expr(sb(Expr::TailCall(Box::new((
1240 "other".to_string(),
1241 vec![sb(Expr::Literal(Literal::Int(2)))],
1242 )))));
1243 let lowered_known = lowered::lower_fn_body(&known_body, &ctx, "loop");
1244 assert!(matches!(
1245 lowered_known.expr(ExprId(1)),
1246 LoweredExpr::TailCall {
1247 target: LoweredTailCallTarget::KnownFunction(name),
1248 ..
1249 } if name == "other"
1250 ));
1251
1252 let unknown_body = FnBody::from_expr(sb(Expr::TailCall(Box::new((
1253 "Result.Ok".to_string(),
1254 vec![sb(Expr::Literal(Literal::Int(3)))],
1255 )))));
1256 let lowered_unknown = lowered::lower_fn_body(&unknown_body, &ctx, "loop");
1257 assert!(matches!(
1258 lowered_unknown.expr(ExprId(1)),
1259 LoweredExpr::TailCall {
1260 target: LoweredTailCallTarget::Unknown(name),
1261 ..
1262 } if name == "Result.Ok"
1263 ));
1264 }
1265}