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

difftreelog

source

crates/jsonnet-evaluator/src/lib.rs12.4 KiBsourcehistory
1#![feature(box_syntax, box_patterns)]2#![feature(type_alias_impl_trait)]3#![feature(debug_non_exhaustive)]4#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]5mod ctx;6mod dynamic;7mod error;8mod evaluate;9mod obj;10mod val;1112use closure::closure;13pub use ctx::*;14pub use dynamic::*;15pub use error::*;16pub use evaluate::*;17use jsonnet_parser::*;18pub use obj::*;19use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc};20pub use val::*;2122rc_fn_helper!(23	Binding,24	binding,25	dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<Val>26);27rc_fn_helper!(28	LazyBinding,29	lazy_binding,30	dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>31);32rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Result<Val>);33rc_fn_helper!(34	FunctionDefault,35	function_default,36	dyn Fn(Context, LocExpr) -> Result<Val>37);3839pub struct FileData(String, LocExpr, Option<Val>);40#[derive(Default)]41pub struct EvaluationStateInternals {42	/// Used for stack-overflows and stacktraces43	stack: RefCell<Vec<StackTraceElement>>,44	/// Contains file source codes and evaluated results for imports and pretty45	/// printing stacktraces46	files: RefCell<HashMap<PathBuf, FileData>>,47	globals: RefCell<HashMap<String, Val>>,48}4950thread_local! {51	pub static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)52}53pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {54	EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))55}56pub(crate) fn create_error<T>(err: Error) -> Result<T> {57	with_state(|s| s.error(err))58}59pub(crate) fn push<T>(e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {60	with_state(|s| s.push(e, comment, f))61}6263#[derive(Default, Clone)]64pub struct EvaluationState(Rc<EvaluationStateInternals>);65impl EvaluationState {66	pub fn add_file(67		&self,68		name: PathBuf,69		code: String,70	) -> std::result::Result<(), Box<dyn std::error::Error>> {71		self.0.files.borrow_mut().insert(72			name.clone(),73			FileData(74				code.clone(),75				parse(76					&code,77					&ParserSettings {78						file_name: name,79						loc_data: true,80					},81				)?,82				None,83			),84		);8586		Ok(())87	}88	pub fn add_parsed_file(89		&self,90		name: PathBuf,91		code: String,92		parsed: LocExpr,93	) -> std::result::Result<(), Box<dyn std::error::Error>> {94		self.095			.files96			.borrow_mut()97			.insert(name, FileData(code, parsed, None));9899		Ok(())100	}101	pub fn get_source(&self, name: &PathBuf) -> Option<String> {102		let ro_map = self.0.files.borrow();103		ro_map104			.get(name)105			.map(|value|value.0.clone())106	}107	pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {108		self.begin_state();109		let expr: LocExpr = {110			let ro_map = self.0.files.borrow();111			let value = ro_map112				.get(name)113				.unwrap_or_else(|| panic!("file not added: {:?}", name));114			if value.2.is_some() {115				return Ok(value.2.clone().unwrap());116			}117			value.1.clone()118		};119		let value = evaluate(self.create_default_context()?, &expr)?;120		{121			self.0122				.files123				.borrow_mut()124				.get_mut(name)125				.unwrap()126				.2127				.replace(value.clone());128		}129		self.end_state();130		Ok(value)131	}132133	pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {134		let parsed = parse(135			&code,136			&ParserSettings {137				file_name: PathBuf::from("raw.jsonnet"),138				loc_data: true,139			},140		);141		self.begin_state();142		let value = evaluate(self.create_default_context()?, &parsed.unwrap());143		self.end_state();144		value145	}146147	pub fn add_global(&self, name: String, value: Val) {148		self.0.globals.borrow_mut().insert(name, value);149	}150151	pub fn add_stdlib(&self) {152		self.begin_state();153		use jsonnet_stdlib::STDLIB_STR;154		if cfg!(feature = "serialized-stdlib") {155			self.add_parsed_file(156				PathBuf::from("std.jsonnet"),157				STDLIB_STR.to_owned(),158				bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))159					.expect("deserialize stdlib"),160			)161			.unwrap();162		} else {163			self.add_file(PathBuf::from("std.jsonnet"), STDLIB_STR.to_owned())164				.unwrap();165		}166		let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();167		self.add_global("std".to_owned(), val);168		self.end_state();169	}170171	pub fn create_default_context(&self) -> Result<Context> {172		let globals = self.0.globals.borrow();173		let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();174		for (name, value) in globals.iter() {175			new_bindings.insert(176				name.clone(),177				lazy_binding!(178					closure!(clone value, |_self, _super_obj| Ok(lazy_val!(closure!(clone value, ||Ok(value.clone())))))179				),180			);181		}182		Context::new().extend(new_bindings, None, None, None)183	}184185	pub fn push<T>(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {186		{187			let mut stack = self.0.stack.borrow_mut();188			if stack.len() > 5000 {189				drop(stack);190				return self.error(Error::StackOverflow);191			} else {192				stack.push(StackTraceElement(e, comment));193			}194		}195		let result = f();196		self.0.stack.borrow_mut().pop();197		result198	}199	pub fn print_stack_trace(&self) {200		for e in self.stack_trace().0 {201			println!("{:?} - {:?}", e.0, e.1)202		}203	}204	pub fn stack_trace(&self) -> StackTrace {205		StackTrace(self.0.stack.borrow().iter().rev().cloned().collect())206	}207	pub fn error<T>(&self, err: Error) -> Result<T> {208		Err(LocError(err, self.stack_trace()))209	}210211	fn begin_state(&self) {212		EVAL_STATE.with(|v| v.borrow_mut().replace(self.clone()));213	}214	fn end_state(&self) {215		EVAL_STATE.with(|v| v.borrow_mut().take());216	}217}218219#[cfg(test)]220pub mod tests {221	use super::Val;222	use crate::EvaluationState;223	use jsonnet_parser::*;224	use std::path::PathBuf;225226	#[test]227	fn eval_state_stacktrace() {228		let state = EvaluationState::default();229		state230			.push(231				loc_expr!(232					Expr::Num(0.0),233					true,234					(PathBuf::from("test1.jsonnet"), 10, 20)235				),236				"outer".to_owned(),237				|| {238					state.push(239						loc_expr!(240							Expr::Num(0.0),241							true,242							(PathBuf::from("test2.jsonnet"), 30, 40)243						),244						"inner".to_owned(),245						|| {246							state.print_stack_trace();247							Ok(())248						},249					)?;250					Ok(())251				},252			)253			.unwrap();254	}255256	#[test]257	fn eval_state_standard() {258		let state = EvaluationState::default();259		state.add_stdlib();260		assert_eq!(261			state262				.parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)263				.unwrap(),264			Val::Bool(true)265		);266	}267268	macro_rules! eval {269		($str: expr) => {270			evaluate(271				Context::new(),272				EvaluationState::default(),273				&parse(274					$str,275					&ParserSettings {276						loc_data: true,277						file_name: "test.jsonnet".to_owned(),278					},279					)280				.unwrap(),281				)282		};283	}284285	macro_rules! eval_stdlib {286		($str: expr) => {{287			let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";288			evaluate(289				Context::new(),290				EvaluationState::default(),291				&parse(292					&(std + $str),293					&ParserSettings {294						loc_data: true,295						file_name: "test.jsonnet".to_owned(),296					},297					)298				.unwrap(),299				)300			}};301	}302303	macro_rules! assert_eval {304		($str: expr) => {305			assert_eq!(306				evaluate(307					Context::new(),308					EvaluationState::default(),309					&parse(310						$str,311						&ParserSettings {312							loc_data: true,313							file_name: "test.jsonnet".to_owned(),314						}315						)316					.unwrap()317					),318				Val::Bool(true)319				)320		};321	}322	macro_rules! assert_json {323		($str: expr, $out: expr) => {324			assert_eq!(325				format!(326					"{}",327					evaluate(328						Context::new(),329						EvaluationState::default(),330						&parse(331							$str,332							&ParserSettings {333								loc_data: true,334								file_name: "test.jsonnet".to_owned(),335							}336						)337						.unwrap()338						)339					),340				$out341				)342		};343	}344	macro_rules! assert_json_stdlib {345		($str: expr, $out: expr) => {346			assert_eq!(format!("{}", eval_stdlib!($str)), $out)347		};348	}349	macro_rules! assert_eval_neg {350		($str: expr) => {351			assert_eq!(352				evaluate(353					Context::new(),354					EvaluationState::default(),355					&parse(356						$str,357						&ParserSettings {358							loc_data: true,359							file_name: "test.jsonnet".to_owned(),360						}361						)362					.unwrap()363					),364				Val::Bool(false)365				)366		};367	}368369	/*370	/// Sanity checking, before trusting to another tests371	#[test]372	fn equality_operator() {373		assert_eval!("2 == 2");374		assert_eval_neg!("2 != 2");375		assert_eval!("2 != 3");376		assert_eval_neg!("2 == 3");377		assert_eval!("'Hello' == 'Hello'");378		assert_eval_neg!("'Hello' != 'Hello'");379		assert_eval!("'Hello' != 'World'");380		assert_eval_neg!("'Hello' == 'World'");381	}382383	#[test]384	fn math_evaluation() {385		assert_eval!("2 + 2 * 2 == 6");386		assert_eval!("3 + (2 + 2 * 2) == 9");387	}388389	#[test]390	fn string_concat() {391		assert_eval!("'Hello' + 'World' == 'HelloWorld'");392		assert_eval!("'Hello' * 3 == 'HelloHelloHello'");393		assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");394	}395396	#[test]397	fn local() {398		assert_eval!("local a = 2; local b = 3; a + b == 5");399		assert_eval!("local a = 1, b = a + 1; a + b == 3");400		assert_eval!("local a = 1; local a = 2; a == 2");401	}402403	#[test]404	fn object_lazyness() {405		assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);406	}407408	#[test]409	fn object_inheritance() {410		assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);411	}412413	#[test]414	fn test_object() {415		assert_json!("{a:2}", r#"{"a":2}"#);416		assert_json!("{a:2+2}", r#"{"a":4}"#);417		assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);418		assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);419		assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);420		assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);421		assert_json!(422			r#"423				{424					name: "Alice",425					welcome: "Hello " + self.name + "!",426				}427			"#,428			r#"{"name":"Alice","welcome":"Hello Alice!"}"#429		);430		assert_json!(431			r#"432				{433					name: "Alice",434					welcome: "Hello " + self.name + "!",435				} + {436					name: "Bob"437				}438			"#,439			r#"{"name":"Bob","welcome":"Hello Bob!"}"#440		);441	}442443	#[test]444	fn functions() {445		assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");446		assert_json!(447			r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,448			r#""HelloDearWorld""#449		);450	}451452	#[test]453	fn local_methods() {454		assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");455		assert_json!(456			r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,457			r#""HelloDearWorld""#458		);459	}460461	#[test]462	fn object_locals() {463		assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);464		assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);465		assert_json!(466			r#"{local a = function (b) {[b]:4}, test: a("test")}"#,467			r#"{"test":{"test":4}}"#468		);469	}470471	#[test]472	fn direct_self() {473		println!(474			"{:#?}",475			eval!(476				r#"477					{478						local me = self,479						a: 3,480						b(): me.a,481					}482				"#483			)484		);485	}486487	#[test]488	fn indirect_self() {489		// `self` assigned to `me` was lost when being490		// referenced from field491		eval_stdlib!(492			r#"{493				local me = self,494				a: 3,495				b: me.a,496			}.b"#497		);498	}499500	// We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly501	#[test]502	fn std_assert_ok() {503		eval_stdlib!("std.assertEqual(4.5 << 2, 16)");504	}505506	#[test]507	#[should_panic]508	fn std_assert_failure() {509		eval_stdlib!("std.assertEqual(4.5 << 2, 15)");510	}511512	#[test]513	fn string_is_string() {514		assert_eq!(515			eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),516			Val::Bool(false)517		);518	}519520	#[test]521	fn base64_works() {522		assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);523	}524525	#[test]526	fn utf8_chars() {527		assert_json_stdlib!(528			r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,529			r#"{"c":128526,"l":1}"#530		)531	}532533	#[test]534	fn json() {535		assert_json_stdlib!(536			r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,537			r#""{\n"a": 3,\n"b": 4,\n"c": 6\n}""#538		);539	}540541	#[test]542	fn test() {543		assert_json_stdlib!(544			r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,545			"[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"546		);547	}548549	#[test]550	fn sjsonnet() {551		eval!(552			r#"553			local x0 = {k: 1};554			local x1 = {k: x0.k + x0.k};555			local x2 = {k: x1.k + x1.k};556			local x3 = {k: x2.k + x2.k};557			local x4 = {k: x3.k + x3.k};558			local x5 = {k: x4.k + x4.k};559			local x6 = {k: x5.k + x5.k};560			local x7 = {k: x6.k + x6.k};561			local x8 = {k: x7.k + x7.k};562			local x9 = {k: x8.k + x8.k};563			local x10 = {k: x9.k + x9.k};564			local x11 = {k: x10.k + x10.k};565			local x12 = {k: x11.k + x11.k};566			local x13 = {k: x12.k + x12.k};567			local x14 = {k: x13.k + x13.k};568			local x15 = {k: x14.k + x14.k};569			local x16 = {k: x15.k + x15.k};570			local x17 = {k: x16.k + x16.k};571			local x18 = {k: x17.k + x17.k};572			local x19 = {k: x18.k + x18.k};573			local x20 = {k: x19.k + x19.k};574			local x21 = {k: x20.k + x20.k};575			x21.k576		"#577		);578	}579	*/580}