12#![cfg_attr(feature = "nightly", feature(thread_local))]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)]404142extern crate self as jrsonnet_evaluator;4344mod ctx;45mod dynamic;46pub mod error;47mod evaluate;48pub mod function;49pub mod gc;50mod import;51mod integrations;52mod map;53mod obj;54pub mod stack;55pub mod stdlib;56pub mod trace;57pub mod typed;58pub mod val;5960use std::{61 any::Any,62 cell::{Ref, RefCell, RefMut},63 collections::HashMap,64 fmt::{self, Debug},65 path::Path,66 rc::Rc,67};6869pub use ctx::*;70pub use dynamic::*;71use error::{Error::*, LocError, Result, ResultExt};72pub use evaluate::*;73use function::{CallLocation, TlaArg};74use gc::{GcHashMap, TraceBox};75use hashbrown::hash_map::RawEntryMut;76pub use import::*;77use jrsonnet_gcmodule::{Cc, Trace};78pub use jrsonnet_interner::{IBytes, IStr};79pub use jrsonnet_parser as parser;80use jrsonnet_parser::*;81pub use obj::*;82use stack::check_depth;83use trace::{CompactFormat, TraceFormat};84pub use val::{ManifestFormat, Thunk, Val};85868788pub trait Unbound: Trace {89 90 type Bound;91 92 fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;93}94959697#[derive(Clone, Trace)]98pub enum MaybeUnbound {99 100 Unbound(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),101 102 Bound(Thunk<Val>),103}104105impl Debug for MaybeUnbound {106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {107 write!(f, "MaybeUnbound")108 }109}110impl MaybeUnbound {111 112 pub fn evaluate(113 &self,114 s: State,115 sup: Option<ObjValue>,116 this: Option<ObjValue>,117 ) -> Result<Thunk<Val>> {118 match self {119 Self::Unbound(v) => v.bind(s, sup, this),120 Self::Bound(v) => Ok(v.clone()),121 }122 }123}124125126127pub trait ContextInitializer {128 129 fn initialize(&self, state: State, for_file: Source) -> Context;130 131 132 fn as_any(&self) -> &dyn Any;133}134135136pub struct DummyContextInitializer;137impl ContextInitializer for DummyContextInitializer {138 fn initialize(&self, _state: State, _for_file: Source) -> Context {139 Context::default()140 }141 fn as_any(&self) -> &dyn Any {142 self143 }144}145146147pub struct EvaluationSettings {148 149 pub max_trace: usize,150 151 pub tla_vars: HashMap<IStr, TlaArg>,152 153 154 pub context_initializer: Box<dyn ContextInitializer>,155 156 pub import_resolver: Box<dyn ImportResolver>,157 158 pub manifest_format: ManifestFormat,159 160 pub trace_format: Box<dyn TraceFormat>,161}162impl Default for EvaluationSettings {163 fn default() -> Self {164 Self {165 max_trace: 20,166 context_initializer: Box::new(DummyContextInitializer),167 tla_vars: HashMap::default(),168 import_resolver: Box::new(DummyImportResolver),169 manifest_format: ManifestFormat::Json {170 padding: 4,171 #[cfg(feature = "exp-preserve-order")]172 preserve_order: false,173 },174 trace_format: Box::new(CompactFormat {175 padding: 4,176 resolver: trace::PathResolver::Absolute,177 }),178 }179 }180}181182struct FileData {183 string: Option<IStr>,184 bytes: Option<IBytes>,185 parsed: Option<LocExpr>,186 evaluated: Option<Val>,187188 evaluating: bool,189}190impl FileData {191 fn new_string(data: IStr) -> Self {192 Self {193 string: Some(data),194 bytes: None,195 parsed: None,196 evaluated: None,197 evaluating: false,198 }199 }200 fn new_bytes(data: IBytes) -> Self {201 Self {202 string: None,203 bytes: Some(data),204 parsed: None,205 evaluated: None,206 evaluating: false,207 }208 }209}210211#[derive(Default)]212pub struct EvaluationStateInternals {213 214 file_cache: RefCell<GcHashMap<SourcePath, FileData>>,215 216 settings: RefCell<EvaluationSettings>,217}218219220#[derive(Default, Clone)]221pub struct State(Rc<EvaluationStateInternals>);222223impl State {224 225 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {226 let mut file_cache = self.file_cache();227 let mut file = file_cache.raw_entry_mut().from_key(&path);228229 let file = match file {230 RawEntryMut::Occupied(ref mut d) => d.get_mut(),231 RawEntryMut::Vacant(v) => {232 let data = self.settings().import_resolver.load_file_contents(&path)?;233 v.insert(234 path.clone(),235 FileData::new_string(236 std::str::from_utf8(&data)237 .map_err(|_| ImportBadFileUtf8(path.clone()))?238 .into(),239 ),240 )241 .1242 }243 };244 if let Some(str) = &file.string {245 return Ok(str.clone());246 }247 if file.string.is_none() {248 file.string = Some(249 file.bytes250 .as_ref()251 .expect("either string or bytes should be set")252 .clone()253 .cast_str()254 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?,255 );256 }257 Ok(file.string.as_ref().expect("just set").clone())258 }259 260 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {261 let mut file_cache = self.file_cache();262 let mut file = file_cache.raw_entry_mut().from_key(&path);263264 let file = match file {265 RawEntryMut::Occupied(ref mut d) => d.get_mut(),266 RawEntryMut::Vacant(v) => {267 let data = self.settings().import_resolver.load_file_contents(&path)?;268 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))269 .1270 }271 };272 if let Some(str) = &file.bytes {273 return Ok(str.clone());274 }275 if file.bytes.is_none() {276 file.bytes = Some(277 file.string278 .as_ref()279 .expect("either string or bytes should be set")280 .clone()281 .cast_bytes(),282 );283 }284 Ok(file.bytes.as_ref().expect("just set").clone())285 }286 287 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {288 let mut file_cache = self.file_cache();289 let mut file = file_cache.raw_entry_mut().from_key(&path);290291 let file = match file {292 RawEntryMut::Occupied(ref mut d) => d.get_mut(),293 RawEntryMut::Vacant(v) => {294 let data = self.settings().import_resolver.load_file_contents(&path)?;295 v.insert(296 path.clone(),297 FileData::new_string(298 std::str::from_utf8(&data)299 .map_err(|_| ImportBadFileUtf8(path.clone()))?300 .into(),301 ),302 )303 .1304 }305 };306 if let Some(val) = &file.evaluated {307 return Ok(val.clone());308 }309 if file.string.is_none() {310 file.string = Some(311 std::str::from_utf8(312 file.bytes313 .as_ref()314 .expect("either string or bytes should be set"),315 )316 .map_err(|_| ImportBadFileUtf8(path.clone()))?317 .into(),318 );319 }320 let code = file.string.as_ref().expect("just set");321 let file_name = Source::new(path.clone(), code.clone());322 if file.parsed.is_none() {323 file.parsed = Some(324 jrsonnet_parser::parse(325 code,326 &ParserSettings {327 file_name: file_name.clone(),328 },329 )330 .map_err(|e| ImportSyntaxError {331 path: file_name.clone(),332 error: Box::new(e),333 })?,334 );335 }336 let parsed = file.parsed.as_ref().expect("just set").clone();337 if file.evaluating {338 throw!(InfiniteRecursionDetected)339 }340 file.evaluating = true;341 342 drop(file_cache);343 let res = evaluate(344 self.clone(),345 self.create_default_context(file_name),346 &parsed,347 );348349 let mut file_cache = self.file_cache();350 let mut file = file_cache.raw_entry_mut().from_key(&path);351352 let file = match file {353 RawEntryMut::Occupied(ref mut d) => d.get_mut(),354 RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),355 };356 file.evaluating = false;357 match res {358 Ok(v) => {359 file.evaluated = Some(v.clone());360 Ok(v)361 }362 Err(e) => Err(e),363 }364 }365366 367 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {368 let resolved = self.resolve_from(from, path)?;369 self.import_resolved(resolved)370 }371 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {372 let resolved = self.resolve(path)?;373 self.import_resolved(resolved)374 }375376 377 pub fn create_default_context(&self, source: Source) -> Context {378 let context_initializer = &self.settings().context_initializer;379 context_initializer.initialize(self.clone(), source)380 }381382 383 pub fn push<T>(384 e: CallLocation<'_>,385 frame_desc: impl FnOnce() -> String,386 f: impl FnOnce() -> Result<T>,387 ) -> Result<T> {388 let _guard = check_depth()?;389390 f().with_description_src(e, frame_desc)391 }392393 394 pub fn push_val(395 &self,396 e: &ExprLocation,397 frame_desc: impl FnOnce() -> String,398 f: impl FnOnce() -> Result<Val>,399 ) -> Result<Val> {400 let _guard = check_depth()?;401402 f().with_description_src(e, frame_desc)403 }404 405 pub fn push_description<T>(406 frame_desc: impl FnOnce() -> String,407 f: impl FnOnce() -> Result<T>,408 ) -> Result<T> {409 let _guard = check_depth()?;410411 f().with_description(frame_desc)412 }413414 415 416 pub fn stringify_err(&self, e: &LocError) -> String {417 let mut out = String::new();418 self.settings()419 .trace_format420 .write_trace(&mut out, self, e)421 .unwrap();422 out423 }424425 pub fn manifest(&self, val: Val) -> Result<IStr> {426 Self::push_description(427 || "manifestification".to_string(),428 || val.manifest(self.clone(), &self.manifest_format()),429 )430 }431 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {432 val.manifest_multi(self.clone(), &self.manifest_format())433 }434 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {435 val.manifest_stream(self.clone(), &self.manifest_format())436 }437438 439 pub fn with_tla(&self, val: Val) -> Result<Val> {440 Ok(match val {441 Val::Func(func) => State::push_description(442 || "during TLA call".to_owned(),443 || {444 func.evaluate(445 self.clone(),446 self.create_default_context(Source::new_virtual(447 "<tla>".into(),448 IStr::empty(),449 )),450 CallLocation::native(),451 &self.settings().tla_vars,452 true,453 )454 },455 )?,456 v => v,457 })458 }459}460461462impl State {463 fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {464 self.0.file_cache.borrow_mut()465 }466 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {467 self.0.settings.borrow()468 }469 pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {470 self.0.settings.borrow_mut()471 }472}473474475impl State {476 477 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {478 let code = code.into();479 let source = Source::new_virtual(name.into(), code.clone());480 let parsed = jrsonnet_parser::parse(481 &code,482 &ParserSettings {483 file_name: source.clone(),484 },485 )486 .map_err(|e| ImportSyntaxError {487 path: source.clone(),488 error: Box::new(e),489 })?;490 evaluate(self.clone(), self.create_default_context(source), &parsed)491 }492}493494495impl State {496 pub fn add_tla(&self, name: IStr, value: Val) {497 self.settings_mut()498 .tla_vars499 .insert(name, TlaArg::Val(value));500 }501 pub fn add_tla_str(&self, name: IStr, value: IStr) {502 self.settings_mut()503 .tla_vars504 .insert(name, TlaArg::String(value));505 }506 pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {507 let source_name = format!("<top-level-arg:{name}>");508 let source = Source::new_virtual(source_name.into(), code.into());509 let parsed = jrsonnet_parser::parse(510 code,511 &ParserSettings {512 file_name: source.clone(),513 },514 )515 .map_err(|e| ImportSyntaxError {516 path: source,517 error: Box::new(e),518 })?;519 self.settings_mut()520 .tla_vars521 .insert(name, TlaArg::Code(parsed));522 Ok(())523 }524525 526 #[allow(clippy::missing_panics_doc)]527 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {528 self.import_resolver().resolve_from(from, path.as_ref())529 }530531 532 #[allow(clippy::missing_panics_doc)]533 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {534 self.import_resolver().resolve(path.as_ref())535 }536 pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {537 Ref::map(self.settings(), |s| &*s.import_resolver)538 }539 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {540 self.settings_mut().import_resolver = resolver;541 }542 pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {543 Ref::map(self.settings(), |s| &*s.context_initializer)544 }545546 pub fn manifest_format(&self) -> ManifestFormat {547 self.settings().manifest_format.clone()548 }549 pub fn set_manifest_format(&self, format: ManifestFormat) {550 self.settings_mut().manifest_format = format;551 }552553 pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {554 Ref::map(self.settings(), |s| &*s.trace_format)555 }556 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {557 self.settings_mut().trace_format = format;558 }559560 pub fn max_trace(&self) -> usize {561 self.settings().max_trace562 }563 pub fn set_max_trace(&self, trace: usize) {564 self.settings_mut().max_trace = trace;565 }566}