git.delta.rocks / jrsonnet / refs/commits / 020afa68e92f

difftreelog

feat native callbacks

Lach2020-08-23parent: #1feb056.patch.diff
in: master

10 files changed

modifiedbindings/jsonnet/src/interop.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/interop.rs
+++ b/bindings/jsonnet/src/interop.rs
@@ -1,7 +1,7 @@
 //! Jrsonnet specific additional binding helpers
 
-use crate::import::jsonnet_import_callback;
-use jrsonnet_evaluator::EvaluationState;
+use crate::{import::jsonnet_import_callback, native::jsonnet_native_callback};
+use jrsonnet_evaluator::{EvaluationState, Val};
 use std::{
 	ffi::c_void,
 	os::raw::{c_char, c_int},
@@ -15,6 +15,13 @@
 		found_here: *mut *const c_char,
 		success: &mut c_int,
 	) -> *const c_char;
+
+	#[allow(improper_ctypes)]
+	pub fn _jrsonnet_static_native_callback(
+		ctx: *const c_void,
+		argv: *const *const Val,
+		success: *mut c_int,
+	) -> *mut Val;
 }
 
 /// # Safety
@@ -26,6 +33,17 @@
 	jsonnet_import_callback(vm, _jrsonnet_static_import_callback, ctx)
 }
 
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jrsonnet_apply_static_native_callback(
+	vm: &EvaluationState,
+	name: *const c_char,
+	ctx: *mut c_void,
+	raw_params: *const *const c_char,
+) {
+	jsonnet_native_callback(vm, name, _jrsonnet_static_native_callback, ctx, raw_params)
+}
+
 #[no_mangle]
 pub extern "C" fn jrsonnet_set_trace_format(vm: &EvaluationState, format: u8) {
 	use jrsonnet_evaluator::trace::JSFormat;
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -1,5 +1,6 @@
 pub mod import;
 pub mod interop;
+pub mod native;
 pub mod val_extract;
 pub mod val_make;
 pub mod val_modify;
@@ -90,11 +91,6 @@
 #[allow(clippy::boxed_local)]
 pub unsafe extern "C" fn jsonnet_json_destroy(_vm: &EvaluationState, v: *mut Val) {
 	Box::from_raw(v);
-}
-
-#[no_mangle]
-pub extern "C" fn jsonnet_native_callback() {
-	todo!()
 }
 
 #[no_mangle]
addedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- /dev/null
+++ b/bindings/jsonnet/src/native.rs
@@ -0,0 +1,55 @@
+use jrsonnet_evaluator::{error::Error, native::NativeCallback, EvaluationState, Val};
+use jrsonnet_parser::{Param, ParamsDesc};
+use std::{
+	ffi::{c_void, CStr},
+	os::raw::{c_char, c_int},
+	rc::Rc,
+};
+
+type JsonnetNativeCallback = unsafe extern "C" fn(
+	ctx: *const c_void,
+	argv: *const *const Val,
+	success: *mut c_int,
+) -> *mut Val;
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_native_callback(
+	vm: &EvaluationState,
+	name: *const c_char,
+	cb: JsonnetNativeCallback,
+	ctx: *const c_void,
+	mut raw_params: *const *const c_char,
+) {
+	let name = CStr::from_ptr(name).to_str().expect("utf8 name").into();
+	let mut params = Vec::new();
+	loop {
+		if (*raw_params).is_null() {
+			break;
+		}
+		let param = CStr::from_ptr(*raw_params).to_str().expect("not utf8");
+		params.push(Param(param.into(), None));
+		raw_params = raw_params.offset(1);
+	}
+	let params = ParamsDesc(Rc::new(params));
+
+	vm.add_native(
+		name,
+		Rc::new(NativeCallback::new(params, move |args| {
+			let mut n_args = Vec::new();
+			for a in args {
+				n_args.push(Some(Box::new(a.clone())));
+			}
+			n_args.push(None);
+			let mut success = 1;
+			let v = cb(ctx, &n_args as *const _ as *const *const Val, &mut success);
+			let v = *Box::from_raw(v);
+			if success == 1 {
+				Ok(v)
+			} else {
+				let e = v.try_cast_str("native error").expect("error msg");
+				Err(Error::RuntimeError(e).into())
+			}
+		})),
+	)
+}
modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs
@@ -117,7 +117,7 @@
 			}
 			buf.push('}');
 		}
