git.delta.rocks / jrsonnet / refs/commits / 409a660d0753

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs8.5 KiBsourcehistory
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(Debug, Clone)]135pub enum Val {136	Bool(bool),137	Null,138	Str(Rc<str>),139	Num(f64),140	Lazy(LazyVal),141	Arr(Rc<Vec<Val>>),142	Obj(ObjValue),143	Func(Rc<FuncDesc>),144145	// Library functions implemented in native146	Intristic(Rc<str>, Rc<str>),147}148macro_rules! matches_unwrap {149	($e: expr, $p: pat, $r: expr) => {150		match $e {151			$p => $r,152			_ => panic!("no match"),153			}154	};155}156impl Val {157	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity158	pub fn new_checked_num(num: f64) -> Result<Val> {159		if num.is_finite() {160			Ok(Val::Num(num))161		} else {162			throw!(RuntimeError("overflow".into()))163		}164	}165166	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {167		let this_type = self.value_type()?;168		if this_type != val_type {169			throw!(TypeMismatch(context, vec![val_type], this_type))170		} else {171			Ok(())172		}173	}174	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {175		self.assert_type(context, ValType::Bool)?;176		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))177	}178	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {179		self.assert_type(context, ValType::Str)?;180		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))181	}182	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {183		self.assert_type(context, ValType::Num)?;184		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))185	}186	pub fn unwrap_if_lazy(&self) -> Result<Self> {187		Ok(if let Val::Lazy(v) = self {188			v.evaluate()?.unwrap_if_lazy()?189		} else {190			self.clone()191		})192	}193	pub fn value_type(&self) -> Result<ValType> {194		Ok(match self {195			Val::Str(..) => ValType::Str,196			Val::Num(..) => ValType::Num,197			Val::Arr(..) => ValType::Arr,198			Val::Obj(..) => ValType::Obj,199			Val::Func(..) => ValType::Func,200			Val::Bool(_) => ValType::Bool,201			Val::Null => ValType::Null,202			Val::Intristic(_, _) => ValType::Func,203			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,204		})205	}206207	pub fn into_string(self) -> Result<Rc<str>> {208		Ok(match self.unwrap_if_lazy()? {209			Val::Bool(true) => "true".into(),210			Val::Bool(false) => "false".into(),211			Val::Null => "null".into(),212			Val::Str(s) => s,213			v => manifest_json_ex(214				&v,215				&ManifestJsonOptions {216					padding: &"",217					mtype: ManifestType::ToString,218				},219			)?220			.into(),221		})222	}223224	/// For manifestification225	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {226		manifest_json_ex(227			&self,228			&ManifestJsonOptions {229				padding: &" ".repeat(padding),230				mtype: ManifestType::Manifest,231			},232		)233		.map(|s| s.into())234	}235236	/// Calls std.manifestJson237	#[cfg(feature = "faster")]238	pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {239		manifest_json_ex(240			&self,241			&ManifestJsonOptions {242				padding: &" ".repeat(padding),243				mtype: ManifestType::Std,244			},245		)246		.map(|s| s.into())247	}248249	/// Calls std.manifestJson250	#[cfg(not(feature = "faster"))]251	pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {252		with_state(|s| {253			let ctx = s254				.create_default_context()?255				.with_var("__tmp__to_json__".into(), self)?;256			Ok(evaluate(257				ctx,258				&el!(Expr::Apply(259					el!(Expr::Index(260						el!(Expr::Var("std".into())),261						el!(Expr::Str("manifestJsonEx".into()))262					)),263					ArgsDesc(vec![264						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),265						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))266					]),267					false268				)),269			)?270			.try_cast_str("to json")?)271		})272	}273	pub fn into_yaml(self, padding: usize) -> Result<Rc<str>> {274		with_state(|s| {275			let ctx = s276				.create_default_context()?277				.with_var("__tmp__to_json__".into(), self);278			Ok(evaluate(279				ctx,280				&el!(Expr::Apply(281					el!(Expr::Index(282						el!(Expr::Var("std".into())),283						el!(Expr::Str("manifestYamlDoc".into()))284					)),285					ArgsDesc(vec![286						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),287						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))288					]),289					false290				)),291			)?292			.try_cast_str("to json")?)293		})294	}295}296297fn is_function_like(val: &Val) -> bool {298	matches!(val, Val::Func(_) | Val::Intristic(_, _))299}300301/// Implements std.primitiveEquals builtin302pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {303	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {304		(Val::Bool(a), Val::Bool(b)) => a == b,305		(Val::Null, Val::Null) => true,306		(Val::Str(a), Val::Str(b)) => a == b,307		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,308		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(309			"primitiveEquals operates on primitive types, got array".into(),310		)),311		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(312			"primitiveEquals operates on primitive types, got object".into(),313		)),314		(a, b) if is_function_like(&a) && is_function_like(&b) => {315			throw!(RuntimeError("cannot test equality of functions".into()))316		}317		(_, _) => false,318	})319}320321/// Native implementation of std.equals322pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {323	let val_a = val_a.unwrap_if_lazy()?;324	let val_b = val_b.unwrap_if_lazy()?;325326	if val_a.value_type()? != val_b.value_type()? {327		return Ok(false);328	}329	match (val_a, val_b) {330		// Cant test for ptr equality, because all fields needs to be evaluated331		(Val::Arr(a), Val::Arr(b)) => {332			if a.len() != b.len() {333				return Ok(false);334			}335			for (a, b) in a.iter().zip(b.iter()) {336				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {337					return Ok(false);338				}339			}340			Ok(true)341		}342		(Val::Obj(a), Val::Obj(b)) => {343			let fields = a.visible_fields();344			if fields != b.visible_fields() {345				return Ok(false);346			}347			for field in fields {348				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {349					return Ok(false);350				}351			}352			Ok(true)353		}354		(a, b) => Ok(primitive_equals(&a, &b)?),355	}356}