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

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs10.2 KiBsourcehistory
1use crate::{2	create_error_result, evaluate,3	function::{parse_function_call, parse_function_call_map, place_args},4	with_state, Context, Error, ObjValue, Result,5};6use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};7use std::{8	cell::RefCell,9	collections::HashMap,10	fmt::{Debug, Display},11	rc::Rc,12};1314enum LazyValInternals {15	Computed(Val),16	Waiting(Box<dyn Fn() -> Result<Val>>),17}18#[derive(Clone)]19pub struct LazyVal(Rc<RefCell<LazyValInternals>>);20impl LazyVal {21	pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {22		LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))23	}24	pub fn new_resolved(val: Val) -> Self {25		LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))26	}27	pub fn evaluate(&self) -> Result<Val> {28		let new_value = match &*self.0.borrow() {29			LazyValInternals::Computed(v) => return Ok(v.clone()),30			LazyValInternals::Waiting(f) => f()?,31		};32		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());33		Ok(new_value)34	}35}3637#[macro_export]38macro_rules! lazy_val {39	($f: expr) => {40		$crate::LazyVal::new(Box::new($f))41	};42}43#[macro_export]44macro_rules! resolved_lazy_val {45	($f: expr) => {46		$crate::LazyVal::new_resolved($f)47	};48}49impl Debug for LazyVal {50	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {51		write!(f, "Lazy")52	}53}54impl PartialEq for LazyVal {55	fn eq(&self, other: &Self) -> bool {56		Rc::ptr_eq(&self.0, &other.0)57	}58}5960#[derive(Debug, PartialEq)]61pub struct FuncDesc {62	pub name: Rc<str>,63	pub ctx: Context,64	pub params: ParamsDesc,65	pub body: LocExpr,66}67impl FuncDesc {68	/// This function is always inlined to make tailstrict work69	pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {70		let ctx = parse_function_call(71			call_ctx,72			Some(self.ctx.clone()),73			&self.params,74			args,75			tailstrict,76		)?;77		evaluate(ctx, &self.body)78	}7980	pub fn evaluate_map(81		&self,82		call_ctx: Context,83		args: &HashMap<Rc<str>, Val>,84		tailstrict: bool,85	) -> Result<Val> {86		let ctx = parse_function_call_map(87			call_ctx,88			Some(self.ctx.clone()),89			&self.params,90			args,91			tailstrict,92		)?;93		evaluate(ctx, &self.body)94	}9596	pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {97		let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;98		evaluate(ctx, &self.body)99	}100}101102#[derive(Debug, Clone, Copy, PartialEq)]103pub enum ValType {104	Bool,105	Null,106	Str,107	Num,108	Arr,109	Obj,110	Func,111}112impl ValType {113	pub fn name(&self) -> &'static str {114		use ValType::*;115		match self {116			Bool => "boolean",117			Null => "null",118			Str => "string",119			Num => "number",120			Arr => "array",121			Obj => "object",122			Func => "function",123		}124	}125}126impl Display for ValType {127	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {128		write!(f, "{}", self.name())129	}130}131132#[derive(Debug, Clone)]133pub enum Val {134	Bool(bool),135	Null,136	Str(Rc<str>),137	Num(f64),138	Lazy(LazyVal),139	Arr(Rc<Vec<Val>>),140	Obj(ObjValue),141	Func(Rc<FuncDesc>),142143	// Library functions implemented in native144	Intristic(Rc<str>, Rc<str>),145}146macro_rules! matches_unwrap {147	($e: expr, $p: pat, $r: expr) => {148		match $e {149			$p => $r,150			_ => panic!("no match"),151			}152	};153}154impl Val {155	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity156	pub fn new_checked_num(num: f64) -> Result<Val> {157		if num.is_finite() {158			Ok(Val::Num(num))159		} else {160			create_error_result(Error::RuntimeError("overflow".into()))161		}162	}163164	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {165		let this_type = self.value_type()?;166		if this_type != val_type {167			create_error_result(Error::TypeMismatch(context, vec![val_type], this_type))168		} else {169			Ok(())170		}171	}172	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {173		self.assert_type(context, ValType::Bool)?;174		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))175	}176	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {177		self.assert_type(context, ValType::Str)?;178		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))179	}180	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {181		self.assert_type(context, ValType::Num)?;182		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))183	}184	pub fn unwrap_if_lazy(&self) -> Result<Self> {185		Ok(if let Val::Lazy(v) = self {186			v.evaluate()?.unwrap_if_lazy()?187		} else {188			self.clone()189		})190	}191	pub fn value_type(&self) -> Result<ValType> {192		Ok(match self {193			Val::Str(..) => ValType::Str,194			Val::Num(..) => ValType::Num,195			Val::Arr(..) => ValType::Arr,196			Val::Obj(..) => ValType::Obj,197			Val::Func(..) => ValType::Func,198			Val::Bool(_) => ValType::Bool,199			Val::Null => ValType::Null,200			Val::Intristic(_, _) => ValType::Func,201			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,202		})203	}204	#[cfg(feature = "faster")]205	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {206		manifest_json_ex(&self, &" ".repeat(padding)).map(|s| s.into())207	}208	#[cfg(not(feature = "faster"))]209	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {210		with_state(|s| {211			let ctx = s212				.create_default_context()?213				.with_var("__tmp__to_json__".into(), self)?;214			Ok(evaluate(215				ctx,216				&el!(Expr::Apply(217					el!(Expr::Index(218						el!(Expr::Var("std".into())),219						el!(Expr::Str("manifestJsonEx".into()))220					)),221					ArgsDesc(vec![222						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),223						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))224					]),225					false226				)),227			)?228			.try_cast_str("to json")?)229		})230	}231	pub fn into_yaml(self, padding: usize) -> Result<Rc<str>> {232		with_state(|s| {233			let ctx = s234				.create_default_context()?235				.with_var("__tmp__to_json__".into(), self)?;236			Ok(evaluate(237				ctx,238				&el!(Expr::Apply(239					el!(Expr::Index(240						el!(Expr::Var("std".into())),241						el!(Expr::Str("manifestYamlDoc".into()))242					)),243					ArgsDesc(vec![244						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),245						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))246					]),247					false248				)),249			)?250			.try_cast_str("to json")?)251		})252	}253}254255fn is_function_like(val: &Val) -> bool {256	matches!(val, Val::Func(_) | Val::Intristic(_, _))257}258259/// Implements std.primitiveEquals builtin260pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {261	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {262		(Val::Bool(a), Val::Bool(b)) => a == b,263		(Val::Null, Val::Null) => true,264		(Val::Str(a), Val::Str(b)) => a == b,265		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,266		(Val::Arr(_), Val::Arr(_)) => create_error_result(Error::RuntimeError(267			"primitiveEquals operates on primitive types, got array".into(),268		))?,269		(Val::Obj(_), Val::Obj(_)) => create_error_result(Error::RuntimeError(270			"primitiveEquals operates on primitive types, got object".into(),271		))?,272		(a, b) if is_function_like(&a) && is_function_like(&b) => create_error_result(273			Error::RuntimeError("cannot test equality of functions".into()),274		)?,275		(_, _) => false,276	})277}278279/// Native implementation of std.equals280pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {281	let val_a = val_a.unwrap_if_lazy()?;282	let val_b = val_b.unwrap_if_lazy()?;283284	if val_a.value_type()? != val_b.value_type()? {285		return Ok(false);286	}287	match (val_a, val_b) {288		// Cant test for ptr equality, because all fields needs to be evaluated289		(Val::Arr(a), Val::Arr(b)) => {290			if a.len() != b.len() {291				return Ok(false);292			}293			for (a, b) in a.iter().zip(b.iter()) {294				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {295					return Ok(false);296				}297			}298			Ok(true)299		}300		(Val::Obj(a), Val::Obj(b)) => {301			let fields = a.visible_fields();302			if fields != b.visible_fields() {303				return Ok(false);304			}305			for field in fields {306				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {307					return Ok(false);308				}309			}310			Ok(true)311		}312		(a, b) => Ok(primitive_equals(&a, &b)?),313	}314}315316pub fn manifest_json_ex(val: &Val, padding: &str) -> Result<String> {317	let mut out = String::new();318	manifest_json_ex_buf(val, &mut out, padding, &mut String::new())?;319	Ok(out)320}321fn manifest_json_ex_buf(322	val: &Val,323	buf: &mut String,324	padding: &str,325	cur_padding: &mut String,326) -> Result<()> {327	use std::fmt::Write;328	match val.unwrap_if_lazy()? {329		Val::Bool(v) => {330			if v {331				buf.push_str("true");332			} else {333				buf.push_str("false");334			}335		}336		Val::Null => buf.push_str("null"),337		Val::Str(s) => buf.push_str(&escape_string_json(&s)),338		Val::Num(n) => write!(buf, "{}", n).unwrap(),339		Val::Arr(items) => {340			buf.push_str("[\n");341			if !items.is_empty() {342				let old_len = cur_padding.len();343				cur_padding.push_str(padding);344				for (i, item) in items.iter().enumerate() {345					if i != 0 {346						buf.push_str(",\n")347					}348					buf.push_str(cur_padding);349					manifest_json_ex_buf(item, buf, padding, cur_padding)?;350				}351				cur_padding.truncate(old_len);352			}353			buf.push('\n');354			buf.push_str(cur_padding);355			buf.push(']');356		}357		Val::Obj(obj) => {358			buf.push_str("{\n");359			let fields = obj.visible_fields();360			if !fields.is_empty() {361				let old_len = cur_padding.len();362				cur_padding.push_str(padding);363				for (i, field) in fields.into_iter().enumerate() {364					if i != 0 {365						buf.push_str(",\n")366					}367					buf.push_str(cur_padding);368					buf.push_str(&escape_string_json(&field));369					buf.push_str(": ");370					manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, padding, cur_padding)?;371				}372				cur_padding.truncate(old_len);373			}374			buf.push('\n');375			buf.push_str(cur_padding);376			buf.push('}');377		}378		Val::Func(_) | Val::Intristic(_, _) => {379			create_error_result(Error::RuntimeError("tried to manifest function".into()))?380		}381		Val::Lazy(_) => unreachable!(),382	};383	Ok(())384}385pub fn escape_string_json(s: &str) -> String {386	use std::fmt::Write;387	let mut out = String::new();388	out.push('"');389	for c in s.chars() {390		match c {391			'"' => out.push_str("\\\""),392			'\\' => out.push_str("\\\\"),393			'\u{0008}' => out.push_str("\\b"),394			'\u{000c}' => out.push_str("\\f"),395			'\n' => out.push_str("\\n"),396			'\r' => out.push_str("\\r"),397			'\t' => out.push_str("\\t"),398			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {399				write!(out, "\\u{:04x}", c as u32).unwrap()400			}401			c => out.push(c),402		}403	}404	out.push('"');405	out406}407408#[test]409fn json_test() {410	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")411}