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

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs12.1 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	}205206	pub fn into_string(self) -> Result<Rc<str>> {207		Ok(match self.unwrap_if_lazy()? {208			Val::Bool(true) => "true".into(),209			Val::Bool(false) => "false".into(),210			Val::Null => "null".into(),211			Val::Str(s) => s,212			v => manifest_json_ex(213				&v,214				&ManifestJsonOptions {215					padding: &"",216					mtype: ManifestType::ToString,217				},218			)?219			.into(),220		})221	}222223	/// For manifestification224	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {225		manifest_json_ex(226			&self,227			&ManifestJsonOptions {228				padding: &" ".repeat(padding),229				mtype: ManifestType::Manifest,230			},231		)232		.map(|s| s.into())233	}234235	/// Calls std.manifestJson236	#[cfg(feature = "faster")]237	pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {238		manifest_json_ex(239			&self,240			&ManifestJsonOptions {241				padding: &" ".repeat(padding),242				mtype: ManifestType::Std,243			},244		)245		.map(|s| s.into())246	}247248	/// Calls std.manifestJson249	#[cfg(not(feature = "faster"))]250	pub fn into_std_json(self, padding: usize) -> Result<Rc<str>> {251		with_state(|s| {252			let ctx = s253				.create_default_context()?254				.with_var("__tmp__to_json__".into(), self)?;255			Ok(evaluate(256				ctx,257				&el!(Expr::Apply(258					el!(Expr::Index(259						el!(Expr::Var("std".into())),260						el!(Expr::Str("manifestJsonEx".into()))261					)),262					ArgsDesc(vec![263						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),264						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))265					]),266					false267				)),268			)?269			.try_cast_str("to json")?)270		})271	}272	pub fn into_yaml(self, padding: usize) -> Result<Rc<str>> {273		with_state(|s| {274			let ctx = s275				.create_default_context()?276				.with_var("__tmp__to_json__".into(), self);277			Ok(evaluate(278				ctx,279				&el!(Expr::Apply(280					el!(Expr::Index(281						el!(Expr::Var("std".into())),282						el!(Expr::Str("manifestYamlDoc".into()))283					)),284					ArgsDesc(vec![285						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),286						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))287					]),288					false289				)),290			)?291			.try_cast_str("to json")?)292		})293	}294}295296fn is_function_like(val: &Val) -> bool {297	matches!(val, Val::Func(_) | Val::Intristic(_, _))298}299300/// Implements std.primitiveEquals builtin301pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {302	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {303		(Val::Bool(a), Val::Bool(b)) => a == b,304		(Val::Null, Val::Null) => true,305		(Val::Str(a), Val::Str(b)) => a == b,306		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,307		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(308			"primitiveEquals operates on primitive types, got array".into(),309		)),310		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(311			"primitiveEquals operates on primitive types, got object".into(),312		)),313		(a, b) if is_function_like(&a) && is_function_like(&b) => {314			throw!(RuntimeError("cannot test equality of functions".into()))315		}316		(_, _) => false,317	})318}319320/// Native implementation of std.equals321pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {322	let val_a = val_a.unwrap_if_lazy()?;323	let val_b = val_b.unwrap_if_lazy()?;324325	if val_a.value_type()? != val_b.value_type()? {326		return Ok(false);327	}328	match (val_a, val_b) {329		// Cant test for ptr equality, because all fields needs to be evaluated330		(Val::Arr(a), Val::Arr(b)) => {331			if a.len() != b.len() {332				return Ok(false);333			}334			for (a, b) in a.iter().zip(b.iter()) {335				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {336					return Ok(false);337				}338			}339			Ok(true)340		}341		(Val::Obj(a), Val::Obj(b)) => {342			let fields = a.visible_fields();343			if fields != b.visible_fields() {344				return Ok(false);345			}346			for field in fields {347				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {348					return Ok(false);349				}350			}351			Ok(true)352		}353		(a, b) => Ok(primitive_equals(&a, &b)?),354	}355}356357#[derive(PartialEq)]358pub enum ManifestType {359	// Applied in manifestification360	Manifest,361	/// Used for std.manifestJson362	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest363	Std,364	// No line breaks, used in `obj+''`365	ToString,366}367368pub struct ManifestJsonOptions<'s> {369	pub padding: &'s str,370	pub mtype: ManifestType,371}372373pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {374	let mut out = String::new();375	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;376	Ok(out)377}378fn manifest_json_ex_buf(379	val: &Val,380	buf: &mut String,381	cur_padding: &mut String,382	options: &ManifestJsonOptions<'_>,383) -> Result<()> {384	use std::fmt::Write;385	match val.unwrap_if_lazy()? {386		Val::Bool(v) => {387			if v {388				buf.push_str("true");389			} else {390				buf.push_str("false");391			}392		}393		Val::Null => buf.push_str("null"),394		Val::Str(s) => buf.push_str(&escape_string_json(&s)),395		Val::Num(n) => write!(buf, "{}", n).unwrap(),396		Val::Arr(items) => {397			buf.push('[');398			if !items.is_empty() {399				if options.mtype != ManifestType::ToString {400					buf.push('\n');401				}402403				let old_len = cur_padding.len();404				cur_padding.push_str(options.padding);405				for (i, item) in items.iter().enumerate() {406					if i != 0 {407						buf.push(',');408						if options.mtype == ManifestType::ToString {409							buf.push(' ');410						} else {411							buf.push('\n');412						}413					}414					buf.push_str(cur_padding);415					manifest_json_ex_buf(item, buf, cur_padding, options)?;416				}417				cur_padding.truncate(old_len);418419				if options.mtype != ManifestType::ToString {420					buf.push('\n');421					buf.push_str(cur_padding);422				}423			} else if options.mtype == ManifestType::Std {424				buf.push_str("\n\n");425				buf.push_str(cur_padding);426			} else if options.mtype == ManifestType::ToString {427				buf.push(' ');428			}429			buf.push(']');430		}431		Val::Obj(obj) => {432			buf.push('{');433			let fields = obj.visible_fields();434			if !fields.is_empty() {435				if options.mtype != ManifestType::ToString {436					buf.push('\n');437				}438439				let old_len = cur_padding.len();440				cur_padding.push_str(options.padding);441				for (i, field) in fields.into_iter().enumerate() {442					if i != 0 {443						buf.push(',');444						if options.mtype == ManifestType::ToString {445							buf.push(' ');446						} else {447							buf.push('\n');448						}449					}450					buf.push_str(cur_padding);451					buf.push_str(&escape_string_json(&field));452					buf.push_str(": ");453					manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, cur_padding, options)?;454				}455				cur_padding.truncate(old_len);456457				if options.mtype != ManifestType::ToString {458					buf.push('\n');459					buf.push_str(cur_padding);460				}461			} else if options.mtype == ManifestType::Std {462				buf.push_str("\n\n");463				buf.push_str(cur_padding);464			} else if options.mtype == ManifestType::ToString {465				buf.push(' ');466			}467			buf.push('}');468		}469		Val::Func(_) | Val::Intristic(_, _) => {470			throw!(RuntimeError("tried to manifest function".into()))471		}472		Val::Lazy(_) => unreachable!(),473	};474	Ok(())475}476pub fn escape_string_json(s: &str) -> String {477	use std::fmt::Write;478	let mut out = String::new();479	out.push('"');480	for c in s.chars() {481		match c {482			'"' => out.push_str("\\\""),483			'\\' => out.push_str("\\\\"),484			'\u{0008}' => out.push_str("\\b"),485			'\u{000c}' => out.push_str("\\f"),486			'\n' => out.push_str("\\n"),487			'\r' => out.push_str("\\r"),488			'\t' => out.push_str("\\t"),489			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {490				write!(out, "\\u{:04x}", c as u32).unwrap()491			}492			c => out.push(c),493		}494	}495	out.push('"');496	out497}498499#[test]500fn json_test() {501	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")502}