12#![cfg_attr(feature = "nightly", feature(thread_local, type_alias_impl_trait))]3#![deny(unsafe_op_in_unsafe_fn)]4#![warn(5 clippy::all,6 clippy::nursery,7 clippy::pedantic,8 9 elided_lifetimes_in_paths,10 explicit_outlives_requirements,11 noop_method_call,12 single_use_lifetimes,13 variant_size_differences,14 rustdoc::all15)]16#![allow(17 macro_expanded_macro_exports_accessed_by_absolute_paths,18 clippy::ptr_arg,19 20 clippy::must_use_candidate,21 22 clippy::missing_errors_doc,23 24 clippy::needless_pass_by_value,25 26 clippy::wildcard_imports,27 clippy::enum_glob_use,28 clippy::module_name_repetitions,29 30 clippy::cast_precision_loss,31 clippy::cast_possible_wrap,32 clippy::cast_possible_truncation,33 clippy::cast_sign_loss,34 35 36 clippy::use_self,37 38 clippy::iter_with_drain,39 clippy::type_repetition_in_bounds,40 41 clippy::missing_const_for_fn,42 43 clippy::missing_panics_doc,44 45 46 47 clippy::mutable_key_type,48 49 clippy::redundant_pub_crate,50)]515253extern crate self as jrsonnet_evaluator;5455mod arr;56#[cfg(feature = "async-import")]57pub mod async_import;58mod ctx;59mod dynamic;60pub mod error;61mod evaluate;62pub mod function;63pub mod gc;64mod import;65mod integrations;66pub mod manifest;67mod map;68mod obj;69pub mod stack;70pub mod stdlib;71mod tla;72pub mod trace;73pub mod typed;74pub mod val;7576use std::{77 any::Any,78 cell::{Ref, RefCell, RefMut},79 fmt::{self, Debug},80 path::Path,81};8283pub use ctx::*;84pub use dynamic::*;85pub use error::{Error, ErrorKind::*, Result, ResultExt};86pub use evaluate::*;87use function::CallLocation;88use gc::{GcHashMap, TraceBox};89use hashbrown::hash_map::RawEntryMut;90pub use import::*;91use jrsonnet_gcmodule::{Cc, Trace};92pub use jrsonnet_interner::{IBytes, IStr};93#[doc(hidden)]94pub use jrsonnet_macros;95pub use jrsonnet_parser as parser;96use jrsonnet_parser::*;97pub use obj::*;98use stack::check_depth;99pub use tla::apply_tla;100pub use val::{Thunk, Val};101102103104pub trait Unbound: Trace {105 106 type Bound;107 108 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;109}110111112113#[derive(Clone, Trace)]114pub enum MaybeUnbound {115 116 Unbound(Cc<TraceBox<dyn Unbound<Bound = Val>>>),117 118 Bound(Thunk<Val>),119}120121impl Debug for MaybeUnbound {122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {123 write!(f, "MaybeUnbound")124 }125}126impl MaybeUnbound {127 128 pub fn evaluate(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {129 match self {130 Self::Unbound(v) => v.bind(sup, this),131 Self::Bound(v) => Ok(v.evaluate()?),132 }133 }134}135136137138pub trait ContextInitializer: Trace {139 140 fn reserve_vars(&self) -> usize {141 0142 }143 144 145 146 fn initialize(&self, state: State, for_file: Source) -> Context {147 let mut builder = ContextBuilder::with_capacity(state, self.reserve_vars());148 self.populate(for_file, &mut builder);149 builder.build()150 }151 152 153 fn populate(&self, for_file: Source, builder: &mut ContextBuilder);154 155 156 fn as_any(&self) -> &dyn Any;157}158159160impl ContextInitializer for () {161 fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {}162 fn as_any(&self) -> &dyn Any {163 self164 }165}166167macro_rules! impl_context_initializer {168 ($($gen:ident)*) => {169 #[allow(non_snake_case)]170 impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {171 fn reserve_vars(&self) -> usize {172 let mut out = 0;173 let ($($gen,)*) = self;174 $(out += $gen.reserve_vars();)*175 out176 }177 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {178 let ($($gen,)*) = self;179 $($gen.populate(for_file.clone(), builder);)*180 }181 fn as_any(&self) -> &dyn Any {182 self183 }184 }185 };186 ($($cur:ident)* @ $c:ident $($rest:ident)*) => {187 impl_context_initializer!($($cur)*);188 impl_context_initializer!($($cur)* $c @ $($rest)*);189 };190 ($($cur:ident)* @) => {191 impl_context_initializer!($($cur)*);192 }193}194impl_context_initializer! {195 A @ B C D E F G196}197198199#[derive(Trace)]200pub struct EvaluationSettings {201 202 203 pub context_initializer: TraceBox<dyn ContextInitializer>,204 205 pub import_resolver: TraceBox<dyn ImportResolver>,206}207impl Default for EvaluationSettings {208 fn default() -> Self {209 Self {210 context_initializer: tb!(()),211 import_resolver: tb!(DummyImportResolver),212 }213 }214}215216#[derive(Trace)]217struct FileData {218 string: Option<IStr>,219 bytes: Option<IBytes>,220 parsed: Option<LocExpr>,221 evaluated: Option<Val>,222223 evaluating: bool,224}225impl FileData {226 fn new_string(data: IStr) -> Self {227 Self {228 string: Some(data),229 bytes: None,230 parsed: None,231 evaluated: None,232 evaluating: false,233 }234 }235 fn new_bytes(data: IBytes) -> Self {236 Self {237 string: None,238 bytes: Some(data),239 parsed: None,240 evaluated: None,241 evaluating: false,242 }243 }244 pub(crate) fn get_string(&mut self) -> Option<IStr> {245 if self.string.is_none() {246 self.string = Some(247 self.bytes248 .as_ref()249 .expect("either string or bytes should be set")250 .clone()251 .cast_str()?,252 );253 }254 Some(self.string.clone().expect("just set"))255 }256}257258#[derive(Default, Trace)]259pub struct EvaluationStateInternals {260 261 file_cache: RefCell<GcHashMap<SourcePath, FileData>>,262 263 settings: RefCell<EvaluationSettings>,264}265266267#[derive(Default, Clone, Trace)]268pub struct State(Cc<EvaluationStateInternals>);269270impl State {271 272 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {273 let mut file_cache = self.file_cache();274 let mut file = file_cache.raw_entry_mut().from_key(&path);275276 let file = match file {277 RawEntryMut::Occupied(ref mut d) => d.get_mut(),278 RawEntryMut::Vacant(v) => {279 let data = self.settings().import_resolver.load_file_contents(&path)?;280 v.insert(281 path.clone(),282 FileData::new_string(283 std::str::from_utf8(&data)284 .map_err(|_| ImportBadFileUtf8(path.clone()))?285 .into(),286 ),287 )288 .1289 }290 };291 Ok(file292 .get_string()293 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?)294 }295 296 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {297 let mut file_cache = self.file_cache();298 let mut file = file_cache.raw_entry_mut().from_key(&path);299300 let file = match file {301 RawEntryMut::Occupied(ref mut d) => d.get_mut(),302 RawEntryMut::Vacant(v) => {303 let data = self.settings().import_resolver.load_file_contents(&path)?;304 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))305 .1306 }307 };308 if let Some(str) = &file.bytes {309 return Ok(str.clone());310 }311 if file.bytes.is_none() {312 file.bytes = Some(313 file.string314 .as_ref()315 .expect("either string or bytes should be set")316 .clone()317 .cast_bytes(),318 );319 }320 Ok(file.bytes.as_ref().expect("just set").clone())321 }322 323 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {324 let mut file_cache = self.file_cache();325 let mut file = file_cache.raw_entry_mut().from_key(&path);326327 let file = match file {328 RawEntryMut::Occupied(ref mut d) => d.get_mut(),329 RawEntryMut::Vacant(v) => {330 let data = self.settings().import_resolver.load_file_contents(&path)?;331 v.insert(332 path.clone(),333 FileData::new_string(334 std::str::from_utf8(&data)335 .map_err(|_| ImportBadFileUtf8(path.clone()))?336 .into(),337 ),338 )339 .1340 }341 };342 if let Some(val) = &file.evaluated {343 return Ok(val.clone());344 }345 let code = file346 .get_string()347 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?;348 let file_name = Source::new(path.clone(), code.clone());349 if file.parsed.is_none() {350 file.parsed = Some(351 jrsonnet_parser::parse(352 &code,353 &ParserSettings {354 source: file_name.clone(),355 },356 )357 .map_err(|e| ImportSyntaxError {358 path: file_name.clone(),359 error: Box::new(e),360 })?,361 );362 }363 let parsed = file.parsed.as_ref().expect("just set").clone();364 if file.evaluating {365 bail!(InfiniteRecursionDetected)366 }367 file.evaluating = true;368 369 drop(file_cache);370 let res = evaluate(self.create_default_context(file_name), &parsed);371372 let mut file_cache = self.file_cache();373 let mut file = file_cache.raw_entry_mut().from_key(&path);374375 let RawEntryMut::Occupied(file) = &mut file else {376 unreachable!("this file was just here!")377 };378 let file = file.get_mut();379 file.evaluating = false;380 match res {381 Ok(v) => {382 file.evaluated = Some(v.clone());383 Ok(v)384 }385 Err(e) => Err(e),386 }387 }388389 390 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {391 let resolved = self.resolve_from(from, path)?;392 self.import_resolved(resolved)393 }394 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {395 let resolved = self.resolve(path)?;396 self.import_resolved(resolved)397 }398399 400 pub fn create_default_context(&self, source: Source) -> Context {401 let context_initializer = &self.settings().context_initializer;402 context_initializer.initialize(self.clone(), source)403 }404405 406 pub fn create_default_context_with(407 &self,408 source: Source,409 context_initializer: impl ContextInitializer,410 ) -> Context {411 let default_initializer = &self.settings().context_initializer;412 let mut builder = ContextBuilder::with_capacity(413 self.clone(),414 default_initializer.reserve_vars() + context_initializer.reserve_vars(),415 );416 default_initializer.populate(source.clone(), &mut builder);417 context_initializer.populate(source, &mut builder);418419 builder.build()420 }421422 423 pub fn push<T>(424 e: CallLocation<'_>,425 frame_desc: impl FnOnce() -> String,426 f: impl FnOnce() -> Result<T>,427 ) -> Result<T> {428 let _guard = check_depth()?;429430 f().with_description_src(e, frame_desc)431 }432433 434 pub fn push_val(435 &self,436 e: &ExprLocation,437 frame_desc: impl FnOnce() -> String,438 f: impl FnOnce() -> Result<Val>,439 ) -> Result<Val> {440 let _guard = check_depth()?;441442 f().with_description_src(e, frame_desc)443 }444 445 pub fn push_description<T>(446 frame_desc: impl FnOnce() -> String,447 f: impl FnOnce() -> Result<T>,448 ) -> Result<T> {449 let _guard = check_depth()?;450451 f().with_description(frame_desc)452 }453}454455456impl State {457 fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {458 self.0.file_cache.borrow_mut()459 }460 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {461 self.0.settings.borrow()462 }463 pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {464 self.0.settings.borrow_mut()465 }466 pub fn add_global(&self, name: IStr, value: Thunk<Val>) {467 #[derive(Trace)]468 struct GlobalsCtx {469 globals: RefCell<GcHashMap<IStr, Thunk<Val>>>,470 inner: TraceBox<dyn ContextInitializer>,471 }472 impl ContextInitializer for GlobalsCtx {473 fn reserve_vars(&self) -> usize {474 self.inner.reserve_vars() + self.globals.borrow().len()475 }476 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {477 self.inner.populate(for_file, builder);478 for (name, val) in self.globals.borrow().iter() {479 builder.bind(name.clone(), val.clone());480 }481 }482483 fn as_any(&self) -> &dyn Any {484 self485 }486 }487 let mut settings = self.settings_mut();488 let initializer = &mut settings.context_initializer;489 if let Some(global) = initializer.as_any().downcast_ref::<GlobalsCtx>() {490 global.globals.borrow_mut().insert(name, value);491 } else {492 let inner = std::mem::replace(&mut settings.context_initializer, tb!(()));493 settings.context_initializer = tb!(GlobalsCtx {494 globals: {495 let mut out = GcHashMap::with_capacity(1);496 out.insert(name, value);497 RefCell::new(out)498 },499 inner500 });501 }502 }503}504505#[derive(Trace)]506pub struct InitialUnderscore(pub Thunk<Val>);507impl ContextInitializer for InitialUnderscore {508 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {509 builder.bind("_", self.0.clone());510 }511512 fn as_any(&self) -> &dyn Any {513 self514 }515}516517518impl State {519 520 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {521 let code = code.into();522 let source = Source::new_virtual(name.into(), code.clone());523 let parsed = jrsonnet_parser::parse(524 &code,525 &ParserSettings {526 source: source.clone(),527 },528 )529 .map_err(|e| ImportSyntaxError {530 path: source.clone(),531 error: Box::new(e),532 })?;533 evaluate(self.create_default_context(source), &parsed)534 }535 536 pub fn evaluate_snippet_with(537 &self,538 name: impl Into<IStr>,539 code: impl Into<IStr>,540 context_initializer: impl ContextInitializer,541 ) -> Result<Val> {542 let code = code.into();543 let source = Source::new_virtual(name.into(), code.clone());544 let parsed = jrsonnet_parser::parse(545 &code,546 &ParserSettings {547 source: source.clone(),548 },549 )550 .map_err(|e| ImportSyntaxError {551 path: source.clone(),552 error: Box::new(e),553 })?;554 evaluate(555 self.create_default_context_with(source, context_initializer),556 &parsed,557 )558 }559}560561562impl State {563 564 #[allow(clippy::missing_panics_doc)]565 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {566 self.import_resolver().resolve_from(from, path.as_ref())567 }568569 570 #[allow(clippy::missing_panics_doc)]571 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {572 self.import_resolver().resolve(path.as_ref())573 }574 pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {575 Ref::map(self.settings(), |s| &*s.import_resolver)576 }577 pub fn set_import_resolver(&self, resolver: impl ImportResolver) {578 self.settings_mut().import_resolver = tb!(resolver);579 }580 pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {581 Ref::map(self.settings(), |s| &*s.context_initializer)582 }583 pub fn set_context_initializer(&self, initializer: impl ContextInitializer) {584 self.settings_mut().context_initializer = tb!(initializer);585 }586}