--- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -1,7 +1,7 @@ //! faster std.format impl #![allow(clippy::too_many_arguments)] -use crate::{error::Error::*, throw, to_string, LocError, ObjValue, Result, Val, ValType}; +use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val, ValType}; #[derive(Debug, Clone)] pub enum FormatError { --- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -3,7 +3,9 @@ context_creator, equals, error::Error::*, escape_string_json, future_wrapper, lazy_val, manifest_json_ex, parse_args, primitive_equals, - push, throw, with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, + push, throw, + val::ManifestJsonOptions, + with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ManifestType, ObjMember, ObjValue, Result, Val, ValType, }; use closure::closure; @@ -405,355 +407,372 @@ let lazy = evaluate(context.clone(), value)?; let value = lazy.unwrap_if_lazy()?; Ok(match value { - Val::Intristic(ns, name) => match (&ns as &str, &name as &str) { - // arr/string/function - ("std", "length") => parse_args!(context, "std.length", args, 1, [ - 0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj]; - ], { - Ok(match x { - Val::Str(n) => Val::Num(n.chars().count() as f64), - Val::Arr(i) => Val::Num(i.len() as f64), - Val::Obj(o) => Val::Num( - o.fields_visibility() - .into_iter() - .filter(|(_k, v)| *v) - .count() as f64, - ), - _ => unreachable!(), - }) - })?, - // any - ("std", "type") => parse_args!(context, "std.type", args, 1, [ - 0, x, vec![]; - ], { - Ok(Val::Str(x.value_type()?.name().into())) - })?, - // length, idx=>any - ("std", "makeArray") => noinline!(parse_args!(context, "std.makeArray", args, 2, [ - 0, sz: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - ], { - if sz < 0.0 { - throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into())); - } - let mut out = Vec::with_capacity(sz as usize); - for i in 0..sz as usize { - out.push(func.evaluate_values( - Context::new(), - &[Val::Num(i as f64)] - )?) - } - Ok(Val::Arr(Rc::new(out))) - }))?, - // string - ("std", "codepoint") => parse_args!(context, "std.codepoint", args, 1, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - assert!( - str.chars().count() == 1, - "std.codepoint should receive single char string" - ); - Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64)) - })?, - // object, includeHidden - ("std", "objectFieldsEx") => { - noinline!(parse_args!(context, "std.objectFieldsEx",args, 2, [ - 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj]; - 1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool]; - ], { - let mut out = obj.fields_visibility() - .into_iter() - .filter(|(_k, v)| *v || inc_hidden) - .map(|(k, _v)|k) - .collect::>(); - out.sort(); - Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect()))) - }))? - } - // object, field, includeHidden - ("std", "objectHasEx") => parse_args!(context, "std.objectHasEx", args, 3, [ - 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj]; - 1, f: [Val::Str]!!Val::Str, vec![ValType::Str]; - 2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool]; - ], { - Ok(Val::Bool( - obj.fields_visibility() - .into_iter() - .filter(|(_k, v)| *v || inc_hidden) - .any(|(k, _v)| *k == *f), - )) - })?, - ("std", "primitiveEquals") => parse_args!(context, "std.primitiveEquals", args, 2, [ - 0, a, vec![]; - 1, b, vec![]; - ], { - Ok(Val::Bool(primitive_equals(&a, &b)?)) - })?, - // faster - ("std", "equals") => parse_args!(context, "std.equals", args, 2, [ - 0, a, vec![]; - 1, b, vec![]; - ], { - Ok(Val::Bool(equals(&a, &b)?)) - })?, - ("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [ - 0, a: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, b: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(a % b)) - })?, - ("std", "floor") => parse_args!(context, "std.floor", args, 1, [ - 0, x: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(x.floor())) - })?, - ("std", "log") => parse_args!(context, "std.log", args, 2, [ - 0, n: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(n.ln())) - })?, - ("std", "trace") => parse_args!(context, "std.trace", args, 2, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - 1, rest, vec![]; - ], { - eprint!("TRACE:"); - if let Some(loc) = loc { - with_state(|s|{ - let locs = s.map_source_locations(&loc.0, &[loc.1]); - eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line); - }); - } - eprintln!(" {}", str); - Ok(rest) - })?, - ("std", "pow") => parse_args!(context, "std.modulo", args, 2, [ - 0, x: [Val::Num]!!Val::Num, vec![ValType::Num]; - 1, n: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - Ok(Val::Num(x.powf(n))) - })?, - ("std", "extVar") => parse_args!(context, "std.extVar", args, 2, [ - 0, x: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else( - || UndefinedExternalVariable(x), - )?) - })?, - ("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [ - 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - ], { - Ok(Val::Arr(Rc::new( - arr.iter() - .cloned() - .filter(|e| { - func - .evaluate_values(context.clone(), &[e.clone()]) - .unwrap() - .try_cast_bool("filter predicate") - .unwrap() + Val::Intristic(ns, name) => push( + loc, + || format!("intristic <{}.{}> call", ns, name), + || { + Ok(match (&ns as &str, &name as &str) { + // arr/string/function + ("std", "length") => parse_args!(context, "std.length", args, 1, [ + 0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj]; + ], { + Ok(match x { + Val::Str(n) => Val::Num(n.chars().count() as f64), + Val::Arr(i) => Val::Num(i.len() as f64), + Val::Obj(o) => Val::Num( + o.fields_visibility() + .into_iter() + .filter(|(_k, v)| *v) + .count() as f64, + ), + _ => unreachable!(), }) - .collect(), - ))) - }))?, - // faster - ("std", "foldl") => noinline!(parse_args!(context, "std.foldl", args, 3, [ - 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - 2, init, vec![]; - ], { - let mut acc = init; - for i in arr.iter().cloned() { - acc = func.evaluate_values(context.clone(), &[acc, i])?; - } - Ok(acc) - }))?, - // faster - ("std", "foldr") => noinline!(parse_args!(context, "std.foldr", args, 3, [ - 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - 2, init, vec![]; - ], { - let mut acc = init; - for i in arr.iter().rev().cloned() { - acc = func.evaluate_values(context.clone(), &[acc, i])?; - } - Ok(acc) - }))?, - // faster - #[allow(non_snake_case)] - ("std", "sortImpl") => noinline!(parse_args!(context, "std.sort", args, 2, [ - 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - 1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func]; - ], { - if arr.len() <= 1 { - return Ok(Val::Arr(arr)) - } - let mut new_arr = arr.iter().cloned().collect::>(); - match keyF.evaluate_values(context.clone(), &[new_arr[0].clone()])? { - Val::Str(_) => { - let mut err = None; - new_arr.sort_by_cached_key(|k| { - match keyF.evaluate_values(context.clone(), &[k.clone()]) { - Ok(Val::Str(v)) => v, - Ok(_) => { - err = Some(LocError::new(RuntimeError("types of all array elements should equal".into()))); - "".into() - } - Err(e) => { - err = Some(e); - "".into() - } + })?, + // any + ("std", "type") => parse_args!(context, "std.type", args, 1, [ + 0, x, vec![]; + ], { + Ok(Val::Str(x.value_type()?.name().into())) + })?, + // length, idx=>any + ("std", "makeArray") => { + noinline!(parse_args!(context, "std.makeArray", args, 2, [ + 0, sz: [Val::Num]!!Val::Num, vec![ValType::Num]; + 1, func: [Val::Func]!!Val::Func, vec![ValType::Func]; + ], { + if sz < 0.0 { + throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into())); } - }); - if let Some(e) = err { - return Err(e); + let mut out = Vec::with_capacity(sz as usize); + for i in 0..sz as usize { + out.push(func.evaluate_values( + Context::new(), + &[Val::Num(i as f64)] + )?) + } + Ok(Val::Arr(Rc::new(out))) + }))? + } + // string + ("std", "codepoint") => parse_args!(context, "std.codepoint", args, 1, [ + 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; + ], { + assert!( + str.chars().count() == 1, + "std.codepoint should receive single char string" + ); + Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64)) + })?, + // object, includeHidden + ("std", "objectFieldsEx") => { + noinline!(parse_args!(context, "std.objectFieldsEx",args, 2, [ + 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj]; + 1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool]; + ], { + let mut out = obj.fields_visibility() + .into_iter() + .filter(|(_k, v)| *v || inc_hidden) + .map(|(k, _v)|k) + .collect::>(); + out.sort(); + Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect()))) + }))? + } + // object, field, includeHidden + ("std", "objectHasEx") => parse_args!(context, "std.objectHasEx", args, 3, [ + 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj]; + 1, f: [Val::Str]!!Val::Str, vec![ValType::Str]; + 2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool]; + ], { + Ok(Val::Bool( + obj.fields_visibility() + .into_iter() + .filter(|(_k, v)| *v || inc_hidden) + .any(|(k, _v)| *k == *f), + )) + })?, + ("std", "primitiveEquals") => { + parse_args!(context, "std.primitiveEquals", args, 2, [ + 0, a, vec![]; + 1, b, vec![]; + ], { + Ok(Val::Bool(primitive_equals(&a, &b)?)) + })? + } + // faster + ("std", "equals") => parse_args!(context, "std.equals", args, 2, [ + 0, a, vec![]; + 1, b, vec![]; + ], { + Ok(Val::Bool(equals(&a, &b)?)) + })?, + ("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [ + 0, a: [Val::Num]!!Val::Num, vec![ValType::Num]; + 1, b: [Val::Num]!!Val::Num, vec![ValType::Num]; + ], { + Ok(Val::Num(a % b)) + })?, + ("std", "floor") => parse_args!(context, "std.floor", args, 1, [ + 0, x: [Val::Num]!!Val::Num, vec![ValType::Num]; + ], { + Ok(Val::Num(x.floor())) + })?, + ("std", "log") => parse_args!(context, "std.log", args, 2, [ + 0, n: [Val::Num]!!Val::Num, vec![ValType::Num]; + ], { + Ok(Val::Num(n.ln())) + })?, + ("std", "trace") => parse_args!(context, "std.trace", args, 2, [ + 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; + 1, rest, vec![]; + ], { + eprint!("TRACE:"); + if let Some(loc) = loc { + with_state(|s|{ + let locs = s.map_source_locations(&loc.0, &[loc.1]); + eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line); + }); } - }, - Val::Num(_) => { - let mut err = None; - new_arr.sort_unstable_by(|a, b| { - match (keyF.evaluate_values(context.clone(), &[a.clone()]), keyF.evaluate_values(context.clone(), &[b.clone()])) { - (Ok(Val::Num(a)), Ok(Val::Num(b))) => a.partial_cmp(&b).unwrap(), - (Ok(_a), Ok(_b)) => { - err = Some(RuntimeError("types of all array elements should equal".into()).into()); - Ordering::Equal + eprintln!(" {}", str); + Ok(rest) + })?, + ("std", "pow") => parse_args!(context, "std.modulo", args, 2, [ + 0, x: [Val::Num]!!Val::Num, vec![ValType::Num]; + 1, n: [Val::Num]!!Val::Num, vec![ValType::Num]; + ], { + Ok(Val::Num(x.powf(n))) + })?, + ("std", "extVar") => parse_args!(context, "std.extVar", args, 2, [ + 0, x: [Val::Str]!!Val::Str, vec![ValType::Str]; + ], { + Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else( + || UndefinedExternalVariable(x), + )?) + })?, + ("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [ + 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; + 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; + ], { + Ok(Val::Arr(Rc::new( + arr.iter() + .cloned() + .filter(|e| { + func + .evaluate_values(context.clone(), &[e.clone()]) + .unwrap() + .try_cast_bool("filter predicate") + .unwrap() + }) + .collect(), + ))) + }))?, + // faster + ("std", "foldl") => noinline!(parse_args!(context, "std.foldl", args, 3, [ + 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; + 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; + 2, init, vec![]; + ], { + let mut acc = init; + for i in arr.iter().cloned() { + acc = func.evaluate_values(context.clone(), &[acc, i])?; + } + Ok(acc) + }))?, + // faster + ("std", "foldr") => noinline!(parse_args!(context, "std.foldr", args, 3, [ + 0, func: [Val::Func]!!Val::Func, vec![ValType::Func]; + 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; + 2, init, vec![]; + ], { + let mut acc = init; + for i in arr.iter().rev().cloned() { + acc = func.evaluate_values(context.clone(), &[acc, i])?; + } + Ok(acc) + }))?, + // faster + #[allow(non_snake_case)] + ("std", "sortImpl") => noinline!(parse_args!(context, "std.sort", args, 2, [ + 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; + 1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func]; + ], { + if arr.len() <= 1 { + return Ok(Val::Arr(arr)) + } + let mut new_arr = arr.iter().cloned().collect::>(); + match keyF.evaluate_values(context.clone(), &[new_arr[0].clone()])? { + Val::Str(_) => { + let mut err = None; + new_arr.sort_by_cached_key(|k| { + match keyF.evaluate_values(context.clone(), &[k.clone()]) { + Ok(Val::Str(v)) => v, + Ok(_) => { + err = Some(LocError::new(RuntimeError("types of all array elements should equal".into()))); + "".into() + } + Err(e) => { + err = Some(e); + "".into() + } + } + }); + if let Some(e) = err { + return Err(e); } - (Err(e), _) | (_, Err(e)) => { - err = Some(e); - Ordering::Equal + }, + Val::Num(_) => { + let mut err = None; + new_arr.sort_unstable_by(|a, b| { + match (keyF.evaluate_values(context.clone(), &[a.clone()]), keyF.evaluate_values(context.clone(), &[b.clone()])) { + (Ok(Val::Num(a)), Ok(Val::Num(b))) => a.partial_cmp(&b).unwrap(), + (Ok(_a), Ok(_b)) => { + err = Some(RuntimeError("types of all array elements should equal".into()).into()); + Ordering::Equal + } + (Err(e), _) | (_, Err(e)) => { + err = Some(e); + Ordering::Equal + } + } + }); + if let Some(e) = err { + return Err(e); } - } - }); - if let Some(e) = err { - return Err(e); + }, + _ => throw!(RuntimeError("keys should be number or string".into())) } - }, - _ => throw!(RuntimeError("keys should be number or string".into())) - } - 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, vec![] - ], { - push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{ - Ok(match vals { - Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()), - Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()), - o => Val::Str(format_arr(&str, &[o])?.into()), - }) - }) - })?, - // 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]; - ], { - let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0)); - for i in from as usize..=to as usize { - out.push(Val::Num(i as f64)); - } - Ok(Val::Arr(Rc::new(out))) - })?, - ("std", "char") => parse_args!(context, "std.char", args, 1, [ - 0, n: [Val::Num]!!Val::Num, vec![ValType::Num]; - ], { - let mut out = String::new(); - out.push(std::char::from_u32(n as u32).ok_or_else(|| - 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]; - ], { - Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect()))) - })?, - ("std", "md5") => noinline!(parse_args!(context, "std.md5", args, 1, [ - 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into())) - }))?, - // faster - ("std", "base64") => parse_args!(context, "std.base64", args, 1, [ - 0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str]; - ], { - Ok(Val::Str(match input { - Val::Str(s) => { - base64::encode(s.bytes().collect::>()).into() - }, - Val::Arr(a) => { - base64::encode(a.iter().map(|v| { - Ok(v.clone().try_cast_num("base64 array")? as u8) - }).collect::>>()?).into() - }, - _ => unreachable!() - })) - })?, - // faster - ("std", "join") => noinline!(parse_args!(context, "std.join", args, 2, [ - 0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr]; - 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; - ], { - Ok(match sep { - Val::Arr(joiner_items) => { - let mut out = Vec::new(); + 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, vec![] + ], { + push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{ + Ok(match vals { + Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()), + Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()), + o => Val::Str(format_arr(&str, &[o])?.into()), + }) + }) + })?, + // faster + ("std", "range") => parse_args!(context, "std.range", args, 2, [ + 0, from: [Val::Num]!!Val::Num, vec![ValType::Num]; + 1, to: [Val::Num]!!Val::Num, vec![ValType::Num]; + ], { + let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0)); + for i in from as usize..=to as usize { + out.push(Val::Num(i as f64)); + } + Ok(Val::Arr(Rc::new(out))) + })?, + ("std", "char") => parse_args!(context, "std.char", args, 1, [ + 0, n: [Val::Num]!!Val::Num, vec![ValType::Num]; + ], { + let mut out = String::new(); + out.push(std::char::from_u32(n as u32).ok_or_else(|| + 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]; + ], { + Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect()))) + })?, + ("std", "md5") => noinline!(parse_args!(context, "std.md5", args, 1, [ + 0, str: [Val::Str]!!Val::Str, vec![ValType::Str]; + ], { + Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into())) + }))?, + // faster + ("std", "base64") => parse_args!(context, "std.base64", args, 1, [ + 0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str]; + ], { + Ok(Val::Str(match input { + Val::Str(s) => { + base64::encode(s.bytes().collect::>()).into() + }, + Val::Arr(a) => { + base64::encode(a.iter().map(|v| { + Ok(v.clone().try_cast_num("base64 array")? as u8) + }).collect::>>()?).into() + }, + _ => unreachable!() + })) + })?, + // faster + ("std", "join") => noinline!(parse_args!(context, "std.join", args, 2, [ + 0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr]; + 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; + ], { + Ok(match sep { + Val::Arr(joiner_items) => { + let mut out = Vec::new(); - let mut first = true; - for item in arr.iter().cloned() { - if let Val::Arr(items) = item.unwrap_if_lazy()? { - if !first { - out.reserve(joiner_items.len()); - out.extend(joiner_items.iter().cloned()); + let mut first = true; + for item in arr.iter().cloned() { + if let Val::Arr(items) = item.unwrap_if_lazy()? { + if !first { + out.reserve(joiner_items.len()); + out.extend(joiner_items.iter().cloned()); + } + first = false; + out.reserve(items.len()); + out.extend(items.iter().cloned()); + } else { + throw!(RuntimeError("in std.join all items should be arrays".into())); + } } - first = false; - out.reserve(items.len()); - out.extend(items.iter().cloned()); - } else { - throw!(RuntimeError("in std.join all items should be arrays".into())); - } - } - Val::Arr(Rc::new(out)) - }, - Val::Str(sep) => { - let mut out = String::new(); + Val::Arr(Rc::new(out)) + }, + Val::Str(sep) => { + let mut out = String::new(); - let mut first = true; - for item in arr.iter().cloned() { - if let Val::Str(item) = item.unwrap_if_lazy()? { - if !first { - out += &sep; + let mut first = true; + for item in arr.iter().cloned() { + if let Val::Str(item) = item.unwrap_if_lazy()? { + if !first { + out += &sep; + } + first = false; + out += &item; + } else { + throw!(RuntimeError("in std.join all items should be strings".into())); + } } - first = false; - out += &item; - } else { - throw!(RuntimeError("in std.join all items should be strings".into())); - } - } - Val::Str(out.into()) - }, - _ => unreachable!() + Val::Str(out.into()) + }, + _ => unreachable!() + }) + }))?, + // Faster + ("std", "escapeStringJson") => { + parse_args!(context, "std.escapeStringJson", args, 1, [ + 0, str_: [Val::Str]!!Val::Str, vec![ValType::Str]; + ], { + Ok(Val::Str(escape_string_json(&str_).into())) + })? + } + // Faster + ("std", "manifestJsonEx") => { + parse_args!(context, "std.manifestJsonEx", args, 2, [ + 0, value, vec![]; + 1, indent: [Val::Str]!!Val::Str, vec![ValType::Str]; + ], { + Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions { + padding: &indent, + mtype: ManifestType::Std, + })?.into())) + })? + } + (ns, name) => throw!(IntristicNotFound(ns.into(), name.into())), }) - }))?, - // Faster - ("std", "escapeStringJson") => parse_args!(context, "std.escapeStringJson", args, 1, [ - 0, str_: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Str(escape_string_json(&str_).into())) - })?, - // Faster - ("std", "manifestJsonEx") => parse_args!(context, "std.manifestJsonEx", args, 2, [ - 0, value, vec![]; - 1, indent: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(Val::Str(manifest_json_ex(&value, &indent)?.into())) - })?, - (ns, name) => throw!(IntristicNotFound(ns.into(), name.into())), - }, + }, + )?, Val::Func(f) => { let body = || f.evaluate(context, args, tailstrict); if tailstrict {