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