git.delta.rocks / jrsonnet / refs/commits / 32f6ee5b9541

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::{ManifestFormat, Thunk, Val};6263pub trait Unbound: Trace {64	type Bound;65	fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;66}6768#[derive(Clone, Trace)]69pub enum LazyBinding {70	Bindable(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),71	Bound(Thunk<Val>),72}7374impl Debug for LazyBinding {75	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {76		write!(f, "LazyBinding")77	}78}79impl LazyBinding {80	pub fn evaluate(81		&self,82		s: State,83		sup: Option<ObjValue>,84		this: Option<ObjValue>,85	) -> Result<Thunk<Val>> {86		match self {87			Self::Bindable(v) => v.bind(s, sup, this),88			Self::Bound(v) => Ok(v.clone()),89		}90	}91}9293pub struct EvaluationSettings {94	/// Limits recursion by limiting the number of stack frames95	pub max_stack: usize,96	/// Limits amount of stack trace items preserved97	pub max_trace: usize,98	/// Used for s`td.extVar`99	pub ext_vars: HashMap<IStr, Val>,100	/// Used for ext.native101	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,102	/// TLA vars103	pub tla_vars: HashMap<IStr, TlaArg>,104	/// Global variables are inserted in default context105	pub globals: HashMap<IStr, Val>,106	/// Used to resolve file locations/contents107	pub import_resolver: Box<dyn ImportResolver>,108	/// Used in manifestification functions109	pub manifest_format: ManifestFormat,110	/// Used for bindings111	pub trace_format: Box<dyn TraceFormat>,112}113impl Default for EvaluationSettings {114	fn default() -> Self {115		Self {116			max_stack: 200,117			max_trace: 20,118			globals: HashMap::default(),119			ext_vars: HashMap::default(),120			ext_natives: HashMap::default(),121			tla_vars: HashMap::default(),122			import_resolver: Box::new(DummyImportResolver),123			manifest_format: ManifestFormat::Json {124				padding: 4,125				#[cfg(feature = "exp-preserve-order")]126				preserve_order: false,127			},128			trace_format: Box::new(CompactFormat {129				padding: 4,130				resolver: trace::PathResolver::Absolute,131			}),132		}133	}134}135136#[derive(Default)]137struct EvaluationData {138	/// Used for stack overflow detection, stacktrace is populated on unwind139	stack_depth: usize,140	/// Updated every time stack entry is popt141	stack_generation: usize,142143	breakpoints: Breakpoints,144	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces145	files: GcHashMap<Rc<Path>, FileData>,146	str_files: GcHashMap<Rc<Path>, IStr>,147	bin_files: GcHashMap<Rc<Path>, Rc<[u8]>>,148}149150pub struct FileData {151	source_code: IStr,152	parsed: LocExpr,153	evaluated: Option<Val>,154}155156#[allow(clippy::type_complexity)]157pub struct Breakpoint {158	loc: ExprLocation,159	collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,160}161#[derive(Default)]162struct Breakpoints(Vec<Rc<Breakpoint>>);163impl Breakpoints {164	fn insert(165		&self,166		stack_depth: usize,167		stack_generation: usize,168		loc: &ExprLocation,169		result: Result<Val>,170	) -> Result<Val> {171		if self.0.is_empty() {172			return result;173		}174		for item in &self.0 {175			if item.loc.belongs_to(loc) {176				let mut collected = item.collected.borrow_mut();177				let (depth, vals) = collected.entry(stack_generation).or_default();178				if stack_depth > *depth {179					vals.clear();180				}181				vals.push(result.clone());182			}183		}184		result185	}186}187188#[derive(Default)]189pub struct EvaluationStateInternals {190	/// Internal state191	data: RefCell<EvaluationData>,192	/// Settings, safe to change at runtime193	settings: RefCell<EvaluationSettings>,194}195196/// Maintains stack trace and import resolution197#[derive(Default, Clone)]198pub struct State(Rc<EvaluationStateInternals>);199200impl State {201	/// Parses and adds file as loaded202	pub fn add_file(&self, path: Rc<Path>, source_code: IStr) -> Result<LocExpr> {203		let parsed = parse(204			&source_code,205			&ParserSettings {206				file_name: path.clone(),207			},208		)209		.map_err(|error| ImportSyntaxError {210			error: Box::new(error),211			path: path.clone(),212			source_code: source_code.clone(),213		})?;214		self.add_parsed_file(path, source_code, parsed.clone())?;215216		Ok(parsed)217	}218219	pub fn reset_evaluation_state(&self, name: &Path) {220		self.data_mut()221			.files222			.get_mut(name)223			.expect("file not found")224			.evaluated225			.take();226	}227228	/// Adds file by source code and parsed expr229	pub fn add_parsed_file(230		&self,231		name: Rc<Path>,232		source_code: IStr,233		parsed: LocExpr,234	) -> Result<()> {235		self.data_mut().files.insert(236			name,237			FileData {238				source_code,239				parsed,240				evaluated: None,241			},242		);243244		Ok(())245	}246	pub fn get_source(&self, name: &Path) -> Option<IStr> {247		let ro_map = &self.data().files;248		ro_map.get(name).map(|value| value.source_code.clone())249	}250	pub fn map_source_locations(&self, file: &Path, locs: &[usize]) -> Vec<CodeLocation> {251		offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs)252	}253	pub fn map_from_source_location(254		&self,255		file: &Path,256		line: usize,257		column: usize,258	) -> Option<usize> {259		location_to_offset(260			&self.get_source(file).expect("file not found"),261			line,262			column,263		)264	}265	pub fn import_file(&self, from: &Path, path: &Path) -> Result<Val> {266		let file_path = self.resolve_file(from, path)?;267		{268			let data = self.data();269			let files = &data.files;270			if files.contains_key(&file_path as &Path) {271				drop(data);272				return self.evaluate_loaded_file_raw(&file_path);273			}274		}275		let contents = self.load_file_str(&file_path)?;276		self.add_file(file_path.clone(), contents)?;277		self.evaluate_loaded_file_raw(&file_path)278	}279	pub(crate) fn import_file_str(&self, from: &Path, path: &Path) -> Result<IStr> {280		let path = self.resolve_file(from, path)?;281		if !self.data().str_files.contains_key(&path) {282			let file_str = self.load_file_str(&path)?;283			self.data_mut().str_files.insert(path.clone(), file_str);284		}285		Ok(self.data().str_files.get(&path).cloned().unwrap())286	}287	pub(crate) fn import_file_bin(&self, from: &Path, path: &Path) -> Result<Rc<[u8]>> {288		let path = self.resolve_file(from, path)?;289		if !self.data().bin_files.contains_key(&path) {290			let file_bin = self.load_file_bin(&path)?;291			self.data_mut().bin_files.insert(path.clone(), file_bin);292		}293		Ok(self.data().bin_files.get(&path).cloned().unwrap())294	}295296	fn evaluate_loaded_file_raw(&self, name: &Path) -> Result<Val> {297		let expr: LocExpr = {298			let ro_map = &self.data().files;299			let value = ro_map300				.get(name)301				.unwrap_or_else(|| panic!("file not added: {:?}", name));302			if let Some(ref evaluated) = value.evaluated {303				return Ok(evaluated.clone());304			}305			value.parsed.clone()306		};307		let value = evaluate(self.clone(), self.create_default_context(), &expr)?;308		{309			self.data_mut()310				.files311				.get_mut(name)312				.unwrap()313				.evaluated314				.replace(value.clone());315		}316		Ok(value)317	}318319	/// Adds standard library global variable (std) to this evaluator320	pub fn with_stdlib(&self) -> &Self {321		use jrsonnet_stdlib::STDLIB_STR;322		let std_path: Rc<Path> = PathBuf::from("std.jsonnet").into();323324		self.add_parsed_file(325			std_path.clone(),326			STDLIB_STR.to_owned().into(),327			stdlib::get_parsed_stdlib(),328		)329		.expect("stdlib is correct");330		let val = self331			.evaluate_loaded_file_raw(&std_path)332			.expect("stdlib is correct");333		self.settings_mut().globals.insert("std".into(), val);334		self335	}336337	/// Creates context with all passed global variables338	pub fn create_default_context(&self) -> Context {339		let globals = &self.settings().globals;340		let mut new_bindings = GcHashMap::with_capacity(globals.len());341		for (name, value) in globals.iter() {342			new_bindings.insert(name.clone(), Thunk::evaluated(value.clone()));343		}344		Context::new().extend(new_bindings, None, None, None)345	}346347	/// Executes code creating a new stack frame348	pub fn push<T>(349		&self,350		e: CallLocation,351		frame_desc: impl FnOnce() -> String,352		f: impl FnOnce() -> Result<T>,353	) -> Result<T> {354		{355			let mut data = self.data_mut();356			let stack_depth = &mut data.stack_depth;357			if *stack_depth > self.max_stack() {358				// Error creation uses data, so i drop guard here359				drop(data);360				throw!(StackOverflow);361			}362			*stack_depth += 1;363		}364		let result = f();365		{366			let mut data = self.data_mut();367			data.stack_depth -= 1;368			data.stack_generation += 1;369		}370		if let Err(mut err) = result {371			err.trace_mut().0.push(StackTraceElement {372				location: e.0.cloned(),373				desc: frame_desc(),374			});375			return Err(err);376		}377		result378	}379380	/// Executes code creating a new stack frame381	pub fn push_val(382		&self,383		e: &ExprLocation,384		frame_desc: impl FnOnce() -> String,385		f: impl FnOnce() -> Result<Val>,386	) -> Result<Val> {387		{388			let mut data = self.data_mut();389			let stack_depth = &mut data.stack_depth;390			if *stack_depth > self.max_stack() {391				// Error creation uses data, so i drop guard here392				drop(data);393				throw!(StackOverflow);394			}395			*stack_depth += 1;396		}397		let mut result = f();398		{399			let mut data = self.data_mut();400			data.stack_depth -= 1;401			data.stack_generation += 1;402			result = data403				.breakpoints404				.insert(data.stack_depth, data.stack_generation, e, result);405		}406		if let Err(mut err) = result {407			err.trace_mut().0.push(StackTraceElement {408				location: Some(e.clone()),409				desc: frame_desc(),410			});411			return Err(err);412		}413		result414	}415	/// Executes code creating a new stack frame416	pub fn push_description<T>(417		&self,418		frame_desc: impl FnOnce() -> String,419		f: impl FnOnce() -> Result<T>,420	) -> Result<T> {421		{422			let mut data = self.data_mut();423			let stack_depth = &mut data.stack_depth;424			if *stack_depth > self.max_stack() {425				// Error creation uses data, so i drop guard here426				drop(data);427				throw!(StackOverflow);428			}429			*stack_depth += 1;430		}431		let result = f();432		{433			let mut data = self.data_mut();434			data.stack_depth -= 1;435			data.stack_generation += 1;436		}437		if let Err(mut err) = result {438			err.trace_mut().0.push(StackTraceElement {439				location: None,440				desc: frame_desc(),441			});442			return Err(err);443		}444		result445	}446447	/// # Panics448	/// In case of formatting failure449	pub fn stringify_err(&self, e: &LocError) -> String {450		let mut out = String::new();451		self.settings()452			.trace_format453			.write_trace(&mut out, self, e)454			.unwrap();455		out456	}457458	pub fn manifest(&self, val: Val) -> Result<IStr> {459		self.push_description(460			|| "manifestification".to_string(),461			|| val.manifest(self.clone(), &self.manifest_format()),462		)463	}464	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {465		val.manifest_multi(self.clone(), &self.manifest_format())466	}467	pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {468		val.manifest_stream(self.clone(), &self.manifest_format())469	}470471	/// If passed value is function then call with set TLA472	pub fn with_tla(&self, val: Val) -> Result<Val> {473		Ok(match val {474			Val::Func(func) => self.push_description(475				|| "during TLA call".to_owned(),476				|| {477					func.evaluate(478						self.clone(),479						self.create_default_context(),480						CallLocation::native(),481						&self.settings().tla_vars,482						true,483					)484				},485			)?,486			v => v,487		})488	}489}490491/// Internals492impl State {493	fn data(&self) -> Ref<EvaluationData> {494		self.0.data.borrow()495	}496	fn data_mut(&self) -> RefMut<EvaluationData> {497		self.0.data.borrow_mut()498	}499	pub fn settings(&self) -> Ref<EvaluationSettings> {500		self.0.settings.borrow()501	}502	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {503		self.0.settings.borrow_mut()504	}505}506507/// Raw methods evaluate passed values but don't perform TLA execution508impl State {509	pub fn evaluate_file_raw(&self, name: &Path) -> Result<Val> {510		self.import_file(&std::env::current_dir().expect("cwd"), name)511	}512	pub fn evaluate_file_raw_nocwd(&self, name: &Path) -> Result<Val> {513		self.import_file(&PathBuf::from("."), name)514	}515	/// Parses and evaluates the given snippet516	pub fn evaluate_snippet_raw(&self, source: Rc<Path>, code: IStr) -> Result<Val> {517		let parsed = parse(518			&code,519			&ParserSettings {520				file_name: source.clone(),521			},522		)523		.map_err(|e| ImportSyntaxError {524			path: source.clone(),525			source_code: code.clone(),526			error: Box::new(e),527		})?;528		self.add_parsed_file(source, code, parsed.clone())?;529		self.evaluate_expr_raw(parsed)530	}531	/// Evaluates the parsed expression532	pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {533		evaluate(self.clone(), self.create_default_context(), &code)534	}535}536537/// Settings utilities538impl State {539	pub fn add_ext_var(&self, name: IStr, value: Val) {540		self.settings_mut().ext_vars.insert(name, value);541	}542	pub fn add_ext_str(&self, name: IStr, value: IStr) {543		self.add_ext_var(name, Val::Str(value));544	}545	pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> {546		let value =547			self.evaluate_snippet_raw(PathBuf::from(format!("ext_code {}", name)).into(), code)?;548		self.add_ext_var(name, value);549		Ok(())550	}551552	pub fn add_tla(&self, name: IStr, value: Val) {553		self.settings_mut()554			.tla_vars555			.insert(name, TlaArg::Val(value));556	}557	pub fn add_tla_str(&self, name: IStr, value: IStr) {558		self.settings_mut()559			.tla_vars560			.insert(name, TlaArg::String(value));561	}562	pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> {563		let parsed = self.add_file(PathBuf::from(format!("tla_code {}", name)).into(), code)?;564		self.settings_mut()565			.tla_vars566			.insert(name, TlaArg::Code(parsed));567		Ok(())568	}569570	pub fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {571		self.settings().import_resolver.resolve_file(from, path)572	}573	pub fn load_file_str(&self, path: &Path) -> Result<IStr> {574		self.settings().import_resolver.load_file_str(path)575	}576	pub fn load_file_bin(&self, path: &Path) -> Result<Rc<[u8]>> {577		self.settings().import_resolver.load_file_bin(path)578	}579580	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {581		Ref::map(self.settings(), |s| &*s.import_resolver)582	}583	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {584		self.settings_mut().import_resolver = resolver;585	}586587	pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {588		self.settings_mut().ext_natives.insert(name, cb);589	}590591	pub fn manifest_format(&self) -> ManifestFormat {592		self.settings().manifest_format.clone()593	}594	pub fn set_manifest_format(&self, format: ManifestFormat) {595		self.settings_mut().manifest_format = format;596	}597598	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {599		Ref::map(self.settings(), |s| &*s.trace_format)600	}601	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {602		self.settings_mut().trace_format = format;603	}604605	pub fn max_trace(&self) -> usize {606		self.settings().max_trace607	}608	pub fn set_max_trace(&self, trace: usize) {609		self.settings_mut().max_trace = trace;610	}611612	pub fn max_stack(&self) -> usize {613		self.settings().max_stack614	}615	pub fn set_max_stack(&self, trace: usize) {616		self.settings_mut().max_stack = trace;617	}618}619620pub fn cc_ptr_eq<T>(a: &Cc<T>, b: &Cc<T>) -> bool {621	let a = a as &T;622	let b = b as &T;623	std::ptr::eq(a, b)624}625626fn weak_raw<T>(a: Weak<T>) -> *const () {627	unsafe { std::mem::transmute(a) }628}629fn weak_ptr_eq<T>(a: Weak<T>, b: Weak<T>) -> bool {630	std::ptr::eq(weak_raw(a), weak_raw(b))631}632633#[test]634fn weak_unsafe() {635	let a = Cc::new(1);636	let b = Cc::new(2);637638	let aw1 = a.clone().downgrade();639	let aw2 = a.clone().downgrade();640	let aw3 = a.clone().downgrade();641642	let bw = b.clone().downgrade();643644	assert!(weak_ptr_eq(aw1, aw2));645	assert!(!weak_ptr_eq(aw3, bw));646}