use std::collections::{BTreeMap, HashSet};
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::chunk::{CachedChunk, CachedCompiledFunction};
use crate::value::VmError;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ModuleImportSpec {
pub path: String,
pub selected_names: Option<Vec<String>>,
pub is_pub: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ModuleArtifact {
pub imports: Vec<ModuleImportSpec>,
pub init_chunk: Option<CachedChunk>,
pub functions: BTreeMap<String, CachedCompiledFunction>,
pub public_names: HashSet<String>,
pub public_type_names: HashSet<String>,
pub public_type_schemas: BTreeMap<String, String>,
}
pub fn compile_module_artifact(
program: &[harn_parser::SNode],
module_source_file: Option<String>,
) -> Result<ModuleArtifact, VmError> {
let imports = program
.iter()
.filter_map(|node| match &node.node {
harn_parser::Node::ImportDecl { path, is_pub } => Some(ModuleImportSpec {
path: path.clone(),
selected_names: None,
is_pub: *is_pub,
}),
harn_parser::Node::SelectiveImport {
names,
path,
is_pub,
} => Some(ModuleImportSpec {
path: path.clone(),
selected_names: Some(names.clone()),
is_pub: *is_pub,
}),
_ => None,
})
.collect();
let init_nodes: Vec<harn_parser::SNode> = program
.iter()
.filter(|sn| {
let inner = match &sn.node {
harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
_ => sn,
};
matches!(
&inner.node,
harn_parser::Node::LetBinding { .. } | harn_parser::Node::ConstBinding { .. }
)
})
.cloned()
.collect();
let init_chunk = if init_nodes.is_empty() {
None
} else {
Some(
crate::Compiler::new()
.compile(&init_nodes)
.map_err(|e| VmError::Runtime(format!("Import init compile error: {e}")))?
.freeze_for_cache(),
)
};
let mut functions = BTreeMap::new();
let mut public_names = HashSet::new();
let mut public_type_names = HashSet::new();
for node in program {
let inner = match &node.node {
harn_parser::Node::AttributedDecl { inner, .. } => inner.as_ref(),
_ => node,
};
if let harn_parser::Node::TypeDecl {
name, is_pub: true, ..
} = &inner.node
{
public_type_names.insert(name.clone());
continue;
}
let harn_parser::Node::FnDecl {
name,
type_params,
params,
body,
is_pub,
..
} = &inner.node
else {
continue;
};
let mut compiler = crate::Compiler::new();
compiler.collect_type_aliases(program);
let func_chunk = compiler
.compile_fn_body(type_params, params, body, module_source_file.clone())
.map_err(|e| VmError::Runtime(format!("Import compile error: {e}")))?;
functions.insert(name.clone(), func_chunk.freeze_for_cache());
if *is_pub {
public_names.insert(name.clone());
}
}
let public_type_schemas = crate::Compiler::lower_public_type_schemas(program)
.into_iter()
.map(|(name, schema)| (name, crate::stdlib::json::vm_value_to_json(&schema)))
.collect();
Ok(ModuleArtifact {
imports,
init_chunk,
functions,
public_names,
public_type_names,
public_type_schemas,
})
}
pub fn compile_module_artifact_from_source(
source_path: &Path,
source: &str,
) -> Result<ModuleArtifact, VmError> {
let mut lexer = harn_lexer::Lexer::new(source);
let tokens = lexer.tokenize().map_err(|e| {
VmError::Runtime(format!(
"Import lex error in {}: {e}",
source_path.display()
))
})?;
let mut parser = harn_parser::Parser::new(tokens);
let program = parser.parse().map_err(|e| {
VmError::Runtime(format!(
"Import parse error in {}: {e}",
source_path.display()
))
})?;
compile_module_artifact(&program, Some(source_path.display().to_string()))
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::compile_module_artifact_from_source;
#[test]
fn type_only_modules_export_schemas_without_init_bytecode() {
let source = r"
pub type UserShape = {name: string, active?: bool}
pub type UserList = list<UserShape>
";
let artifact =
compile_module_artifact_from_source(Path::new("<test>/schemas.harn"), source)
.expect("module compiles");
assert!(
artifact.init_chunk.is_none(),
"erased type aliases must not inflate module init bytecode"
);
assert!(artifact.public_type_names.contains("UserShape"));
assert!(artifact.public_type_names.contains("UserList"));
assert!(artifact.public_type_schemas.contains_key("UserShape"));
assert!(artifact.public_type_schemas.contains_key("UserList"));
}
}