git.delta.rocks / jrsonnet / refs/commits / 38dfcbef8382

difftreelog

source

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