git.delta.rocks / jrsonnet / refs/commits / 1b7abe84e611

difftreelog

perf faster format

Лач2020-07-20parent: #9d883c6.patch.diff
in: master

6 files changed

modifiedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/build.rs
+++ b/crates/jrsonnet-evaluator/build.rs
@@ -39,7 +39,7 @@
 								if **name == *"join" || **name == *"manifestJsonEx" ||
 								**name == *"escapeStringJson" || **name == *"equals" ||
 								**name == *"base64" || **name == *"foldl" || **name == *"foldr" ||
-								**name == *"sortImpl" || **name == *"range"
+								**name == *"sortImpl" || **name == *"format" || **name == *"range"
 							)
 						})
 						.collect(),
addedcrates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/builtin/format.rs
@@ -0,0 +1,714 @@
+//! faster std.format impl
+#![allow(clippy::too_many_arguments)]
+
+use crate::{
+	create_error, create_error_result, to_string, Error, LocError, ObjValue, Val, ValType,
+};
+
+#[derive(Debug)]
+pub enum FormatError {
+	TruncatedFormatCode,
+	UnrecognizedConversionType(char),
+	ValueError(LocError),
+
+	NotEnoughValues,
+
+	CannotUseStarWidthWithObject,
+	MappingKeysRequired,
+	NoSuchField(Rc<str>),
+}
+impl From<LocError> for FormatError {
+	fn from(e: LocError) -> Self {
+		Self::ValueError(e)
+	}
+}
+use std::rc::Rc;
+use FormatError::*;
+
+pub fn try_parse_mapping_key(str: &str) -> Result<(&str, &str), FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	if bytes[0] == b'(' {
+		let mut i = 1;
+		while i < bytes.len() {
+			if bytes[i] == b')' {
+				return Ok((&str[1..i as usize], &str[i as usize + 1..]));
+			}
+			i += 1;
+		}
+		Err(TruncatedFormatCode)
+	} else {
+		Ok(("", str))
+	}
+}
+
+#[cfg(test)]
+pub mod tests_key {
+	use super::*;
+
+	#[test]
+	fn parse_key() {
+		assert_eq!(
+			try_parse_mapping_key("(hello ) world").unwrap(),
+			("hello ", " world")
+		);
+		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));
+		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));
+		assert_eq!(
+			try_parse_mapping_key(" () world").unwrap(),
+			("", " () world")
+		);
+	}
+
+	#[test]
+	#[should_panic]
+	fn parse_key_missing_start() {
+		try_parse_mapping_key("").unwrap();
+	}
+
+	#[test]
+	#[should_panic]
+	fn parse_key_missing_end() {
+		try_parse_mapping_key("(   ").unwrap();
+	}
+}
+
+#[derive(Default, Debug)]
+pub struct CFlags {
+	pub alt: bool,
+	pub zero: bool,
+	pub left: bool,
+	pub blank: bool,
+	pub sign: bool,
+}
+
+pub fn try_parse_cflags(str: &str) -> Result<(CFlags, &str), FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	let mut i = 0;
+	let mut out = CFlags::default();
+	loop {
+		if bytes.len() == i {
+			return Err(TruncatedFormatCode);
+		}
+		match bytes[i] {
+			b'#' => out.alt = true,
+			b'0' => out.zero = true,
+			b'-' => out.left = true,
+			b' ' => out.blank = true,
+			b'+' => out.sign = true,
+			_ => break,
+		}
+		i += 1;
+	}
+	Ok((out, &str[i..]))
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Width {
+	Star,
+	Fixed(usize),
+}
+pub fn try_parse_field_width(str: &str) -> Result<(Width, &str), FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	if bytes[0] == b'*' {
+		return Ok((Width::Star, &str[1..]));
+	}
+	let mut out: usize = 0;
+	let mut digits = 0;
+	while let Some(digit) = (bytes[digits] as char).to_digit(10) {
+		out *= 10;
+		out += digit as usize;
+		digits += 1;
+		if digits == bytes.len() {
+			return Err(TruncatedFormatCode);
+		}
+	}
+	Ok((Width::Fixed(out), &str[digits..]))
+}
+
+pub fn try_parse_precision(str: &str) -> Result<(Option<Width>, &str), FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	if bytes[0] == b'.' {
+		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))
+	} else {
+		Ok((None, str))
+	}
+}
+
+// Only skips
+pub fn try_parse_length_modifier(str: &str) -> Result<&str, FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	let mut idx = 0;
+	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {
+		idx += 1;
+		if bytes.len() == idx {
+			return Err(TruncatedFormatCode);
+		}
+	}
+	Ok(&str[idx..])
+}
+
+#[derive(Debug)]
+pub enum ConvTypeV {
+	Decimal,
+	Octal,
+	Hexadecimal,
+	Scientific,
+	Float,
+	Shorter,
+	Char,
+	String,
+	Percent,
+}
+pub struct ConvType {
+	v: ConvTypeV,
+	caps: bool,
+}
+
+pub fn parse_conversion_type(str: &str) -> Result<(ConvType, &str), FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+
+	let code = str.as_bytes()[0];
+	let v: (ConvTypeV, bool) = match code {
+		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),
+		b'o' => (ConvTypeV::Octal, false),
+		b'x' => (ConvTypeV::Hexadecimal, false),
+		b'X' => (ConvTypeV::Hexadecimal, true),
+		b'e' => (ConvTypeV::Scientific, false),
+		b'E' => (ConvTypeV::Scientific, true),
+		b'f' => (ConvTypeV::Float, false),
+		b'F' => (ConvTypeV::Float, true),
+		b'g' => (ConvTypeV::Shorter, false),
+		b'G' => (ConvTypeV::Shorter, true),
+		b'c' => (ConvTypeV::Char, false),
+		b's' => (ConvTypeV::String, false),
+		b'%' => (ConvTypeV::Percent, false),
+		c => return Err(UnrecognizedConversionType(c as char)),
+	};
+
+	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))
+}
+
+#[derive(Debug)]
+pub struct Code<'s> {
+	mkey: &'s str,
+	cflags: CFlags,
+	width: Width,
+	precision: Option<Width>,
+	convtype: ConvTypeV,
+	caps: bool,
+}
+pub fn parse_code(str: &str) -> Result<(Code, &str), FormatError> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let (mkey, str) = try_parse_mapping_key(str)?;
+	let (cflags, str) = try_parse_cflags(str)?;
+	let (width, str) = try_parse_field_width(str)?;
+	let (precision, str) = try_parse_precision(str)?;
+	let str = try_parse_length_modifier(str)?;
+	let (convtype, str) = parse_conversion_type(str)?;
+
+	Ok((
+		Code {
+			mkey,
+			cflags,
+			width,
+			precision,
+			convtype: convtype.v,
+			caps: convtype.caps,
+		},
+		str,
+	))
+}
+
+#[derive(Debug)]
+pub enum Element<'s> {
+	String(&'s str),
+	Code(Code<'s>),
+}
+pub fn parse_codes(mut str: &str) -> Result<Vec<Element>, FormatError> {
+	let mut bytes = str.as_bytes();
+	let mut out = vec![];
+	let mut offset = 0;
+
+	loop {
+		while offset != bytes.len() && bytes[offset] != b'%' {
+			offset += 1;
+		}
+		if offset == bytes.len() {
+			return Ok(out);
+		}
+		out.push(Element::String(&str[0..offset]));
+		str = &str[offset + 1..];
+		let (code, nstr) = parse_code(str)?;
+		str = nstr;
+		bytes = str.as_bytes();
+		offset = 0;
+
+		out.push(Element::Code(code))
+	}
+}
+
+const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
+
+#[inline]
+pub fn render_integer(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+	radix: i64,
+	prefix: &str,
+	caps: bool,
+) {
+	// Digit char indexes in reverse order, i.e
+	// for radix = 16 and n = 12f: [15, 2, 1]
+	let digits = if iv == 0 {
+		vec![0u8]
+	} else {
+		let mut v = iv.abs();
+		let mut nums = Vec::with_capacity(1);
+		while v > 0 {
+			nums.push((v % radix) as u8);
+			v /= radix;
+		}
+		nums
+	};
+	let neg = iv < 0;
+	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
+	let zp2 = zp
+		.max(precision)
+		.saturating_sub(prefix.len() + digits.len());
+
+	if neg {
+		out.push('-')
+	} else if sign {
+		out.push('+');
+	} else if blank {
+		out.push(' ');
+	}
+
+	out.reserve(zp2);
+	for _ in 0..zp2 {
+		out.push('0');
+	}
+	out.push_str(&prefix);
+
+	for digit in digits.into_iter().rev() {
+		let ch = NUMBERS[digit as usize] as char;
+		out.push(if caps { ch.to_ascii_uppercase() } else { ch });
+	}
+}
+
+pub fn render_decimal(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+) {
+	render_integer(out, iv, padding, precision, blank, sign, 10, "", false)
+}
+pub fn render_octal(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	alt: bool,
+	blank: bool,
+	sign: bool,
+) {
+	render_integer(
+		out,
+		iv,
+		padding,
+		precision,
+		blank,
+		sign,
+		8,
+		if alt && iv != 0 { "0" } else { "" },
+		false,
+	)
+}
+pub fn render_hexadecimal(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	alt: bool,
+	blank: bool,
+	sign: bool,
+	caps: bool,
+) {
+	render_integer(
+		out,
+		iv,
+		padding,
+		precision,
+		blank,
+		sign,
+		16,
+		match (alt, caps) {
+			(true, true) => "0X",
+			(true, false) => "0x",
+			(false, _) => "",
+		},
+		caps,
+	)
+}
+
+pub fn render_float(
+	out: &mut String,
+	n: f64,
+	mut padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+	ensure_pt: bool,
+	trailing: bool,
+) {
+	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
+	padding = padding.saturating_sub(dot_size + precision);
+	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
+	if precision == 0 {
+		if ensure_pt {
+			out.push('.');
+		}
+		return;
+	}
+	let frac = (n.fract() * 10.0_f64.powf(precision as f64) + 0.5).floor();
+	if trailing || frac > 0.0 {
+		out.push('.');
+		let mut frac_str = String::new();
+		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
+		let mut trim = frac_str.len();
+		if !trailing {
+			for b in frac_str.as_bytes().iter().rev() {
+				if *b == b'0' {
+					trim -= 1;
+				}
+			}
+		}
+		out.push_str(&frac_str[..trim]);
+	} else if ensure_pt {
+		out.push('.');
+	}
+}
+
+pub fn render_float_sci(
+	out: &mut String,
+	n: f64,
+	mut padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+	ensure_pt: bool,
+	trailing: bool,
+	caps: bool,
+) {
+	let exponent = n.log10().floor();
+	let mantissa = if exponent as i16 == -324 {
+		n * 10.0 / 10.0_f64.powf(exponent + 1.0)
+	} else {
+		n / 10.0_f64.powf(exponent)
+	};
+	let mut exponent_str = String::new();
+	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
+
+	// +1 for e
+	padding = padding.saturating_sub(exponent_str.len() + 1);
+
+	render_float(
+		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,
+	);
+	out.push(if caps { 'E' } else { 'e' });
+	out.push_str(&exponent_str);
+}
+
+pub fn format_code(
+	out: &mut String,
+	value: &Val,
+	code: &Code,
+	width: usize,
+	precision: Option<usize>,
+) -> Result<(), FormatError> {
+	let clfags = &code.cflags;
+	let (fpprec, iprec) = match precision {
+		Some(v) => (v, v),
+		None => (6, 0),
+	};
+	let padding = if clfags.zero && !clfags.left {
+		width
+	} else {
+		0
+	};
+
+	// TODO: If left padded, can optimize by writing directly to out
+	let mut tmp_out = String::new();
+
+	match code.convtype {
+		ConvTypeV::String => tmp_out.push_str(&to_string(value)?),
+		ConvTypeV::Decimal => {
+			let value = value.clone().try_cast_num("%d/%u/%i requires number")?;
+			render_decimal(
+				&mut tmp_out,
+				value as i64,
+				padding,
+				iprec,
+				clfags.blank,
+				clfags.sign,
+			);
+		}
+		ConvTypeV::Octal => {
+			let value = value.clone().try_cast_num("%o requires number")?;
+			render_octal(
+				&mut tmp_out,
+				value as i64,
+				padding,
+				iprec,
+				clfags.alt,
+				clfags.blank,
+				clfags.sign,
+			);
+		}
+		ConvTypeV::Hexadecimal => {
+			let value = value.clone().try_cast_num("%x/%X requires number")?;
+			render_hexadecimal(
+				&mut tmp_out,
+				value as i64,
+				padding,
+				iprec,
+				clfags.alt,
+				clfags.blank,
+				clfags.sign,
+				code.caps,
+			);
+		}
+		ConvTypeV::Scientific => {
+			let value = value.clone().try_cast_num("%e/%E requires number")?;
+			render_float_sci(
+				&mut tmp_out,
+				value,
+				padding,
+				fpprec,
+				clfags.blank,
+				clfags.sign,
+				clfags.alt,
+				true,
+				code.caps,
+			);
+		}
+		ConvTypeV::Float => {
+			let value = value.clone().try_cast_num("%e/%E requires number")?;
+			render_float(
+				&mut tmp_out,
+				value,
+				padding,
+				fpprec,
+				clfags.blank,
+				clfags.sign,
+				clfags.alt,
+				true,
+			);
+		}
+		ConvTypeV::Shorter => {
+			let value = value.clone().try_cast_num("%g/%G requires number")?;
+			let exponent = value.log10().floor();
+			if exponent < -4.0 || exponent >= fpprec as f64 {
+				render_float_sci(
+					&mut tmp_out,
+					value,
+					padding,
+					fpprec - 1,
+					clfags.blank,
+					clfags.sign,
+					clfags.alt,
+					clfags.alt,
+					code.caps,
+				);
+			} else {
+				let digits_before_pt = 1.max(exponent as usize + 1);
+				render_float(
+					&mut tmp_out,
+					value,
+					padding,
+					fpprec - digits_before_pt,
+					clfags.blank,
+					clfags.sign,
+					clfags.alt,
+					clfags.alt,
+				);
+			}
+		}
+		ConvTypeV::Char => match value.clone().unwrap_if_lazy()? {
+			Val::Num(n) => tmp_out.push(
+				std::char::from_u32(n as u32)
+					.ok_or_else(|| create_error(Error::InvalidUnicodeCodepointGot(n as u32)))?,
+			),
+			Val::Str(s) => {
+				if s.chars().count() != 1 {
+					create_error_result(Error::RuntimeError(
+						format!("%c expected 1 char string, got {}", s.chars().count()).into(),
+					))?;
+				}
+				tmp_out.push_str(&s);
+			}
+			_ => {
+				create_error_result(Error::TypeMismatch(
+					"%c requires number/string",
+					vec![ValType::Num, ValType::Str],
+					value.value_type()?,
+				))?;
+			}
+		},
+		ConvTypeV::Percent => tmp_out.push('%'),
+	};
+
+	let padding = width.saturating_sub(tmp_out.len());
+
+	if !clfags.left {
+		for _ in 0..padding {
+			out.push(' ');
+		}
+	}
+	out.push_str(&tmp_out);
+	if clfags.left {
+		for _ in 0..padding {
+			out.push(' ');
+		}
+	}
+
+	Ok(())
+}
+
+pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String, FormatError> {
+	let codes = parse_codes(&str)?;
+	let mut out = String::new();
+
+	for code in codes {
+		match code {
+			Element::String(s) => {
+				out.push_str(s);
+			}
+			Element::Code(c) => {
+				let width = match c.width {
+					Width::Star => {
+						if values.is_empty() {
+							return Err(FormatError::NotEnoughValues);
+						}
+						let value = &values[0];
+						values = &values[1..];
+						value.clone().try_cast_num("field width")? as usize
+					}
+					Width::Fixed(n) => n,
+				};
+				let precision = match c.precision {
+					Some(Width::Star) => {
+						if values.is_empty() {
+							return Err(FormatError::NotEnoughValues);
+						}
+						let value = &values[0];
+						values = &values[1..];
+						Some(value.clone().try_cast_num("field precision")? as usize)
+					}
+					Some(Width::Fixed(n)) => Some(n),
+					None => None,
+				};
+				if values.is_empty() {
+					return Err(FormatError::NotEnoughValues);
+				}
+				let value = &values[0];
+				values = &values[1..];
+
+				format_code(&mut out, value, &c, width, precision)?;
+			}
+		}
+	}
+
+	Ok(out)
+}
+
+pub fn format_obj(str: &str, values: &ObjValue) -> Result<String, FormatError> {
+	let codes = parse_codes(&str)?;
+	let mut out = String::new();
+
+	for code in codes {
+		match code {
+			Element::String(s) => {
+				out.push_str(s);
+			}
+			Element::Code(c) => {
+				// TODO: Operate on ref
+				let f: Rc<str> = c.mkey.into();
+				if f.is_empty() {
+					return Err(FormatError::MappingKeysRequired);
+				}
+				let width = match c.width {
+					Width::Star => {
+						return Err(FormatError::CannotUseStarWidthWithObject);
+					}
+					Width::Fixed(n) => n,
+				};
+				let precision = match c.precision {
+					Some(Width::Star) => {
+						return Err(FormatError::CannotUseStarWidthWithObject);
+					}
+					Some(Width::Fixed(n)) => Some(n),
+					None => None,
+				};
+				let value = if let Some(v) = values.get(f.clone())? {
+					v
+				} else {
+					return Err(FormatError::NoSuchField(f));
+				};
+
+				format_code(&mut out, &value, &c, width, precision)?;
+			}
+		}
+	}
+
+	Ok(out)
+}
+
+#[cfg(test)]
+pub mod test_format {
+	use super::*;
+
+	#[test]
+	fn parse() {
+		println!("{:?}", parse_codes("Hello %s world!!! %s %(aaa)s ww"));
+	}
+
+	#[test]
+	fn octals() {
+		assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");
+		assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");
+		assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), "  10");
+		assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");
+		assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");
+		assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");
+		assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10  ");
+		assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");
+		assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,2 +1,4 @@
 pub mod stdlib;
 pub use stdlib::*;
+
+pub mod format;
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -60,6 +60,7 @@
 	StringManifestOutputIsNotAString,
 
 	ImportCallbackError(String),
+	InvalidUnicodeCodepointGot(u32),
 }
 
 #[derive(Clone, Debug)]
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -1,4 +1,5 @@
 use crate::{
+	builtin::format::{format_arr, format_obj},
 	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,
@@ -634,6 +635,17 @@
 				Ok(Val::Arr(Rc::new(new_arr)))
 			}))?,
 			// faster
