use std::path::{Path, PathBuf};
use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
use crate::script_ctx::ScriptCtx;
pub fn install(interp: &mut Interpreter<ScriptCtx>) {
interp.register_fn(
"require",
Arity::Exact(1),
|_args: &[Value], _ctx: &mut ScriptCtx, sp| {
Err(EvalError::native_fn(
"require",
"require is only available when the interpreter is driven \
by `tatara-script` (or an embedder that installs the \
require hook). See tatara-lisp-script/src/require_hook.rs.",
sp,
))
},
);
}
pub fn resolve_require_path(ctx: &ScriptCtx, target: &str) -> Result<PathBuf, String> {
let candidate = Path::new(target);
let absolute = if candidate.is_absolute() {
candidate.to_path_buf()
} else if let Some(current) = ctx.current_file.as_ref().and_then(|p| p.parent()) {
current.join(candidate)
} else {
std::env::current_dir()
.map_err(|e| format!("cwd: {e}"))?
.join(candidate)
};
std::fs::canonicalize(&absolute).map_err(|e| format!("require {absolute:?}: {e}"))
}
pub fn plan_require(ctx: &mut ScriptCtx, target: &str) -> Result<Option<PathBuf>, String> {
let canonical = resolve_require_path(ctx, target)?;
if ctx.required.contains(&canonical) {
return Ok(None);
}
ctx.required.insert(canonical.clone());
Ok(Some(canonical))
}
pub fn read_forms(path: &Path) -> Result<Vec<tatara_lisp::Spanned>, String> {
let src = std::fs::read_to_string(path).map_err(|e| format!("read {path:?}: {e}"))?;
tatara_lisp::read_spanned(&src).map_err(|e| format!("parse {path:?}: {e:?}"))
}