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