123#![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 stdlib;55pub mod trace;56pub mod typed;57pub mod val;5859use std::{60 any::Any,61 cell::{Ref, RefCell, RefMut},62 collections::HashMap,63 fmt::{self, Debug},64 path::Path,65 rc::Rc,66};6768pub use ctx::*;69pub use dynamic::*;70use error::{Error::*, LocError, Result, StackTraceElement};71pub use evaluate::*;72use function::{CallLocation, TlaArg};73use gc::{GcHashMap, TraceBox};74use hashbrown::hash_map::RawEntryMut;75pub use import::*;76use jrsonnet_gcmodule::{Cc, Trace};77pub use jrsonnet_interner::{IBytes, IStr};78pub use jrsonnet_parser as parser;79use jrsonnet_parser::*;80pub use obj::*;81use trace::{CompactFormat, TraceFormat};82pub use val::{ManifestFormat, Thunk, Val};83848586pub trait Unbound: Trace {87 88 type Bound;89 90 fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;91}92939495#[derive(Clone, Trace)]96pub enum MaybeUnbound {97 98 Unbound(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),99 100 Bound(Thunk<Val>),101}102103impl Debug for MaybeUnbound {104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {105 write!(f, "MaybeUnbound")106 }107}108impl MaybeUnbound {109 110 pub fn evaluate(111 &self,112 s: State,113 sup: Option<ObjValue>,114 this: Option<ObjValue>,115 ) -> Result<Thunk<Val>> {116 match self {117 Self::Unbound(v) => v.bind(s, sup, this),118 Self::Bound(v) => Ok(v.clone()),119 }120 }121}122123124125pub trait ContextInitializer {126 127 fn initialize(&self, state: State, for_file: Source) -> Context;128 129 130 fn as_any(&self) -> &dyn Any;131}132133134pub struct DummyContextInitializer;135impl ContextInitializer for DummyContextInitializer {136 fn initialize(&self, _state: State, _for_file: Source) -> Context {137 Context::default()138 }139 fn as_any(&self) -> &dyn Any {140 self141 }142}143144145pub struct EvaluationSettings {146 147 pub max_stack: usize,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_stack: 200,166 max_trace: 20,167 context_initializer: Box::new(DummyContextInitializer),168 tla_vars: HashMap::default(),169 import_resolver: Box::new(DummyImportResolver),170 manifest_format: ManifestFormat::Json {171 padding: 4,172 #[cfg(feature = "exp-preserve-order")]173 preserve_order: false,174 },175 trace_format: Box::new(CompactFormat {176 padding: 4,177 resolver: trace::PathResolver::Absolute,178 }),179 }180 }181}182183#[derive(Default)]184struct EvaluationData {185 186 stack_depth: usize,187 188 stack_generation: usize,189190 breakpoints: Breakpoints,191192 193 files: GcHashMap<SourcePath, FileData>,194}195struct FileData {196 string: Option<IStr>,197 bytes: Option<IBytes>,198 parsed: Option<LocExpr>,199 evaluated: Option<Val>,200201 evaluating: bool,202}203impl FileData {204 fn new_string(data: IStr) -> Self {205 Self {206 string: Some(data),207 bytes: None,208 parsed: None,209 evaluated: None,210 evaluating: false,211 }212 }213 fn new_bytes(data: IBytes) -> Self {214 Self {215 string: None,216 bytes: Some(data),217 parsed: None,218 evaluated: None,219 evaluating: false,220 }221 }222}223224#[allow(clippy::type_complexity)]225pub struct Breakpoint {226 loc: ExprLocation,227 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,228}229#[derive(Default)]230struct Breakpoints(Vec<Rc<Breakpoint>>);231impl Breakpoints {232 fn insert(233 &self,234 stack_depth: usize,235 stack_generation: usize,236 loc: &ExprLocation,237 result: Result<Val>,238 ) -> Result<Val> {239 if self.0.is_empty() {240 return result;241 }242 for item in &self.0 {243 if item.loc.belongs_to(loc) {244 let mut collected = item.collected.borrow_mut();245 let (depth, vals) = collected.entry(stack_generation).or_default();246 if stack_depth > *depth {247 vals.clear();248 }249 vals.push(result.clone());250 }251 }252 result253 }254}255256#[derive(Default)]257pub struct EvaluationStateInternals {258 259 data: RefCell<EvaluationData>,260 261 settings: RefCell<EvaluationSettings>,262}263264265#[derive(Default, Clone)]266pub struct State(Rc<EvaluationStateInternals>);267268impl State {269 270 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {271 let mut data = self.data_mut();272 let mut file = data.files.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(str) = &file.string {290 return Ok(str.clone());291 }292 if file.string.is_none() {293 file.string = Some(294 file.bytes295 .as_ref()296 .expect("either string or bytes should be set")297 .clone()298 .cast_str()299 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?,300 );301 }302 Ok(file.string.as_ref().expect("just set").clone())303 }304 305 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {306 let mut data = self.data_mut();307 let mut file = data.files.raw_entry_mut().from_key(&path);308309 let file = match file {310 RawEntryMut::Occupied(ref mut d) => d.get_mut(),311 RawEntryMut::Vacant(v) => {312 let data = self.settings().import_resolver.load_file_contents(&path)?;313 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))314 .1315 }316 };317 if let Some(str) = &file.bytes {318 return Ok(str.clone());319 }320 if file.bytes.is_none() {321 file.bytes = Some(322 file.string323 .as_ref()324 .expect("either string or bytes should be set")325 .clone()326 .cast_bytes(),327 );328 }329 Ok(file.bytes.as_ref().expect("just set").clone())330 }331 332 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {333 let mut data = self.data_mut();334 let mut file = data.files.raw_entry_mut().from_key(&path);335336 let file = match file {337 RawEntryMut::Occupied(ref mut d) => d.get_mut(),338 RawEntryMut::Vacant(v) => {339 let data = self.settings().import_resolver.load_file_contents(&path)?;340 v.insert(341 path.clone(),342 FileData::new_string(343 std::str::from_utf8(&data)344 .map_err(|_| ImportBadFileUtf8(path.clone()))?345 .into(),346 ),347 )348 .1349 }350 };351 if let Some(val) = &file.evaluated {352 return Ok(val.clone());353 }354 if file.string.is_none() {355 file.string = Some(356 std::str::from_utf8(357 file.bytes358 .as_ref()359 .expect("either string or bytes should be set"),360 )361 .map_err(|_| ImportBadFileUtf8(path.clone()))?362 .into(),363 );364 }365 let code = file.string.as_ref().expect("just set");366 let file_name = Source::new(path.clone(), code.clone());367 if file.parsed.is_none() {368 file.parsed = Some(369 jrsonnet_parser::parse(370 code,371 &ParserSettings {372 file_name: file_name.clone(),373 },374 )375 .map_err(|e| ImportSyntaxError {376 path: file_name.clone(),377 error: Box::new(e),378 })?,379 );380 }381 let parsed = file.parsed.as_ref().expect("just set").clone();382 if file.evaluating {383 throw!(InfiniteRecursionDetected)384 }385 file.evaluating = true;386 387 drop(data);388 let res = evaluate(389 self.clone(),390 self.create_default_context(file_name),391 &parsed,392 );393394 let mut data = self.data_mut();395 let mut file = data.files.raw_entry_mut().from_key(&path);396397 let file = match file {398 RawEntryMut::Occupied(ref mut d) => d.get_mut(),399 RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),400 };401 file.evaluating = false;402 match res {403 Ok(v) => {404 file.evaluated = Some(v.clone());405 Ok(v)406 }407 Err(e) => Err(e),408 }409 }410411 412 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {413 let resolved = self.resolve_from(from, path)?;414 self.import_resolved(resolved)415 }416 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {417 let resolved = self.resolve(path)?;418 self.import_resolved(resolved)419 }420421 422 pub fn create_default_context(&self, source: Source) -> Context {423 let context_initializer = &self.settings().context_initializer;424 context_initializer.initialize(self.clone(), source)425 }426427 428 pub fn push<T>(429 &self,430 e: CallLocation<'_>,431 frame_desc: impl FnOnce() -> String,432 f: impl FnOnce() -> Result<T>,433 ) -> Result<T> {434 {435 let mut data = self.data_mut();436 let stack_depth = &mut data.stack_depth;437 if *stack_depth > self.max_stack() {438 439 drop(data);440 throw!(StackOverflow);441 }442 *stack_depth += 1;443 }444 let result = f();445 {446 let mut data = self.data_mut();447 data.stack_depth -= 1;448 data.stack_generation += 1;449 }450 if let Err(mut err) = result {451 err.trace_mut().0.push(StackTraceElement {452 location: e.0.cloned(),453 desc: frame_desc(),454 });455 return Err(err);456 }457 result458 }459460 461 pub fn push_val(462 &self,463 e: &ExprLocation,464 frame_desc: impl FnOnce() -> String,465 f: impl FnOnce() -> Result<Val>,466 ) -> Result<Val> {467 {468 let mut data = self.data_mut();469 let stack_depth = &mut data.stack_depth;470 if *stack_depth > self.max_stack() {471 472 drop(data);473 throw!(StackOverflow);474 }475 *stack_depth += 1;476 }477 let mut result = f();478 {479 let mut data = self.data_mut();480 data.stack_depth -= 1;481 data.stack_generation += 1;482 result = data483 .breakpoints484 .insert(data.stack_depth, data.stack_generation, e, result);485 }486 if let Err(mut err) = result {487 err.trace_mut().0.push(StackTraceElement {488 location: Some(e.clone()),489 desc: frame_desc(),490 });491 return Err(err);492 }493 result494 }495 496 pub fn push_description<T>(497 &self,498 frame_desc: impl FnOnce() -> String,499 f: impl FnOnce() -> Result<T>,500 ) -> Result<T> {501 {502 let mut data = self.data_mut();503 let stack_depth = &mut data.stack_depth;504 if *stack_depth > self.max_stack() {505 506 drop(data);507 throw!(StackOverflow);508 }509 *stack_depth += 1;510 }511 let result = f();512 {513 let mut data = self.data_mut();514 data.stack_depth -= 1;515 data.stack_generation += 1;516 }517 if let Err(mut err) = result {518 err.trace_mut().0.push(StackTraceElement {519 location: None,520 desc: frame_desc(),521 });522 return Err(err);523 }524 result525 }526527 528 529 pub fn stringify_err(&self, e: &LocError) -> String {530 let mut out = String::new();531 self.settings()532 .trace_format533 .write_trace(&mut out, self, e)534 .unwrap();535 out536 }537538 pub fn manifest(&self, val: Val) -> Result<IStr> {539 self.push_description(540 || "manifestification".to_string(),541 || val.manifest(self.clone(), &self.manifest_format()),542 )543 }544 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {545 val.manifest_multi(self.clone(), &self.manifest_format())546 }547 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {548 val.manifest_stream(self.clone(), &self.manifest_format())549 }550551 552 pub fn with_tla(&self, val: Val) -> Result<Val> {553 Ok(match val {554 Val::Func(func) => self.push_description(555 || "during TLA call".to_owned(),556 || {557 func.evaluate(558 self.clone(),559 self.create_default_context(Source::new_virtual(560 "<tla>".into(),561 IStr::empty(),562 )),563 CallLocation::native(),564 &self.settings().tla_vars,565 true,566 )567 },568 )?,569 v => v,570 })571 }572}573574575impl State {576 fn data_mut(&self) -> RefMut<'_, EvaluationData> {577 self.0.data.borrow_mut()578 }579 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {580 self.0.settings.borrow()581 }582 pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {583 self.0.settings.borrow_mut()584 }585}586587588impl State {589 590 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {591 let code = code.into();592 let source = Source::new_virtual(name.into(), code.clone());593 let parsed = jrsonnet_parser::parse(594 &code,595 &ParserSettings {596 file_name: source.clone(),597 },598 )599 .map_err(|e| ImportSyntaxError {600 path: source.clone(),601 error: Box::new(e),602 })?;603 evaluate(self.clone(), self.create_default_context(source), &parsed)604 }605}606607608impl State {609 pub fn add_tla(&self, name: IStr, value: Val) {610 self.settings_mut()611 .tla_vars612 .insert(name, TlaArg::Val(value));613 }614 pub fn add_tla_str(&self, name: IStr, value: IStr) {615 self.settings_mut()616 .tla_vars617 .insert(name, TlaArg::String(value));618 }619 pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {620 let source_name = format!("<top-level-arg:{name}>");621 let source = Source::new_virtual(source_name.into(), code.into());622 let parsed = jrsonnet_parser::parse(623 code,624 &ParserSettings {625 file_name: source.clone(),626 },627 )628 .map_err(|e| ImportSyntaxError {629 path: source,630 error: Box::new(e),631 })?;632 self.settings_mut()633 .tla_vars634 .insert(name, TlaArg::Code(parsed));635 Ok(())636 }637638 639 #[allow(clippy::missing_panics_doc)]640 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {641 self.import_resolver().resolve_from(from, path.as_ref())642 }643644 645 #[allow(clippy::missing_panics_doc)]646 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {647 self.import_resolver().resolve(path.as_ref())648 }649 pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {650 Ref::map(self.settings(), |s| &*s.import_resolver)651 }652 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {653 self.settings_mut().import_resolver = resolver;654 }655 pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {656 Ref::map(self.settings(), |s| &*s.context_initializer)657 }658659 pub fn manifest_format(&self) -> ManifestFormat {660 self.settings().manifest_format.clone()661 }662 pub fn set_manifest_format(&self, format: ManifestFormat) {663 self.settings_mut().manifest_format = format;664 }665666 pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {667 Ref::map(self.settings(), |s| &*s.trace_format)668 }669 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {670 self.settings_mut().trace_format = format;671 }672673 pub fn max_trace(&self) -> usize {674 self.settings().max_trace675 }676 pub fn set_max_trace(&self, trace: usize) {677 self.settings_mut().max_trace = trace;678 }679680 pub fn max_stack(&self) -> usize {681 self.settings().max_stack682 }683 pub fn set_max_stack(&self, trace: usize) {684 self.settings_mut().max_stack = trace;685 }686}