Skip to main content

aver/interpreter/
core.rs

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        // Result and Option namespaces — constructors for Ok/Err/Some/None
21        {
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    /// Mark a set of function names as eligible for auto-memoization.
204    pub fn enable_memo(&mut self, fns: HashSet<String>) {
205        self.memo_fns = fns;
206    }
207
208    /// Register a named effect set alias.
209    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    /// Expand effect names one level: aliases → concrete effect names.
378    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    // -------------------------------------------------------------------------
391    // Environment management
392    // -------------------------------------------------------------------------
393    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                // Slots frames are indexed by slot, not by name — skip in name-based lookup
424                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    /// O(1) slot-based variable lookup for resolved function bodies.
461    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                // Fallback — shouldn't happen if resolver is correct
467                Err(RuntimeError::Error(
468                    "Resolved lookup on non-Slots frame".to_string(),
469                ))
470            }
471        }
472    }
473
474    /// Define a value in the current Slots frame at the given slot index.
475    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}