12#![cfg_attr(feature = "nightly", feature(thread_local))]3#![feature(type_alias_impl_trait)]4#![deny(unsafe_op_in_unsafe_fn)]5#![warn(6 clippy::all,7 clippy::nursery,8 clippy::pedantic,9 10 elided_lifetimes_in_paths,11 explicit_outlives_requirements,12 noop_method_call,13 single_use_lifetimes,14 variant_size_differences,15 rustdoc::all16)]17#![allow(18 macro_expanded_macro_exports_accessed_by_absolute_paths,19 clippy::ptr_arg,20 21 clippy::must_use_candidate,22 23 clippy::missing_errors_doc,24 25 clippy::needless_pass_by_value,26 27 clippy::wildcard_imports,28 clippy::enum_glob_use,29 clippy::module_name_repetitions,30 31 clippy::cast_precision_loss,32 clippy::cast_possible_wrap,33 clippy::cast_possible_truncation,34 clippy::cast_sign_loss,35 36 37 clippy::use_self,38 39 clippy::iter_with_drain,40 41 clippy::missing_const_for_fn,42)]434445extern crate self as jrsonnet_evaluator;4647mod arr;48mod ctx;49mod dynamic;50pub mod error;51mod evaluate;52pub mod function;53pub mod gc;54mod import;55mod integrations;56pub mod manifest;57mod map;58mod obj;59pub mod stack;60pub mod stdlib;61mod tla;62pub mod trace;63pub mod typed;64pub mod val;6566use std::{67 any::Any,68 cell::{Ref, RefCell, RefMut},69 fmt::{self, Debug},70 path::Path,71};7273pub use ctx::*;74pub use dynamic::*;75pub use error::{Error, ErrorKind::*, Result, ResultExt};76pub use evaluate::*;77use function::CallLocation;78use gc::{GcHashMap, TraceBox};79use hashbrown::hash_map::RawEntryMut;80pub use import::*;81use jrsonnet_gcmodule::{Cc, Trace};82pub use jrsonnet_interner::{IBytes, IStr};83pub use jrsonnet_parser as parser;84use jrsonnet_parser::*;85pub use obj::*;86use stack::check_depth;87pub use tla::apply_tla;88pub use val::{Thunk, Val};89909192pub trait Unbound: Trace {93 94 type Bound;95 96 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;97}9899100101#[derive(Clone, Trace)]102pub enum MaybeUnbound {103 104 Unbound(Cc<TraceBox<dyn Unbound<Bound = Val>>>),105 106 Bound(Thunk<Val>),107}108109impl Debug for MaybeUnbound {110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {111 write!(f, "MaybeUnbound")112 }113}114impl MaybeUnbound {115 116 pub fn evaluate(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {117 match self {118 Self::Unbound(v) => v.bind(sup, this),119 Self::Bound(v) => Ok(v.evaluate()?),120 }121 }122}123124125126pub trait ContextInitializer: Trace {127 128 fn initialize(&self, state: State, for_file: Source) -> Context;129 130 131 fn as_any(&self) -> &dyn Any;132}133134135#[derive(Trace)]136pub struct DummyContextInitializer;137impl ContextInitializer for DummyContextInitializer {138 fn initialize(&self, state: State, _for_file: Source) -> Context {139 ContextBuilder::new(state).build()140 }141 fn as_any(&self) -> &dyn Any {142 self143 }144}145146147#[derive(Trace)]148pub struct EvaluationSettings {149 150 151 pub context_initializer: TraceBox<dyn ContextInitializer>,152 153 pub import_resolver: TraceBox<dyn ImportResolver>,154}155impl Default for EvaluationSettings {156 fn default() -> Self {157 Self {158 context_initializer: tb!(DummyContextInitializer),159 import_resolver: tb!(DummyImportResolver),160 }161 }162}163164#[derive(Trace)]165struct FileData {166 string: Option<IStr>,167 bytes: Option<IBytes>,168 parsed: Option<LocExpr>,169 evaluated: Option<Val>,170171 evaluating: bool,172}173impl FileData {174 fn new_string(data: IStr) -> Self {175 Self {176 string: Some(data),177 bytes: None,178 parsed: None,179 evaluated: None,180 evaluating: false,181 }182 }183 fn new_bytes(data: IBytes) -> Self {184 Self {185 string: None,186 bytes: Some(data),187 parsed: None,188 evaluated: None,189 evaluating: false,190 }191 }192}193194#[derive(Default, Trace)]195pub struct EvaluationStateInternals {196 197 file_cache: RefCell<GcHashMap<SourcePath, FileData>>,198 199 settings: RefCell<EvaluationSettings>,200}201202203#[derive(Default, Clone, Trace)]204pub struct State(Cc<EvaluationStateInternals>);205206impl State {207 208 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {209 let mut file_cache = self.file_cache();210 let mut file = file_cache.raw_entry_mut().from_key(&path);211212 let file = match file {213 RawEntryMut::Occupied(ref mut d) => d.get_mut(),214 RawEntryMut::Vacant(v) => {215 let data = self.settings().import_resolver.load_file_contents(&path)?;216 v.insert(217 path.clone(),218 FileData::new_string(219 std::str::from_utf8(&data)220 .map_err(|_| ImportBadFileUtf8(path.clone()))?221 .into(),222 ),223 )224 .1225 }226 };227 if let Some(str) = &file.string {228 return Ok(str.clone());229 }230 if file.string.is_none() {231 file.string = Some(232 file.bytes233 .as_ref()234 .expect("either string or bytes should be set")235 .clone()236 .cast_str()237 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?,238 );239 }240 Ok(file.string.as_ref().expect("just set").clone())241 }242 243 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {244 let mut file_cache = self.file_cache();245 let mut file = file_cache.raw_entry_mut().from_key(&path);246247 let file = match file {248 RawEntryMut::Occupied(ref mut d) => d.get_mut(),249 RawEntryMut::Vacant(v) => {250 let data = self.settings().import_resolver.load_file_contents(&path)?;251 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))252 .1253 }254 };255 if let Some(str) = &file.bytes {256 return Ok(str.clone());257 }258 if file.bytes.is_none() {259 file.bytes = Some(260 file.string261 .as_ref()262 .expect("either string or bytes should be set")263 .clone()264 .cast_bytes(),265 );266 }267 Ok(file.bytes.as_ref().expect("just set").clone())268 }269 270 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {271 let mut file_cache = self.file_cache();272 let mut file = file_cache.raw_entry_mut().from_key(&path);273274 let file = match file {275 RawEntryMut::Occupied(ref mut d) => d.get_mut(),276 RawEntryMut::Vacant(v) => {277 let data = self.settings().import_resolver.load_file_contents(&path)?;278 v.insert(279 path.clone(),280 FileData::new_string(281 std::str::from_utf8(&data)282 .map_err(|_| ImportBadFileUtf8(path.clone()))?283 .into(),284 ),285 )286 .1287 }288 };289 if let Some(val) = &file.evaluated {290 return Ok(val.clone());291 }292 if file.string.is_none() {293 file.string = Some(294 std::str::from_utf8(295 file.bytes296 .as_ref()297 .expect("either string or bytes should be set"),298 )299 .map_err(|_| ImportBadFileUtf8(path.clone()))?300 .into(),301 );302 }303 let code = file.string.as_ref().expect("just set");304 let file_name = Source::new(path.clone(), code.clone());305 if file.parsed.is_none() {306 file.parsed = Some(307 jrsonnet_parser::parse(308 code,309 &ParserSettings {310 source: file_name.clone(),311 },312 )313 .map_err(|e| ImportSyntaxError {314 path: file_name.clone(),315 error: Box::new(e),316 })?,317 );318 }319 let parsed = file.parsed.as_ref().expect("just set").clone();320 if file.evaluating {321 throw!(InfiniteRecursionDetected)322 }323 file.evaluating = true;324 325 drop(file_cache);326 let res = evaluate(self.create_default_context(file_name), &parsed);327328 let mut file_cache = self.file_cache();329 let mut file = file_cache.raw_entry_mut().from_key(&path);330331 let RawEntryMut::Occupied(file) = &mut file else {332 unreachable!("this file was just here!")333 };334 let file = file.get_mut();335 file.evaluating = false;336 match res {337 Ok(v) => {338 file.evaluated = Some(v.clone());339 Ok(v)340 }341 Err(e) => Err(e),342 }343 }344345 346 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {347 let resolved = self.resolve_from(from, path)?;348 self.import_resolved(resolved)349 }350 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {351 let resolved = self.resolve(path)?;352 self.import_resolved(resolved)353 }354355 356 pub fn create_default_context(&self, source: Source) -> Context {357 let context_initializer = &self.settings().context_initializer;358 context_initializer.initialize(self.clone(), source)359 }360361 362 pub fn push<T>(363 e: CallLocation<'_>,364 frame_desc: impl FnOnce() -> String,365 f: impl FnOnce() -> Result<T>,366 ) -> Result<T> {367 let _guard = check_depth()?;368369 f().with_description_src(e, frame_desc)370 }371372 373 pub fn push_val(374 &self,375 e: &ExprLocation,376 frame_desc: impl FnOnce() -> String,377 f: impl FnOnce() -> Result<Val>,378 ) -> Result<Val> {379 let _guard = check_depth()?;380381 f().with_description_src(e, frame_desc)382 }383 384 pub fn push_description<T>(385 frame_desc: impl FnOnce() -> String,386 f: impl FnOnce() -> Result<T>,387 ) -> Result<T> {388 let _guard = check_depth()?;389390 f().with_description(frame_desc)391 }392}393394395impl State {396 fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {397 self.0.file_cache.borrow_mut()398 }399 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {400 self.0.settings.borrow()401 }402 pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {403 self.0.settings.borrow_mut()404 }405}406407408impl State {409 410 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {411 let code = code.into();412 let source = Source::new_virtual(name.into(), code.clone());413 let parsed = jrsonnet_parser::parse(414 &code,415 &ParserSettings {416 source: source.clone(),417 },418 )419 .map_err(|e| ImportSyntaxError {420 path: source.clone(),421 error: Box::new(e),422 })?;423 evaluate(self.create_default_context(source), &parsed)424 }425}426427428impl State {429 430 #[allow(clippy::missing_panics_doc)]431 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {432 self.import_resolver().resolve_from(from, path.as_ref())433 }434435 436 #[allow(clippy::missing_panics_doc)]437 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {438 self.import_resolver().resolve(path.as_ref())439 }440 pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {441 Ref::map(self.settings(), |s| &*s.import_resolver)442 }443 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {444 self.settings_mut().import_resolver = TraceBox(resolver);445 }446 pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {447 Ref::map(self.settings(), |s| &*s.context_initializer)448 }449}