-		Val::Func(_) | Val::Intristic(_, _) => {
+		Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => {
 			throw!(RuntimeError("tried to manifest function".into()))
 		}
 		Val::Lazy(_) => unreachable!(),
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -33,6 +33,7 @@
 	FunctionParameterNotBoundInCall(Rc<str>),
 
 	UndefinedExternalVariable(Rc<str>),
+	UndefinedExternalFunction(Rc<str>),
 
 	FieldMustBeStringGot(ValType),
 
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -5,9 +5,9 @@
 	},
 	context_creator, equals,
 	error::Error::*,
-	future_wrapper, lazy_val, parse_args, primitive_equals, push, throw, with_state, Context,
-	ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember, ObjValue, Result, Val,
-	ValType,
+	future_wrapper, lazy_val, parse_args, parse_function_call, primitive_equals, push, throw,
+	with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember,
+	ObjValue, Result, Val, ValType,
 };
 use closure::closure;
 use jrsonnet_parser::{
@@ -549,13 +549,20 @@
 					], {
 						Ok(Val::Num(x.powf(n)))
 					})?,
-					("std", "extVar") => parse_args!(context, "std.extVar", args, 2, [
+					("std", "extVar") => parse_args!(context, "std.extVar", args, 1, [
 						0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
 					], {
 						Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(
 							|| UndefinedExternalVariable(x),
 						)?)
 					})?,
+					("std", "native") => parse_args!(context, "std.native", args, 1, [
+						0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
+					], {
+						Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::NativeExt(x.clone(), v)).ok_or_else(
+							|| UndefinedExternalFunction(x),
+						)?)
+					})?,
 					("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [
 						0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
 						1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
@@ -780,6 +787,18 @@
 				})
 			},
 		)?,
+		Val::NativeExt(n, f) => push(
+			loc,
+			|| format!("native <{}> call", n),
+			|| {
+				let args = parse_function_call(context, None, &f.params, args, true)?;
+				let mut out_args = Vec::with_capacity(f.params.len());
+				for p in f.params.0.iter() {
+					out_args.push(args.binding(p.0.clone())?.evaluate()?);
+				}
+				Ok(f.call(&out_args)?)
+			},
+		)?,
 		Val::Func(f) => {
 			let body = || f.evaluate(context, args, tailstrict);
 			if tailstrict {
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -40,7 +40,7 @@
 				}
 				Value::Object(out)
 			}
-			Val::Func(_) | Val::Intristic(_, _) => {
+			Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => {
 				throw!(RuntimeError("tried to manifest function".into()))
 			}
 		})
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -10,6 +10,7 @@
 mod import;
 mod integrations;
 mod map;
+pub mod native;
 mod obj;
 pub mod trace;
 mod val;
@@ -21,6 +22,7 @@
 pub use function::parse_function_call;
 pub use import::*;
 use jrsonnet_parser::*;
+use native::NativeCallback;
 pub use obj::*;
 use std::{
 	cell::{Ref, RefCell, RefMut},
@@ -60,6 +62,8 @@
 	pub max_trace: usize,
 	/// Used for std.extVar
 	pub ext_vars: HashMap<Rc<str>, Val>,
+	/// Used for ext.native
+	pub ext_natives: HashMap<Rc<str>, Rc<NativeCallback>>,
 	/// TLA vars
 	pub tla_vars: HashMap<Rc<str>, Val>,
 	/// Global variables are inserted in default context
@@ -78,6 +82,7 @@
 			max_trace: 20,
 			globals: Default::default(),
 			ext_vars: Default::default(),
+			ext_natives: Default::default(),
 			tla_vars: Default::default(),
 			import_resolver: Box::new(DummyImportResolver),
 			manifest_format: ManifestFormat::Json(4),
@@ -415,6 +420,10 @@
 		self.settings_mut().import_resolver = resolver;
 	}
 
+	pub fn add_native(&self, name: Rc<str>, cb: Rc<NativeCallback>) {
+		self.settings_mut().ext_natives.insert(name, cb);
+	}
+
 	pub fn manifest_format(&self) -> ManifestFormat {
 		self.settings().manifest_format.clone()
 	}
@@ -850,4 +859,30 @@
 		);
 		assert_eval!("{ x: 1, y: 2 } == { x: 1, y: 2 }")
 	}
