#![cfg_attr(feature = "nightly", feature(thread_local, type_alias_impl_trait))]
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(
clippy::all,
clippy::nursery,
clippy::pedantic,
elided_lifetimes_in_paths,
explicit_outlives_requirements,
noop_method_call,
single_use_lifetimes,
variant_size_differences,
rustdoc::all
)]
#![allow(
macro_expanded_macro_exports_accessed_by_absolute_paths,
clippy::ptr_arg,
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::needless_pass_by_value,
clippy::wildcard_imports,
clippy::enum_glob_use,
clippy::module_name_repetitions,
clippy::cast_precision_loss,
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::use_self,
clippy::iter_with_drain,
clippy::type_repetition_in_bounds,
clippy::missing_const_for_fn,
)]
extern crate self as jrsonnet_evaluator;
mod arr;
#[cfg(feature = "async-import")]
pub mod async_import;
mod ctx;
mod dynamic;
pub mod error;
mod evaluate;
pub mod function;
pub mod gc;
mod import;
mod integrations;
pub mod manifest;
mod map;
mod obj;
pub mod stack;
pub mod stdlib;
mod tla;
pub mod trace;
pub mod typed;
pub mod val;
use std::{
any::Any,
cell::{Ref, RefCell, RefMut},
fmt::{self, Debug},
path::Path,
};
pub use ctx::*;
pub use dynamic::*;
pub use error::{Error, ErrorKind::*, Result, ResultExt};
pub use evaluate::*;
use function::CallLocation;
use gc::{GcHashMap, TraceBox};
use hashbrown::hash_map::RawEntryMut;
pub use import::*;
use jrsonnet_gcmodule::{Cc, Trace};
pub use jrsonnet_interner::{IBytes, IStr};
pub use jrsonnet_parser as parser;
use jrsonnet_parser::*;
pub use obj::*;
use stack::check_depth;
pub use tla::apply_tla;
pub use val::{Thunk, Val};
pub trait Unbound: Trace {
type Bound;
fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;
}
#[derive(Clone, Trace)]
pub enum MaybeUnbound {
Unbound(Cc<TraceBox<dyn Unbound<Bound = Val>>>),
Bound(Thunk<Val>),
}
impl Debug for MaybeUnbound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MaybeUnbound")
}
}
impl MaybeUnbound {
pub fn evaluate(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {
match self {
Self::Unbound(v) => v.bind(sup, this),
Self::Bound(v) => Ok(v.evaluate()?),
}
}
}
pub trait ContextInitializer: Trace {
fn initialize(&self, state: State, for_file: Source) -> Context;
fn as_any(&self) -> &dyn Any;
}
#[derive(Trace)]
pub struct DummyContextInitializer;
impl ContextInitializer for DummyContextInitializer {
fn initialize(&self, state: State, _for_file: Source) -> Context {
ContextBuilder::new(state).build()
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Trace)]
pub struct EvaluationSettings {
pub context_initializer: TraceBox<dyn ContextInitializer>,
pub import_resolver: TraceBox<dyn ImportResolver>,
}
impl Default for EvaluationSettings {
fn default() -> Self {
Self {
context_initializer: tb!(DummyContextInitializer),
import_resolver: tb!(DummyImportResolver),
}
}
}
#[derive(Trace)]
struct FileData {
string: Option<IStr>,
bytes: Option<IBytes>,
parsed: Option<LocExpr>,
evaluated: Option<Val>,
evaluating: bool,
}
impl FileData {
fn new_string(data: IStr) -> Self {
Self {
string: Some(data),
bytes: None,
parsed: None,
evaluated: None,
evaluating: false,
}
}
fn new_bytes(data: IBytes) -> Self {
Self {
string: None,
bytes: Some(data),
parsed: None,
evaluated: None,
evaluating: false,
}
}
pub(crate) fn get_string(&mut self) -> Option<IStr> {
if self.string.is_none() {
self.string = Some(
self.bytes
.as_ref()
.expect("either string or bytes should be set")
.clone()
.cast_str()?,
);
}
Some(self.string.clone().expect("just set"))
}
}
#[derive(Default, Trace)]
pub struct EvaluationStateInternals {
file_cache: RefCell<GcHashMap<SourcePath, FileData>>,
settings: RefCell<EvaluationSettings>,
}
#[derive(Default, Clone, Trace)]
pub struct State(Cc<EvaluationStateInternals>);
impl State {
pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {
let mut file_cache = self.file_cache();
let mut file = file_cache.raw_entry_mut().from_key(&path);
let file = match file {
RawEntryMut::Occupied(ref mut d) => d.get_mut(),
RawEntryMut::Vacant(v) => {
let data = self.settings().import_resolver.load_file_contents(&path)?;
v.insert(
path.clone(),
FileData::new_string(
std::str::from_utf8(&data)
.map_err(|_| ImportBadFileUtf8(path.clone()))?
.into(),
),
)
.1
}
};
Ok(file
.get_string()
.ok_or_else(|| ImportBadFileUtf8(path.clone()))?)
}
pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {
let mut file_cache = self.file_cache();
let mut file = file_cache.raw_entry_mut().from_key(&path);
let file = match file {
RawEntryMut::Occupied(ref mut d) => d.get_mut(),
RawEntryMut::Vacant(v) => {
let data = self.settings().import_resolver.load_file_contents(&path)?;
v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))
.1
}
};
if let Some(str) = &file.bytes {
return Ok(str.clone());
}
if file.bytes.is_none() {
file.bytes = Some(
file.string
.as_ref()
.expect("either string or bytes should be set")
.clone()
.cast_bytes(),
);
}
Ok(file.bytes.as_ref().expect("just set").clone())
}
pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {
let mut file_cache = self.file_cache();
let mut file = file_cache.raw_entry_mut().from_key(&path);
let file = match file {
RawEntryMut::Occupied(ref mut d) => d.get_mut(),
RawEntryMut::Vacant(v) => {
let data = self.settings().import_resolver.load_file_contents(&path)?;
v.insert(
path.clone(),
FileData::new_string(
std::str::from_utf8(&data)
.map_err(|_| ImportBadFileUtf8(path.clone()))?
.into(),
),
)
.1
}
};
if let Some(val) = &file.evaluated {
return Ok(val.clone());
}
let code = file
.get_string()
.ok_or_else(|| ImportBadFileUtf8(path.clone()))?;
let file_name = Source::new(path.clone(), code.clone());
if file.parsed.is_none() {
file.parsed = Some(
jrsonnet_parser::parse(
&code,
&ParserSettings {
source: file_name.clone(),
},
)
.map_err(|e| ImportSyntaxError {
path: file_name.clone(),
error: Box::new(e),
})?,
);
}
let parsed = file.parsed.as_ref().expect("just set").clone();
if file.evaluating {
throw!(InfiniteRecursionDetected)
}
file.evaluating = true;
drop(file_cache);
let res = evaluate(self.create_default_context(file_name), &parsed);
let mut file_cache = self.file_cache();
let mut file = file_cache.raw_entry_mut().from_key(&path);
let RawEntryMut::Occupied(file) = &mut file else {
unreachable!("this file was just here!")
};
let file = file.get_mut();
file.evaluating = false;
match res {
Ok(v) => {
file.evaluated = Some(v.clone());
Ok(v)
}
Err(e) => Err(e),
}
}
pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {
let resolved = self.resolve_from(from, path)?;
self.import_resolved(resolved)
}
pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {
let resolved = self.resolve(path)?;
self.import_resolved(resolved)
}
pub fn create_default_context(&self, source: Source) -> Context {
let context_initializer = &self.settings().context_initializer;
context_initializer.initialize(self.clone(), source)
}
pub fn push<T>(
e: CallLocation<'_>,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
let _guard = check_depth()?;
f().with_description_src(e, frame_desc)
}
pub fn push_val(
&self,
e: &ExprLocation,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<Val>,
) -> Result<Val> {
let _guard = check_depth()?;
f().with_description_src(e, frame_desc)
}
pub fn push_description<T>(
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
let _guard = check_depth()?;
f().with_description(frame_desc)
}
}
impl State {
fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {
self.0.file_cache.borrow_mut()
}
pub fn settings(&self) -> Ref<'_, EvaluationSettings> {
self.0.settings.borrow()
}
pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {
self.0.settings.borrow_mut()
}
}
impl State {
pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
let code = code.into();
let source = Source::new_virtual(name.into(), code.clone());
let parsed = jrsonnet_parser::parse(
&code,
&ParserSettings {
source: source.clone(),
},
)
.map_err(|e| ImportSyntaxError {
path: source.clone(),
error: Box::new(e),
})?;
evaluate(self.create_default_context(source), &parsed)
}
}
impl State {
#[allow(clippy::missing_panics_doc)]
pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
self.import_resolver().resolve_from(from, path.as_ref())
}
#[allow(clippy::missing_panics_doc)]
pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {
self.import_resolver().resolve(path.as_ref())
}
pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {
Ref::map(self.settings(), |s| &*s.import_resolver)
}
pub fn set_import_resolver(&self, resolver: impl ImportResolver) {
self.settings_mut().import_resolver = tb!(resolver);
}
pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {
Ref::map(self.settings(), |s| &*s.context_initializer)
}
pub fn set_context_initializer(&self, initializer: impl ContextInitializer) {
self.settings_mut().context_initializer = tb!(initializer);
}
}