1#![warn(clippy::all, clippy::nursery, clippy::pedantic)]2#![allow(3 macro_expanded_macro_exports_accessed_by_absolute_paths,4 clippy::ptr_arg,5 6 clippy::must_use_candidate,7 8 clippy::missing_errors_doc,9 10 clippy::needless_pass_by_value,11 12 clippy::wildcard_imports,13 clippy::enum_glob_use,14 clippy::module_name_repetitions,15 16 clippy::cast_precision_loss,17 clippy::cast_possible_wrap,18 clippy::cast_possible_truncation,19 clippy::cast_sign_loss,20 21 22 clippy::use_self,23 24 clippy::iter_with_drain,25)]262728extern crate self as jrsonnet_evaluator;2930mod ctx;31mod dynamic;32pub mod error;33mod evaluate;34pub mod function;35pub mod gc;36mod import;37mod integrations;38mod map;39mod obj;40pub mod stdlib;41pub mod trace;42pub mod typed;43pub mod val;4445use std::{46 any::Any,47 borrow::Cow,48 cell::{Ref, RefCell, RefMut},49 collections::HashMap,50 fmt::{self, Debug},51 path::Path,52 rc::Rc,53};5455pub use ctx::*;56pub use dynamic::*;57use error::{Error::*, LocError, Result, StackTraceElement};58pub use evaluate::*;59use function::{CallLocation, TlaArg};60use gc::{GcHashMap, TraceBox};61use hashbrown::hash_map::RawEntryMut;62pub use import::*;63use jrsonnet_gcmodule::{Cc, Trace};64pub use jrsonnet_interner::{IBytes, IStr};65pub use jrsonnet_parser as parser;66use jrsonnet_parser::*;67pub use obj::*;68use trace::{CompactFormat, TraceFormat};69pub use val::{ManifestFormat, Thunk, Val};7071pub trait Unbound: Trace {72 type Bound;73 fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;74}7576#[derive(Clone, Trace)]77pub enum LazyBinding {78 Bindable(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),79 Bound(Thunk<Val>),80}8182impl Debug for LazyBinding {83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {84 write!(f, "LazyBinding")85 }86}87impl LazyBinding {88 pub fn evaluate(89 &self,90 s: State,91 sup: Option<ObjValue>,92 this: Option<ObjValue>,93 ) -> Result<Thunk<Val>> {94 match self {95 Self::Bindable(v) => v.bind(s, sup, this),96 Self::Bound(v) => Ok(v.clone()),97 }98 }99}100101102103pub trait ContextInitializer {104 fn initialize(&self, state: State, for_file: Source) -> Context;105106 107 108 109 110 111 unsafe fn as_any(&self) -> &dyn Any;112}113114115pub struct DummyContextInitializer;116impl ContextInitializer for DummyContextInitializer {117 fn initialize(&self, _state: State, _for_file: Source) -> Context {118 Context::default()119 }120 unsafe fn as_any(&self) -> &dyn Any {121 panic!("`as_any(&self)` is not supported by dummy initializer")122 }123}124125pub struct EvaluationSettings {126 127 pub max_stack: usize,128 129 pub max_trace: usize,130 131 pub tla_vars: HashMap<IStr, TlaArg>,132 133 134 pub context_initializer: Box<dyn ContextInitializer>,135 136 pub import_resolver: Box<dyn ImportResolver>,137 138 pub manifest_format: ManifestFormat,139 140 pub trace_format: Box<dyn TraceFormat>,141}142impl Default for EvaluationSettings {143 fn default() -> Self {144 Self {145 max_stack: 200,146 max_trace: 20,147 context_initializer: Box::new(DummyContextInitializer),148 tla_vars: HashMap::default(),149 import_resolver: Box::new(DummyImportResolver),150 manifest_format: ManifestFormat::Json {151 padding: 4,152 #[cfg(feature = "exp-preserve-order")]153 preserve_order: false,154 },155 trace_format: Box::new(CompactFormat {156 padding: 4,157 resolver: trace::PathResolver::Absolute,158 }),159 }160 }161}162163#[derive(Default)]164struct EvaluationData {165 166 stack_depth: usize,167 168 stack_generation: usize,169170 breakpoints: Breakpoints,171172 173 files: GcHashMap<SourcePath, FileData>,174}175struct FileData {176 string: Option<IStr>,177 bytes: Option<IBytes>,178 parsed: Option<LocExpr>,179 evaluated: Option<Val>,180181 evaluating: bool,182}183impl FileData {184 fn new_string(data: IStr) -> Self {185 Self {186 string: Some(data),187 bytes: None,188 parsed: None,189 evaluated: None,190 evaluating: false,191 }192 }193 fn new_bytes(data: IBytes) -> Self {194 Self {195 string: None,196 bytes: Some(data),197 parsed: None,198 evaluated: None,199 evaluating: false,200 }201 }202}203204#[allow(clippy::type_complexity)]205pub struct Breakpoint {206 loc: ExprLocation,207 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,208}209#[derive(Default)]210struct Breakpoints(Vec<Rc<Breakpoint>>);211impl Breakpoints {212 fn insert(213 &self,214 stack_depth: usize,215 stack_generation: usize,216 loc: &ExprLocation,217 result: Result<Val>,218 ) -> Result<Val> {219 if self.0.is_empty() {220 return result;221 }222 for item in &self.0 {223 if item.loc.belongs_to(loc) {224 let mut collected = item.collected.borrow_mut();225 let (depth, vals) = collected.entry(stack_generation).or_default();226 if stack_depth > *depth {227 vals.clear();228 }229 vals.push(result.clone());230 }231 }232 result233 }234}235236#[derive(Default)]237pub struct EvaluationStateInternals {238 239 data: RefCell<EvaluationData>,240 241 settings: RefCell<EvaluationSettings>,242}243244245#[derive(Default, Clone)]246pub struct State(Rc<EvaluationStateInternals>);247248impl State {249 250 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {251 let mut data = self.data_mut();252 let mut file = data.files.raw_entry_mut().from_key(&path);253254 let file = match file {255 RawEntryMut::Occupied(ref mut d) => d.get_mut(),256 RawEntryMut::Vacant(v) => {257 let data = self.settings().import_resolver.load_file_contents(&path)?;258 v.insert(259 path.clone(),260 FileData::new_string(261 std::str::from_utf8(&data)262 .map_err(|_| ImportBadFileUtf8(path.clone()))?263 .into(),264 ),265 )266 .1267 }268 };269 if let Some(str) = &file.string {270 return Ok(str.clone());271 }272 if file.string.is_none() {273 file.string = Some(274 file.bytes275 .as_ref()276 .expect("either string or bytes should be set")277 .clone()278 .cast_str()279 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?,280 );281 }282 Ok(file.string.as_ref().expect("just set").clone())283 }284 285 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {286 let mut data = self.data_mut();287 let mut file = data.files.raw_entry_mut().from_key(&path);288289 let file = match file {290 RawEntryMut::Occupied(ref mut d) => d.get_mut(),291 RawEntryMut::Vacant(v) => {292 let data = self.settings().import_resolver.load_file_contents(&path)?;293 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))294 .1295 }296 };297 if let Some(str) = &file.bytes {298 return Ok(str.clone());299 }300 if file.bytes.is_none() {301 file.bytes = Some(302 file.string303 .as_ref()304 .expect("either string or bytes should be set")305 .clone()306 .cast_bytes(),307 );308 }309 Ok(file.bytes.as_ref().expect("just set").clone())310 }311 312 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {313 let mut data = self.data_mut();314 let mut file = data.files.raw_entry_mut().from_key(&path);315316 let file = match file {317 RawEntryMut::Occupied(ref mut d) => d.get_mut(),318 RawEntryMut::Vacant(v) => {319 let data = self.settings().import_resolver.load_file_contents(&path)?;320 v.insert(321 path.clone(),322 FileData::new_string(323 std::str::from_utf8(&data)324 .map_err(|_| ImportBadFileUtf8(path.clone()))?325 .into(),326 ),327 )328 .1329 }330 };331 if let Some(val) = &file.evaluated {332 return Ok(val.clone());333 }334 if file.string.is_none() {335 file.string = Some(336 std::str::from_utf8(337 file.bytes338 .as_ref()339 .expect("either string or bytes should be set"),340 )341 .map_err(|_| ImportBadFileUtf8(path.clone()))?342 .into(),343 );344 }345 let code = file.string.as_ref().expect("just set");346 let file_name =347 Source::new(path.clone(), code.clone()).expect("resolver should return correct name");348 if file.parsed.is_none() {349 file.parsed = Some(350 jrsonnet_parser::parse(351 code,352 &ParserSettings {353 file_name: file_name.clone(),354 },355 )356 .map_err(|e| ImportSyntaxError {357 path: file_name.clone(),358 error: Box::new(e),359 })?,360 );361 }362 let parsed = file.parsed.as_ref().expect("just set").clone();363 if file.evaluating {364 throw!(InfiniteRecursionDetected)365 }366 file.evaluating = true;367 368 drop(data);369 let res = evaluate(370 self.clone(),371 self.create_default_context(file_name),372 &parsed,373 );374375 let mut data = self.data_mut();376 let mut file = data.files.raw_entry_mut().from_key(&path);377378 let file = match file {379 RawEntryMut::Occupied(ref mut d) => d.get_mut(),380 RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),381 };382 file.evaluating = false;383 match res {384 Ok(v) => {385 file.evaluated = Some(v.clone());386 Ok(v)387 }388 Err(e) => Err(e),389 }390 }391 pub fn import(&self, from: &Path, path: &str) -> Result<Val> {392 let resolved = self.resolve_file(from, path)?;393 self.import_resolved(resolved)394 }395396 397 pub fn create_default_context(&self, source: Source) -> Context {398 let context_initializer = &self.settings().context_initializer;399 context_initializer.initialize(self.clone(), source)400 }401402 403 pub fn push<T>(404 &self,405 e: CallLocation,406 frame_desc: impl FnOnce() -> String,407 f: impl FnOnce() -> Result<T>,408 ) -> Result<T> {409 {410 let mut data = self.data_mut();411 let stack_depth = &mut data.stack_depth;412 if *stack_depth > self.max_stack() {413 414 drop(data);415 throw!(StackOverflow);416 }417 *stack_depth += 1;418 }419 let result = f();420 {421 let mut data = self.data_mut();422 data.stack_depth -= 1;423 data.stack_generation += 1;424 }425 if let Err(mut err) = result {426 err.trace_mut().0.push(StackTraceElement {427 location: e.0.cloned(),428 desc: frame_desc(),429 });430 return Err(err);431 }432 result433 }434435 436 pub fn push_val(437 &self,438 e: &ExprLocation,439 frame_desc: impl FnOnce() -> String,440 f: impl FnOnce() -> Result<Val>,441 ) -> Result<Val> {442 {443 let mut data = self.data_mut();444 let stack_depth = &mut data.stack_depth;445 if *stack_depth > self.max_stack() {446 447 drop(data);448 throw!(StackOverflow);449 }450 *stack_depth += 1;451 }452 let mut result = f();453 {454 let mut data = self.data_mut();455 data.stack_depth -= 1;456 data.stack_generation += 1;457 result = data458 .breakpoints459 .insert(data.stack_depth, data.stack_generation, e, result);460 }461 if let Err(mut err) = result {462 err.trace_mut().0.push(StackTraceElement {463 location: Some(e.clone()),464 desc: frame_desc(),465 });466 return Err(err);467 }468 result469 }470 471 pub fn push_description<T>(472 &self,473 frame_desc: impl FnOnce() -> String,474 f: impl FnOnce() -> Result<T>,475 ) -> Result<T> {476 {477 let mut data = self.data_mut();478 let stack_depth = &mut data.stack_depth;479 if *stack_depth > self.max_stack() {480 481 drop(data);482 throw!(StackOverflow);483 }484 *stack_depth += 1;485 }486 let result = f();487 {488 let mut data = self.data_mut();489 data.stack_depth -= 1;490 data.stack_generation += 1;491 }492 if let Err(mut err) = result {493 err.trace_mut().0.push(StackTraceElement {494 location: None,495 desc: frame_desc(),496 });497 return Err(err);498 }499 result500 }501502 503 504 pub fn stringify_err(&self, e: &LocError) -> String {505 let mut out = String::new();506 self.settings()507 .trace_format508 .write_trace(&mut out, self, e)509 .unwrap();510 out511 }512513 pub fn manifest(&self, val: Val) -> Result<IStr> {514 self.push_description(515 || "manifestification".to_string(),516 || val.manifest(self.clone(), &self.manifest_format()),517 )518 }519 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {520 val.manifest_multi(self.clone(), &self.manifest_format())521 }522 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {523 val.manifest_stream(self.clone(), &self.manifest_format())524 }525526 527 pub fn with_tla(&self, val: Val) -> Result<Val> {528 Ok(match val {529 Val::Func(func) => self.push_description(530 || "during TLA call".to_owned(),531 || {532 func.evaluate(533 self.clone(),534 self.create_default_context(Source::new_virtual(535 Cow::Borrowed("<tla>"),536 IStr::empty(),537 )),538 CallLocation::native(),539 &self.settings().tla_vars,540 true,541 )542 },543 )?,544 v => v,545 })546 }547}548549550impl State {551 552 553 554 fn data_mut(&self) -> RefMut<EvaluationData> {555 self.0.data.borrow_mut()556 }557 pub fn settings(&self) -> Ref<EvaluationSettings> {558 self.0.settings.borrow()559 }560 pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {561 self.0.settings.borrow_mut()562 }563}564565566impl State {567 568 pub fn evaluate_snippet(&self, name: String, code: impl Into<IStr>) -> Result<Val> {569 let code = code.into();570 let source = Source::new_virtual(Cow::Owned(name), code.clone());571 let parsed = jrsonnet_parser::parse(572 &code,573 &ParserSettings {574 file_name: source.clone(),575 },576 )577 .map_err(|e| ImportSyntaxError {578 path: source.clone(),579 error: Box::new(e),580 })?;581 evaluate(self.clone(), self.create_default_context(source), &parsed)582 }583}584585586impl State {587 pub fn add_tla(&self, name: IStr, value: Val) {588 self.settings_mut()589 .tla_vars590 .insert(name, TlaArg::Val(value));591 }592 pub fn add_tla_str(&self, name: IStr, value: IStr) {593 self.settings_mut()594 .tla_vars595 .insert(name, TlaArg::String(value));596 }597 pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {598 let source_name = format!("<top-level-arg:{}>", name);599 let source = Source::new_virtual(Cow::Owned(source_name.clone()), code.into());600 let parsed = jrsonnet_parser::parse(601 code,602 &ParserSettings {603 file_name: source.clone(),604 },605 )606 .map_err(|e| ImportSyntaxError {607 path: source,608 error: Box::new(e),609 })?;610 self.settings_mut()611 .tla_vars612 .insert(name, TlaArg::Code(parsed));613 Ok(())614 }615616 pub fn resolve_file(&self, from: &Path, path: &str) -> Result<SourcePath> {617 self.settings()618 .import_resolver619 .resolve_file_relative(from, path.as_ref())620 }621622 pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {623 Ref::map(self.settings(), |s| &*s.import_resolver)624 }625 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {626 self.settings_mut().import_resolver = resolver;627 }628 pub fn context_initializer(&self) -> Ref<dyn ContextInitializer> {629 Ref::map(self.settings(), |s| &*s.context_initializer)630 }631632 pub fn manifest_format(&self) -> ManifestFormat {633 self.settings().manifest_format.clone()634 }635 pub fn set_manifest_format(&self, format: ManifestFormat) {636 self.settings_mut().manifest_format = format;637 }638639 pub fn trace_format(&self) -> Ref<dyn TraceFormat> {640 Ref::map(self.settings(), |s| &*s.trace_format)641 }642 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {643 self.settings_mut().trace_format = format;644 }645646 pub fn max_trace(&self) -> usize {647 self.settings().max_trace648 }649 pub fn set_max_trace(&self, trace: usize) {650 self.settings_mut().max_trace = trace;651 }652653 pub fn max_stack(&self) -> usize {654 self.settings().max_stack655 }656 pub fn set_max_stack(&self, trace: usize) {657 self.settings_mut().max_stack = trace;658 }659}