+			("std", "format") => parse_args!(context, "std.format", args, 2, [
+				0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+				1, vals: [Val::Arr|Val::Obj], vec![ValType::Arr, ValType::Obj];
+			], {
+				match vals {
+					Val::Arr(vals) => Val::Str(format_arr(&str, &vals).unwrap().into()),
+					Val::Obj(obj) => Val::Str(format_obj(&str, &obj).unwrap().into()),
+					_ => unreachable!()
+				}
+			}),
+			// faster
 			("std", "range") => parse_args!(context, "std.range", args, 2, [
 				0, from: [Val::Num]!!Val::Num, vec![ValType::Num];
 				0, to: [Val::Num]!!Val::Num, vec![ValType::Num];
@@ -648,9 +660,11 @@
 				0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
 			], {
 				let mut out = String::new();
-				out.push(std::char::from_u32(n as u32).unwrap());
-				Val::Str(out.into())
-			}),
+				out.push(std::char::from_u32(n as u32).ok_or_else(||
+					create_error(crate::error::Error::InvalidUnicodeCodepointGot(n as u32))
+				)?);
+				Ok(Val::Str(out.into()))
+			})?,
 			("std", "encodeUTF8") => parse_args!(context, "std.encodeUtf8", args, 1, [
 				0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
 			], {
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, 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}
after · crates/jrsonnet-evaluator/src/val.rs
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}407408pub fn to_string(val: &Val) -> Result<Rc<str>> {409	Ok(match val {410		Val::Bool(true) => "true".into(),411		Val::Null => "null".into(),412		Val::Str(s) => s.clone(),413		v => v.clone().into_json(0)?,414	})415}416417#[test]418fn json_test() {419	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")420}