use std::cell::RefCell;
use std::collections::{BTreeMap, HashSet};
use std::future::Future;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::rc::Rc;
use crate::value::{ModuleFunctionRegistry, VmClosure, VmEnv, VmError, VmValue};
use super::{ScopeSpan, Vm};
#[derive(Clone)]
pub(crate) struct LoadedModule {
pub(crate) functions: BTreeMap<String, Rc<VmClosure>>,
pub(crate) public_names: HashSet<String>,
}
pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
let synthetic_current_file = base.join("__harn_import_base__.harn");
if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
return resolved;
}
let mut file_path = base.join(path);
if !file_path.exists() && file_path.extension().is_none() {
file_path.set_extension("harn");
}
file_path
}
impl Vm {
async fn load_module_from_source(
&mut self,
synthetic: PathBuf,
source: &str,
) -> Result<LoadedModule, VmError> {
if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
return Ok(loaded);
}
self.source_cache
.insert(synthetic.clone(), source.to_string());
let mut lexer = harn_lexer::Lexer::new(source);
let tokens = lexer.tokenize().map_err(|e| {
VmError::Runtime(format!("Import lex error in {}: {e}", synthetic.display()))
})?;
let mut parser = harn_parser::Parser::new(tokens);
let program = parser.parse().map_err(|e| {
VmError::Runtime(format!(
"Import parse error in {}: {e}",
synthetic.display()
))
})?;
self.imported_paths.push(synthetic.clone());
let loaded = self
.import_declarations(&program, None, Some(&synthetic))
.await?;
self.imported_paths.pop();
self.module_cache.insert(synthetic, loaded.clone());
Ok(loaded)
}
fn export_loaded_module(
&mut self,
module_path: &Path,
loaded: &LoadedModule,
selected_names: Option<&[String]>,
) -> Result<(), VmError> {
let export_names: Vec<String> = if let Some(names) = selected_names {
names.to_vec()
} else if !loaded.public_names.is_empty() {
loaded.public_names.iter().cloned().collect()
} else {
loaded.functions.keys().cloned().collect()
};
let module_name = module_path.display().to_string();
for name in export_names {
let Some(closure) = loaded.functions.get(&name) else {
return Err(VmError::Runtime(format!(
"Import error: '{name}' is not defined in {module_name}"
)));
};
if let Some(VmValue::Closure(_)) = self.env.get(&name) {
return Err(VmError::Runtime(format!(
"Import collision: '{name}' is already defined when importing {module_name}. \
Use selective imports to disambiguate: import {{ {name} }} from \"...\""
)));
}
self.env
.define(&name, VmValue::Closure(Rc::clone(closure)), false)?;
}
Ok(())
}
pub(super) fn execute_import<'a>(
&'a mut self,
path: &'a str,
selected_names: Option<&'a [String]>,
) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + 'a>> {
Box::pin(async move {
let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
if let Some(module) = path.strip_prefix("std/") {
if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
if self.imported_paths.contains(&synthetic) {
return Ok(());
}
let loaded = self
.load_module_from_source(synthetic.clone(), source)
.await?;
self.export_loaded_module(&synthetic, &loaded, selected_names)?;
return Ok(());
}
return Err(VmError::Runtime(format!(
"Unknown stdlib module: std/{module}"
)));
}
let base = self
.source_dir
.clone()
.unwrap_or_else(|| PathBuf::from("."));
let file_path = resolve_module_import_path(&base, path);
let canonical = file_path
.canonicalize()
.unwrap_or_else(|_| file_path.clone());
if self.imported_paths.contains(&canonical) {
return Ok(());
}
if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
return self.export_loaded_module(&canonical, &loaded, selected_names);
}
self.imported_paths.push(canonical.clone());
let source = std::fs::read_to_string(&file_path).map_err(|e| {
VmError::Runtime(format!(
"Import error: cannot read '{}': {e}",
file_path.display()
))
})?;
self.source_cache.insert(canonical.clone(), source.clone());
self.source_cache.insert(file_path.clone(), source.clone());
let mut lexer = harn_lexer::Lexer::new(&source);
let tokens = lexer
.tokenize()
.map_err(|e| VmError::Runtime(format!("Import lex error: {e}")))?;
let mut parser = harn_parser::Parser::new(tokens);
let program = parser
.parse()
.map_err(|e| VmError::Runtime(format!("Import parse error: {e}")))?;
let loaded = self
.import_declarations(&program, Some(&file_path), Some(&file_path))
.await?;
self.imported_paths.pop();
self.module_cache.insert(canonical.clone(), loaded.clone());
self.export_loaded_module(&canonical, &loaded, selected_names)?;
Ok(())
})
}
fn import_declarations<'a>(
&'a mut self,
program: &'a [harn_parser::SNode],
file_path: Option<&'a Path>,
debug_source_file: Option<&'a Path>,
) -> Pin<Box<dyn Future<Output = Result<LoadedModule, VmError>> + 'a>> {
Box::pin(async move {
let caller_env = self.env.clone();
let old_source_dir = self.source_dir.clone();
self.env = VmEnv::new();
if let Some(fp) = file_path {
if let Some(parent) = fp.parent() {
self.source_dir = Some(parent.to_path_buf());
}
}
for node in program {
match &node.node {
harn_parser::Node::ImportDecl { path: sub_path, .. } => {
self.execute_import(sub_path, None).await?;
}
harn_parser::Node::SelectiveImport {
names,
path: sub_path,
..
} => {
self.execute_import(sub_path, Some(names)).await?;
}
_ => {}
}
}
let module_state: crate::value::ModuleState = {
let mut init_env = self.env.clone();
let init_nodes: Vec<harn_parser::SNode> = program
.iter()
.filter(|sn| {
matches!(
&sn.node,
harn_parser::Node::VarBinding { .. }
| harn_parser::Node::LetBinding { .. }
)
})
.cloned()
.collect();
if !init_nodes.is_empty() {
let init_compiler = crate::Compiler::new();
let init_chunk = init_compiler
.compile(&init_nodes)
.map_err(|e| VmError::Runtime(format!("Import init compile error: {e}")))?;
let saved_env = std::mem::replace(&mut self.env, init_env);
let saved_frames = std::mem::take(&mut self.frames);
let saved_handlers = std::mem::take(&mut self.exception_handlers);
let saved_iterators = std::mem::take(&mut self.iterators);
let saved_deadlines = std::mem::take(&mut self.deadlines);
let init_result = self.run_chunk(&init_chunk).await;
init_env = std::mem::replace(&mut self.env, saved_env);
self.frames = saved_frames;
self.exception_handlers = saved_handlers;
self.iterators = saved_iterators;
self.deadlines = saved_deadlines;
init_result?;
}
Rc::new(RefCell::new(init_env))
};
let module_env = self.env.clone();
let registry: ModuleFunctionRegistry = Rc::new(RefCell::new(BTreeMap::new()));
let source_dir = file_path.and_then(|fp| fp.parent().map(|p| p.to_path_buf()));
let mut functions: BTreeMap<String, Rc<VmClosure>> = BTreeMap::new();
let mut public_names: HashSet<String> = HashSet::new();
for node in program {
let inner = match &node.node {
harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
_ => node,
};
let harn_parser::Node::FnDecl {
name,
params,
body,
is_pub,
..
} = &inner.node
else {
continue;
};
let mut compiler = crate::Compiler::new();
let module_source_file = debug_source_file.map(|p| p.display().to_string());
let func_chunk = compiler
.compile_fn_body(params, body, module_source_file)
.map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
let closure = Rc::new(VmClosure {
func: Rc::new(func_chunk),
env: module_env.clone(),
source_dir: source_dir.clone(),
module_functions: Some(Rc::clone(®istry)),
module_state: Some(Rc::clone(&module_state)),
});
registry
.borrow_mut()
.insert(name.clone(), Rc::clone(&closure));
self.env
.define(name, VmValue::Closure(Rc::clone(&closure)), false)?;
module_state.borrow_mut().define(
name,
VmValue::Closure(Rc::clone(&closure)),
false,
)?;
functions.insert(name.clone(), Rc::clone(&closure));
if *is_pub {
public_names.insert(name.clone());
}
}
for node in program {
let (sub_path, selective_names, is_pub_import) = match &node.node {
harn_parser::Node::ImportDecl {
path: sub_path,
is_pub,
} => (sub_path.clone(), None, *is_pub),
harn_parser::Node::SelectiveImport {
names,
path: sub_path,
is_pub,
} => (sub_path.clone(), Some(names.clone()), *is_pub),
_ => continue,
};
if !is_pub_import {
continue;
}
let cache_key = self.cache_key_for_import(&sub_path);
let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
return Err(VmError::Runtime(format!(
"Re-export error: imported module '{sub_path}' was not loaded"
)));
};
let names_to_reexport: Vec<String> = match selective_names {
Some(names) => names,
None => {
if loaded.public_names.is_empty() {
loaded.functions.keys().cloned().collect()
} else {
loaded.public_names.iter().cloned().collect()
}
}
};
for name in names_to_reexport {
let Some(closure) = loaded.functions.get(&name) else {
return Err(VmError::Runtime(format!(
"Re-export error: '{name}' is not exported by '{sub_path}'"
)));
};
if let Some(existing) = functions.get(&name) {
if !Rc::ptr_eq(existing, closure) {
return Err(VmError::Runtime(format!(
"Re-export collision: '{name}' is defined here and also \
re-exported from '{sub_path}'"
)));
}
}
functions.insert(name.clone(), Rc::clone(closure));
public_names.insert(name);
}
}
self.env = caller_env;
self.source_dir = old_source_dir;
Ok(LoadedModule {
functions,
public_names,
})
})
}
fn cache_key_for_import(&self, path: &str) -> PathBuf {
if let Some(module) = path.strip_prefix("std/") {
return PathBuf::from(format!("<stdlib>/{module}.harn"));
}
let base = self
.source_dir
.clone()
.unwrap_or_else(|| PathBuf::from("."));
let file_path = resolve_module_import_path(&base, path);
file_path.canonicalize().unwrap_or(file_path)
}
pub async fn load_module_exports(
&mut self,
path: &Path,
) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
let path_str = path.to_string_lossy().into_owned();
self.execute_import(&path_str, None).await?;
let mut file_path = if path.is_absolute() {
path.to_path_buf()
} else {
self.source_dir
.clone()
.unwrap_or_else(|| PathBuf::from("."))
.join(path)
};
if !file_path.exists() && file_path.extension().is_none() {
file_path.set_extension("harn");
}
let canonical = file_path
.canonicalize()
.unwrap_or_else(|_| file_path.clone());
let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
VmError::Runtime(format!(
"Import error: failed to cache loaded module '{}'",
canonical.display()
))
})?;
let export_names: Vec<String> = if loaded.public_names.is_empty() {
loaded.functions.keys().cloned().collect()
} else {
loaded.public_names.iter().cloned().collect()
};
let mut exports = BTreeMap::new();
for name in export_names {
let Some(closure) = loaded.functions.get(&name) else {
return Err(VmError::Runtime(format!(
"Import error: exported function '{name}' is missing from {}",
canonical.display()
)));
};
exports.insert(name, Rc::clone(closure));
}
Ok(exports)
}
pub async fn load_module_exports_from_source(
&mut self,
source_key: impl Into<PathBuf>,
source: &str,
) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
let synthetic = source_key.into();
let loaded = self
.load_module_from_source(synthetic.clone(), source)
.await?;
let export_names: Vec<String> = if loaded.public_names.is_empty() {
loaded.functions.keys().cloned().collect()
} else {
loaded.public_names.iter().cloned().collect()
};
let mut exports = BTreeMap::new();
for name in export_names {
let Some(closure) = loaded.functions.get(&name) else {
return Err(VmError::Runtime(format!(
"Import error: exported function '{name}' is missing from {}",
synthetic.display()
)));
};
exports.insert(name, Rc::clone(closure));
}
Ok(exports)
}
pub async fn load_module_exports_from_import(
&mut self,
import_path: &str,
) -> Result<BTreeMap<String, Rc<VmClosure>>, VmError> {
self.execute_import(import_path, None).await?;
if let Some(module) = import_path.strip_prefix("std/") {
let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
VmError::Runtime(format!(
"Import error: failed to cache loaded module '{}'",
synthetic.display()
))
})?;
let mut exports = BTreeMap::new();
let export_names: Vec<String> = if loaded.public_names.is_empty() {
loaded.functions.keys().cloned().collect()
} else {
loaded.public_names.iter().cloned().collect()
};
for name in export_names {
let Some(closure) = loaded.functions.get(&name) else {
return Err(VmError::Runtime(format!(
"Import error: exported function '{name}' is missing from {}",
synthetic.display()
)));
};
exports.insert(name, Rc::clone(closure));
}
return Ok(exports);
}
let base = self
.source_dir
.clone()
.unwrap_or_else(|| PathBuf::from("."));
let file_path = resolve_module_import_path(&base, import_path);
self.load_module_exports(&file_path).await
}
}