use std::path::{Path, PathBuf};
use std::sync::Arc;
use cljrs_gc::GcConfig;
use cljrs_runtime::tiered::{Env, EvalError, GlobalEnv, eval};
use cljrs_value::Value;
use crate::native;
pub fn build_gc_config(
soft_limit_mb: Option<usize>,
hard_limit_mb: Option<usize>,
) -> Arc<GcConfig> {
match (soft_limit_mb, hard_limit_mb) {
(Some(soft), Some(hard)) => Arc::new(GcConfig::with_limits(
soft * 1024 * 1024,
hard * 1024 * 1024,
)),
(Some(soft), None) => Arc::new(GcConfig::with_hard_limit(soft * 1024 * 1024)),
(None, Some(hard)) => Arc::new(GcConfig::with_hard_limit(hard * 1024 * 1024)),
(None, None) => Arc::new(GcConfig::new()),
}
}
#[derive(Clone, Copy, Default)]
pub struct VersioningFlags {
pub verify_commit_signatures: bool,
pub enforce_native_versions: bool,
}
static JIT_ENABLED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn configure_jit(threshold: Option<u32>) {
if std::env::var("CLJRS_NO_JIT").is_ok() {
return;
}
if let Some(0) = threshold {
return; }
if let Some(t) = threshold {
cljrs_runtime::tiered::jit_state::set_jit_threshold(t);
}
JIT_ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
}
pub fn setup_globals(
src_paths: Vec<PathBuf>,
gc_config: Arc<GcConfig>,
versioning: VersioningFlags,
) -> Arc<GlobalEnv> {
let runtime = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::Tiered)
.source_paths(src_paths)
.gc_config(gc_config)
.build()
.unwrap_or_else(|e| {
eprintln!("failed to start the runtime: {e}");
std::process::exit(1);
});
if JIT_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
cljrs_compiler::jit::install(&runtime);
}
cljrs_stdlib::install(&runtime);
let globals = runtime.into_globals();
if versioning.verify_commit_signatures {
globals
.verify_commit_signatures
.store(true, std::sync::atomic::Ordering::Relaxed);
}
if versioning.enforce_native_versions {
globals.set_enforce_native_versions(true);
}
native::pinned::install(&globals);
if let Ok(cwd) = std::env::current_dir() {
apply_deps_config(&globals, &cwd);
}
#[cfg(feature = "async")]
ASYNC_DRIVER.with(|d| {
let guard = d.borrow();
let init = |g: &Arc<GlobalEnv>| {
cljrs_async::init(g);
cljrs_io::init(g);
#[cfg(feature = "net")]
cljrs_net::init(g);
#[cfg(feature = "charset")]
cljrs_charset::init(g);
};
match guard.as_ref() {
Some(drv) => drv.local.block_on(&drv.rt, async { init(&globals) }),
None => init(&globals),
}
});
#[cfg(feature = "base64")]
cljrs_base64::init(&globals);
globals
}
fn apply_deps_config(globals: &Arc<GlobalEnv>, cwd: &Path) {
match cljrs_project::config::load_config(cwd) {
Ok(Some(config)) => {
{
let mut paths = globals.source_paths.write().unwrap();
for p in &config.paths {
if !paths.contains(p) {
paths.push(p.clone());
}
}
}
add_dep_source_paths(globals, &config);
if config.verify_commit_signatures {
globals
.verify_commit_signatures
.store(true, std::sync::atomic::Ordering::Relaxed);
}
globals.load_trusted_signers(&config);
if config.enforce_native_versions {
globals.set_enforce_native_versions(true);
}
if let Some(rust_config) = &config.rust {
native::load_project_lib(rust_config, globals);
}
*globals.deps_config.write().unwrap() = Some(Arc::new(config));
}
Ok(None) => {}
Err(e) => eprintln!("cljrs: warning: could not load cljrs.edn: {e}"),
}
}
fn add_dep_source_paths(globals: &Arc<GlobalEnv>, config: &cljrs_project::config::DepsConfig) {
for (name, dep) in &config.deps {
let root = match dep {
cljrs_project::config::Dependency::Local { root } => {
if root.is_dir() {
root.clone()
} else {
eprintln!(
"cljrs: warning: local dep {name} not found at {}",
root.display()
);
continue;
}
}
cljrs_project::config::Dependency::Git(git) => {
match cljrs_project::vcs::worktree_at_commit(&git.url, &git.sha) {
Ok(p) => p,
Err(e) => {
eprintln!(
"cljrs: warning: git dep {name} ({}) is not available ({e}); \
run `cljrs deps fetch`",
git.url
);
continue;
}
}
}
};
let mut paths = globals.source_paths.write().unwrap();
for p in dep_source_paths(&root) {
if p.is_dir() && !paths.contains(&p) {
paths.push(p);
}
}
}
}
pub fn collect_dep_src_paths(config: &cljrs_project::config::DepsConfig) -> Vec<PathBuf> {
let mut paths = Vec::new();
for (name, dep) in &config.deps {
let root = match dep {
cljrs_project::config::Dependency::Local { root } => {
if root.is_dir() {
root.clone()
} else {
eprintln!(
"cljrs: warning: local dep {name} not found at {}",
root.display()
);
continue;
}
}
cljrs_project::config::Dependency::Git(git) => {
match cljrs_project::vcs::worktree_at_commit(&git.url, &git.sha) {
Ok(p) => p,
Err(e) => {
eprintln!(
"cljrs: warning: git dep {name} ({}) is not available ({e}); \
run `cljrs deps fetch`",
git.url
);
continue;
}
}
}
};
for p in dep_source_paths(&root) {
if p.is_dir() && !paths.contains(&p) {
paths.push(p);
}
}
}
paths
}
fn dep_source_paths(root: &Path) -> Vec<PathBuf> {
let cfg_path = root.join("cljrs.edn");
if cfg_path.exists()
&& let Ok(src) = std::fs::read_to_string(&cfg_path)
&& let Ok(parsed) = cljrs_project::config::parse_config(&src, &cfg_path)
&& !parsed.paths.is_empty()
{
return parsed.paths;
}
vec![root.join("src")]
}
pub fn file_to_namespace(root: &PathBuf, file: &Path) -> Option<String> {
let rel = file.strip_prefix(root).ok()?;
let stem = rel.with_extension(""); let ns = stem
.to_string_lossy()
.replace(std::path::MAIN_SEPARATOR, ".")
.replace('_', "-");
Some(ns)
}
pub fn eval_source(src: &str, filename: &str, globals: Arc<GlobalEnv>) -> miette::Result<Value> {
let mut env = Env::new(globals, "user");
eval_in(&mut env, src, filename)
}
pub fn run_source(
src: &str,
filename: &str,
globals: Arc<GlobalEnv>,
args: &[String],
) -> miette::Result<()> {
let mut env = Env::new(globals, "user");
eval_in(&mut env, src, filename)?;
call_main_if_defined(&mut env, args)?;
Ok(())
}
fn call_main_if_defined(env: &mut Env, args: &[String]) -> miette::Result<()> {
let resolved = eval_in(env, "(resolve '-main)", "<main-check>").unwrap_or(Value::Nil);
if resolved == Value::Nil {
return Ok(());
}
let escaped: Vec<String> = args.iter().map(|s| escape_clojure_string(s)).collect();
let call = format!("(-main {})", escaped.join(" "));
let result = eval_in(env, &call, "<main>")?;
await_main_result(result)?;
Ok(())
}
#[cfg(feature = "async")]
fn await_main_result(value: Value) -> miette::Result<()> {
ASYNC_DRIVER.with(|d| {
let guard = d.borrow();
match guard.as_ref() {
Some(drv) => {
drv.local
.block_on(&drv.rt, cljrs_async::eval_async::await_value(value))
.map_err(format_eval_error)?;
Ok(())
}
None => Ok(()),
}
})
}
#[cfg(not(feature = "async"))]
fn await_main_result(_value: Value) -> miette::Result<()> {
Ok(())
}
fn escape_clojure_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c => out.push(c),
}
}
out.push('"');
out
}
pub fn eval_in(env: &mut Env, src: &str, filename: &str) -> miette::Result<Value> {
let mut parser = cljrs_reader::Parser::new(src.to_string(), filename.to_string());
let forms = parser.parse_all().map_err(miette::Report::from)?;
let mut result = Value::Nil;
for form in forms {
let _alloc_frame = cljrs_gc::push_alloc_frame();
result = eval_form(&form, env).map_err(format_eval_error)?;
}
Ok(result)
}
#[cfg(feature = "async")]
struct AsyncDriver {
rt: tokio::runtime::Runtime,
local: tokio::task::LocalSet,
}
#[cfg(feature = "async")]
thread_local! {
static ASYNC_DRIVER: std::cell::RefCell<Option<AsyncDriver>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(feature = "async")]
pub fn with_async_driver<T>(f: impl FnOnce() -> T) -> T {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to build Tokio runtime");
let local = tokio::task::LocalSet::new();
ASYNC_DRIVER.with(|d| *d.borrow_mut() = Some(AsyncDriver { rt, local }));
let result = f();
ASYNC_DRIVER.with(|d| *d.borrow_mut() = None);
result
}
#[cfg(feature = "async")]
#[allow(clippy::result_large_err)]
pub fn eval_form(form: &cljrs_reader::Form, env: &mut Env) -> Result<Value, EvalError> {
ASYNC_DRIVER.with(|d| {
let guard = d.borrow();
match guard.as_ref() {
Some(drv) => drv
.local
.block_on(&drv.rt, cljrs_async::eval_async::eval_async(form, env)),
None => eval(form, env),
}
})
}
#[cfg(not(feature = "async"))]
#[allow(clippy::result_large_err)]
pub fn eval_form(form: &cljrs_reader::Form, env: &mut Env) -> Result<Value, EvalError> {
eval(form, env)
}
pub fn format_eval_error(e: EvalError) -> miette::Report {
match e {
EvalError::Thrown(val) => miette::miette!("Unhandled exception: {}", val),
EvalError::UnboundSymbol(s) => miette::miette!("Unable to resolve symbol: {}", s),
EvalError::Arity {
name,
expected,
got,
} => miette::miette!("Wrong number of args ({got}) passed to {name}; expected {expected}"),
EvalError::NotCallable(s) => miette::miette!("Not a function: {}", s),
EvalError::Runtime(msg) => miette::miette!("{}", msg),
EvalError::GasExhausted => miette::miette!("gas exhausted"),
EvalError::ForbiddenEffect(operation) => {
miette::miette!("effect forbidden in transaction function: {operation}")
}
EvalError::Read(e) => miette::Report::from(e),
EvalError::Recur(_) => miette::miette!("recur outside of loop/fn"),
EvalError::CommitSignatureVerificationFailed { commit, reason } => {
miette::miette!("commit {commit:?} failed signature verification: {reason}")
}
}
}