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