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