Skip to main content

aver/interpreter/
core.rs

1use super::*;
2
3impl Default for Interpreter {
4    fn default() -> Self {
5        Self::new()
6    }
7}
8
9impl Interpreter {
10    pub fn new() -> Self {
11        let mut arena = Arena::new();
12        let mut global: HashMap<String, NanValue> = HashMap::new();
13
14        // Register all namespaces directly as NanValue (no Value→NanValue conversion)
15        args::register_nv(&mut global, &mut arena);
16        console::register_nv(&mut global, &mut arena);
17        http::register_nv(&mut global, &mut arena);
18        http_server::register_nv(&mut global, &mut arena);
19        disk::register_nv(&mut global, &mut arena);
20        env::register_nv(&mut global, &mut arena);
21        random::register_nv(&mut global, &mut arena);
22        tcp::register_nv(&mut global, &mut arena);
23        #[cfg(feature = "terminal")]
24        terminal::register_nv(&mut global, &mut arena);
25        time::register_nv(&mut global, &mut arena);
26        bool::register_nv(&mut global, &mut arena);
27        int::register_nv(&mut global, &mut arena);
28        float::register_nv(&mut global, &mut arena);
29        string::register_nv(&mut global, &mut arena);
30        list::register_nv(&mut global, &mut arena);
31        map::register_nv(&mut global, &mut arena);
32        char::register_nv(&mut global, &mut arena);
33        byte::register_nv(&mut global, &mut arena);
34
35        // Result namespace
36        {
37            use std::rc::Rc;
38            let mut members: Vec<(Rc<str>, NanValue)> = Vec::new();
39            let ok_idx = arena.push_builtin("__ctor:Result.Ok");
40            members.push((Rc::from("Ok"), NanValue::new_builtin(ok_idx)));
41            let err_idx = arena.push_builtin("__ctor:Result.Err");
42            members.push((Rc::from("Err"), NanValue::new_builtin(err_idx)));
43            for (name, builtin_name) in result::extra_members() {
44                let idx = arena.push_builtin(&builtin_name);
45                members.push((Rc::from(name), NanValue::new_builtin(idx)));
46            }
47            let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
48                name: Rc::from("Result"),
49                members,
50            });
51            global.insert("Result".to_string(), NanValue::new_namespace(ns_idx));
52        }
53        // Option namespace
54        {
55            use std::rc::Rc;
56            let mut members: Vec<(Rc<str>, NanValue)> = Vec::new();
57            let some_idx = arena.push_builtin("__ctor:Option.Some");
58            members.push((Rc::from("Some"), NanValue::new_builtin(some_idx)));
59            members.push((Rc::from("None"), NanValue::NONE));
60            for (name, builtin_name) in option::extra_members() {
61                let idx = arena.push_builtin(&builtin_name);
62                members.push((Rc::from(name), NanValue::new_builtin(idx)));
63            }
64            let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
65                name: Rc::from("Option"),
66                members,
67            });
68            global.insert("Option".to_string(), NanValue::new_namespace(ns_idx));
69        }
70
71        let mut record_schemas = HashMap::new();
72        record_schemas.insert(
73            "HttpResponse".to_string(),
74            vec![
75                "status".to_string(),
76                "body".to_string(),
77                "headers".to_string(),
78            ],
79        );
80        record_schemas.insert(
81            "HttpRequest".to_string(),
82            vec![
83                "method".to_string(),
84                "path".to_string(),
85                "body".to_string(),
86                "headers".to_string(),
87            ],
88        );
89        record_schemas.insert(
90            "Header".to_string(),
91            vec!["name".to_string(), "value".to_string()],
92        );
93        record_schemas.insert(
94            "Tcp.Connection".to_string(),
95            vec!["id".to_string(), "host".to_string(), "port".to_string()],
96        );
97        #[cfg(feature = "terminal")]
98        record_schemas.insert(
99            "Terminal.Size".to_string(),
100            vec!["width".to_string(), "height".to_string()],
101        );
102
103        Interpreter {
104            env: vec![EnvFrame::Owned(global)],
105            env_base: 1,
106            arena,
107            module_cache: HashMap::new(),
108            record_schemas,
109            call_stack: Vec::new(),
110            active_local_slots: None,
111            memo_fns: HashSet::new(),
112            memo_cache: HashMap::new(),
113            execution_mode: ExecutionMode::Normal,
114            recorded_effects: Vec::new(),
115            replay_effects: Vec::new(),
116            replay_pos: 0,
117            validate_replay_args: false,
118            recording_sink: None,
119            verify_match_coverage: None,
120            runtime_policy: None,
121            cli_args: Vec::new(),
122        }
123    }
124
125    pub fn execution_mode(&self) -> ExecutionMode {
126        self.execution_mode
127    }
128
129    pub fn set_execution_mode_normal(&mut self) {
130        self.execution_mode = ExecutionMode::Normal;
131        self.recorded_effects.clear();
132        self.replay_effects.clear();
133        self.replay_pos = 0;
134        self.validate_replay_args = false;
135    }
136
137    pub fn start_recording(&mut self) {
138        self.execution_mode = ExecutionMode::Record;
139        self.recorded_effects.clear();
140        self.replay_effects.clear();
141        self.replay_pos = 0;
142        self.validate_replay_args = false;
143    }
144
145    pub fn start_replay(&mut self, effects: Vec<EffectRecord>, validate_args: bool) {
146        self.execution_mode = ExecutionMode::Replay;
147        self.replay_effects = effects;
148        self.replay_pos = 0;
149        self.validate_replay_args = validate_args;
150        self.recorded_effects.clear();
151    }
152
153    pub fn take_recorded_effects(&mut self) -> Vec<EffectRecord> {
154        std::mem::take(&mut self.recorded_effects)
155    }
156
157    pub fn replay_progress(&self) -> (usize, usize) {
158        (self.replay_pos, self.replay_effects.len())
159    }
160
161    pub fn ensure_replay_consumed(&self) -> Result<(), RuntimeError> {
162        if self.execution_mode == ExecutionMode::Replay
163            && self.replay_pos < self.replay_effects.len()
164        {
165            return Err(RuntimeError::ReplayUnconsumed {
166                remaining: self.replay_effects.len() - self.replay_pos,
167            });
168        }
169        Ok(())
170    }
171
172    pub fn configure_recording_sink(&mut self, cfg: RecordingConfig) {
173        self.recording_sink = Some(RecordingSink {
174            path: cfg.path,
175            request_id: cfg.request_id,
176            timestamp: cfg.timestamp,
177            program_file: cfg.program_file,
178            module_root: cfg.module_root,
179            entry_fn: cfg.entry_fn,
180            input: cfg.input,
181        });
182    }
183
184    pub fn recording_sink_path(&self) -> Option<std::path::PathBuf> {
185        self.recording_sink.as_ref().map(|s| s.path.clone())
186    }
187
188    pub fn persist_recording_snapshot(&self, output: RecordedOutcome) -> Result<(), RuntimeError> {
189        let Some(sink) = &self.recording_sink else {
190            return Ok(());
191        };
192
193        let recording = SessionRecording {
194            schema_version: 1,
195            request_id: sink.request_id.clone(),
196            timestamp: sink.timestamp.clone(),
197            program_file: sink.program_file.clone(),
198            module_root: sink.module_root.clone(),
199            entry_fn: sink.entry_fn.clone(),
200            input: sink.input.clone(),
201            effects: self.recorded_effects.clone(),
202            output,
203        };
204
205        let json = session_recording_to_string_pretty(&recording);
206        std::fs::write(&sink.path, json).map_err(|e| {
207            RuntimeError::Error(format!(
208                "Cannot write recording '{}': {}",
209                sink.path.display(),
210                e
211            ))
212        })?;
213        Ok(())
214    }
215
216    /// Mark a set of function names as eligible for auto-memoization.
217    pub fn enable_memo(&mut self, fns: HashSet<String>) {
218        self.memo_fns = fns;
219    }
220
221    /// Register a named effect set alias.
222    /// Set the runtime policy from an `aver.toml` configuration.
223    pub fn set_runtime_policy(&mut self, config: crate::config::ProjectConfig) {
224        self.runtime_policy = Some(config);
225    }
226
227    /// Set command-line arguments available via `Args.get()`.
228    pub fn set_cli_args(&mut self, args: Vec<String>) {
229        self.cli_args = args;
230    }
231
232    /// Check whether a builtin call is permitted by the runtime policy.
233    /// Skipped in Replay mode (deterministic playback).
234    pub(super) fn check_runtime_policy(
235        &self,
236        name: &str,
237        args: &[Value],
238    ) -> Result<(), RuntimeError> {
239        if self.execution_mode == ExecutionMode::Replay {
240            return Ok(());
241        }
242        let Some(policy) = &self.runtime_policy else {
243            return Ok(());
244        };
245
246        if name.starts_with("Http.") {
247            if let Some(Value::Str(url)) = args.first() {
248                policy
249                    .check_http_host(name, url)
250                    .map_err(RuntimeError::Error)?;
251            }
252        } else if name.starts_with("Disk.") {
253            if let Some(Value::Str(path)) = args.first() {
254                policy
255                    .check_disk_path(name, path)
256                    .map_err(RuntimeError::Error)?;
257            }
258        } else if name.starts_with("Env.")
259            && let Some(Value::Str(key)) = args.first()
260        {
261            policy
262                .check_env_key(name, key)
263                .map_err(RuntimeError::Error)?;
264        }
265
266        Ok(())
267    }
268
269    pub fn start_verify_match_coverage(&mut self, fn_name: &str) {
270        let Ok(fn_val) = self.lookup(fn_name) else {
271            self.verify_match_coverage = None;
272            return;
273        };
274        let Value::Fn(function) = fn_val else {
275            self.verify_match_coverage = None;
276            return;
277        };
278
279        let mut expected = std::collections::BTreeMap::new();
280        Self::collect_match_sites_from_fn_body(function.body.as_ref(), &mut expected);
281        if expected.is_empty() {
282            self.verify_match_coverage = None;
283            return;
284        }
285
286        self.verify_match_coverage = Some(VerifyMatchCoverageTracker {
287            target_fn: fn_name.to_string(),
288            expected_arms: expected,
289            visited_arms: HashMap::new(),
290        });
291    }
292
293    pub fn finish_verify_match_coverage(&mut self) -> Vec<VerifyMatchCoverageMiss> {
294        let Some(tracker) = self.verify_match_coverage.take() else {
295            return vec![];
296        };
297
298        let mut misses = Vec::new();
299        for ((line, arm_count), expected_total) in tracker.expected_arms {
300            let visited = tracker.visited_arms.get(&(line, arm_count));
301            let mut missing = Vec::new();
302            for arm_idx in 0..expected_total {
303                let covered = visited.is_some_and(|set| set.contains(&arm_idx));
304                if !covered {
305                    missing.push(arm_idx);
306                }
307            }
308            if !missing.is_empty() {
309                misses.push(VerifyMatchCoverageMiss {
310                    line,
311                    total_arms: expected_total,
312                    missing_arms: missing,
313                });
314            }
315        }
316        misses
317    }
318
319    pub(super) fn note_verify_match_arm(&mut self, line: usize, arm_count: usize, arm_idx: usize) {
320        let Some(tracker) = self.verify_match_coverage.as_mut() else {
321            return;
322        };
323        let Some(frame) = self.call_stack.last() else {
324            return;
325        };
326        if frame.name.as_str() != tracker.target_fn {
327            return;
328        }
329        let key = (line, arm_count);
330        if !tracker.expected_arms.contains_key(&key) {
331            return;
332        }
333        tracker.visited_arms.entry(key).or_default().insert(arm_idx);
334    }
335
336    fn collect_match_sites_from_fn_body(
337        body: &FnBody,
338        out: &mut std::collections::BTreeMap<MatchSiteKey, usize>,
339    ) {
340        for stmt in body.stmts() {
341            Self::collect_match_sites_from_stmt(stmt, out);
342        }
343    }
344
345    fn collect_match_sites_from_stmt(
346        stmt: &Stmt,
347        out: &mut std::collections::BTreeMap<MatchSiteKey, usize>,
348    ) {
349        match stmt {
350            Stmt::Binding(_, _, expr) | Stmt::Expr(expr) => {
351                Self::collect_match_sites_from_expr(expr, out);
352            }
353        }
354    }
355
356    fn collect_match_sites_from_expr(
357        expr: &Expr,
358        out: &mut std::collections::BTreeMap<MatchSiteKey, usize>,
359    ) {
360        match expr {
361            Expr::Match {
362                subject,
363                arms,
364                line,
365            } => {
366                out.insert((*line, arms.len()), arms.len());
367                Self::collect_match_sites_from_expr(subject, out);
368                for arm in arms {
369                    Self::collect_match_sites_from_expr(&arm.body, out);
370                }
371            }
372            Expr::FnCall(fn_expr, args) => {
373                Self::collect_match_sites_from_expr(fn_expr, out);
374                for arg in args {
375                    Self::collect_match_sites_from_expr(arg, out);
376                }
377            }
378            Expr::BinOp(_, left, right) => {
379                Self::collect_match_sites_from_expr(left, out);
380                Self::collect_match_sites_from_expr(right, out);
381            }
382            Expr::Attr(obj, _) | Expr::ErrorProp(obj) => {
383                Self::collect_match_sites_from_expr(obj, out);
384            }
385            Expr::Constructor(_, maybe_arg) => {
386                if let Some(arg) = maybe_arg {
387                    Self::collect_match_sites_from_expr(arg, out);
388                }
389            }
390            Expr::InterpolatedStr(parts) => {
391                for part in parts {
392                    if let StrPart::Parsed(expr) = part {
393                        Self::collect_match_sites_from_expr(expr, out);
394                    }
395                }
396            }
397            Expr::List(items) | Expr::Tuple(items) => {
398                for item in items {
399                    Self::collect_match_sites_from_expr(item, out);
400                }
401            }
402            Expr::MapLiteral(entries) => {
403                for (key, value) in entries {
404                    Self::collect_match_sites_from_expr(key, out);
405                    Self::collect_match_sites_from_expr(value, out);
406                }
407            }
408            Expr::RecordCreate { fields, .. } => {
409                for (_, expr) in fields {
410                    Self::collect_match_sites_from_expr(expr, out);
411                }
412            }
413            Expr::RecordUpdate { base, updates, .. } => {
414                Self::collect_match_sites_from_expr(base, out);
415                for (_, expr) in updates {
416                    Self::collect_match_sites_from_expr(expr, out);
417                }
418            }
419            Expr::TailCall(boxed) => {
420                for arg in &boxed.1 {
421                    Self::collect_match_sites_from_expr(arg, out);
422                }
423            }
424            Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved(_) => {}
425        }
426    }
427
428    // -------------------------------------------------------------------------
429    // Environment management
430    // -------------------------------------------------------------------------
431    pub(super) fn push_env(&mut self, frame: EnvFrame) {
432        self.env.push(frame);
433    }
434
435    pub(super) fn pop_env(&mut self) {
436        if self.env.len() > 1 {
437            self.env.pop();
438        }
439    }
440
441    pub(super) fn last_owned_scope_mut(
442        &mut self,
443    ) -> Result<&mut HashMap<String, NanValue>, RuntimeError> {
444        let frame = self
445            .env
446            .last_mut()
447            .ok_or_else(|| RuntimeError::Error("No active scope".to_string()))?;
448        match frame {
449            EnvFrame::Owned(scope) => Ok(scope),
450            EnvFrame::Shared(_) | EnvFrame::Slots(_) => Err(RuntimeError::Error(
451                "Cannot define name in non-owned frame".to_string(),
452            )),
453        }
454    }
455
456    /// Primary lookup — returns NanValue (Copy, 8 bytes, no clone needed).
457    pub(super) fn lookup_nv(&self, name: &str) -> Result<NanValue, RuntimeError> {
458        let env = &self.env;
459        let mut i = env.len();
460        let base = self.env_base;
461        while i > base {
462            i -= 1;
463            match &env[i] {
464                EnvFrame::Owned(scope) => {
465                    if let Some(v) = scope.get(name) {
466                        return Ok(*v);
467                    }
468                }
469                EnvFrame::Shared(scope) => {
470                    if let Some(v) = scope.get(name) {
471                        return Ok(*v);
472                    }
473                }
474                EnvFrame::Slots(_) => {}
475            }
476        }
477        match &env[0] {
478            EnvFrame::Owned(scope) => scope.get(name).copied(),
479            EnvFrame::Shared(scope) => scope.get(name).copied(),
480            EnvFrame::Slots(_) => None,
481        }
482        .ok_or_else(|| RuntimeError::Error(format!("Undefined variable: '{}'", name)))
483    }
484
485    pub(super) fn global_scope_clone(&self) -> Result<HashMap<String, NanValue>, RuntimeError> {
486        let frame = self
487            .env
488            .first()
489            .ok_or_else(|| RuntimeError::Error("No global scope".to_string()))?;
490        match frame {
491            EnvFrame::Owned(scope) => Ok(scope.clone()),
492            EnvFrame::Shared(scope) => Ok((**scope).clone()),
493            EnvFrame::Slots(_) => Err(RuntimeError::Error(
494                "Invalid global scope frame: Slots".to_string(),
495            )),
496        }
497    }
498
499    /// Public lookup — returns old Value for callers not yet migrated.
500    pub fn lookup(&self, name: &str) -> Result<Value, RuntimeError> {
501        let nv = self.lookup_nv(name)?;
502        Ok(nv.to_value(&self.arena))
503    }
504
505    /// Public define — accepts old Value, converts to NanValue internally.
506    pub fn define(&mut self, name: String, val: Value) {
507        let nv = NanValue::from_value(&val, &mut self.arena);
508        if let Ok(scope) = self.last_owned_scope_mut() {
509            scope.insert(name, nv);
510        }
511    }
512
513    /// Define with NanValue directly.
514    pub(super) fn define_nv(&mut self, name: String, nv: NanValue) {
515        if let Ok(scope) = self.last_owned_scope_mut() {
516            scope.insert(name, nv);
517        }
518    }
519
520    fn alias_exposed_type_namespaces(&mut self, module_val: &Value) {
521        let Value::Namespace { members, .. } = module_val else {
522            return;
523        };
524        for (name, member) in members {
525            if matches!(member, Value::Namespace { .. }) {
526                self.define(name.clone(), member.clone());
527            }
528        }
529    }
530
531    /// O(1) slot-based variable lookup — returns NanValue (Copy, no clone).
532    pub(super) fn lookup_slot(&self, slot: u16) -> Result<NanValue, RuntimeError> {
533        let idx = self.env.len() - 1;
534        match &self.env[idx] {
535            EnvFrame::Slots(v) => Ok(v[slot as usize]),
536            _ => Err(RuntimeError::Error(
537                "Resolved lookup on non-Slots frame".to_string(),
538            )),
539        }
540    }
541
542    /// Define a value in the current Slots frame at the given slot index.
543    pub(super) fn define_slot(&mut self, slot: u16, val: NanValue) {
544        let idx = self.env.len() - 1;
545        if let EnvFrame::Slots(v) = &mut self.env[idx] {
546            v[slot as usize] = val;
547        }
548    }
549
550    pub fn define_module_path(&mut self, path: &str, val: Value) -> Result<(), RuntimeError> {
551        let alias_source = val.clone();
552        let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
553        if parts.is_empty() {
554            return Err(RuntimeError::Error("Empty module path".to_string()));
555        }
556        if parts.len() == 1 {
557            self.define(parts[0].to_string(), val);
558            self.alias_exposed_type_namespaces(&alias_source);
559            return Ok(());
560        }
561
562        let head = parts[0];
563        let tail = &parts[1..];
564
565        let result = {
566            let scope = self.last_owned_scope_mut()?;
567            if let Some(existing_nv) = scope.remove(head) {
568                let existing = existing_nv.to_value(&self.arena);
569                match existing {
570                    Value::Namespace { name, mut members } => {
571                        Self::insert_namespace_path(&mut members, tail, val)?;
572                        let ns = Value::Namespace { name, members };
573                        let nv = NanValue::from_value(&ns, &mut self.arena);
574                        // Re-borrow scope after arena mutation
575                        let scope = self.last_owned_scope_mut()?;
576                        scope.insert(head.to_string(), nv);
577                        Ok(())
578                    }
579                    _ => {
580                        // Put it back
581                        let scope2 = self.last_owned_scope_mut()?;
582                        scope2.insert(head.to_string(), existing_nv);
583                        Err(RuntimeError::Error(format!(
584                            "Cannot mount module '{}': '{}' is not a namespace",
585                            parts.join("."),
586                            head
587                        )))
588                    }
589                }
590            } else {
591                let mut members = HashMap::new();
592                Self::insert_namespace_path(&mut members, tail, val)?;
593                let ns = Value::Namespace {
594                    name: head.to_string(),
595                    members,
596                };
597                let nv = NanValue::from_value(&ns, &mut self.arena);
598                let scope = self.last_owned_scope_mut()?;
599                scope.insert(head.to_string(), nv);
600                Ok(())
601            }
602        };
603
604        if result.is_ok() {
605            self.alias_exposed_type_namespaces(&alias_source);
606        }
607        result
608    }
609
610    pub(super) fn insert_namespace_path(
611        scope: &mut HashMap<String, Value>,
612        parts: &[&str],
613        val: Value,
614    ) -> Result<(), RuntimeError> {
615        if parts.len() == 1 {
616            scope.insert(parts[0].to_string(), val);
617            return Ok(());
618        }
619
620        let head = parts[0];
621        let tail = &parts[1..];
622
623        if let Some(existing) = scope.remove(head) {
624            match existing {
625                Value::Namespace { name, mut members } => {
626                    Self::insert_namespace_path(&mut members, tail, val)?;
627                    scope.insert(head.to_string(), Value::Namespace { name, members });
628                    Ok(())
629                }
630                _ => Err(RuntimeError::Error(format!(
631                    "Cannot mount module '{}': '{}' is not a namespace",
632                    parts.join("."),
633                    head
634                ))),
635            }
636        } else {
637            let mut members = HashMap::new();
638            Self::insert_namespace_path(&mut members, tail, val)?;
639            scope.insert(
640                head.to_string(),
641                Value::Namespace {
642                    name: head.to_string(),
643                    members,
644                },
645            );
646            Ok(())
647        }
648    }
649
650    pub(super) fn module_cache_key(path: &Path) -> String {
651        canonicalize_path(path).to_string_lossy().to_string()
652    }
653
654    pub(super) fn module_decl(items: &[TopLevel]) -> Option<&Module> {
655        items.iter().find_map(|item| {
656            if let TopLevel::Module(m) = item {
657                Some(m)
658            } else {
659                None
660            }
661        })
662    }
663
664    pub(super) fn exposed_set(items: &[TopLevel]) -> Option<HashSet<String>> {
665        Self::module_decl(items).and_then(|m| {
666            if m.exposes.is_empty() {
667                None
668            } else {
669                Some(m.exposes.iter().cloned().collect())
670            }
671        })
672    }
673
674    pub(super) fn cycle_display(loading: &[String], next: &str) -> String {
675        let mut chain = loading
676            .iter()
677            .map(|key| {
678                Path::new(key)
679                    .file_stem()
680                    .and_then(|s| s.to_str())
681                    .unwrap_or(key)
682                    .to_string()
683            })
684            .collect::<Vec<_>>();
685        chain.push(
686            Path::new(next)
687                .file_stem()
688                .and_then(|s| s.to_str())
689                .unwrap_or(next)
690                .to_string(),
691        );
692        chain.join(" -> ")
693    }
694
695    pub fn load_module(
696        &mut self,
697        name: &str,
698        base_dir: &str,
699        loading: &mut Vec<String>,
700        loading_set: &mut HashSet<String>,
701    ) -> Result<Value, RuntimeError> {
702        let path = find_module_file(name, base_dir).ok_or_else(|| {
703            RuntimeError::Error(format!("Module '{}' not found in '{}'", name, base_dir))
704        })?;
705        let cache_key = Self::module_cache_key(&path);
706
707        if let Some(cached) = self.module_cache.get(&cache_key) {
708            return Ok(cached.clone());
709        }
710
711        if loading_set.contains(&cache_key) {
712            return Err(RuntimeError::Error(format!(
713                "Circular import: {}",
714                Self::cycle_display(loading, &cache_key)
715            )));
716        }
717
718        loading.push(cache_key.clone());
719        loading_set.insert(cache_key.clone());
720        let result = (|| -> Result<Value, RuntimeError> {
721            let src = std::fs::read_to_string(&path).map_err(|e| {
722                RuntimeError::Error(format!("Cannot read '{}': {}", path.display(), e))
723            })?;
724            let mut items = parse_source(&src).map_err(|e| {
725                RuntimeError::Error(format!("Parse error in '{}': {}", path.display(), e))
726            })?;
727            require_module_declaration(&items, &path.to_string_lossy())
728                .map_err(RuntimeError::Error)?;
729            crate::resolver::resolve_program(&mut items);
730
731            if let Some(module) = Self::module_decl(&items) {
732                let expected = name.rsplit('.').next().unwrap_or(name);
733                if module.name != expected {
734                    return Err(RuntimeError::Error(format!(
735                        "Module name mismatch: expected '{}' (from '{}'), found '{}' in '{}'",
736                        expected,
737                        name,
738                        module.name,
739                        path.display()
740                    )));
741                }
742            }
743
744            let mut sub = Interpreter::new();
745
746            if let Some(module) = Self::module_decl(&items) {
747                for dep_name in &module.depends {
748                    let dep_ns = self.load_module(dep_name, base_dir, loading, loading_set)?;
749                    sub.define_module_path(dep_name, dep_ns)?;
750                }
751            }
752
753            for item in &items {
754                if let TopLevel::TypeDef(td) = item {
755                    sub.register_type_def(td);
756                }
757            }
758            for item in &items {
759                if let TopLevel::FnDef(fd) = item {
760                    sub.exec_fn_def(fd)?;
761                }
762            }
763            // Re-encode sub's NanValues into self's arena so that
764            // home_globals NanValue indices are valid in self.arena.
765            let sub_globals_nv = sub.global_scope_clone()?;
766            let mut reencoded_globals: HashMap<String, NanValue> =
767                HashMap::with_capacity(sub_globals_nv.len());
768            for (k, nv) in &sub_globals_nv {
769                let old_val = nv.to_value(&sub.arena);
770                let new_nv = NanValue::from_value(&old_val, &mut self.arena);
771                reencoded_globals.insert(k.clone(), new_nv);
772            }
773            let module_globals = Rc::new(reencoded_globals);
774
775            let exposed = Self::exposed_set(&items);
776            let opaque_set: HashSet<String> = Self::module_decl(&items)
777                .map(|m| m.exposes_opaque.iter().cloned().collect())
778                .unwrap_or_default();
779            let mut members = HashMap::new();
780            for item in &items {
781                match item {
782                    TopLevel::FnDef(fd) => {
783                        let include = match &exposed {
784                            Some(set) => set.contains(&fd.name),
785                            None => !fd.name.starts_with('_'),
786                        };
787                        if include {
788                            let mut val = sub.lookup(&fd.name).map_err(|_| {
789                                RuntimeError::Error(format!(
790                                    "Failed to export '{}.{}'",
791                                    name, fd.name
792                                ))
793                            })?;
794                            if let Value::Fn(function) = &mut val {
795                                Rc::make_mut(function).home_globals =
796                                    Some(Rc::clone(&module_globals));
797                            }
798                            members.insert(fd.name.clone(), val);
799                        }
800                    }
801                    TopLevel::TypeDef(TypeDef::Sum {
802                        name: type_name, ..
803                    }) => {
804                        // Skip opaque sum types — do not expose constructors.
805                        if opaque_set.contains(type_name) {
806                            continue;
807                        }
808                        let include = match &exposed {
809                            Some(set) => set.contains(type_name),
810                            None => !type_name.starts_with('_'),
811                        };
812                        if include {
813                            let val = sub.lookup(type_name).map_err(|_| {
814                                RuntimeError::Error(format!(
815                                    "Failed to export '{}.{}'",
816                                    name, type_name
817                                ))
818                            })?;
819                            members.insert(type_name.clone(), val);
820                        }
821                    }
822                    _ => {}
823                }
824            }
825
826            Ok(Value::Namespace {
827                name: name.to_string(),
828                members,
829            })
830        })();
831        loading.pop();
832        loading_set.remove(&cache_key);
833
834        match result {
835            Ok(ns) => {
836                self.module_cache.insert(cache_key, ns.clone());
837                Ok(ns)
838            }
839            Err(e) => Err(e),
840        }
841    }
842}