use std::path::PathBuf;
use std::sync::Arc;
use cljrs_gc::GcConfig;
use crate::builtins::builtins;
use crate::env::env::{Env, GlobalEnv};
use crate::interp::{eval, special};
use crate::mode::{ExecutionMode, TierState};
#[derive(Debug, thiserror::Error)]
pub enum BuildError {
#[error("failed to parse embedded source {origin}: {message}")]
EmbeddedSource { origin: String, message: String },
}
#[derive(Clone, Debug)]
pub struct Runtime {
globals: Arc<GlobalEnv>,
}
impl Runtime {
pub fn builder() -> RuntimeBuilder {
RuntimeBuilder::new()
}
pub fn from_globals(globals: Arc<GlobalEnv>) -> Self {
Self { globals }
}
pub fn globals(&self) -> &Arc<GlobalEnv> {
&self.globals
}
pub fn into_globals(self) -> Arc<GlobalEnv> {
self.globals
}
pub fn env(&self, ns: &str) -> Env {
Env::new(self.globals.clone(), ns)
}
pub fn execution_mode(&self) -> ExecutionMode {
self.globals.execution_mode()
}
pub fn tier_state(&self) -> TierState {
self.globals.tier_state()
}
}
pub struct RuntimeBuilder {
execution_mode: ExecutionMode,
source_paths: Vec<PathBuf>,
gc_config: Option<Arc<GcConfig>>,
gc_config_from_env: bool,
register_gc_roots: bool,
builtin_sources: Vec<(String, &'static str)>,
eager_clojure_test: bool,
}
impl Default for RuntimeBuilder {
fn default() -> Self {
Self::new()
}
}
impl RuntimeBuilder {
pub fn new() -> Self {
Self {
execution_mode: ExecutionMode::default(),
source_paths: Vec::new(),
gc_config: None,
gc_config_from_env: true,
register_gc_roots: true,
builtin_sources: Vec::new(),
eager_clojure_test: false,
}
}
pub fn execution_mode(mut self, mode: ExecutionMode) -> Self {
self.execution_mode = mode;
self
}
pub fn source_paths(mut self, paths: Vec<PathBuf>) -> Self {
self.source_paths = paths;
self
}
pub fn gc_config(mut self, config: Arc<GcConfig>) -> Self {
self.gc_config = Some(config);
self
}
pub fn gc_config_from_env(mut self, enabled: bool) -> Self {
self.gc_config_from_env = enabled;
self
}
pub fn register_gc_roots(mut self, enabled: bool) -> Self {
self.register_gc_roots = enabled;
self
}
pub fn builtin_source(mut self, ns: impl Into<String>, src: &'static str) -> Self {
self.builtin_sources.push((ns.into(), src));
self
}
pub fn eager_clojure_test(mut self, enabled: bool) -> Self {
self.eager_clojure_test = enabled;
self
}
pub fn build(self) -> Result<Runtime, BuildError> {
let globals = GlobalEnv::new(self.execution_mode);
builtins::register_all(&globals, "clojure.core");
globals.get_or_create_ns("user");
globals.refer_all("user", "clojure.core");
eval_embedded(&globals, builtins::BOOTSTRAP_SOURCE, "<bootstrap>")?;
globals.refer_all("user", "clojure.core");
globals.mark_loaded("clojure.core");
for (ns, src) in &self.builtin_sources {
globals.register_builtin_source(ns, src);
}
if self.eager_clojure_test {
eval_embedded(&globals, builtins::CLOJURE_TEST_SOURCE, "<clojure.test>")?;
globals.mark_loaded("clojure.test");
}
if !self.source_paths.is_empty() {
globals.set_source_paths(self.source_paths);
}
if self.gc_config_from_env {
cljrs_gc::HEAP.set_config_from_env();
}
if let Some(config) = self.gc_config {
globals.set_gc_config(config.clone());
cljrs_gc::HEAP.set_config(config);
}
if self.register_gc_roots {
register_namespace_roots(&globals);
}
special::sync_star_ns(&mut Env::new(globals.clone(), "user"));
if std::env::var("CLJRS_NO_IR").is_err() {
let target = self.execution_mode.target_tier();
if target.ir_enabled() {
crate::tiered::jit_state::set_bootstrap_arity_watermark(
crate::interp::arity::next_arity_id(),
);
}
globals.set_tier_state(target);
}
Ok(Runtime { globals })
}
}
fn eval_embedded(globals: &Arc<GlobalEnv>, src: &str, origin: &str) -> Result<(), BuildError> {
let mut parser = cljrs_reader::Parser::new(src.to_string(), origin.to_string());
let forms = parser.parse_all().map_err(|e| BuildError::EmbeddedSource {
origin: origin.to_string(),
message: format!("{e:?}"),
})?;
let mut env = Env::new(globals.clone(), "clojure.core");
for form in forms {
let _alloc_frame = cljrs_gc::push_alloc_frame();
if let Err(e) = eval::eval(&form, &mut env) {
eprintln!("[{origin} warning] {}: {:?}", form.span.start, e);
}
}
Ok(())
}
fn register_namespace_roots(globals: &Arc<GlobalEnv>) {
let weak = Arc::downgrade(globals);
cljrs_gc::HEAP.register_root_tracer(move |visitor| {
use cljrs_gc::GcVisitor as _;
let Some(globals) = weak.upgrade() else {
return;
};
let namespaces = globals.namespaces.read().unwrap();
for ns_ptr in namespaces.values() {
visitor.visit(ns_ptr);
}
});
}