pub mod alloc;
pub mod collection;
pub mod crypto;
pub mod dispatch;
pub mod doc_sources;
pub mod docs;
pub mod domain;
#[cfg(feature = "sys")]
pub mod entropy;
pub mod erase;
pub mod errors;
pub mod host_surface;
pub mod hosted;
pub mod inputs;
pub mod json;
pub mod literal;
pub mod messages;
#[cfg(feature = "sys")]
pub mod net;
pub mod nix;
pub mod observe;
pub mod paths;
pub mod pipeline;
pub mod project_rules;
#[cfg(feature = "sys")]
pub mod provide;
pub mod session;
pub mod spread;
pub mod stdlib;
#[cfg(feature = "sys")]
pub mod sys;
pub mod timings;
pub mod uses;
pub mod xml;
pub use erase::{erase_types, to_sexps};
pub use inputs::{declarations, install_input_primitives, Declaration, InputError, Inputs};
pub use pipeline::{
parse, parse_tree_with_depth, parse_with_depth, run, run_with_inputs, Run, RunError,
};
pub use stdlib::install_blue_stdlib;
use tatara_lisp_eval::{install_full_stdlib_with, Interpreter};
pub fn interpreter<H: 'static>(host: &mut H) -> Interpreter<H> {
let mut interp = Interpreter::new();
install_full_stdlib_with(&mut interp, host);
stdlib::install_blue_stdlib(&mut interp);
json::install_json_stdlib(&mut interp);
crypto::install_crypto_stdlib(&mut interp);
nix::install_nix_stdlib(&mut interp);
xml::install_xml_stdlib(&mut interp);
#[cfg(feature = "sys")]
sys::install_sys_stdlib(&mut interp);
attach_node(&mut interp);
apply_execution_bounds(&mut interp);
interp
}
pub fn attach_node<H: 'static>(interp: &mut Interpreter<H>) {
dispatch::install(interp);
blue_lang_proc::install(interp, blue_lang_proc::Node::new(node_config()));
}
pub fn attach_node_in_frame<H: 'static>(interp: &mut Interpreter<H>, frame: &blue_lang_waku::Waku) {
attach_node_held(interp, &Hold::from(frame.clone()));
}
pub fn attach_node_held<H: 'static>(interp: &mut Interpreter<H>, hold: &Hold) {
let mut config = node_config();
let refused = hold.withheld_host_names();
#[cfg(feature = "sys")]
let provisions = hold.provisions.clone();
config.child = std::sync::Arc::new(move || {
let mut child = child_interpreter();
refuse_host_names(&mut child, &refused, "this process's frame");
#[cfg(feature = "sys")]
provide::provide(&mut child, &provisions);
child
});
dispatch::install(interp);
blue_lang_proc::install(interp, blue_lang_proc::Node::new(config));
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Hold {
pub frame: blue_lang_waku::Waku,
pub provisions: blue_lang_waku::Provisions,
}
impl From<blue_lang_waku::Waku> for Hold {
fn from(frame: blue_lang_waku::Waku) -> Self {
Self {
frame,
provisions: blue_lang_waku::Provisions::default(),
}
}
}
impl Hold {
#[must_use]
pub fn withheld_host_names(&self) -> Vec<(&'static str, blue_lang_waku::Capability)> {
let reach = self.provisions.widen(&self.frame.reach);
blue_lang_waku::Capability::host_effects()
.into_iter()
.filter(|cap| !reach.grants(*cap))
.flat_map(|cap| cap.names().into_iter().map(move |n| (n, cap)))
.collect()
}
pub fn bind<H: 'static>(&self, interp: &mut Interpreter<H>, who: &'static str) {
refuse_host_names(interp, &self.withheld_host_names(), who);
#[cfg(feature = "sys")]
provide::provide(interp, &self.provisions);
}
}
#[must_use]
pub fn ungranted_host_names(
frame: &blue_lang_waku::Waku,
) -> Vec<(&'static str, blue_lang_waku::Capability)> {
Hold::from(frame.clone()).withheld_host_names()
}
pub const BREACH: &str = "capability-breach";
#[must_use]
pub fn breach(
name: &str,
cap: blue_lang_waku::Capability,
who: &str,
at: tatara_lisp::Span,
) -> tatara_lisp_eval::EvalError {
tatara_lisp_eval::EvalError::fatal(
BREACH,
format!(
"`{name}` reaches the host ({}), which {who} does not grant",
cap.label()
),
at,
)
}
#[must_use]
pub fn is_breach(e: &tatara_lisp_eval::EvalError) -> bool {
matches!(e, tatara_lisp_eval::EvalError::Fatal { kind, .. } if &**kind == BREACH)
}
pub fn refuse_host_names<H: 'static>(
interp: &mut Interpreter<H>,
refused: &[(&'static str, blue_lang_waku::Capability)],
who: &'static str,
) {
refuse_host_names_noting(interp, refused, who, |_, _, _| {});
}
pub fn refuse_host_names_noting<H: 'static>(
interp: &mut Interpreter<H>,
refused: &[(&'static str, blue_lang_waku::Capability)],
who: &'static str,
note: fn(&mut H, blue_lang_waku::Capability, &'static str),
) {
for &(name, cap) in refused {
if interp.lookup_global(name).is_none() {
continue;
}
interp.register_fn(
name,
tatara_lisp_eval::ffi::Arity::Any,
move |_: &[tatara_lisp_eval::Value], host: &mut H, span| {
note(host, cap, name);
Err(breach(name, cap, who, span))
},
);
}
}
fn node_config() -> blue_lang_proc::NodeConfig {
let b = execution_bounds();
let mut config = blue_lang_proc::NodeConfig::new(std::sync::Arc::new(child_interpreter));
config.budget = tatara_lisp_eval::vm::Budget {
fuel: b.max_steps,
max_depth: Some(b.max_call_depth),
quantum: Some(blue_lang_proc::node::DEFAULT_QUANTUM),
};
config.clock = if cfg!(feature = "sys") {
blue_lang_proc::ClockKind::Real
} else {
blue_lang_proc::ClockKind::Virtual
};
config
}
fn child_interpreter() -> Interpreter<()> {
let mut interp = match HOSTLESS_BASE.lock() {
Ok(base) => base.fork(),
Err(_) => interpreter(&mut ()),
};
apply_execution_bounds(&mut interp);
interp
}
static EXECUTION_BOUNDS: std::sync::OnceLock<ExecutionBounds> = std::sync::OnceLock::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExecutionBounds {
pub max_call_depth: usize,
pub max_steps: Option<usize>,
}
impl ExecutionBounds {
pub const DEFAULT: Self = Self {
max_call_depth: tatara_lisp_eval::vm::DEFAULT_MAX_DEPTH,
max_steps: None,
};
}
pub fn set_execution_bounds(bounds: ExecutionBounds) {
let _ = EXECUTION_BOUNDS.set(bounds);
}
#[must_use]
pub fn execution_bounds() -> ExecutionBounds {
EXECUTION_BOUNDS
.get()
.copied()
.unwrap_or(ExecutionBounds::DEFAULT)
}
fn apply_execution_bounds<H: 'static>(interp: &mut Interpreter<H>) {
let b = execution_bounds();
let _ = interp.set_budget(tatara_lisp_eval::vm::Budget {
fuel: b.max_steps,
max_depth: Some(b.max_call_depth),
quantum: None,
});
}
static HOSTLESS_BASE: std::sync::LazyLock<std::sync::Mutex<Interpreter<()>>> =
std::sync::LazyLock::new(|| {
std::sync::Mutex::new(timings::stage(timings::Stage::Runtime, || {
interpreter(&mut ())
}))
});
pub fn interpreter_hostless() -> Interpreter<()> {
let mut interp = match HOSTLESS_BASE.lock() {
Ok(base) => base.fork(),
Err(_) => interpreter(&mut ()),
};
apply_execution_bounds(&mut interp);
attach_node(&mut interp);
interp
}
#[must_use]
pub fn lower_to_spanned(forms: &[tatara_lisp::Sexp]) -> Vec<tatara_lisp::Spanned> {
forms
.iter()
.map(tatara_lisp::Spanned::from_sexp_synthetic)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use tatara_lisp_eval::Value;
fn eval(src: &str) -> Value {
let forms = tatara_lisp::read_spanned(src).expect("read");
let mut interp = interpreter_hostless();
interp.eval_program(&forms, &mut ()).expect("eval")
}
#[test]
fn both_layers_are_installed() {
assert!(matches!(eval("(+ 1 2)"), Value::Int(3)));
assert!(matches!(eval("(mod 7 3)"), Value::Int(1)));
assert!(matches!(eval("(inc 41)"), Value::Int(42)));
assert!(matches!(eval("(first (list 9 8))"), Value::Int(9)));
assert!(matches!(eval(r#"(length "héllo")"#), Value::Int(5)));
assert!(matches!(
eval(r#"(json_parse "{\"outcome\":\"ok\"}")"#),
Value::List(_)
));
assert!(matches!(eval(r#"(blake3_hex "")"#), Value::Str(_)));
}
#[test]
fn a_bare_interpreter_lacks_the_stdlib() {
let forms = tatara_lisp::read_spanned("(mod 7 3)").expect("read");
let mut bare = Interpreter::new();
tatara_lisp_eval::install_primitives(&mut bare);
assert!(
bare.eval_program(&forms, &mut ()).is_err(),
"if a bare interpreter already resolved `mod`, this crate would be measuring nothing"
);
}
}