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::{ScopeSpan, Vm};
11
12#[derive(Clone)]
13pub(crate) struct LoadedModule {
14 pub(crate) functions: BTreeMap<String, Rc<VmClosure>>,
15 pub(crate) public_names: HashSet<String>,
16}
17
18pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
19 let synthetic_current_file = base.join("__harn_import_base__.harn");
20 if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
21 return resolved;
22 }
23
24 let mut file_path = base.join(path);
25
26 if !file_path.exists() && file_path.extension().is_none() {
27 file_path.set_extension("harn");
28 }
29
30 file_path
31}
32
33impl Vm {
34 async fn load_module_from_source(
35 &mut self,
36 synthetic: PathBuf,
37 source: &str,
38 ) -> Result<LoadedModule, VmError> {
39 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
40 return Ok(loaded);
41 }
42
43 let mut lexer = harn_lexer::Lexer::new(source);
44 let tokens = lexer.tokenize().map_err(|e| {
45 VmError::Runtime(format!("Import lex error in {}: {e}", synthetic.display()))
46 })?;
47 let mut parser = harn_parser::Parser::new(tokens);
48 let program = parser.parse().map_err(|e| {
49 VmError::Runtime(format!(
50 "Import parse error in {}: {e}",
51 synthetic.display()
52 ))
53 })?;
54
55 self.imported_paths.push(synthetic.clone());
56 let loaded = self.import_declarations(&program, None).await?;
57 self.imported_paths.pop();
58 self.module_cache.insert(synthetic, loaded.clone());
59 Ok(loaded)
60 }
61
62 fn export_loaded_module(
63 &mut self,
64 module_path: &Path,
65 loaded: &LoadedModule,
66 selected_names: Option<&[String]>,
67 ) -> Result<(), VmError> {
68 let export_names: Vec<String> = if let Some(names) = selected_names {
69 names.to_vec()
70 } else if !loaded.public_names.is_empty() {
71 loaded.public_names.iter().cloned().collect()
72 } else {
73 loaded.functions.keys().cloned().collect()
74 };
75
76 let module_name = module_path.display().to_string();
77 for name in export_names {
78 let Some(closure) = loaded.functions.get(&name) else {
79 return Err(VmError::Runtime(format!(
80 "Import error: '{name}' is not defined in {module_name}"
81 )));
82 };
83 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
84 return Err(VmError::Runtime(format!(
85 "Import collision: '{name}' is already defined when importing {module_name}. \
86 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
87 )));
88 }
89 self.env
90 .define(&name, VmValue::Closure(Rc::clone(closure)), false)?;
91 }
92 Ok(())
93 }
94
95 pub(super) fn execute_import<'a>(
97 &'a mut self,
98 path: &'a str,
99 selected_names: Option<&'a [String]>,
100 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
101 Box::pin(async move {
102 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
103
104 if let Some(module) = path.strip_prefix("std/") {
105 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
106 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
107 if self.imported_paths.contains(&synthetic) {
108 return Ok(());
109 }
110 let loaded = self
111 .load_module_from_source(synthetic.clone(), source)
112 .await?;
113 self.export_loaded_module(&synthetic, &loaded, selected_names)?;
114 return Ok(());
115 }
116 return Err(VmError::Runtime(format!(
117 "Unknown stdlib module: std/{module}"
118 )));
119 }
120
121 let base = self
122 .source_dir
123 .clone()
124 .unwrap_or_else(|| PathBuf::from("."));
125 let file_path = resolve_module_import_path(&base, path);
126
127 let canonical = file_path
128 .canonicalize()
129 .unwrap_or_else(|_| file_path.clone());
130 if self.imported_paths.contains(&canonical) {
131 return Ok(());
132 }
133 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
134 return self.export_loaded_module(&canonical, &loaded, selected_names);
135 }
136 self.imported_paths.push(canonical.clone());
137
138 let source = std::fs::read_to_string(&file_path).map_err(|e| {
139 VmError::Runtime(format!(
140 "Import error: cannot read '{}': {e}",
141 file_path.display()
142 ))
143 })?;
144
145 let mut lexer = harn_lexer::Lexer::new(&source);
146 let tokens = lexer
147 .tokenize()
148 .map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
149 let mut parser = harn_parser::Parser::new(tokens);
150 let program = parser
151 .parse()
152 .map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
153
154 let loaded = self.import_declarations(&program, Some(&file_path)).await?;
155 self.imported_paths.pop();
156 self.module_cache.insert(canonical.clone(), loaded.clone());
157 self.export_loaded_module(&canonical, &loaded, selected_names)?;
158
159 Ok(())
160 })
161 }
162
163 fn import_declarations<'a>(
165 &'a mut self,
166 program: &'a [harn_parser::SNode],
167 file_path: Option<&'a Path>,
168 ) -> Pin<Box<dyn Future<Output = Result<LoadedModule, VmError>> + 'a>> {
169 Box::pin(async move {
170 let caller_env = self.env.clone();
171 let old_source_dir = self.source_dir.clone();
172 self.env = VmEnv::new();
173 if let Some(fp) = file_path {
174 if let Some(parent) = fp.parent() {
175 self.source_dir = Some(parent.to_path_buf());
176 }
177 }
178
179 for node in program {
180 match &node.node {
181 harn_parser::Node::ImportDecl { path: sub_path, .. } => {
182 self.execute_import(sub_path, None).await?;
183 }
184 harn_parser::Node::SelectiveImport {
185 names,
186 path: sub_path,
187 ..
188 } => {
189 self.execute_import(sub_path, Some(names)).await?;
190 }
191 _ => {}
192 }
193 }
194
195 let module_state: crate::value::ModuleState = {
201 let mut init_env = self.env.clone();
202 let init_nodes: Vec<harn_parser::SNode> = program
203 .iter()
204 .filter(|sn| {
205 matches!(
206 &sn.node,
207 harn_parser::Node::VarBinding { .. }
208 | harn_parser::Node::LetBinding { .. }
209 )
210 })
211 .cloned()
212 .collect();
213 if !init_nodes.is_empty() {
214 let init_compiler = crate::Compiler::new();
215 let init_chunk = init_compiler
216 .compile(&init_nodes)
217 .map_err(|e| VmError::Runtime(format!("Import init compile error: {e}")))?;
218 let saved_env = std::mem::replace(&mut self.env, init_env);
221 let saved_frames = std::mem::take(&mut self.frames);
222 let saved_handlers = std::mem::take(&mut self.exception_handlers);
223 let saved_iterators = std::mem::take(&mut self.iterators);
224 let saved_deadlines = std::mem::take(&mut self.deadlines);
225 let init_result = self.run_chunk(&init_chunk).await;
226 init_env = std::mem::replace(&mut self.env, saved_env);
227 self.frames = saved_frames;
228 self.exception_handlers = saved_handlers;
229 self.iterators = saved_iterators;
230 self.deadlines = saved_deadlines;
231 init_result?;
232 }
233 Rc::new(RefCell::new(init_env))
234 };
235
236 let module_env = self.env.clone();
237 let registry: ModuleFunctionRegistry = Rc::new(RefCell::new(BTreeMap::new()));
238 let source_dir = file_path.and_then(|fp| fp.parent().map(|p| p.to_path_buf()));
239 let mut functions: BTreeMap<String, Rc<VmClosure>> = BTreeMap::new();
240 let mut public_names: HashSet<String> = HashSet::new();
241
242 for node in program {
243 let inner = match &node.node {
247 harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
248 _ => node,
249 };
250 let harn_parser::Node::FnDecl {
251 name,
252 params,
253 body,
254 is_pub,
255 ..
256 } = &inner.node
257 else {
258 continue;
259 };
260
261 let mut compiler = crate::Compiler::new();
262 let module_source_file = file_path.map(|p| p.display().to_string());
263 let func_chunk = compiler
264 .compile_fn_body(params, body, module_source_file)
265 .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
266 let closure = Rc::new(VmClosure {
267 func: Rc::new(func_chunk),
268 env: module_env.clone(),
269 source_dir: source_dir.clone(),
270 module_functions: Some(Rc::clone(®istry)),
271 module_state: Some(Rc::clone(&module_state)),
272 });
273 registry
274 .borrow_mut()
275 .insert(name.clone(), Rc::clone(&closure));
276 self.env
277 .define(name, VmValue::Closure(Rc::clone(&closure)), false)?;
278 module_state.borrow_mut().define(
285 name,
286 VmValue::Closure(Rc::clone(&closure)),
287 false,
288 )?;
289 functions.insert(name.clone(), Rc::clone(&closure));
290 if *is_pub {
291 public_names.insert(name.clone());
292 }
293 }
294
295 for node in program {
300 let (sub_path, selective_names, is_pub_import) = match &node.node {
301 harn_parser::Node::ImportDecl {
302 path: sub_path,
303 is_pub,
304 } => (sub_path.clone(), None, *is_pub),
305 harn_parser::Node::SelectiveImport {
306 names,
307 path: sub_path,
308 is_pub,
309 } => (sub_path.clone(), Some(names.clone()), *is_pub),
310 _ => continue,
311 };
312 if !is_pub_import {
313 continue;
314 }
315 let cache_key = self.cache_key_for_import(&sub_path);
316 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
317 return Err(VmError::Runtime(format!(
318 "Re-export error: imported module '{sub_path}' was not loaded"
319 )));
320 };
321 let names_to_reexport: Vec<String> = match selective_names {
322 Some(names) => names,
323 None => {
324 if loaded.public_names.is_empty() {
325 loaded.functions.keys().cloned().collect()
326 } else {
327 loaded.public_names.iter().cloned().collect()
328 }
329 }
330 };
331 for name in names_to_reexport {
332 let Some(closure) = loaded.functions.get(&name) else {
333 return Err(VmError::Runtime(format!(
334 "Re-export error: '{name}' is not exported by '{sub_path}'"
335 )));
336 };
337 if let Some(existing) = functions.get(&name) {
338 if !Rc::ptr_eq(existing, closure) {
339 return Err(VmError::Runtime(format!(
340 "Re-export collision: '{name}' is defined here and also \
341 re-exported from '{sub_path}'"
342 )));
343 }
344 }
345 functions.insert(name.clone(), Rc::clone(closure));
346 public_names.insert(name);
347 }
348 }
349
350 self.env = caller_env;
351 self.source_dir = old_source_dir;
352
353 Ok(LoadedModule {
354 functions,
355 public_names,
356 })
357 })
358 }
359
360 fn cache_key_for_import(&self, path: &str) -> PathBuf {
365 if let Some(module) = path.strip_prefix("std/") {
366 return PathBuf::from(format!("<stdlib>/{module}.harn"));
367 }
368 let base = self
369 .source_dir
370 .clone()
371 .unwrap_or_else(|| PathBuf::from("."));
372 let file_path = resolve_module_import_path(&base, path);
373 file_path.canonicalize().unwrap_or(file_path)
374 }
375
376 pub async fn load_module_exports(
379 &mut self,
380 path: &Path,
381 ) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
382 let path_str = path.to_string_lossy().into_owned();
383 self.execute_import(&path_str, None).await?;
384
385 let mut file_path = if path.is_absolute() {
386 path.to_path_buf()
387 } else {
388 self.source_dir
389 .clone()
390 .unwrap_or_else(|| PathBuf::from("."))
391 .join(path)
392 };
393 if !file_path.exists() && file_path.extension().is_none() {
394 file_path.set_extension("harn");
395 }
396
397 let canonical = file_path
398 .canonicalize()
399 .unwrap_or_else(|_| file_path.clone());
400 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
401 VmError::Runtime(format!(
402 "Import error: failed to cache loaded module '{}'",
403 canonical.display()
404 ))
405 })?;
406
407 let export_names: Vec<String> = if loaded.public_names.is_empty() {
408 loaded.functions.keys().cloned().collect()
409 } else {
410 loaded.public_names.iter().cloned().collect()
411 };
412
413 let mut exports = BTreeMap::new();
414 for name in export_names {
415 let Some(closure) = loaded.functions.get(&name) else {
416 return Err(VmError::Runtime(format!(
417 "Import error: exported function '{name}' is missing from {}",
418 canonical.display()
419 )));
420 };
421 exports.insert(name, Rc::clone(closure));
422 }
423
424 Ok(exports)
425 }
426
427 pub async fn load_module_exports_from_source(
430 &mut self,
431 source_key: impl Into<PathBuf>,
432 source: &str,
433 ) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
434 let synthetic = source_key.into();
435 let loaded = self
436 .load_module_from_source(synthetic.clone(), source)
437 .await?;
438 let export_names: Vec<String> = if loaded.public_names.is_empty() {
439 loaded.functions.keys().cloned().collect()
440 } else {
441 loaded.public_names.iter().cloned().collect()
442 };
443
444 let mut exports = BTreeMap::new();
445 for name in export_names {
446 let Some(closure) = loaded.functions.get(&name) else {
447 return Err(VmError::Runtime(format!(
448 "Import error: exported function '{name}' is missing from {}",
449 synthetic.display()
450 )));
451 };
452 exports.insert(name, Rc::clone(closure));
453 }
454
455 Ok(exports)
456 }
457
458 pub async fn load_module_exports_from_import(
462 &mut self,
463 import_path: &str,
464 ) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
465 self.execute_import(import_path, None).await?;
466
467 if let Some(module) = import_path.strip_prefix("std/") {
468 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
469 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
470 VmError::Runtime(format!(
471 "Import error: failed to cache loaded module '{}'",
472 synthetic.display()
473 ))
474 })?;
475 let mut exports = BTreeMap::new();
476 let export_names: Vec<String> = if loaded.public_names.is_empty() {
477 loaded.functions.keys().cloned().collect()
478 } else {
479 loaded.public_names.iter().cloned().collect()
480 };
481 for name in export_names {
482 let Some(closure) = loaded.functions.get(&name) else {
483 return Err(VmError::Runtime(format!(
484 "Import error: exported function '{name}' is missing from {}",
485 synthetic.display()
486 )));
487 };
488 exports.insert(name, Rc::clone(closure));
489 }
490 return Ok(exports);
491 }
492
493 let base = self
494 .source_dir
495 .clone()
496 .unwrap_or_else(|| PathBuf::from("."));
497 let file_path = resolve_module_import_path(&base, import_path);
498 self.load_module_exports(&file_path).await
499 }
500}