+
+	#[test]
+	fn native_ext() -> crate::error::Result<()> {
+		use super::native::NativeCallback;
+		let evaluator = EvaluationState::default();
+
+		evaluator.with_stdlib();
+		evaluator.settings_mut().ext_natives.insert(
+			"native_add".into(),
+			Rc::new(NativeCallback::new(
+				ParamsDesc(Rc::new(vec![
+					Param("a".into(), None),
+					Param("b".into(), None),
+				])),
+				|args| match (&args[0], &args[1]) {
+					(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),
+					(_, _) => todo!(),
+				},
+			)),
+		);
+		evaluator.evaluate_snippet_raw(
+			Rc::new(PathBuf::from("test.jsonnet")),
+			"std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(),
+		)?;
+		Ok(())
+	}
 }
addedcrates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/native.rs
@@ -0,0 +1,24 @@
+use crate::{error::Result, Val};
+use jrsonnet_parser::ParamsDesc;
+use std::fmt::Debug;
+
+pub struct NativeCallback {
+	pub params: ParamsDesc,
+	handler: Box<dyn Fn(&[Val]) -> Result<Val>>,
+}
+impl NativeCallback {
+	pub fn new(params: ParamsDesc, handler: impl Fn(&[Val]) -> Result<Val> + 'static) -> Self {
+		Self {
+			params,
+			handler: Box::new(handler),
+		}
+	}
+	pub fn call(&self, args: &[Val]) -> Result<Val> {
+		(self.handler)(args)
+	}
+}
+impl Debug for NativeCallback {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		f.debug_struct("NativeCallback").finish()
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use crate::{2	builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},3	error::Error::*,4	evaluate,5	function::{parse_function_call, parse_function_call_map, place_args},6	throw, with_state, Context, ObjValue, Result,7};8use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};9use std::{10	cell::RefCell,11	collections::HashMap,12	fmt::{Debug, Display},13	rc::Rc,14};1516enum LazyValInternals {17	Computed(Val),18	Waiting(Box<dyn Fn() -> Result<Val>>),19}20#[derive(Clone)]21pub struct LazyVal(Rc<RefCell<LazyValInternals>>);22impl LazyVal {23	pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {24		LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))25	}26	pub fn new_resolved(val: Val) -> Self {27		LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))28	}29	pub fn evaluate(&self) -> Result<Val> {30		let new_value = match &*self.0.borrow() {31			LazyValInternals::Computed(v) => return Ok(v.clone()),32			LazyValInternals::Waiting(f) => f()?,33		};34		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());35		Ok(new_value)36	}37}3839#[macro_export]40macro_rules! lazy_val {41	($f: expr) => {42		$crate::LazyVal::new(Box::new($f))43	};44}45#[macro_export]46macro_rules! resolved_lazy_val {47	($f: expr) => {48		$crate::LazyVal::new_resolved($f)49	};50}51impl Debug for LazyVal {52	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {53		write!(f, "Lazy")54	}55}56impl PartialEq for LazyVal {57	fn eq(&self, other: &Self) -> bool {58		Rc::ptr_eq(&self.0, &other.0)59	}60}6162#[derive(Debug, PartialEq)]63pub struct FuncDesc {64	pub name: Rc<str>,65	pub ctx: Context,66	pub params: ParamsDesc,67	pub body: LocExpr,68}69impl FuncDesc {70	/// This function is always inlined to make tailstrict work71	pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {72		let ctx = parse_function_call(73			call_ctx,74			Some(self.ctx.clone()),75			&self.params,76			args,77			tailstrict,78		)?;79		evaluate(ctx, &self.body)80	}8182	pub fn evaluate_map(83		&self,84		call_ctx: Context,85		args: &HashMap<Rc<str>, Val>,86		tailstrict: bool,87	) -> Result<Val> {88		let ctx = parse_function_call_map(89			call_ctx,90			Some(self.ctx.clone()),91			&self.params,92			args,93			tailstrict,94		)?;95		evaluate(ctx, &self.body)96	}9798	pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {99		let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;100		evaluate(ctx, &self.body)101	}102}103104#[derive(Debug, Clone, Copy, PartialEq)]105pub enum ValType {106	Bool,107	Null,108	Str,109	Num,110	Arr,111	Obj,112	Func,113}114impl ValType {115	pub fn name(&self) -> &'static str {116		use ValType::*;117		match self {118			Bool => "boolean",119			Null => "null",120			Str => "string",121			Num => "number",122			Arr => "array",123			Obj => "object",124			Func => "function",125		}126	}127}128impl Display for ValType {129	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {130		write!(f, "{}", self.name())131	}132}133134#[derive(Clone)]135pub enum ManifestFormat {136	YamlStream(Box<ManifestFormat>),137	Yaml(usize),138	Json(usize),139	String,140}141142#[derive(Debug, Clone)]143pub enum Val {144	Bool(bool),145	Null,146	Str(Rc<str>),147	Num(f64),148	Lazy(LazyVal),149	Arr(Rc<Vec<Val>>),150	Obj(ObjValue),151	Func(Rc<FuncDesc>),152153	// Library functions implemented in native154	Intristic(Rc<str>, Rc<str>),155}156macro_rules! matches_unwrap {157	($e: expr, $p: pat, $r: expr) => {158		match $e {159			$p => $r,160			_ => panic!("no match"),161			}162	};163}164impl Val {165	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity166	pub fn new_checked_num(num: f64) -> Result<Val> {167		if num.is_finite() {168			Ok(Val::Num(num))169		} else {170			throw!(RuntimeError("overflow".into()))171		}172	}173174	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {175		let this_type = self.value_type()?;176		if this_type != val_type {177			throw!(TypeMismatch(context, vec![val_type], this_type))178		} else {179			Ok(())180		}181	}182	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {183		self.assert_type(context, ValType::Bool)?;184		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))185	}186	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {187		self.assert_type(context, ValType::Str)?;188		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))189	}190	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {191		self.assert_type(context, ValType::Num)?;192		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))193	}194	pub fn unwrap_if_lazy(&self) -> Result<Self> {195		Ok(if let Val::Lazy(v) = self {196			v.evaluate()?.unwrap_if_lazy()?197		} else {198			self.clone()199		})200	}201	pub fn value_type(&self) -> Result<ValType> {202		Ok(match self {203			Val::Str(..) => ValType::Str,204			Val::Num(..) => ValType::Num,205			Val::Arr(..) => ValType::Arr,206			Val::Obj(..) => ValType::Obj,207			Val::Func(..) => ValType::Func,208			Val::Bool(_) => ValType::Bool,209			Val::Null => ValType::Null,210			Val::Intristic(_, _) => ValType::Func,211			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,212		})213	}214215	pub fn into_string(self) -> Result<Rc<str>> {216		Ok(match self.unwrap_if_lazy()? {217			Val::Bool(true) => "true".into(),218			Val::Bool(false) => "false".into(),219			Val::Null => "null".into(),220			Val::Str(s) => s,221			v => manifest_json_ex(222				&v,223				&ManifestJsonOptions {224					padding: &"",225					mtype: ManifestType::ToString,226				},227			)?228			.into(),229		})230	}231232233	pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {234		Ok(match ty {235			ManifestFormat::YamlStream(format) => {236				let arr = match self {237					Val::Arr(a) => a,238					_ => throw!(StreamManifestOutputIsNotAArray),239				};240				let mut out = String::new();241242				match format as &ManifestFormat {243					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),244					ManifestFormat::String => throw!(StreamManifestCannotNestString),245					_ => {}246				};247248				if !arr.is_empty() {249					for v in arr.iter() {250						out.push_str("---\n");251						out.push_str(&v.manifest(format)?);252						out.push_str("\n");253					}254					out.push_str("...");255				}256257				out.into()258			}259			ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,260			ManifestFormat::Json(padding) => self.to_json(*padding)?,261			ManifestFormat::String => match self {262				Val::Str(s) => s.clone(),263				_ => throw!(StringManifestOutputIsNotAString),264			},265		})266	}267268	/// For manifestification269	pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {270		manifest_json_ex(271			self,272			&ManifestJsonOptions {273				padding: &" ".repeat(padding),274				mtype: if padding == 0 {275					ManifestType::Minify276				} else {277					ManifestType::Manifest278				},279			},280		)281		.map(|s| s.into())282	}283284	/// Calls std.manifestJson285	#[cfg(feature = "faster")]286	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {287		manifest_json_ex(288			&self,289			&ManifestJsonOptions {290				padding: &" ".repeat(padding),291				mtype: ManifestType::Std,292			},293		)294		.map(|s| s.into())295	}296297	/// Calls std.manifestJson298	#[cfg(not(feature = "faster"))]299	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {300		with_state(|s| {301			let ctx = s302				.create_default_context()?303				.with_var("__tmp__to_json__".into(), self.clone())?;304			Ok(evaluate(305				ctx,306				&el!(Expr::Apply(307					el!(Expr::Index(308						el!(Expr::Var("std".into())),309						el!(Expr::Str("manifestJsonEx".into()))310					)),311					ArgsDesc(vec![312						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),313						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))314					]),315					false316				)),317			)?318			.try_cast_str("to json")?)319		})320	}321	pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {322		with_state(|s| {323			let ctx = s324				.create_default_context()?325				.with_var("__tmp__to_json__".into(), self.clone());326			Ok(evaluate(327				ctx,328				&el!(Expr::Apply(329					el!(Expr::Index(330						el!(Expr::Var("std".into())),331						el!(Expr::Str("manifestYamlDoc".into()))332					)),333					ArgsDesc(vec![334						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),335						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))336					]),337					false338				)),339			)?340			.try_cast_str("to json")?)341		})342	}343}344345fn is_function_like(val: &Val) -> bool {346	matches!(val, Val::Func(_) | Val::Intristic(_, _))347}348349/// Implements std.primitiveEquals builtin350pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {351	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {352		(Val::Bool(a), Val::Bool(b)) => a == b,353		(Val::Null, Val::Null) => true,354		(Val::Str(a), Val::Str(b)) => a == b,355		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,356		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(357			"primitiveEquals operates on primitive types, got array".into(),358		)),359		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(360			"primitiveEquals operates on primitive types, got object".into(),361		)),362		(a, b) if is_function_like(&a) && is_function_like(&b) => {363			throw!(RuntimeError("cannot test equality of functions".into()))364		}365		(_, _) => false,366	})367}368369/// Native implementation of std.equals370pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {371	let val_a = val_a.unwrap_if_lazy()?;372	let val_b = val_b.unwrap_if_lazy()?;373374	if val_a.value_type()? != val_b.value_type()? {375		return Ok(false);376	}377	match (val_a, val_b) {378		// Cant test for ptr equality, because all fields needs to be evaluated379		(Val::Arr(a), Val::Arr(b)) => {380			if a.len() != b.len() {381				return Ok(false);382			}383			for (a, b) in a.iter().zip(b.iter()) {384				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {385					return Ok(false);386				}387			}388			Ok(true)389		}390		(Val::Obj(a), Val::Obj(b)) => {391			let fields = a.visible_fields();392			if fields != b.visible_fields() {393				return Ok(false);394			}395			for field in fields {396				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {397					return Ok(false);398				}399			}400			Ok(true)401		}402		(a, b) => Ok(primitive_equals(&a, &b)?),403	}404}
after · crates/jrsonnet-evaluator/src/val.rs
1use crate::{2	builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},3	error::Error::*,4	evaluate,5	function::{parse_function_call, parse_function_call_map, place_args},6	native::NativeCallback,7	throw, with_state, Context, ObjValue, Result,8};9use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};10use std::{11	cell::RefCell,12	collections::HashMap,13	fmt::{Debug, Display},14	rc::Rc,15};1617enum LazyValInternals {18	Computed(Val),19	Waiting(Box<dyn Fn() -> Result<Val>>),20}21#[derive(Clone)]22pub struct LazyVal(Rc<RefCell<LazyValInternals>>);23impl LazyVal {24	pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {25		LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))26	}27	pub fn new_resolved(val: Val) -> Self {28		LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))29	}30	pub fn evaluate(&self) -> Result<Val> {31		let new_value = match &*self.0.borrow() {32			LazyValInternals::Computed(v) => return Ok(v.clone()),33			LazyValInternals::Waiting(f) => f()?,34		};35		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());36		Ok(new_value)37	}38}3940#[macro_export]41macro_rules! lazy_val {42	($f: expr) => {43		$crate::LazyVal::new(Box::new($f))44	};45}46#[macro_export]47macro_rules! resolved_lazy_val {48	($f: expr) => {49		$crate::LazyVal::new_resolved($f)50	};51}52impl Debug for LazyVal {53	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {54		write!(f, "Lazy")55	}56}57impl PartialEq for LazyVal {58	fn eq(&self, other: &Self) -> bool {59		Rc::ptr_eq(&self.0, &other.0)60	}61}6263#[derive(Debug, PartialEq)]64pub struct FuncDesc {65	pub name: Rc<str>,66	pub ctx: Context,67	pub params: ParamsDesc,68	pub body: LocExpr,69}70impl FuncDesc {71	/// This function is always inlined to make tailstrict work72	pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {73		let ctx = parse_function_call(74			call_ctx,75			Some(self.ctx.clone()),76			&self.params,77			args,78			tailstrict,79		)?;80		evaluate(ctx, &self.body)81	}8283	pub fn evaluate_map(84		&self,85		call_ctx: Context,86		args: &HashMap<Rc<str>, Val>,87		tailstrict: bool,88	) -> Result<Val> {89		let ctx = parse_function_call_map(90			call_ctx,91			Some(self.ctx.clone()),92			&self.params,93			args,94			tailstrict,95		)?;96		evaluate(ctx, &self.body)97	}9899	pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {100		let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;101		evaluate(ctx, &self.body)102	}103}104105#[derive(Debug, Clone, Copy, PartialEq)]106pub enum ValType {107	Bool,108	Null,109	Str,110	Num,111	Arr,112	Obj,113	Func,114}115impl ValType {116	pub fn name(&self) -> &'static str {117		use ValType::*;118		match self {119			Bool => "boolean",120			Null => "null",121			Str => "string",122			Num => "number",123			Arr => "array",124			Obj => "object",125			Func => "function",126		}127	}128}129impl Display for ValType {130	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {131		write!(f, "{}", self.name())132	}133}134135#[derive(Clone)]136pub enum ManifestFormat {137	YamlStream(Box<ManifestFormat>),138	Yaml(usize),139	Json(usize),140	String,141}142143#[derive(Debug, Clone)]144pub enum Val {145	Bool(bool),146	Null,147	Str(Rc<str>),148	Num(f64),149	Lazy(LazyVal),150	Arr(Rc<Vec<Val>>),151	Obj(ObjValue),152	Func(Rc<FuncDesc>),153154	// Library functions implemented in native155	Intristic(Rc<str>, Rc<str>),156	NativeExt(Rc<str>, Rc<NativeCallback>),157}158macro_rules! matches_unwrap {159	($e: expr, $p: pat, $r: expr) => {160		match $e {161			$p => $r,162			_ => panic!("no match"),163			}164	};165}166impl Val {167	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity168	pub fn new_checked_num(num: f64) -> Result<Val> {169		if num.is_finite() {170			Ok(Val::Num(num))171		} else {172			throw!(RuntimeError("overflow".into()))173		}174	}175176	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {177		let this_type = self.value_type()?;178		if this_type != val_type {179			throw!(TypeMismatch(context, vec![val_type], this_type))180		} else {181			Ok(())182		}183	}184	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {185		self.assert_type(context, ValType::Bool)?;186		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))187	}188	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {189		self.assert_type(context, ValType::Str)?;190		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))191	}192	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {193		self.assert_type(context, ValType::Num)?;194		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))195	}196	pub fn unwrap_if_lazy(&self) -> Result<Self> {197		Ok(if let Val::Lazy(v) = self {198			v.evaluate()?.unwrap_if_lazy()?199		} else {200			self.clone()201		})202	}203	pub fn value_type(&self) -> Result<ValType> {204		Ok(match self {205			Val::Str(..) => ValType::Str,206			Val::Num(..) => ValType::Num,207			Val::Arr(..) => ValType::Arr,208			Val::Obj(..) => ValType::Obj,209			Val::Bool(_) => ValType::Bool,210			Val::Null => ValType::Null,211			Val::Func(..) | Val::Intristic(_, _) | Val::NativeExt(_, _) => ValType::Func,212			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,213		})214	}215216	pub fn into_string(self) -> Result<Rc<str>> {217		Ok(match self.unwrap_if_lazy()? {218			Val::Bool(true) => "true".into(),219			Val::Bool(false) => "false".into(),220			Val::Null => "null".into(),221			Val::Str(s) => s,222			v => manifest_json_ex(223				&v,224				&ManifestJsonOptions {225					padding: &"",226					mtype: ManifestType::ToString,227				},228			)?229			.into(),230		})231	}232233234	pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {235		Ok(match ty {236			ManifestFormat::YamlStream(format) => {237				let arr = match self {238					Val::Arr(a) => a,239					_ => throw!(StreamManifestOutputIsNotAArray),240				};241				let mut out = String::new();242243				match format as &ManifestFormat {244					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),245					ManifestFormat::String => throw!(StreamManifestCannotNestString),246					_ => {}247				};248249				if !arr.is_empty() {250					for v in arr.iter() {251						out.push_str("---\n");252						out.push_str(&v.manifest(format)?);253						out.push_str("\n");254					}255					out.push_str("...");256				}257258				out.into()259			}260			ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,261			ManifestFormat::Json(padding) => self.to_json(*padding)?,262			ManifestFormat::String => match self {263				Val::Str(s) => s.clone(),264				_ => throw!(StringManifestOutputIsNotAString),265			},266		})267	}268269	/// For manifestification270	pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {271		manifest_json_ex(272			self,273			&ManifestJsonOptions {274				padding: &" ".repeat(padding),275				mtype: if padding == 0 {276					ManifestType::Minify277				} else {278					ManifestType::Manifest279				},280			},281		)282		.map(|s| s.into())283	}284285	/// Calls std.manifestJson286	#[cfg(feature = "faster")]287	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {288		manifest_json_ex(289			&self,290			&ManifestJsonOptions {291				padding: &" ".repeat(padding),292				mtype: ManifestType::Std,293			},294		)295		.map(|s| s.into())296	}297298	/// Calls std.manifestJson299	#[cfg(not(feature = "faster"))]300	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {301		with_state(|s| {302			let ctx = s303				.create_default_context()?304				.with_var("__tmp__to_json__".into(), self.clone())?;305			Ok(evaluate(306				ctx,307				&el!(Expr::Apply(308					el!(Expr::Index(309						el!(Expr::Var("std".into())),310						el!(Expr::Str("manifestJsonEx".into()))311					)),312					ArgsDesc(vec![313						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),314						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))315					]),316					false317				)),318			)?319			.try_cast_str("to json")?)320		})321	}322	pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {323		with_state(|s| {324			let ctx = s325				.create_default_context()?326				.with_var("__tmp__to_json__".into(), self.clone());327			Ok(evaluate(328				ctx,329				&el!(Expr::Apply(330					el!(Expr::Index(331						el!(Expr::Var("std".into())),332						el!(Expr::Str("manifestYamlDoc".into()))333					)),334					ArgsDesc(vec![335						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),336						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))337					]),338					false339				)),340			)?341			.try_cast_str("to json")?)342		})343	}344}345346fn is_function_like(val: &Val) -> bool {347	matches!(val, Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _))348}349350/// Implements std.primitiveEquals builtin351pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {352	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {353		(Val::Bool(a), Val::Bool(b)) => a == b,354		(Val::Null, Val::Null) => true,355		(Val::Str(a), Val::Str(b)) => a == b,356		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,357		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(358			"primitiveEquals operates on primitive types, got array".into(),359		)),360		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(361			"primitiveEquals operates on primitive types, got object".into(),362		)),363		(a, b) if is_function_like(&a) && is_function_like(&b) => {364			throw!(RuntimeError("cannot test equality of functions".into()))365		}366		(_, _) => false,367	})368}369370/// Native implementation of std.equals371pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {372	let val_a = val_a.unwrap_if_lazy()?;373	let val_b = val_b.unwrap_if_lazy()?;374375	if val_a.value_type()? != val_b.value_type()? {376		return Ok(false);377	}378	match (val_a, val_b) {379		// Cant test for ptr equality, because all fields needs to be evaluated380		(Val::Arr(a), Val::Arr(b)) => {381			if a.len() != b.len() {382				return Ok(false);383			}384			for (a, b) in a.iter().zip(b.iter()) {385				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {386					return Ok(false);387				}388			}389			Ok(true)390		}391		(Val::Obj(a), Val::Obj(b)) => {392			let fields = a.visible_fields();393			if fields != b.visible_fields() {394				return Ok(false);395			}396			for field in fields {397				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {398					return Ok(false);399				}400			}401			Ok(true)402		}403		(a, b) => Ok(primitive_equals(&a, &b)?),404	}405}