use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use rustc_hash::FxHashMap;
use crate::eval_ir::{eval_ir, IrEvalError, IrValue};
use crate::ir::Program;
use crate::lower_file;
thread_local! {
static EVAL_FILE_STACK: RefCell<Vec<PathBuf>> = const { RefCell::new(Vec::new()) };
static PROGRAM_CACHE: RefCell<FxHashMap<PathBuf, Rc<Program>>> =
RefCell::new(FxHashMap::default());
static IMPORT_VALUE_CACHE: RefCell<FxHashMap<PathBuf, IrValue>> =
RefCell::new(FxHashMap::default());
static FILES_IN_PROGRESS: RefCell<Vec<PathBuf>> = const { RefCell::new(Vec::new()) };
}
pub struct EvalFileGuard;
impl Drop for EvalFileGuard {
fn drop(&mut self) {
EVAL_FILE_STACK.with(|s| {
s.borrow_mut().pop();
});
}
}
pub fn push_eval_file(file: PathBuf) -> EvalFileGuard {
EVAL_FILE_STACK.with(|s| s.borrow_mut().push(file));
EvalFileGuard
}
#[must_use]
pub fn current_eval_dir() -> Option<PathBuf> {
EVAL_FILE_STACK.with(|s| {
s.borrow()
.last()
.and_then(|p| p.parent().map(PathBuf::from))
})
}
struct InProgressGuard;
impl Drop for InProgressGuard {
fn drop(&mut self) {
FILES_IN_PROGRESS.with(|s| {
s.borrow_mut().pop();
});
}
}
fn enter_file(canonical: &Path) -> Result<InProgressGuard, IrEvalError> {
FILES_IN_PROGRESS.with(|s| {
let mut stack = s.borrow_mut();
if stack.iter().any(|p| p == canonical) {
let mut chain: Vec<String> = stack
.iter()
.map(|p| p.display().to_string())
.collect();
chain.push(canonical.display().to_string());
return Err(IrEvalError::ImportCycle(chain.join(" -> ")));
}
stack.push(canonical.to_path_buf());
Ok(InProgressGuard)
})
}
#[must_use]
pub fn program_cache_len() -> usize {
PROGRAM_CACHE.with(|c| c.borrow().len())
}
pub fn clear_import_value_cache() {
IMPORT_VALUE_CACHE.with(|c| c.borrow_mut().clear());
}
pub fn clear_file_caches() {
PROGRAM_CACHE.with(|c| c.borrow_mut().clear());
IMPORT_VALUE_CACHE.with(|c| c.borrow_mut().clear());
}
fn lowered_program(canonical: &Path) -> Result<Rc<Program>, IrEvalError> {
if let Some(hit) = PROGRAM_CACHE.with(|c| c.borrow().get(canonical).cloned()) {
return Ok(hit);
}
let source = std::fs::read_to_string(canonical).map_err(|e| IrEvalError::Io {
context: {
let mut s = String::from("import ");
s.push_str(&canonical.display().to_string());
s
},
message: e.to_string(),
})?;
let program = Rc::new(
lower_file(&source).map_err(|e| IrEvalError::Parse(e.to_string()))?,
);
PROGRAM_CACHE.with(|c| {
c.borrow_mut()
.insert(canonical.to_path_buf(), program.clone())
});
Ok(program)
}
fn eval_file_inner(canonical: &Path) -> Result<IrValue, IrEvalError> {
let _cycle = enter_file(canonical)?;
let program = lowered_program(canonical)?;
let _file = push_eval_file(canonical.to_path_buf());
let mut env = crate::builtins::base_env();
env.set_eval_file(Some(Rc::new(canonical.to_path_buf())));
eval_ir(&program, program.root, &env).and_then(|v| v.force())
}
pub fn eval_ir_file(path: &Path) -> Result<IrValue, IrEvalError> {
let canonical = crate::path::normalize(path);
eval_file_inner(&canonical)
}
pub(crate) fn import(raw: &str) -> Result<IrValue, IrEvalError> {
let resolved = crate::path::resolve_import(current_eval_dir().as_deref(), raw)
.unwrap_or_else(|_| PathBuf::from(raw));
let canonical = crate::path::normalize(&resolved);
if let Some(hit) = IMPORT_VALUE_CACHE.with(|c| c.borrow().get(&canonical).cloned()) {
return Ok(hit);
}
let value = eval_file_inner(&canonical)?;
IMPORT_VALUE_CACHE.with(|c| c.borrow_mut().insert(canonical, value.clone()));
Ok(value)
}