git.delta.rocks / jrsonnet / refs/commits / 433adfa9b8ae

difftreelog

source

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