git.delta.rocks / jrsonnet / refs/commits / 0831da3ed8d9

difftreelog

source

crates/jrsonnet-evaluator/src/lib.rs16.6 KiBsourcehistory
1#![warn(clippy::all, clippy::nursery, clippy::pedantic)]2#![allow(3	macro_expanded_macro_exports_accessed_by_absolute_paths,4	clippy::ptr_arg,5	// Too verbose6	clippy::must_use_candidate,7	// A lot of functions pass around errors thrown by code8	clippy::missing_errors_doc,9	// A lot of pointers have interior Rc10	clippy::needless_pass_by_value,11	// Its fine12	clippy::wildcard_imports,13	clippy::enum_glob_use,14	clippy::module_name_repetitions,15	// TODO: fix individual issues, however this works as intended almost everywhere16	clippy::cast_precision_loss,17	clippy::cast_possible_wrap,18	clippy::cast_possible_truncation,19	clippy::cast_sign_loss,20	// False positives21	// https://github.com/rust-lang/rust-clippy/issues/690222	clippy::use_self,23	// https://github.com/rust-lang/rust-clippy/issues/853924	clippy::iter_with_drain,25)]2627// For jrsonnet-macros28extern 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}100101/// During import, this trait will be called to create initial context for file102/// It may initialize global variables, stdlib for example103pub trait ContextInitializer {104	fn initialize(&self, state: State, for_file: Source) -> Context;105106	/// # Safety107	///108	/// For use only in bindings, should not be used elsewhere.109	/// Implementations which are not intended to be used in bindings110	/// should panic on call to this method.111	unsafe fn as_any(&self) -> &dyn Any;112}113114/// Context initializer, which adds noth115pub 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	/// Limits recursion by limiting the number of stack frames127	pub max_stack: usize,128	/// Limits amount of stack trace items preserved129	pub max_trace: usize,130	/// TLA vars131	pub tla_vars: HashMap<IStr, TlaArg>,132	/// Context initializer, which will be used for imports and everything133	/// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`134	pub context_initializer: Box<dyn ContextInitializer>,135	/// Used to resolve file locations/contents136	pub import_resolver: Box<dyn ImportResolver>,137	/// Used in manifestification functions138	pub manifest_format: ManifestFormat,139	/// Used for bindings140	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	/// Used for stack overflow detection, stacktrace is populated on unwind166	stack_depth: usize,167	/// Updated every time stack entry is popt168	stack_generation: usize,169170	breakpoints: Breakpoints,171172	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces173	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	/// Internal state239	data: RefCell<EvaluationData>,240	/// Settings, safe to change at runtime241	settings: RefCell<EvaluationSettings>,242}243244/// Maintains stack trace and import resolution245#[derive(Default, Clone)]246pub struct State(Rc<EvaluationStateInternals>);247248impl State {249	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise250	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	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise285	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	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise312	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		// Dropping file here, as it borrows data, which may be used in evaluation368		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	/// Creates context with all passed global variables397	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	/// Executes code creating a new stack frame403	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				// Error creation uses data, so i drop guard here414				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	/// Executes code creating a new stack frame436	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				// Error creation uses data, so i drop guard here447				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	/// Executes code creating a new stack frame471	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				// Error creation uses data, so i drop guard here481				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	/// # Panics503	/// In case of formatting failure504	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	/// If passed value is function then call with set TLA527	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}548549/// Internals550impl State {551	// fn data(&self) -> Ref<EvaluationData> {552	// 	self.0.data.borrow()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}564565/// Raw methods evaluate passed values but don't perform TLA execution566impl State {567	/// Parses and evaluates the given snippet568	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}584585/// Settings utilities586impl 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}