git.delta.rocks / jrsonnet / refs/commits / c137fa77fb64

difftreelog

source

crates/jrsonnet-evaluator/src/lib.rs16.3 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)]2122// For jrsonnet-macros23extern crate self as jrsonnet_evaluator;2425mod ctx;26mod dynamic;27pub mod error;28mod evaluate;29pub mod function;30pub mod gc;31mod import;32mod integrations;33mod map;34mod obj;35mod stdlib;36pub mod trace;37pub mod typed;38pub mod val;3940use std::{41	cell::{Ref, RefCell, RefMut},42	collections::HashMap,43	fmt::{self, Debug},44	path::{Path, PathBuf},45	rc::Rc,46};4748pub use ctx::*;49pub use dynamic::*;50use error::{Error::*, LocError, Result, StackTraceElement};51pub use evaluate::*;52use function::{builtin::Builtin, CallLocation, TlaArg};53use gc::{GcHashMap, TraceBox};54use gcmodule::{Cc, Trace, Weak};55pub use import::*;56pub use jrsonnet_interner::IStr;57pub use jrsonnet_parser as parser;58use jrsonnet_parser::*;59pub use obj::*;60use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat};61pub use val::{LazyVal, ManifestFormat, Val};6263pub trait Bindable: Trace + 'static {64	fn bind(65		&self,66		s: State,67		this: Option<ObjValue>,68		super_obj: Option<ObjValue>,69	) -> Result<LazyVal>;70}7172#[derive(Clone, Trace)]73pub enum LazyBinding {74	Bindable(Cc<TraceBox<dyn Bindable>>),75	Bound(LazyVal),76}7778impl Debug for LazyBinding {79	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {80		write!(f, "LazyBinding")81	}82}83impl LazyBinding {84	pub fn evaluate(85		&self,86		s: State,87		this: Option<ObjValue>,88		super_obj: Option<ObjValue>,89	) -> Result<LazyVal> {90		match self {91			Self::Bindable(v) => v.bind(s, this, super_obj),92			Self::Bound(v) => Ok(v.clone()),93		}94	}95}9697pub struct EvaluationSettings {98	/// Limits recursion by limiting the number of stack frames99	pub max_stack: usize,100	/// Limits amount of stack trace items preserved101	pub max_trace: usize,102	/// Used for s`td.extVar`103	pub ext_vars: HashMap<IStr, Val>,104	/// Used for ext.native105	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,106	/// TLA vars107	pub tla_vars: HashMap<IStr, TlaArg>,108	/// Global variables are inserted in default context109	pub globals: HashMap<IStr, Val>,110	/// Used to resolve file locations/contents111	pub import_resolver: Box<dyn ImportResolver>,112	/// Used in manifestification functions113	pub manifest_format: ManifestFormat,114	/// Used for bindings115	pub trace_format: Box<dyn TraceFormat>,116}117impl Default for EvaluationSettings {118	fn default() -> Self {119		Self {120			max_stack: 200,121			max_trace: 20,122			globals: HashMap::default(),123			ext_vars: HashMap::default(),124			ext_natives: HashMap::default(),125			tla_vars: HashMap::default(),126			import_resolver: Box::new(DummyImportResolver),127			manifest_format: ManifestFormat::Json {128				padding: 4,129				#[cfg(feature = "exp-preserve-order")]130				preserve_order: false,131			},132			trace_format: Box::new(CompactFormat {133				padding: 4,134				resolver: trace::PathResolver::Absolute,135			}),136		}137	}138}139140#[derive(Default)]141struct EvaluationData {142	/// Used for stack overflow detection, stacktrace is populated on unwind143	stack_depth: usize,144	/// Updated every time stack entry is popt145	stack_generation: usize,146147	breakpoints: Breakpoints,148	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces149	files: GcHashMap<Rc<Path>, FileData>,150	str_files: GcHashMap<Rc<Path>, IStr>,151	bin_files: GcHashMap<Rc<Path>, Rc<[u8]>>,152}153154pub struct FileData {155	source_code: IStr,156	parsed: LocExpr,157	evaluated: Option<Val>,158}159160#[allow(clippy::type_complexity)]161pub struct Breakpoint {162	loc: ExprLocation,163	collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,164}165#[derive(Default)]166struct Breakpoints(Vec<Rc<Breakpoint>>);167impl Breakpoints {168	fn insert(169		&self,170		stack_depth: usize,171		stack_generation: usize,172		loc: &ExprLocation,173		result: Result<Val>,174	) -> Result<Val> {175		if self.0.is_empty() {176			return result;177		}178		for item in &self.0 {179			if item.loc.belongs_to(loc) {180				let mut collected = item.collected.borrow_mut();181				let (depth, vals) = collected.entry(stack_generation).or_default();182				if stack_depth > *depth {183					vals.clear();184				}185				vals.push(result.clone());186			}187		}188		result189	}190}191192#[derive(Default)]193pub struct EvaluationStateInternals {194	/// Internal state195	data: RefCell<EvaluationData>,196	/// Settings, safe to change at runtime197	settings: RefCell<EvaluationSettings>,198}199200/// Maintains stack trace and import resolution201#[derive(Default, Clone)]202pub struct State(Rc<EvaluationStateInternals>);203204impl State {205	/// Parses and adds file as loaded206	pub fn add_file(&self, path: Rc<Path>, source_code: IStr) -> Result<LocExpr> {207		let parsed = parse(208			&source_code,209			&ParserSettings {210				file_name: path.clone(),211			},212		)213		.map_err(|error| ImportSyntaxError {214			error: Box::new(error),215			path: path.clone(),216			source_code: source_code.clone(),217		})?;218		self.add_parsed_file(path, source_code, parsed.clone())?;219220		Ok(parsed)221	}222223	pub fn reset_evaluation_state(&self, name: &Path) {224		self.data_mut()225			.files226			.get_mut(name)227			.expect("file not found")228			.evaluated229			.take();230	}231232	/// Adds file by source code and parsed expr233	pub fn add_parsed_file(234		&self,235		name: Rc<Path>,236		source_code: IStr,237		parsed: LocExpr,238	) -> Result<()> {239		self.data_mut().files.insert(240			name,241			FileData {242				source_code,243				parsed,244				evaluated: None,245			},246		);247248		Ok(())249	}250	pub fn get_source(&self, name: &Path) -> Option<IStr> {251		let ro_map = &self.data().files;252		ro_map.get(name).map(|value| value.source_code.clone())253	}254	pub fn map_source_locations(&self, file: &Path, locs: &[usize]) -> Vec<CodeLocation> {255		offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs)256	}257	pub fn map_from_source_location(258		&self,259		file: &Path,260		line: usize,261		column: usize,262	) -> Option<usize> {263		location_to_offset(264			&self.get_source(file).expect("file not found"),265			line,266			column,267		)268	}269	pub fn import_file(&self, from: &Path, path: &Path) -> Result<Val> {270		let file_path = self.resolve_file(from, path)?;271		{272			let data = self.data();273			let files = &data.files;274			if files.contains_key(&file_path as &Path) {275				drop(data);276				return self.evaluate_loaded_file_raw(&file_path);277			}278		}279		let contents = self.load_file_str(&file_path)?;280		self.add_file(file_path.clone(), contents)?;281		self.evaluate_loaded_file_raw(&file_path)282	}283	pub(crate) fn import_file_str(&self, from: &Path, path: &Path) -> Result<IStr> {284		let path = self.resolve_file(from, path)?;285		if !self.data().str_files.contains_key(&path) {286			let file_str = self.load_file_str(&path)?;287			self.data_mut().str_files.insert(path.clone(), file_str);288		}289		Ok(self.data().str_files.get(&path).cloned().unwrap())290	}291	pub(crate) fn import_file_bin(&self, from: &Path, path: &Path) -> Result<Rc<[u8]>> {292		let path = self.resolve_file(from, path)?;293		if !self.data().bin_files.contains_key(&path) {294			let file_bin = self.load_file_bin(&path)?;295			self.data_mut().bin_files.insert(path.clone(), file_bin);296		}297		Ok(self.data().bin_files.get(&path).cloned().unwrap())298	}299300	fn evaluate_loaded_file_raw(&self, name: &Path) -> Result<Val> {301		let expr: LocExpr = {302			let ro_map = &self.data().files;303			let value = ro_map304				.get(name)305				.unwrap_or_else(|| panic!("file not added: {:?}", name));306			if let Some(ref evaluated) = value.evaluated {307				return Ok(evaluated.clone());308			}309			value.parsed.clone()310		};311		let value = evaluate(self.clone(), self.create_default_context(), &expr)?;312		{313			self.data_mut()314				.files315				.get_mut(name)316				.unwrap()317				.evaluated318				.replace(value.clone());319		}320		Ok(value)321	}322323	/// Adds standard library global variable (std) to this evaluator324	pub fn with_stdlib(&self) -> &Self {325		use jrsonnet_stdlib::STDLIB_STR;326		let std_path: Rc<Path> = PathBuf::from("std.jsonnet").into();327328		self.add_parsed_file(329			std_path.clone(),330			STDLIB_STR.to_owned().into(),331			stdlib::get_parsed_stdlib(),332		)333		.expect("stdlib is correct");334		let val = self335			.evaluate_loaded_file_raw(&std_path)336			.expect("stdlib is correct");337		self.settings_mut().globals.insert("std".into(), val);338		self339	}340341	/// Creates context with all passed global variables342	pub fn create_default_context(&self) -> Context {343		let globals = &self.settings().globals;344		let mut new_bindings = GcHashMap::with_capacity(globals.len());345		for (name, value) in globals.iter() {346			new_bindings.insert(name.clone(), LazyVal::new_resolved(value.clone()));347		}348		Context::new().extend_bound(new_bindings)349	}350351	/// Executes code creating a new stack frame352	pub fn push<T>(353		&self,354		e: CallLocation,355		frame_desc: impl FnOnce() -> String,356		f: impl FnOnce() -> Result<T>,357	) -> Result<T> {358		{359			let mut data = self.data_mut();360			let stack_depth = &mut data.stack_depth;361			if *stack_depth > self.max_stack() {362				// Error creation uses data, so i drop guard here363				drop(data);364				throw!(StackOverflow);365			}366			*stack_depth += 1;367		}368		let result = f();369		{370			let mut data = self.data_mut();371			data.stack_depth -= 1;372			data.stack_generation += 1;373		}374		if let Err(mut err) = result {375			err.trace_mut().0.push(StackTraceElement {376				location: e.0.cloned(),377				desc: frame_desc(),378			});379			return Err(err);380		}381		result382	}383384	/// Executes code creating a new stack frame385	pub fn push_val(386		&self,387		e: &ExprLocation,388		frame_desc: impl FnOnce() -> String,389		f: impl FnOnce() -> Result<Val>,390	) -> Result<Val> {391		{392			let mut data = self.data_mut();393			let stack_depth = &mut data.stack_depth;394			if *stack_depth > self.max_stack() {395				// Error creation uses data, so i drop guard here396				drop(data);397				throw!(StackOverflow);398			}399			*stack_depth += 1;400		}401		let mut result = f();402		{403			let mut data = self.data_mut();404			data.stack_depth -= 1;405			data.stack_generation += 1;406			result = data407				.breakpoints408				.insert(data.stack_depth, data.stack_generation, e, result);409		}410		if let Err(mut err) = result {411			err.trace_mut().0.push(StackTraceElement {412				location: Some(e.clone()),413				desc: frame_desc(),414			});415			return Err(err);416		}417		result418	}419	/// Executes code creating a new stack frame420	pub fn push_description<T>(421		&self,422		frame_desc: impl FnOnce() -> String,423		f: impl FnOnce() -> Result<T>,424	) -> Result<T> {425		{426			let mut data = self.data_mut();427			let stack_depth = &mut data.stack_depth;428			if *stack_depth > self.max_stack() {429				// Error creation uses data, so i drop guard here430				drop(data);431				throw!(StackOverflow);432			}433			*stack_depth += 1;434		}435		let result = f();436		{437			let mut data = self.data_mut();438			data.stack_depth -= 1;439			data.stack_generation += 1;440		}441		if let Err(mut err) = result {442			err.trace_mut().0.push(StackTraceElement {443				location: None,444				desc: frame_desc(),445			});446			return Err(err);447		}448		result449	}450451	/// # Panics452	/// In case of formatting failure453	pub fn stringify_err(&self, e: &LocError) -> String {454		let mut out = String::new();455		self.settings()456			.trace_format457			.write_trace(&mut out, self, e)458			.unwrap();459		out460	}461462	pub fn manifest(&self, val: Val) -> Result<IStr> {463		self.push_description(464			|| "manifestification".to_string(),465			|| val.manifest(self.clone(), &self.manifest_format()),466		)467	}468	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {469		val.manifest_multi(self.clone(), &self.manifest_format())470	}471	pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {472		val.manifest_stream(self.clone(), &self.manifest_format())473	}474475	/// If passed value is function then call with set TLA476	pub fn with_tla(&self, val: Val) -> Result<Val> {477		Ok(match val {478			Val::Func(func) => self.push_description(479				|| "during TLA call".to_owned(),480				|| {481					func.evaluate(482						self.clone(),483						self.create_default_context(),484						CallLocation::native(),485						&self.settings().tla_vars,486						true,487					)488				},489			)?,490			v => v,491		})492	}493}494495/// Internals496impl State {497	fn data(&self) -> Ref<EvaluationData> {498		self.0.data.borrow()499	}500	fn data_mut(&self) -> RefMut<EvaluationData> {501		self.0.data.borrow_mut()502	}503	pub fn settings(&self) -> Ref<EvaluationSettings> {504		self.0.settings.borrow()505	}506	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {507		self.0.settings.borrow_mut()508	}509}510511/// Raw methods evaluate passed values but don't perform TLA execution512impl State {513	pub fn evaluate_file_raw(&self, name: &Path) -> Result<Val> {514		self.import_file(&std::env::current_dir().expect("cwd"), name)515	}516	pub fn evaluate_file_raw_nocwd(&self, name: &Path) -> Result<Val> {517		self.import_file(&PathBuf::from("."), name)518	}519	/// Parses and evaluates the given snippet520	pub fn evaluate_snippet_raw(&self, source: Rc<Path>, code: IStr) -> Result<Val> {521		let parsed = parse(522			&code,523			&ParserSettings {524				file_name: source.clone(),525			},526		)527		.map_err(|e| ImportSyntaxError {528			path: source.clone(),529			source_code: code.clone(),530			error: Box::new(e),531		})?;532		self.add_parsed_file(source, code, parsed.clone())?;533		self.evaluate_expr_raw(parsed)534	}535	/// Evaluates the parsed expression536	pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {537		evaluate(self.clone(), self.create_default_context(), &code)538	}539}540541/// Settings utilities542impl State {543	pub fn add_ext_var(&self, name: IStr, value: Val) {544		self.settings_mut().ext_vars.insert(name, value);545	}546	pub fn add_ext_str(&self, name: IStr, value: IStr) {547		self.add_ext_var(name, Val::Str(value));548	}549	pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> {550		let value =551			self.evaluate_snippet_raw(PathBuf::from(format!("ext_code {}", name)).into(), code)?;552		self.add_ext_var(name, value);553		Ok(())554	}555556	pub fn add_tla(&self, name: IStr, value: Val) {557		self.settings_mut()558			.tla_vars559			.insert(name, TlaArg::Val(value));560	}561	pub fn add_tla_str(&self, name: IStr, value: IStr) {562		self.settings_mut()563			.tla_vars564			.insert(name, TlaArg::String(value));565	}566	pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> {567		let parsed = self.add_file(PathBuf::from(format!("tla_code {}", name)).into(), code)?;568		self.settings_mut()569			.tla_vars570			.insert(name, TlaArg::Code(parsed));571		Ok(())572	}573574	pub fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {575		self.settings().import_resolver.resolve_file(from, path)576	}577	pub fn load_file_str(&self, path: &Path) -> Result<IStr> {578		self.settings().import_resolver.load_file_str(path)579	}580	pub fn load_file_bin(&self, path: &Path) -> Result<Rc<[u8]>> {581		self.settings().import_resolver.load_file_bin(path)582	}583584	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {585		Ref::map(self.settings(), |s| &*s.import_resolver)586	}587	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {588		self.settings_mut().import_resolver = resolver;589	}590591	pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {592		self.settings_mut().ext_natives.insert(name, cb);593	}594595	pub fn manifest_format(&self) -> ManifestFormat {596		self.settings().manifest_format.clone()597	}598	pub fn set_manifest_format(&self, format: ManifestFormat) {599		self.settings_mut().manifest_format = format;600	}601602	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {603		Ref::map(self.settings(), |s| &*s.trace_format)604	}605	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {606		self.settings_mut().trace_format = format;607	}608609	pub fn max_trace(&self) -> usize {610		self.settings().max_trace611	}612	pub fn set_max_trace(&self, trace: usize) {613		self.settings_mut().max_trace = trace;614	}615616	pub fn max_stack(&self) -> usize {617		self.settings().max_stack618	}619	pub fn set_max_stack(&self, trace: usize) {620		self.settings_mut().max_stack = trace;621	}622}623624pub fn cc_ptr_eq<T>(a: &Cc<T>, b: &Cc<T>) -> bool {625	let a = a as &T;626	let b = b as &T;627	std::ptr::eq(a, b)628}629630fn weak_raw<T>(a: Weak<T>) -> *const () {631	unsafe { std::mem::transmute(a) }632}633fn weak_ptr_eq<T>(a: Weak<T>, b: Weak<T>) -> bool {634	std::ptr::eq(weak_raw(a), weak_raw(b))635}636637#[test]638fn weak_unsafe() {639	let a = Cc::new(1);640	let b = Cc::new(2);641642	let aw1 = a.clone().downgrade();643	let aw2 = a.clone().downgrade();644	let aw3 = a.clone().downgrade();645646	let bw = b.clone().downgrade();647648	assert!(weak_ptr_eq(aw1, aw2));649	assert!(!weak_ptr_eq(aw3, bw));650}