difftreelog
perf faster format
in: master
6 files changed
crates/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(),
crates/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 ");
+ }
+}
crates/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;
crates/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)]
crates/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];
], {
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use 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}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}