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

difftreelog

feat trace function names

Лач2020-07-16parent: #0dad2a6.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -1,7 +1,8 @@
 use crate::{
-	context_creator, create_error, create_error_result, escape_string_json, future_wrapper,
-	lazy_val, manifest_json_ex, parse_args, push, with_state, Context, ContextCreator, Error,
-	FuncDesc, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val, ValType,
+	context_creator, create_error, create_error_result, equals, escape_string_json, future_wrapper,
+	lazy_val, manifest_json_ex, parse_args, primitive_equals, push, with_state, Context,
+	ContextCreator, Error, FuncDesc, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val,
+	ValType,
 };
 use closure::closure;
 use jrsonnet_parser::{
@@ -21,6 +22,7 @@
 				Ok(lazy_val!(
 					closure!(clone b, clone params, clone context_creator, || Ok(evaluate_method(
 						context_creator.0(this.clone(), super_obj.clone())?,
+						b.name.clone(),
 						params.clone(),
 						b.value.clone(),
 					)))
@@ -32,20 +34,24 @@
 			b.name.clone(),
 			LazyBinding::Bindable(Rc::new(move |this, super_obj| {
 				Ok(lazy_val!(closure!(clone context_creator, clone b, ||
-					push(&b.value.1, "thunk", ||{
-						evaluate(
+						evaluate_named(
 							context_creator.0(this.clone(), super_obj.clone())?,
-							&b.value
+							&b.value,
+							b.name.clone()
 						)
-					})
 				)))
 			})),
 		)
 	}
 }
 
-pub fn evaluate_method(ctx: Context, params: ParamsDesc, body: LocExpr) -> Val {
-	Val::Func(FuncDesc { ctx, params, body })
+pub fn evaluate_method(ctx: Context, name: Rc<str>, params: ParamsDesc, body: LocExpr) -> Val {
+	Val::Func(Rc::new(FuncDesc {
+		name,
+		ctx,
+		params,
+		body,
+	}))
 }
 
 pub fn evaluate_field_name(
@@ -289,15 +295,16 @@
 				}
 				let name = name.unwrap();
 				new_members.insert(
-					name,
+					name.clone(),
 					ObjMember {
 						add: false,
 						visibility: Visibility::Hidden,
 						invoke: LazyBinding::Bindable(Rc::new(
-							closure!(clone value, clone context_creator, clone params, |this, super_obj| {
+							closure!(clone value, clone context_creator, clone params, clone name, |this, super_obj| {
 								// TODO: Assert
 								Ok(LazyVal::new_resolved(evaluate_method(
 									context_creator.0(this, super_obj)?,
+									name.clone(),
 									params.clone(),
 									value.clone(),
 								)))
@@ -647,13 +654,22 @@
 			if tailstrict {
 				body()?
 			} else {
-				push(loc, "function call", body)?
+				push(loc, || format!("function <{}> call", f.name), body)?
 			}
 		}
 		v => create_error_result(crate::Error::OnlyFunctionsCanBeCalledGot(v.value_type()?))?,
 	})
 }
 
+pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: Rc<str>) -> Result<Val> {
+	use Expr::*;
+	let LocExpr(expr, _loc) = lexpr;
+	Ok(match &**expr {
+		Function(params, body) => evaluate_method(context, name, params.clone(), body.clone()),
+		_ => evaluate(context, lexpr)?,
+	})
+}
+
 pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {
 	use Expr::*;
 	let LocExpr(expr, loc) = expr;
@@ -680,7 +696,7 @@
 		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,
 		Var(name) => push(
 			loc,
-			|| "var".to_owned(),
+			|| format!("variable <{}>", name),
 			|| Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?),
 		)?,
 		Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
@@ -789,7 +805,9 @@
 			&Val::Obj(evaluate_object(context, t)?),
 		)?,
 		Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?,
-		Function(params, body) => evaluate_method(context, params.clone(), body.clone()),
+		Function(params, body) => {
+			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())
+		}
 		AssertExpr(AssertStmt(value, msg), returned) => {
 			let assertion_result = push(
 				&value.1,
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
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 ctx: Context,62	pub params: ParamsDesc,63	pub body: LocExpr,64}65impl FuncDesc {66	/// This function is always inlined to make tailstrict work67	pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {68		let ctx = parse_function_call(69			call_ctx,70			Some(self.ctx.clone()),71			&self.params,72			args,73			tailstrict,74		)?;75		evaluate(ctx, &self.body)76	}7778	pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {79		let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;80		evaluate(ctx, &self.body)81	}82}8384#[derive(Debug, Clone, Copy, PartialEq)]85pub enum ValType {86	Bool,87	Null,88	Str,89	Num,90	Arr,91	Obj,92	Func,93}94impl ValType {95	pub fn name(&self) -> &'static str {96		use ValType::*;97		match self {98			Bool => "boolean",99			Null => "null",100			Str => "string",101			Num => "number",102			Arr => "array",103			Obj => "object",104			Func => "function",105		}106	}107}108impl Display for ValType {109	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {110		write!(f, "{}", self.name())111	}112}113114#[derive(Debug, Clone)]115pub enum Val {116	Bool(bool),117	Null,118	Str(Rc<str>),119	Num(f64),120	Lazy(LazyVal),121	Arr(Rc<Vec<Val>>),122	Obj(ObjValue),123	Func(Rc<FuncDesc>),124125	// Library functions implemented in native126	Intristic(Rc<str>, Rc<str>),127}128macro_rules! matches_unwrap {129	($e: expr, $p: pat, $r: expr) => {130		match $e {131			$p => $r,132			_ => panic!("no match"),133			}134	};135}136impl Val {137	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity138	pub fn new_checked_num(num: f64) -> Result<Val> {139		if num.is_finite() {140			Ok(Val::Num(num))141		} else {142			create_error_result(Error::RuntimeError("overflow".into()))143		}144	}145146	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {147		let this_type = self.value_type()?;148		if this_type != val_type {149			create_error_result(Error::TypeMismatch(context, vec![val_type], this_type))150		} else {151			Ok(())152		}153	}154	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {155		self.assert_type(context, ValType::Bool)?;156		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))157	}158	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {159		self.assert_type(context, ValType::Str)?;160		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))161	}162	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {163		self.assert_type(context, ValType::Num)?;164		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))165	}166	pub fn unwrap_if_lazy(&self) -> Result<Self> {167		Ok(if let Val::Lazy(v) = self {168			v.evaluate()?.unwrap_if_lazy()?169		} else {170			self.clone()171		})172	}173	pub fn value_type(&self) -> Result<ValType> {174		Ok(match self {175			Val::Str(..) => ValType::Str,176			Val::Num(..) => ValType::Num,177			Val::Arr(..) => ValType::Arr,178			Val::Obj(..) => ValType::Obj,179			Val::Func(..) => ValType::Func,180			Val::Bool(_) => ValType::Bool,181			Val::Null => ValType::Null,182			Val::Intristic(_, _) => ValType::Func,183			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,184		})185	}186	#[cfg(feature = "faster")]187	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {188		manifest_json_ex(&self, &" ".repeat(padding)).map(|s| s.into())189	}190	#[cfg(not(feature = "faster"))]191	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {192		with_state(|s| {193			let ctx = s194				.create_default_context()?195				.with_var("__tmp__to_json__".into(), self)?;196			Ok(evaluate(197				ctx,198				&el!(Expr::Apply(199					el!(Expr::Index(200						el!(Expr::Var("std".into())),201						el!(Expr::Str("manifestJsonEx".into()))202					)),203					ArgsDesc(vec![204						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),205						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))206					]),207					false208				)),209			)?210			.try_cast_str("to json")?)211		})212	}213	pub fn into_yaml(self, padding: usize) -> Result<Rc<str>> {214		with_state(|s| {215			let ctx = s216				.create_default_context()?217				.with_var("__tmp__to_json__".into(), self)?;218			Ok(evaluate(219				ctx,220				&el!(Expr::Apply(221					el!(Expr::Index(222						el!(Expr::Var("std".into())),223						el!(Expr::Str("manifestYamlDoc".into()))224					)),225					ArgsDesc(vec![226						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),227						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))228					]),229					false230				)),231			)?232			.try_cast_str("to json")?)233		})234	}235}236237fn is_function_like(val: &Val) -> bool {238	matches!(val, Val::Func(_) | Val::Intristic(_, _))239}240241/// Implements std.primitiveEquals builtin242pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {243	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {244		(Val::Bool(a), Val::Bool(b)) => a == b,245		(Val::Null, Val::Null) => true,246		(Val::Str(a), Val::Str(b)) => a == b,247		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,248		(Val::Arr(_), Val::Arr(_)) => create_error_result(Error::RuntimeError(249			"primitiveEquals operates on primitive types, got array".into(),250		))?,251		(Val::Obj(_), Val::Obj(_)) => create_error_result(Error::RuntimeError(252			"primitiveEquals operates on primitive types, got object".into(),253		))?,254		(a, b) if is_function_like(&a) && is_function_like(&b) => create_error_result(255			Error::RuntimeError("cannot test equality of functions".into()),256		)?,257		(_, _) => false,258	})259}260261/// Native implementation of std.equals262pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {263	let val_a = val_a.unwrap_if_lazy()?;264	let val_b = val_b.unwrap_if_lazy()?;265266	if val_a.value_type()? != val_b.value_type()? {267		return Ok(false);268	}269	match (val_a, val_b) {270		// Cant test for ptr equality, because all fields needs to be evaluated271		(Val::Arr(a), Val::Arr(b)) => {272			if a.len() != b.len() {273				return Ok(false);274			}275			for (a, b) in a.iter().zip(b.iter()) {276				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {277					return Ok(false);278				}279			}280			Ok(true)281		}282		(Val::Obj(a), Val::Obj(b)) => {283			let fields = a.visible_fields();284			if fields != b.visible_fields() {285				return Ok(false);286			}287			for field in fields {288				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {289					return Ok(false);290				}291			}292			Ok(true)293		}294		(a, b) => Ok(primitive_equals(&a, &b)?),295	}296}297298pub fn manifest_json_ex(val: &Val, padding: &str) -> Result<String> {299	let mut out = String::new();300	manifest_json_ex_buf(val, &mut out, padding, &mut String::new())?;301	Ok(out)302}303fn manifest_json_ex_buf(304	val: &Val,305	buf: &mut String,306	padding: &str,307	cur_padding: &mut String,308) -> Result<()> {309	use std::fmt::Write;310	match val.unwrap_if_lazy()? {311		Val::Bool(v) => {312			if v {313				buf.push_str("true");314			} else {315				buf.push_str("false");316			}317		}318		Val::Null => buf.push_str("null"),319		Val::Str(s) => buf.push_str(&escape_string_json(&s)),320		Val::Num(n) => write!(buf, "{}", n).unwrap(),321		Val::Arr(items) => {322			buf.push_str("[\n");323			if !items.is_empty() {324				let old_len = cur_padding.len();325				cur_padding.push_str(padding);326				for (i, item) in items.iter().enumerate() {327					if i != 0 {328						buf.push_str(",\n")329					}330					buf.push_str(cur_padding);331					manifest_json_ex_buf(item, buf, padding, cur_padding)?;332				}333				cur_padding.truncate(old_len);334			}335			buf.push('\n');336			buf.push_str(cur_padding);337			buf.push(']');338		}339		Val::Obj(obj) => {340			buf.push_str("{\n");341			let fields = obj.visible_fields();342			if !fields.is_empty() {343				let old_len = cur_padding.len();344				cur_padding.push_str(padding);345				for (i, field) in fields.into_iter().enumerate() {346					if i != 0 {347						buf.push_str(",\n")348					}349					buf.push_str(cur_padding);350					buf.push_str(&escape_string_json(&field));351					buf.push_str(": ");352					manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, padding, cur_padding)?;353				}354				cur_padding.truncate(old_len);355			}356			buf.push('\n');357			buf.push_str(cur_padding);358			buf.push('}');359		}360		Val::Func(_) | Val::Intristic(_, _) => create_error_result(Error::RuntimeError("tried to manifest function".into()))?,361		Val::Lazy(_) => unreachable!(),362	};363	Ok(())364}365pub fn escape_string_json(s: &str) -> String {366	use std::fmt::Write;367	let mut out = String::new();368	out.push('"');369	for c in s.chars() {370		match c {371			'"' => out.push_str("\\\""),372			'\\' => out.push_str("\\\\"),373			'\u{0008}' => out.push_str("\\b"),374			'\u{000c}' => out.push_str("\\f"),375			'\n' => out.push_str("\\n"),376			'\r' => out.push_str("\\r"),377			'\t' => out.push_str("\\t"),378			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {379				write!(out, "\\u{:04x}", c as u32).unwrap()380			}381			c => out.push(c),382		}383	}384	out.push('"');385	out386}387388#[test]389fn json_test() {390	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")391}
after · crates/jrsonnet-evaluator/src/val.rs
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(_, _) => create_error_result(Error::RuntimeError("tried to manifest function".into()))?,362		Val::Lazy(_) => unreachable!(),363	};364	Ok(())365}366pub fn escape_string_json(s: &str) -> String {367	use std::fmt::Write;368	let mut out = String::new();369	out.push('"');370	for c in s.chars() {371		match c {372			'"' => out.push_str("\\\""),373			'\\' => out.push_str("\\\\"),374			'\u{0008}' => out.push_str("\\b"),375			'\u{000c}' => out.push_str("\\f"),376			'\n' => out.push_str("\\n"),377			'\r' => out.push_str("\\r"),378			'\t' => out.push_str("\\t"),379			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {380				write!(out, "\\u{:04x}", c as u32).unwrap()381			}382			c => out.push(c),383		}384	}385	out.push('"');386	out387}388389#[test]390fn json_test() {391	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")392}