use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock};
use harn_parser::{Parser, SNode};
pub(super) type ParsedModule = Arc<(String, Vec<SNode>)>;
type FileIdentity = (u64, i128);
type ParseMemoKey = (PathBuf, FileIdentity);
type ParseMemo = Mutex<HashMap<ParseMemoKey, Option<ParsedModule>>>;
fn parse_memo() -> &'static ParseMemo {
static MEMO: OnceLock<ParseMemo> = OnceLock::new();
MEMO.get_or_init(|| Mutex::new(HashMap::new()))
}
const STDLIB_IDENTITY: FileIdentity = (0, -1);
fn file_stat_identity(path: &Path) -> Option<FileIdentity> {
let meta = std::fs::metadata(path).ok()?;
let mtime_ns = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_nanos() as i128)
.unwrap_or(-1);
Some((meta.len(), mtime_ns))
}
pub(super) fn parse_resolved_module(path: &Path) -> Option<ParsedModule> {
let identity = if is_stdlib_virtual_path(path) {
Some(STDLIB_IDENTITY)
} else {
file_stat_identity(path)
};
let Some(identity) = identity else {
return parse_module_uncached(path);
};
let key = (path.to_path_buf(), identity);
if let Some(hit) = parse_memo()
.lock()
.expect("check parse memo lock poisoned")
.get(&key)
{
return hit.clone();
}
let parsed = parse_module_uncached(path);
parse_memo()
.lock()
.expect("check parse memo lock poisoned")
.insert(key, parsed.clone());
parsed
}
fn parse_module_uncached(path: &Path) -> Option<ParsedModule> {
let source = harn_modules::read_module_source(path)?;
let mut lexer = harn_lexer::Lexer::new(&source);
let tokens = lexer.tokenize().ok()?;
let mut parser = Parser::new(tokens);
let program = parser.parse().ok()?;
Some(Arc::new((source, program)))
}
fn is_stdlib_virtual_path(path: &Path) -> bool {
path.to_str()
.is_some_and(|value| value.starts_with("<std>/"))
}