#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use std::sync::Arc;
use crate::env::env::{Env, GlobalEnv, RequireRefer, RequireSpec};
use crate::env::error::{EvalError, EvalResult};
pub fn load_ns(globals: Arc<GlobalEnv>, spec: &RequireSpec, current_ns: &str) -> EvalResult<()> {
#[cfg(not(target_arch = "wasm32"))]
if let Some(ref commit) = spec.version {
return load_versioned_ns(globals, spec, commit, current_ns);
}
#[cfg(target_arch = "wasm32")]
if spec.version.is_some() {
return Err(EvalError::Runtime(
"versioned require is not supported in WASM".to_string(),
));
}
let ns_name = &spec.ns;
if !globals.is_loaded(ns_name) {
let should_load = claim_or_wait(&globals, ns_name)?;
if should_load {
let result = do_load(&globals, ns_name);
globals.loading.lock().unwrap().remove(ns_name.as_ref());
if result.is_ok() {
globals.mark_loaded(ns_name);
}
globals.loading_done.notify_all();
result?;
}
}
if let Some(alias) = &spec.alias {
globals.add_alias(current_ns, alias, ns_name);
}
match &spec.refer {
RequireRefer::None => {}
RequireRefer::All => globals.refer_all(current_ns, ns_name),
RequireRefer::Named(names) => globals.refer_named(current_ns, ns_name, names),
}
Ok(())
}
pub(crate) fn claim_or_wait(globals: &Arc<GlobalEnv>, ns_name: &Arc<str>) -> EvalResult<bool> {
let tid = std::thread::current().id();
loop {
let mut loading = globals.loading.lock().unwrap();
match loading.get(ns_name.as_ref()) {
None => {
loading.insert(ns_name.clone(), tid);
return Ok(true);
}
Some(&owner) if owner == tid => {
return Err(EvalError::Runtime(format!("circular require: {ns_name}")));
}
Some(_) => {
let _guard = globals.loading_done.wait(loading).unwrap();
if globals.is_loaded(ns_name) {
return Ok(false);
}
}
}
}
}
fn do_load(globals: &Arc<GlobalEnv>, ns_name: &Arc<str>) -> EvalResult<()> {
if let Some(loader) = globals.compiled_ns_loader(ns_name) {
globals.get_or_create_ns(ns_name);
if ns_name.as_ref() != "clojure.core" {
globals.refer_all(ns_name, "clojure.core");
}
let saved_ns = globals
.lookup_var("clojure.core", "*ns*")
.and_then(|v| crate::env::dynamics::deref_var(&v));
let result = loader(globals);
if let Some(saved) = saved_ns
&& let Some(var) = globals.lookup_var("clojure.core", "*ns*")
{
var.get().bind(saved);
}
return result;
}
let rel_path = ns_name.replace('.', "/").replace('-', "_");
let src_paths = globals.source_paths.read().unwrap().clone();
let (src, file_path): (String, String) = if let Some(builtin) = globals.builtin_source(ns_name)
{
(builtin.to_owned(), format!("<builtin:{ns_name}>"))
} else if let Some(found) = find_source_file(&rel_path, &src_paths) {
found
} else {
if try_native_require(globals, ns_name)? {
return Ok(());
}
return Err(EvalError::Runtime(format!(
"Could not find namespace {ns_name} on source path"
)));
};
#[cfg(not(target_arch = "wasm32"))]
if !file_path.starts_with("<builtin:") {
let repo_root = globals
.vcs()
.and_then(|vcs| vcs.find_repo_root(Path::new(&file_path)))
.map(|p| p.display().to_string());
let ns_ptr = globals.get_or_create_ns(ns_name);
ns_ptr
.get()
.set_source_location(&file_path, repo_root.as_deref());
}
if ns_name.as_ref() != "clojure.core" {
globals.refer_all(ns_name, "clojure.core");
}
let saved_ns = globals
.lookup_var("clojure.core", "*ns*")
.and_then(|v| crate::env::dynamics::deref_var(&v));
{
let mut env = Env::new(globals.clone(), ns_name);
let mut parser = cljrs_reader::Parser::new(src, file_path);
let forms = parser.parse_all().map_err(EvalError::Read)?;
for form in forms {
let _alloc_frame = cljrs_gc::push_alloc_frame();
(*globals)
.eval(&form, &mut env)
.map_err(|e| annotate(e, ns_name))?;
}
}
if let Some(saved) = saved_ns
&& let Some(var) = globals.lookup_var("clojure.core", "*ns*")
{
var.get().bind(saved);
}
Ok(())
}
fn try_native_require(globals: &Arc<GlobalEnv>, ns_name: &Arc<str>) -> EvalResult<bool> {
let loader = globals.native_require_loader.read().unwrap().clone();
match loader {
Some(loader) => loader(globals, ns_name),
None => Ok(false),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_versioned_ns(
globals: Arc<GlobalEnv>,
spec: &RequireSpec,
commit: &str,
current_ns: &str,
) -> EvalResult<()> {
let versioned_ns_name =
crate::env::versioned::ensure_versioned_ns_loaded(&globals, &spec.ns, commit)?;
apply_alias_refer(&globals, &versioned_ns_name, current_ns, spec);
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
fn apply_alias_refer(
globals: &GlobalEnv,
effective_ns: &Arc<str>,
current_ns: &str,
spec: &RequireSpec,
) {
if let Some(alias) = &spec.alias {
globals.add_alias(current_ns, alias, effective_ns);
}
match &spec.refer {
RequireRefer::None => {}
RequireRefer::All => globals.refer_all(current_ns, effective_ns),
RequireRefer::Named(names) => globals.refer_named(current_ns, effective_ns, names),
}
}
pub(crate) fn find_source_file(
rel: &str,
src_paths: &[std::path::PathBuf],
) -> Option<(String, String)> {
for dir in src_paths {
for ext in &[".cljrs", ".cljc"] {
let path = dir.join(format!("{rel}{ext}"));
if path.exists() {
let src = std::fs::read_to_string(&path).ok()?;
return Some((src, path.display().to_string()));
}
}
}
None
}
pub(crate) fn annotate(e: EvalError, ns_name: &Arc<str>) -> EvalError {
match e {
EvalError::Read(_) => e,
EvalError::Recur(_) => e,
other => EvalError::Runtime(format!("in {ns_name}: {other}")),
}
}