Skip to main content

harn_vm/vm/
imports.rs

1use std::cell::RefCell;
2use std::collections::{BTreeMap, HashSet};
3use std::future::Future;
4use std::path::{Path, PathBuf};
5use std::pin::Pin;
6use std::rc::Rc;
7
8use crate::value::{ModuleFunctionRegistry, VmClosure, VmEnv, VmError, VmValue};
9
10use super::{LoadedModule, ScopeSpan, Vm};
11
12impl Vm {
13    fn export_loaded_module(
14        &mut self,
15        module_path: &Path,
16        loaded: &LoadedModule,
17        selected_names: Option<&[String]>,
18    ) -> Result<(), VmError> {
19        let export_names: Vec<String> = if let Some(names) = selected_names {
20            names.to_vec()
21        } else if !loaded.public_names.is_empty() {
22            loaded.public_names.iter().cloned().collect()
23        } else {
24            loaded.functions.keys().cloned().collect()
25        };
26
27        let module_name = module_path.display().to_string();
28        for name in export_names {
29            let Some(closure) = loaded.functions.get(&name) else {
30                return Err(VmError::Runtime(format!(
31                    "Import error: '{name}' is not defined in {module_name}"
32                )));
33            };
34            if let Some(VmValue::Closure(_)) = self.env.get(&name) {
35                return Err(VmError::Runtime(format!(
36                    "Import collision: '{name}' is already defined when importing {module_name}. \
37                     Use selective imports to disambiguate: import {{ {name} }} from \"...\""
38                )));
39            }
40            self.env
41                .define(&name, VmValue::Closure(Rc::clone(closure)), false)?;
42        }
43        Ok(())
44    }
45
46    /// Execute an import, reading and running the file's declarations.
47    pub(super) fn execute_import<'a>(
48        &'a mut self,
49        path: &'a str,
50        selected_names: Option<&'a [String]>,
51    ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
52        Box::pin(async move {
53            let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
54
55            if let Some(module) = path.strip_prefix("std/") {
56                if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
57                    let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
58                    if self.imported_paths.contains(&synthetic) {
59                        return Ok(());
60                    }
61                    if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
62                        return self.export_loaded_module(&synthetic, &loaded, selected_names);
63                    }
64                    self.imported_paths.push(synthetic.clone());
65
66                    let mut lexer = harn_lexer::Lexer::new(source);
67                    let tokens = lexer.tokenize().map_err(|e| {
68                        VmError::Runtime(format!("stdlib lex error in std/{module}: {e}"))
69                    })?;
70                    let mut parser = harn_parser::Parser::new(tokens);
71                    let program = parser.parse().map_err(|e| {
72                        VmError::Runtime(format!("stdlib parse error in std/{module}: {e}"))
73                    })?;
74
75                    let loaded = self.import_declarations(&program, None).await?;
76                    self.imported_paths.pop();
77                    self.module_cache.insert(synthetic.clone(), loaded.clone());
78                    self.export_loaded_module(&synthetic, &loaded, selected_names)?;
79                    return Ok(());
80                }
81                return Err(VmError::Runtime(format!(
82                    "Unknown stdlib module: std/{module}"
83                )));
84            }
85
86            let base = self
87                .source_dir
88                .clone()
89                .unwrap_or_else(|| PathBuf::from("."));
90            let mut file_path = base.join(path);
91
92            if !file_path.exists() && file_path.extension().is_none() {
93                file_path.set_extension("harn");
94            }
95
96            if !file_path.exists() {
97                let pkg_path = base.join(".harn/packages").join(path);
98                if pkg_path.exists() {
99                    file_path = if pkg_path.is_dir() {
100                        let lib = pkg_path.join("lib.harn");
101                        if lib.exists() {
102                            lib
103                        } else {
104                            pkg_path
105                        }
106                    } else {
107                        pkg_path
108                    };
109                } else {
110                    let mut pkg_harn = pkg_path.clone();
111                    pkg_harn.set_extension("harn");
112                    if pkg_harn.exists() {
113                        file_path = pkg_harn;
114                    }
115                }
116            }
117
118            let canonical = file_path
119                .canonicalize()
120                .unwrap_or_else(|_| file_path.clone());
121            if self.imported_paths.contains(&canonical) {
122                return Ok(());
123            }
124            if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
125                return self.export_loaded_module(&canonical, &loaded, selected_names);
126            }
127            self.imported_paths.push(canonical.clone());
128
129            let source = std::fs::read_to_string(&file_path).map_err(|e| {
130                VmError::Runtime(format!(
131                    "Import error: cannot read '{}': {e}",
132                    file_path.display()
133                ))
134            })?;
135
136            let mut lexer = harn_lexer::Lexer::new(&source);
137            let tokens = lexer
138                .tokenize()
139                .map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
140            let mut parser = harn_parser::Parser::new(tokens);
141            let program = parser
142                .parse()
143                .map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
144
145            let loaded = self.import_declarations(&program, Some(&file_path)).await?;
146            self.imported_paths.pop();
147            self.module_cache.insert(canonical.clone(), loaded.clone());
148            self.export_loaded_module(&canonical, &loaded, selected_names)?;
149
150            Ok(())
151        })
152    }
153
154    /// Process top-level declarations from an imported module.
155    fn import_declarations<'a>(
156        &'a mut self,
157        program: &'a [harn_parser::SNode],
158        file_path: Option<&'a Path>,
159    ) -> Pin<Box<dyn Future<Output = Result<LoadedModule, VmError>> + 'a>> {
160        Box::pin(async move {
161            let caller_env = self.env.clone();
162            let old_source_dir = self.source_dir.clone();
163            self.env = VmEnv::new();
164            if let Some(fp) = file_path {
165                if let Some(parent) = fp.parent() {
166                    self.source_dir = Some(parent.to_path_buf());
167                }
168            }
169
170            for node in program {
171                match &node.node {
172                    harn_parser::Node::ImportDecl { path: sub_path } => {
173                        self.execute_import(sub_path, None).await?;
174                    }
175                    harn_parser::Node::SelectiveImport {
176                        names,
177                        path: sub_path,
178                    } => {
179                        self.execute_import(sub_path, Some(names)).await?;
180                    }
181                    _ => {}
182                }
183            }
184
185            // Route top-level `var`/`let` bindings into a shared
186            // `module_state` rather than `module_env`. If they appeared in
187            // `module_env` (captured by each closure's lexical snapshot),
188            // every call's per-invocation env clone would shadow them and
189            // writes would land in a per-call copy discarded on return.
190            let module_state: crate::value::ModuleState = {
191                let mut init_env = self.env.clone();
192                let init_nodes: Vec<harn_parser::SNode> = program
193                    .iter()
194                    .filter(|sn| {
195                        matches!(
196                            &sn.node,
197                            harn_parser::Node::VarBinding { .. }
198                                | harn_parser::Node::LetBinding { .. }
199                        )
200                    })
201                    .cloned()
202                    .collect();
203                if !init_nodes.is_empty() {
204                    let init_compiler = crate::Compiler::new();
205                    let init_chunk = init_compiler
206                        .compile(&init_nodes)
207                        .map_err(|e| VmError::Runtime(format!("Import init compile error: {e}")))?;
208                    // Save frame state so run_chunk_entry's top-level
209                    // frame-pop doesn't restore self.env.
210                    let saved_env = std::mem::replace(&mut self.env, init_env);
211                    let saved_frames = std::mem::take(&mut self.frames);
212                    let saved_handlers = std::mem::take(&mut self.exception_handlers);
213                    let saved_iterators = std::mem::take(&mut self.iterators);
214                    let saved_deadlines = std::mem::take(&mut self.deadlines);
215                    let init_result = self.run_chunk(&init_chunk).await;
216                    init_env = std::mem::replace(&mut self.env, saved_env);
217                    self.frames = saved_frames;
218                    self.exception_handlers = saved_handlers;
219                    self.iterators = saved_iterators;
220                    self.deadlines = saved_deadlines;
221                    init_result?;
222                }
223                Rc::new(RefCell::new(init_env))
224            };
225
226            let module_env = self.env.clone();
227            let registry: ModuleFunctionRegistry = Rc::new(RefCell::new(BTreeMap::new()));
228            let source_dir = file_path.and_then(|fp| fp.parent().map(|p| p.to_path_buf()));
229            let mut functions: BTreeMap<String, Rc<VmClosure>> = BTreeMap::new();
230            let mut public_names: HashSet<String> = HashSet::new();
231
232            for node in program {
233                // Imports may carry `@deprecated` / `@test` etc. on top-level
234                // fn decls; transparently peel the wrapper before pattern
235                // matching the FnDecl shape.
236                let inner = match &node.node {
237                    harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
238                    _ => node,
239                };
240                let harn_parser::Node::FnDecl {
241                    name,
242                    params,
243                    body,
244                    is_pub,
245                    ..
246                } = &inner.node
247                else {
248                    continue;
249                };
250
251                let mut compiler = crate::Compiler::new();
252                let module_source_file = file_path.map(|p| p.display().to_string());
253                let func_chunk = compiler
254                    .compile_fn_body(params, body, module_source_file)
255                    .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
256                let closure = Rc::new(VmClosure {
257                    func: func_chunk,
258                    env: module_env.clone(),
259                    source_dir: source_dir.clone(),
260                    module_functions: Some(Rc::clone(&registry)),
261                    module_state: Some(Rc::clone(&module_state)),
262                });
263                registry
264                    .borrow_mut()
265                    .insert(name.clone(), Rc::clone(&closure));
266                self.env
267                    .define(name, VmValue::Closure(Rc::clone(&closure)), false)?;
268                // Publish into module_state so sibling fns can be read
269                // as VALUES (e.g. `{handler: other_fn}` or as callbacks).
270                // Closures captured module_env BEFORE fn decls were added,
271                // so their static env alone can't resolve sibling fns.
272                // Direct calls use the module_functions late-binding path;
273                // value reads rely on this module_state entry.
274                module_state.borrow_mut().define(
275                    name,
276                    VmValue::Closure(Rc::clone(&closure)),
277                    false,
278                )?;
279                functions.insert(name.clone(), Rc::clone(&closure));
280                if *is_pub {
281                    public_names.insert(name.clone());
282                }
283            }
284
285            self.env = caller_env;
286            self.source_dir = old_source_dir;
287
288            Ok(LoadedModule {
289                functions,
290                public_names,
291            })
292        })
293    }
294
295    /// Load a module file and return the exported function closures that
296    /// would be visible to a wildcard import.
297    pub async fn load_module_exports(
298        &mut self,
299        path: &Path,
300    ) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
301        let path_str = path.to_string_lossy().into_owned();
302        self.execute_import(&path_str, None).await?;
303
304        let mut file_path = if path.is_absolute() {
305            path.to_path_buf()
306        } else {
307            self.source_dir
308                .clone()
309                .unwrap_or_else(|| PathBuf::from("."))
310                .join(path)
311        };
312        if !file_path.exists() && file_path.extension().is_none() {
313            file_path.set_extension("harn");
314        }
315
316        let canonical = file_path
317            .canonicalize()
318            .unwrap_or_else(|_| file_path.clone());
319        let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
320            VmError::Runtime(format!(
321                "Import error: failed to cache loaded module '{}'",
322                canonical.display()
323            ))
324        })?;
325
326        let export_names: Vec<String> = if loaded.public_names.is_empty() {
327            loaded.functions.keys().cloned().collect()
328        } else {
329            loaded.public_names.iter().cloned().collect()
330        };
331
332        let mut exports = BTreeMap::new();
333        for name in export_names {
334            let Some(closure) = loaded.functions.get(&name) else {
335                return Err(VmError::Runtime(format!(
336                    "Import error: exported function '{name}' is missing from {}",
337                    canonical.display()
338                )));
339            };
340            exports.insert(name, Rc::clone(closure));
341        }
342
343        Ok(exports)
344    }
345}