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
84fn resolve_import_target(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 fn export_loaded_module(
102 &mut self,
103 module_path: &Path,
104 loaded: &LoadedModule,
105 selected_names: Option<&[String]>,
106 ) -> Result<(), VmError> {
107 let export_names: Vec<String> = if let Some(names) = selected_names {
108 names.to_vec()
109 } else if !loaded.public_names.is_empty() {
110 loaded.public_names.iter().cloned().collect()
111 } else {
112 loaded.functions.keys().cloned().collect()
113 };
114
115 let module_name = module_path.display().to_string();
116 for name in export_names {
117 let Some(closure) = loaded.functions.get(&name) else {
118 return Err(VmError::Runtime(format!(
119 "Import error: '{name}' is not defined in {module_name}"
120 )));
121 };
122 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
123 return Err(VmError::Runtime(format!(
124 "Import collision: '{name}' is already defined when importing {module_name}. \
125 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
126 )));
127 }
128 self.env
129 .define(&name, VmValue::Closure(Rc::clone(closure)), false)?;
130 }
131 Ok(())
132 }
133
134 pub(super) fn execute_import<'a>(
136 &'a mut self,
137 path: &'a str,
138 selected_names: Option<&'a [String]>,
139 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
140 Box::pin(async move {
141 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
142
143 if let Some(module) = path.strip_prefix("std/") {
144 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
145 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
146 if self.imported_paths.contains(&synthetic) {
147 return Ok(());
148 }
149 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
150 return self.export_loaded_module(&synthetic, &loaded, selected_names);
151 }
152 self.imported_paths.push(synthetic.clone());
153
154 let mut lexer = harn_lexer::Lexer::new(source);
155 let tokens = lexer.tokenize().map_err(|e| {
156 VmError::Runtime(format!("stdlib lex error in std/{module}: {e}"))
157 })?;
158 let mut parser = harn_parser::Parser::new(tokens);
159 let program = parser.parse().map_err(|e| {
160 VmError::Runtime(format!("stdlib parse error in std/{module}: {e}"))
161 })?;
162
163 let loaded = self.import_declarations(&program, None).await?;
164 self.imported_paths.pop();
165 self.module_cache.insert(synthetic.clone(), loaded.clone());
166 self.export_loaded_module(&synthetic, &loaded, selected_names)?;
167 return Ok(());
168 }
169 return Err(VmError::Runtime(format!(
170 "Unknown stdlib module: std/{module}"
171 )));
172 }
173
174 let base = self
175 .source_dir
176 .clone()
177 .unwrap_or_else(|| PathBuf::from("."));
178 let file_path = resolve_import_target(&base, path);
179
180 let canonical = file_path
181 .canonicalize()
182 .unwrap_or_else(|_| file_path.clone());
183 if self.imported_paths.contains(&canonical) {
184 return Ok(());
185 }
186 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
187 return self.export_loaded_module(&canonical, &loaded, selected_names);
188 }
189 self.imported_paths.push(canonical.clone());
190
191 let source = std::fs::read_to_string(&file_path).map_err(|e| {
192 VmError::Runtime(format!(
193 "Import error: cannot read '{}': {e}",
194 file_path.display()
195 ))
196 })?;
197
198 let mut lexer = harn_lexer::Lexer::new(&source);
199 let tokens = lexer
200 .tokenize()
201 .map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
202 let mut parser = harn_parser::Parser::new(tokens);
203 let program = parser
204 .parse()
205 .map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
206
207 let loaded = self.import_declarations(&program, Some(&file_path)).await?;
208 self.imported_paths.pop();
209 self.module_cache.insert(canonical.clone(), loaded.clone());
210 self.export_loaded_module(&canonical, &loaded, selected_names)?;
211
212 Ok(())
213 })
214 }
215
216 fn import_declarations<'a>(
218 &'a mut self,
219 program: &'a [harn_parser::SNode],
220 file_path: Option<&'a Path>,
221 ) -> Pin<Box<dyn Future<Output = Result<LoadedModule, VmError>> + 'a>> {
222 Box::pin(async move {
223 let caller_env = self.env.clone();
224 let old_source_dir = self.source_dir.clone();
225 self.env = VmEnv::new();
226 if let Some(fp) = file_path {
227 if let Some(parent) = fp.parent() {
228 self.source_dir = Some(parent.to_path_buf());
229 }
230 }
231
232 for node in program {
233 match &node.node {
234 harn_parser::Node::ImportDecl { path: sub_path } => {
235 self.execute_import(sub_path, None).await?;
236 }
237 harn_parser::Node::SelectiveImport {
238 names,
239 path: sub_path,
240 } => {
241 self.execute_import(sub_path, Some(names)).await?;
242 }
243 _ => {}
244 }
245 }
246
247 let module_state: crate::value::ModuleState = {
253 let mut init_env = self.env.clone();
254 let init_nodes: Vec<harn_parser::SNode> = program
255 .iter()
256 .filter(|sn| {
257 matches!(
258 &sn.node,
259 harn_parser::Node::VarBinding { .. }
260 | harn_parser::Node::LetBinding { .. }
261 )
262 })
263 .cloned()
264 .collect();
265 if !init_nodes.is_empty() {
266 let init_compiler = crate::Compiler::new();
267 let init_chunk = init_compiler
268 .compile(&init_nodes)
269 .map_err(|e| VmError::Runtime(format!("Import init compile error: {e}")))?;
270 let saved_env = std::mem::replace(&mut self.env, init_env);
273 let saved_frames = std::mem::take(&mut self.frames);
274 let saved_handlers = std::mem::take(&mut self.exception_handlers);
275 let saved_iterators = std::mem::take(&mut self.iterators);
276 let saved_deadlines = std::mem::take(&mut self.deadlines);
277 let init_result = self.run_chunk(&init_chunk).await;
278 init_env = std::mem::replace(&mut self.env, saved_env);
279 self.frames = saved_frames;
280 self.exception_handlers = saved_handlers;
281 self.iterators = saved_iterators;
282 self.deadlines = saved_deadlines;
283 init_result?;
284 }
285 Rc::new(RefCell::new(init_env))
286 };
287
288 let module_env = self.env.clone();
289 let registry: ModuleFunctionRegistry = Rc::new(RefCell::new(BTreeMap::new()));
290 let source_dir = file_path.and_then(|fp| fp.parent().map(|p| p.to_path_buf()));
291 let mut functions: BTreeMap<String, Rc<VmClosure>> = BTreeMap::new();
292 let mut public_names: HashSet<String> = HashSet::new();
293
294 for node in program {
295 let inner = match &node.node {
299 harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
300 _ => node,
301 };
302 let harn_parser::Node::FnDecl {
303 name,
304 params,
305 body,
306 is_pub,
307 ..
308 } = &inner.node
309 else {
310 continue;
311 };
312
313 let mut compiler = crate::Compiler::new();
314 let module_source_file = file_path.map(|p| p.display().to_string());
315 let func_chunk = compiler
316 .compile_fn_body(params, body, module_source_file)
317 .map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
318 let closure = Rc::new(VmClosure {
319 func: func_chunk,
320 env: module_env.clone(),
321 source_dir: source_dir.clone(),
322 module_functions: Some(Rc::clone(®istry)),
323 module_state: Some(Rc::clone(&module_state)),
324 });
325 registry
326 .borrow_mut()
327 .insert(name.clone(), Rc::clone(&closure));
328 self.env
329 .define(name, VmValue::Closure(Rc::clone(&closure)), false)?;
330 module_state.borrow_mut().define(
337 name,
338 VmValue::Closure(Rc::clone(&closure)),
339 false,
340 )?;
341 functions.insert(name.clone(), Rc::clone(&closure));
342 if *is_pub {
343 public_names.insert(name.clone());
344 }
345 }
346
347 self.env = caller_env;
348 self.source_dir = old_source_dir;
349
350 Ok(LoadedModule {
351 functions,
352 public_names,
353 })
354 })
355 }
356
357 pub async fn load_module_exports(
360 &mut self,
361 path: &Path,
362 ) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
363 let path_str = path.to_string_lossy().into_owned();
364 self.execute_import(&path_str, None).await?;
365
366 let mut file_path = if path.is_absolute() {
367 path.to_path_buf()
368 } else {
369 self.source_dir
370 .clone()
371 .unwrap_or_else(|| PathBuf::from("."))
372 .join(path)
373 };
374 if !file_path.exists() && file_path.extension().is_none() {
375 file_path.set_extension("harn");
376 }
377
378 let canonical = file_path
379 .canonicalize()
380 .unwrap_or_else(|_| file_path.clone());
381 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
382 VmError::Runtime(format!(
383 "Import error: failed to cache loaded module '{}'",
384 canonical.display()
385 ))
386 })?;
387
388 let export_names: Vec<String> = if loaded.public_names.is_empty() {
389 loaded.functions.keys().cloned().collect()
390 } else {
391 loaded.public_names.iter().cloned().collect()
392 };
393
394 let mut exports = BTreeMap::new();
395 for name in export_names {
396 let Some(closure) = loaded.functions.get(&name) else {
397 return Err(VmError::Runtime(format!(
398 "Import error: exported function '{name}' is missing from {}",
399 canonical.display()
400 )));
401 };
402 exports.insert(name, Rc::clone(closure));
403 }
404
405 Ok(exports)
406 }
407
408 pub async fn load_module_exports_from_import(
412 &mut self,
413 import_path: &str,
414 ) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
415 self.execute_import(import_path, None).await?;
416
417 if let Some(module) = import_path.strip_prefix("std/") {
418 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
419 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
420 VmError::Runtime(format!(
421 "Import error: failed to cache loaded module '{}'",
422 synthetic.display()
423 ))
424 })?;
425 let mut exports = BTreeMap::new();
426 let export_names: Vec<String> = if loaded.public_names.is_empty() {
427 loaded.functions.keys().cloned().collect()
428 } else {
429 loaded.public_names.iter().cloned().collect()
430 };
431 for name in export_names {
432 let Some(closure) = loaded.functions.get(&name) else {
433 return Err(VmError::Runtime(format!(
434 "Import error: exported function '{name}' is missing from {}",
435 synthetic.display()
436 )));
437 };
438 exports.insert(name, Rc::clone(closure));
439 }
440 return Ok(exports);
441 }
442
443 let base = self
444 .source_dir
445 .clone()
446 .unwrap_or_else(|| PathBuf::from("."));
447 let file_path = resolve_import_target(&base, import_path);
448 self.load_module_exports(&file_path).await
449 }
450}