1use super::*;
2
3impl Interpreter {
4 pub fn new() -> Self {
5 let mut global = HashMap::new();
6
7 console::register(&mut global);
8 http::register(&mut global);
9 http_server::register(&mut global);
10 disk::register(&mut global);
11 tcp::register(&mut global);
12 int::register(&mut global);
13 float::register(&mut global);
14 string::register(&mut global);
15 list::register(&mut global);
16 map::register(&mut global);
17 char::register(&mut global);
18 byte::register(&mut global);
19
20 {
22 let mut members = HashMap::new();
23 members.insert(
24 "Ok".to_string(),
25 Value::Builtin("__ctor:Result.Ok".to_string()),
26 );
27 members.insert(
28 "Err".to_string(),
29 Value::Builtin("__ctor:Result.Err".to_string()),
30 );
31 for (name, builtin_name) in result::extra_members() {
32 members.insert(name.to_string(), Value::Builtin(builtin_name));
33 }
34 global.insert(
35 "Result".to_string(),
36 Value::Namespace {
37 name: "Result".to_string(),
38 members,
39 },
40 );
41 }
42 {
43 let mut members = HashMap::new();
44 members.insert(
45 "Some".to_string(),
46 Value::Builtin("__ctor:Option.Some".to_string()),
47 );
48 members.insert("None".to_string(), Value::None);
49 for (name, builtin_name) in option::extra_members() {
50 members.insert(name.to_string(), Value::Builtin(builtin_name));
51 }
52 global.insert(
53 "Option".to_string(),
54 Value::Namespace {
55 name: "Option".to_string(),
56 members,
57 },
58 );
59 }
60
61 let rc_global = global
62 .into_iter()
63 .map(|(k, v)| (k, Rc::new(v)))
64 .collect::<HashMap<_, _>>();
65
66 let mut record_schemas = HashMap::new();
67 record_schemas.insert(
68 "HttpResponse".to_string(),
69 vec![
70 "status".to_string(),
71 "body".to_string(),
72 "headers".to_string(),
73 ],
74 );
75 record_schemas.insert(
76 "HttpRequest".to_string(),
77 vec![
78 "method".to_string(),
79 "path".to_string(),
80 "body".to_string(),
81 "headers".to_string(),
82 ],
83 );
84 record_schemas.insert(
85 "Header".to_string(),
86 vec!["name".to_string(), "value".to_string()],
87 );
88 record_schemas.insert(
89 "Tcp.Connection".to_string(),
90 vec!["id".to_string(), "host".to_string(), "port".to_string()],
91 );
92
93 Interpreter {
94 env: vec![EnvFrame::Owned(rc_global)],
95 module_cache: HashMap::new(),
96 record_schemas,
97 call_stack: Vec::new(),
98 effect_aliases: HashMap::new(),
99 active_local_slots: None,
100 memo_fns: HashSet::new(),
101 memo_cache: HashMap::new(),
102 execution_mode: ExecutionMode::Normal,
103 recorded_effects: Vec::new(),
104 replay_effects: Vec::new(),
105 replay_pos: 0,
106 validate_replay_args: false,
107 recording_sink: None,
108 verify_match_coverage: None,
109 }
110 }
111
112 pub fn execution_mode(&self) -> ExecutionMode {
113 self.execution_mode
114 }
115
116 pub fn set_execution_mode_normal(&mut self) {
117 self.execution_mode = ExecutionMode::Normal;
118 self.recorded_effects.clear();
119 self.replay_effects.clear();
120 self.replay_pos = 0;
121 self.validate_replay_args = false;
122 }
123
124 pub fn start_recording(&mut self) {
125 self.execution_mode = ExecutionMode::Record;
126 self.recorded_effects.clear();
127 self.replay_effects.clear();
128 self.replay_pos = 0;
129 self.validate_replay_args = false;
130 }
131
132 pub fn start_replay(&mut self, effects: Vec<EffectRecord>, validate_args: bool) {
133 self.execution_mode = ExecutionMode::Replay;
134 self.replay_effects = effects;
135 self.replay_pos = 0;
136 self.validate_replay_args = validate_args;
137 self.recorded_effects.clear();
138 }
139
140 pub fn take_recorded_effects(&mut self) -> Vec<EffectRecord> {
141 std::mem::take(&mut self.recorded_effects)
142 }
143
144 pub fn replay_progress(&self) -> (usize, usize) {
145 (self.replay_pos, self.replay_effects.len())
146 }
147
148 pub fn ensure_replay_consumed(&self) -> Result<(), RuntimeError> {
149 if self.execution_mode == ExecutionMode::Replay
150 && self.replay_pos < self.replay_effects.len()
151 {
152 return Err(RuntimeError::ReplayUnconsumed {
153 remaining: self.replay_effects.len() - self.replay_pos,
154 });
155 }
156 Ok(())
157 }
158
159 pub fn configure_recording_sink(&mut self, cfg: RecordingConfig) {
160 self.recording_sink = Some(RecordingSink {
161 path: cfg.path,
162 request_id: cfg.request_id,
163 timestamp: cfg.timestamp,
164 program_file: cfg.program_file,
165 module_root: cfg.module_root,
166 entry_fn: cfg.entry_fn,
167 input: cfg.input,
168 });
169 }
170
171 pub fn recording_sink_path(&self) -> Option<std::path::PathBuf> {
172 self.recording_sink.as_ref().map(|s| s.path.clone())
173 }
174
175 pub fn persist_recording_snapshot(&self, output: RecordedOutcome) -> Result<(), RuntimeError> {
176 let Some(sink) = &self.recording_sink else {
177 return Ok(());
178 };
179
180 let recording = SessionRecording {
181 schema_version: 1,
182 request_id: sink.request_id.clone(),
183 timestamp: sink.timestamp.clone(),
184 program_file: sink.program_file.clone(),
185 module_root: sink.module_root.clone(),
186 entry_fn: sink.entry_fn.clone(),
187 input: sink.input.clone(),
188 effects: self.recorded_effects.clone(),
189 output,
190 };
191
192 let json = session_recording_to_string_pretty(&recording);
193 std::fs::write(&sink.path, json).map_err(|e| {
194 RuntimeError::Error(format!(
195 "Cannot write recording '{}': {}",
196 sink.path.display(),
197 e
198 ))
199 })?;
200 Ok(())
201 }
202
203 pub fn enable_memo(&mut self, fns: HashSet<String>) {
205 self.memo_fns = fns;
206 }
207
208 pub fn register_effect_set(&mut self, name: String, effects: Vec<String>) {
210 self.effect_aliases.insert(name, effects);
211 }
212
213 pub fn start_verify_match_coverage(&mut self, fn_name: &str) {
214 let Ok(fn_val) = self.lookup(fn_name) else {
215 self.verify_match_coverage = None;
216 return;
217 };
218 let Value::Fn { body, .. } = fn_val else {
219 self.verify_match_coverage = None;
220 return;
221 };
222
223 let mut expected = std::collections::BTreeMap::new();
224 Self::collect_match_sites_from_fn_body(body.as_ref(), &mut expected);
225 if expected.is_empty() {
226 self.verify_match_coverage = None;
227 return;
228 }
229
230 self.verify_match_coverage = Some(VerifyMatchCoverageTracker {
231 target_fn: fn_name.to_string(),
232 expected_arms: expected,
233 visited_arms: HashMap::new(),
234 });
235 }
236
237 pub fn finish_verify_match_coverage(&mut self) -> Vec<VerifyMatchCoverageMiss> {
238 let Some(tracker) = self.verify_match_coverage.take() else {
239 return vec![];
240 };
241
242 let mut misses = Vec::new();
243 for ((line, arm_count), expected_total) in tracker.expected_arms {
244 let visited = tracker.visited_arms.get(&(line, arm_count));
245 let mut missing = Vec::new();
246 for arm_idx in 0..expected_total {
247 let covered = visited.is_some_and(|set| set.contains(&arm_idx));
248 if !covered {
249 missing.push(arm_idx);
250 }
251 }
252 if !missing.is_empty() {
253 misses.push(VerifyMatchCoverageMiss {
254 line,
255 total_arms: expected_total,
256 missing_arms: missing,
257 });
258 }
259 }
260 misses
261 }
262
263 pub(super) fn note_verify_match_arm(&mut self, line: usize, arm_count: usize, arm_idx: usize) {
264 let Some(tracker) = self.verify_match_coverage.as_mut() else {
265 return;
266 };
267 let Some(frame) = self.call_stack.last() else {
268 return;
269 };
270 if frame.name != tracker.target_fn {
271 return;
272 }
273 let key = (line, arm_count);
274 if !tracker.expected_arms.contains_key(&key) {
275 return;
276 }
277 tracker.visited_arms.entry(key).or_default().insert(arm_idx);
278 }
279
280 fn collect_match_sites_from_fn_body(
281 body: &FnBody,
282 out: &mut std::collections::BTreeMap<MatchSiteKey, usize>,
283 ) {
284 match body {
285 FnBody::Expr(expr) => Self::collect_match_sites_from_expr(expr, out),
286 FnBody::Block(stmts) => {
287 for stmt in stmts {
288 Self::collect_match_sites_from_stmt(stmt, out);
289 }
290 }
291 }
292 }
293
294 fn collect_match_sites_from_stmt(
295 stmt: &Stmt,
296 out: &mut std::collections::BTreeMap<MatchSiteKey, usize>,
297 ) {
298 match stmt {
299 Stmt::Binding(_, _, expr) | Stmt::Expr(expr) => {
300 Self::collect_match_sites_from_expr(expr, out);
301 }
302 }
303 }
304
305 fn collect_match_sites_from_expr(
306 expr: &Expr,
307 out: &mut std::collections::BTreeMap<MatchSiteKey, usize>,
308 ) {
309 match expr {
310 Expr::Match {
311 subject,
312 arms,
313 line,
314 } => {
315 out.insert((*line, arms.len()), arms.len());
316 Self::collect_match_sites_from_expr(subject, out);
317 for arm in arms {
318 Self::collect_match_sites_from_expr(&arm.body, out);
319 }
320 }
321 Expr::FnCall(fn_expr, args) => {
322 Self::collect_match_sites_from_expr(fn_expr, out);
323 for arg in args {
324 Self::collect_match_sites_from_expr(arg, out);
325 }
326 }
327 Expr::BinOp(_, left, right) | Expr::Pipe(left, right) => {
328 Self::collect_match_sites_from_expr(left, out);
329 Self::collect_match_sites_from_expr(right, out);
330 }
331 Expr::Attr(obj, _) | Expr::ErrorProp(obj) => {
332 Self::collect_match_sites_from_expr(obj, out);
333 }
334 Expr::Constructor(_, maybe_arg) => {
335 if let Some(arg) = maybe_arg {
336 Self::collect_match_sites_from_expr(arg, out);
337 }
338 }
339 Expr::InterpolatedStr(parts) => {
340 for part in parts {
341 if let StrPart::Parsed(expr) = part {
342 Self::collect_match_sites_from_expr(expr, out);
343 }
344 }
345 }
346 Expr::List(items) | Expr::Tuple(items) => {
347 for item in items {
348 Self::collect_match_sites_from_expr(item, out);
349 }
350 }
351 Expr::MapLiteral(entries) => {
352 for (key, value) in entries {
353 Self::collect_match_sites_from_expr(key, out);
354 Self::collect_match_sites_from_expr(value, out);
355 }
356 }
357 Expr::RecordCreate { fields, .. } => {
358 for (_, expr) in fields {
359 Self::collect_match_sites_from_expr(expr, out);
360 }
361 }
362 Expr::RecordUpdate { base, updates, .. } => {
363 Self::collect_match_sites_from_expr(base, out);
364 for (_, expr) in updates {
365 Self::collect_match_sites_from_expr(expr, out);
366 }
367 }
368 Expr::TailCall(boxed) => {
369 for arg in &boxed.1 {
370 Self::collect_match_sites_from_expr(arg, out);
371 }
372 }
373 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved(_) => {}
374 }
375 }
376
377 pub(super) fn expand_effects(&self, effects: &[String]) -> Vec<String> {
379 let mut result = Vec::new();
380 for e in effects {
381 if let Some(expanded) = self.effect_aliases.get(e) {
382 result.extend(expanded.iter().cloned());
383 } else {
384 result.push(e.clone());
385 }
386 }
387 result
388 }
389
390 pub(super) fn push_env(&mut self, frame: EnvFrame) {
394 self.env.push(frame);
395 }
396
397 pub(super) fn pop_env(&mut self) {
398 if self.env.len() > 1 {
399 self.env.pop();
400 }
401 }
402
403 pub(super) fn last_owned_scope_mut(
404 &mut self,
405 ) -> Result<&mut HashMap<String, Rc<Value>>, RuntimeError> {
406 let frame = self
407 .env
408 .last_mut()
409 .ok_or_else(|| RuntimeError::Error("No active scope".to_string()))?;
410 match frame {
411 EnvFrame::Owned(scope) => Ok(scope),
412 EnvFrame::Shared(_) | EnvFrame::Slots(_) => Err(RuntimeError::Error(
413 "Cannot define name in non-owned frame".to_string(),
414 )),
415 }
416 }
417
418 pub(super) fn lookup_rc(&self, name: &str) -> Result<&Rc<Value>, RuntimeError> {
419 for frame in self.env.iter().rev() {
420 let found = match frame {
421 EnvFrame::Owned(scope) => scope.get(name),
422 EnvFrame::Shared(scope) => scope.get(name),
423 EnvFrame::Slots(_) => None,
425 };
426 if let Some(v) = found {
427 return Ok(v);
428 }
429 }
430 Err(RuntimeError::Error(format!(
431 "Undefined variable: '{}'",
432 name
433 )))
434 }
435
436 pub(super) fn global_scope_clone(&self) -> Result<HashMap<String, Rc<Value>>, RuntimeError> {
437 let frame = self
438 .env
439 .first()
440 .ok_or_else(|| RuntimeError::Error("No global scope".to_string()))?;
441 match frame {
442 EnvFrame::Owned(scope) => Ok(scope.clone()),
443 EnvFrame::Shared(scope) => Ok((**scope).clone()),
444 EnvFrame::Slots(_) => Err(RuntimeError::Error(
445 "Invalid global scope frame: Slots".to_string(),
446 )),
447 }
448 }
449
450 pub fn lookup(&self, name: &str) -> Result<Value, RuntimeError> {
451 self.lookup_rc(name).map(|rc| (**rc).clone())
452 }
453
454 pub fn define(&mut self, name: String, val: Value) {
455 if let Ok(scope) = self.last_owned_scope_mut() {
456 scope.insert(name, Rc::new(val));
457 }
458 }
459
460 pub(super) fn lookup_slot(&self, slot: u16) -> Result<Value, RuntimeError> {
462 let idx = self.env.len() - 1;
463 match &self.env[idx] {
464 EnvFrame::Slots(v) => Ok(v[slot as usize].as_ref().clone()),
465 _ => {
466 Err(RuntimeError::Error(
468 "Resolved lookup on non-Slots frame".to_string(),
469 ))
470 }
471 }
472 }
473
474 pub(super) fn define_slot(&mut self, slot: u16, val: Value) {
476 let idx = self.env.len() - 1;
477 if let EnvFrame::Slots(v) = &mut self.env[idx] {
478 v[slot as usize] = Rc::new(val);
479 }
480 }
481
482 pub fn define_module_path(&mut self, path: &str, val: Value) -> Result<(), RuntimeError> {
483 let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
484 if parts.is_empty() {
485 return Err(RuntimeError::Error("Empty module path".to_string()));
486 }
487 if parts.len() == 1 {
488 self.define(parts[0].to_string(), val);
489 return Ok(());
490 }
491
492 let scope = self.last_owned_scope_mut()?;
493 let head = parts[0];
494 let tail = &parts[1..];
495
496 if let Some(rc_existing) = scope.remove(head) {
497 let existing = Rc::try_unwrap(rc_existing).unwrap_or_else(|rc| (*rc).clone());
498 match existing {
499 Value::Namespace { name, mut members } => {
500 Self::insert_namespace_path(&mut members, tail, val)?;
501 scope.insert(
502 head.to_string(),
503 Rc::new(Value::Namespace { name, members }),
504 );
505 Ok(())
506 }
507 _ => Err(RuntimeError::Error(format!(
508 "Cannot mount module '{}': '{}' is not a namespace",
509 parts.join("."),
510 head
511 ))),
512 }
513 } else {
514 let mut members = HashMap::new();
515 Self::insert_namespace_path(&mut members, tail, val)?;
516 scope.insert(
517 head.to_string(),
518 Rc::new(Value::Namespace {
519 name: head.to_string(),
520 members,
521 }),
522 );
523 Ok(())
524 }
525 }
526
527 pub(super) fn insert_namespace_path(
528 scope: &mut HashMap<String, Value>,
529 parts: &[&str],
530 val: Value,
531 ) -> Result<(), RuntimeError> {
532 if parts.len() == 1 {
533 scope.insert(parts[0].to_string(), val);
534 return Ok(());
535 }
536
537 let head = parts[0];
538 let tail = &parts[1..];
539
540 if let Some(existing) = scope.remove(head) {
541 match existing {
542 Value::Namespace { name, mut members } => {
543 Self::insert_namespace_path(&mut members, tail, val)?;
544 scope.insert(head.to_string(), Value::Namespace { name, members });
545 Ok(())
546 }
547 _ => Err(RuntimeError::Error(format!(
548 "Cannot mount module '{}': '{}' is not a namespace",
549 parts.join("."),
550 head
551 ))),
552 }
553 } else {
554 let mut members = HashMap::new();
555 Self::insert_namespace_path(&mut members, tail, val)?;
556 scope.insert(
557 head.to_string(),
558 Value::Namespace {
559 name: head.to_string(),
560 members,
561 },
562 );
563 Ok(())
564 }
565 }
566
567 pub(super) fn module_cache_key(path: &Path) -> String {
568 canonicalize_path(path).to_string_lossy().to_string()
569 }
570
571 pub(super) fn module_decl(items: &[TopLevel]) -> Option<&Module> {
572 items.iter().find_map(|item| {
573 if let TopLevel::Module(m) = item {
574 Some(m)
575 } else {
576 None
577 }
578 })
579 }
580
581 pub(super) fn exposed_set(items: &[TopLevel]) -> Option<HashSet<String>> {
582 Self::module_decl(items).and_then(|m| {
583 if m.exposes.is_empty() {
584 None
585 } else {
586 Some(m.exposes.iter().cloned().collect())
587 }
588 })
589 }
590
591 pub(super) fn cycle_display(loading: &[String], next: &str) -> String {
592 let mut chain = loading
593 .iter()
594 .map(|key| {
595 Path::new(key)
596 .file_stem()
597 .and_then(|s| s.to_str())
598 .unwrap_or(key)
599 .to_string()
600 })
601 .collect::<Vec<_>>();
602 chain.push(
603 Path::new(next)
604 .file_stem()
605 .and_then(|s| s.to_str())
606 .unwrap_or(next)
607 .to_string(),
608 );
609 chain.join(" -> ")
610 }
611
612 pub fn load_module(
613 &mut self,
614 name: &str,
615 base_dir: &str,
616 loading: &mut Vec<String>,
617 loading_set: &mut HashSet<String>,
618 ) -> Result<Value, RuntimeError> {
619 let path = find_module_file(name, base_dir).ok_or_else(|| {
620 RuntimeError::Error(format!("Module '{}' not found in '{}'", name, base_dir))
621 })?;
622 let cache_key = Self::module_cache_key(&path);
623
624 if let Some(cached) = self.module_cache.get(&cache_key) {
625 return Ok(cached.clone());
626 }
627
628 if loading_set.contains(&cache_key) {
629 return Err(RuntimeError::Error(format!(
630 "Circular import: {}",
631 Self::cycle_display(loading, &cache_key)
632 )));
633 }
634
635 loading.push(cache_key.clone());
636 loading_set.insert(cache_key.clone());
637 let result = (|| -> Result<Value, RuntimeError> {
638 let src = std::fs::read_to_string(&path).map_err(|e| {
639 RuntimeError::Error(format!("Cannot read '{}': {}", path.display(), e))
640 })?;
641 let mut items = parse_source(&src).map_err(|e| {
642 RuntimeError::Error(format!("Parse error in '{}': {}", path.display(), e))
643 })?;
644 require_module_declaration(&items, &path.to_string_lossy())
645 .map_err(RuntimeError::Error)?;
646 crate::resolver::resolve_program(&mut items);
647
648 if let Some(module) = Self::module_decl(&items) {
649 let expected = name.rsplit('.').next().unwrap_or(name);
650 if module.name != expected {
651 return Err(RuntimeError::Error(format!(
652 "Module name mismatch: expected '{}' (from '{}'), found '{}' in '{}'",
653 expected,
654 name,
655 module.name,
656 path.display()
657 )));
658 }
659 }
660
661 let mut sub = Interpreter::new();
662
663 if let Some(module) = Self::module_decl(&items) {
664 for dep_name in &module.depends {
665 let dep_ns = self.load_module(dep_name, base_dir, loading, loading_set)?;
666 sub.define_module_path(dep_name, dep_ns)?;
667 }
668 }
669
670 for item in &items {
671 if let TopLevel::EffectSet { name, effects } = item {
672 sub.register_effect_set(name.clone(), effects.clone());
673 }
674 }
675 for item in &items {
676 if let TopLevel::TypeDef(td) = item {
677 sub.register_type_def(td);
678 }
679 }
680 for item in &items {
681 if let TopLevel::FnDef(fd) = item {
682 sub.exec_fn_def(fd)?;
683 }
684 }
685 let module_globals = Rc::new(sub.global_scope_clone()?);
686
687 let exposed = Self::exposed_set(&items);
688 let mut members = HashMap::new();
689 for item in &items {
690 match item {
691 TopLevel::FnDef(fd) => {
692 let include = match &exposed {
693 Some(set) => set.contains(&fd.name),
694 None => !fd.name.starts_with('_'),
695 };
696 if include {
697 let mut val = sub.lookup(&fd.name).map_err(|_| {
698 RuntimeError::Error(format!(
699 "Failed to export '{}.{}'",
700 name, fd.name
701 ))
702 })?;
703 if let Value::Fn { home_globals, .. } = &mut val {
704 *home_globals = Some(Rc::clone(&module_globals));
705 }
706 members.insert(fd.name.clone(), val);
707 }
708 }
709 TopLevel::TypeDef(TypeDef::Sum {
710 name: type_name, ..
711 }) => {
712 let include = match &exposed {
713 Some(set) => set.contains(type_name),
714 None => !type_name.starts_with('_'),
715 };
716 if include {
717 let val = sub.lookup(type_name).map_err(|_| {
718 RuntimeError::Error(format!(
719 "Failed to export '{}.{}'",
720 name, type_name
721 ))
722 })?;
723 members.insert(type_name.clone(), val);
724 }
725 }
726 _ => {}
727 }
728 }
729
730 Ok(Value::Namespace {
731 name: name.to_string(),
732 members,
733 })
734 })();
735 loading.pop();
736 loading_set.remove(&cache_key);
737
738 match result {
739 Ok(ns) => {
740 self.module_cache.insert(cache_key, ns.clone());
741 Ok(ns)
742 }
743 Err(e) => Err(e),
744 }
745 }
746}