--- 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 == *"format" || **name == *"range" + **name == *"sortImpl" || **name == *"format" || **name == *"range" || **name == *"reverse" ) }) .collect(), --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -117,9 +117,7 @@ } buf.push('}'); } - Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => { - throw!(RuntimeError("tried to manifest function".into())) - } + Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())), Val::Lazy(_) => unreachable!(), }; Ok(()) --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,5 +1,354 @@ +use crate::{ + equals, + error::{Error::*, Result}, + evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val, + ValType, +}; +use format::{format_arr, format_obj}; +use jrsonnet_parser::{ArgsDesc, ExprLocation}; +use manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType}; +use std::{path::PathBuf, rc::Rc}; + pub mod stdlib; pub use stdlib::*; pub mod format; pub mod manifest; +pub mod sort; + +pub fn call_builtin( + context: Context, + loc: &Option, + ns: &str, + name: &str, + args: &ArgsDesc, +) -> Result { + Ok(match (ns, &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") => 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") => 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, 1, [ + 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", "native") => parse_args!(context, "std.native", args, 1, [ + 0, x: [Val::Str]!!Val::Str, vec![ValType::Str]; + ], { + Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(FuncVal::NativeExt(x.clone(), v))).ok_or_else( + || UndefinedExternalFunction(x), + )?) + })?, + ("std", "filter") => 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") => 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") => 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") => 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)) + } + Ok(Val::Arr(sort::sort(context, arr, keyF)?)) + })?, + // 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") => 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") => 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()); + } + 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(); + + 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())); + } + } + + 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())) + })?, + // Faster + ("std", "reverse") => parse_args!(context, "std.reverse", args, 1, [ + 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr]; + ], { + let mut marr = arr; + Rc::make_mut(&mut marr).reverse(); + Ok(Val::Arr(marr)) + })?, + ("std", "id") => parse_args!(context, "std.id", args, 1, [ + 0, v, vec![]; + ], { + Ok(v) + })?, + (ns, name) => throw!(IntristicNotFound(ns.into(), name.into())), + }) +} --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -1,4 +1,7 @@ -use crate::{builtin::format::FormatError, ValType}; +use crate::{ + builtin::{format::FormatError, sort::SortError}, + ValType, +}; use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType}; use std::{path::PathBuf, rc::Rc}; @@ -69,6 +72,7 @@ InvalidUnicodeCodepointGot(u32), Format(FormatError), + Sort(SortError), } impl From for LocError { fn from(e: Error) -> Self { --- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -1,13 +1,7 @@ use crate::{ - builtin::{ - format::{format_arr, format_obj}, - manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType}, - }, - context_creator, equals, - error::Error::*, - future_wrapper, lazy_val, parse_args, parse_function_call, primitive_equals, push, throw, - with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember, - ObjValue, Result, Val, ValType, + context_creator, error::Error::*, future_wrapper, lazy_val, push, throw, with_state, Context, + ContextCreator, FuncDesc, FuncVal, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val, + ValType, }; use closure::closure; use jrsonnet_parser::{ @@ -15,7 +9,7 @@ ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType, Visibility, }; -use std::{cmp::Ordering, collections::HashMap, path::PathBuf, rc::Rc}; +use std::{collections::HashMap, rc::Rc}; pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc, LazyBinding) { let b = b.clone(); @@ -51,12 +45,12 @@ } pub fn evaluate_method(ctx: Context, name: Rc, params: ParamsDesc, body: LocExpr) -> Val { - Val::Func(Rc::new(FuncDesc { + Val::Func(FuncVal::Normal(Rc::new(FuncDesc { name, ctx, params, body, - })) + }))) } pub fn evaluate_field_name( @@ -389,21 +383,6 @@ }) } -/// Extracts code block and disables inlining for them -/// Fixes WASM to java bytecode compilation failing because of very large method -#[cfg(feature = "unstable")] -macro_rules! noinline { - ($e:expr) => { - (#![inline(never)] move || $e)() - }; -} -#[cfg(not(feature = "unstable"))] -macro_rules! noinline { - ($e:expr) => { - (move || $e)() - }; -} - pub fn evaluate_apply( context: Context, value: &LocExpr, @@ -414,397 +393,12 @@ let lazy = evaluate(context.clone(), value)?; let value = lazy.unwrap_if_lazy()?; Ok(match value { - 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!(), - }) - })?, - // 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, 1, [ - 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", "native") => parse_args!(context, "std.native", args, 1, [ - 0, x: [Val::Str]!!Val::Str, vec![ValType::Str]; - ], { - Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::NativeExt(x.clone(), v)).ok_or_else( - || UndefinedExternalFunction(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); - } - }, - 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); - } - }, - _ => 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]; - 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()); - } - 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(); - - 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())); - } - } - - 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())), - }) - }, - )?, - Val::NativeExt(n, f) => push( - loc, - || format!("native <{}> call", n), - || { - let args = parse_function_call(context, None, &f.params, args, true)?; - let mut out_args = Vec::with_capacity(f.params.len()); - for p in f.params.0.iter() { - out_args.push(args.binding(p.0.clone())?.evaluate()?); - } - Ok(f.call(&out_args)?) - }, - )?, Val::Func(f) => { - let body = || f.evaluate(context, args, tailstrict); + let body = || f.evaluate(context, loc, args, tailstrict); if tailstrict { body()? } else { - push(loc, || format!("function <{}> call", f.name), body)? + push(loc, || format!("function <{}> call", f.name()), body)? } } v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type()?)), @@ -874,7 +468,7 @@ } else if let Some(Val::Str(n)) = v.get("__intristic_namespace__".into())? { - Ok(Val::Intristic(n, s)) + Ok(Val::Func(FuncVal::Intristic(n, s))) } else { throw!(NoSuchField(s)) } --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -40,9 +40,7 @@ } Value::Object(out) } - Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => { - throw!(RuntimeError("tried to manifest function".into())) - } + Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())), }) } } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,12 +1,15 @@ use crate::{ - builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType}, + builtin::{ + call_builtin, + manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType}, + }, error::Error::*, evaluate, function::{parse_function_call, parse_function_call_map, place_args}, native::NativeCallback, throw, with_state, Context, ObjValue, Result, }; -use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc}; +use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LocExpr, ParamsDesc}; use std::{ cell::RefCell, collections::HashMap, @@ -67,18 +70,66 @@ pub params: ParamsDesc, pub body: LocExpr, } -impl FuncDesc { - /// This function is always inlined to make tailstrict work - pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result { - let ctx = parse_function_call( - call_ctx, - Some(self.ctx.clone()), - &self.params, - args, - tailstrict, - )?; - evaluate(ctx, &self.body) + +#[derive(Debug, Clone)] +pub enum FuncVal { + /// Plain function implemented in jsonnet + Normal(Rc), + /// Standard library function + Intristic(Rc, Rc), + /// Library functions implemented in native + NativeExt(Rc, Rc), +} +impl PartialEq for FuncVal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (FuncVal::Normal(a), FuncVal::Normal(b)) => a == b, + (FuncVal::Intristic(ans, an), FuncVal::Intristic(bns, bn)) => ans == bns && an == bn, + (FuncVal::NativeExt(an, _), FuncVal::NativeExt(bn, _)) => an == bn, + (..) => false, + } + } +} +impl FuncVal { + pub fn is_ident(&self) -> bool { + matches!(&self, FuncVal::Intristic(ns, n) if ns as &str == "std" && n as &str == "id") + } + pub fn name(&self) -> Rc { + match self { + FuncVal::Normal(normal) => normal.name.clone(), + FuncVal::Intristic(ns, name) => format!("intristic.{}.{}", ns, name).into(), + FuncVal::NativeExt(n, _) => format!("native.{}", n).into(), + } } + pub fn evaluate( + &self, + call_ctx: Context, + loc: &Option, + args: &ArgsDesc, + tailstrict: bool, + ) -> Result { + match self { + FuncVal::Normal(func) => { + let ctx = parse_function_call( + call_ctx, + Some(func.ctx.clone()), + &func.params, + args, + tailstrict, + )?; + evaluate(ctx, &func.body) + } + FuncVal::Intristic(ns, name) => call_builtin(call_ctx, loc, &ns, &name, args), + FuncVal::NativeExt(_name, handler) => { + let args = parse_function_call(call_ctx, None, &handler.params, args, true)?; + let mut out_args = Vec::with_capacity(handler.params.len()); + for p in handler.params.0.iter() { + out_args.push(args.binding(p.0.clone())?.evaluate()?); + } + Ok(handler.call(&out_args)?) + } + } + } pub fn evaluate_map( &self, @@ -86,19 +137,31 @@ args: &HashMap, Val>, tailstrict: bool, ) -> Result { - let ctx = parse_function_call_map( - call_ctx, - Some(self.ctx.clone()), - &self.params, - args, - tailstrict, - )?; - evaluate(ctx, &self.body) + match self { + FuncVal::Normal(func) => { + let ctx = parse_function_call_map( + call_ctx, + Some(func.ctx.clone()), + &func.params, + args, + tailstrict, + )?; + evaluate(ctx, &func.body) + } + FuncVal::Intristic(_, _) => todo!(), + FuncVal::NativeExt(_, _) => todo!(), + } } pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result { - let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?; - evaluate(ctx, &self.body) + match self { + FuncVal::Normal(func) => { + let ctx = place_args(call_ctx, Some(func.ctx.clone()), &func.params, args)?; + evaluate(ctx, &func.body) + } + FuncVal::Intristic(_, _) => todo!(), + FuncVal::NativeExt(_, _) => todo!(), + } } } @@ -149,11 +212,7 @@ Lazy(LazyVal), Arr(Rc>), Obj(ObjValue), - Func(Rc), - - // Library functions implemented in native - Intristic(Rc, Rc), - NativeExt(Rc, Rc), + Func(FuncVal), } macro_rules! matches_unwrap { ($e: expr, $p: pat, $r: expr) => { @@ -193,6 +252,12 @@ self.assert_type(context, ValType::Num)?; Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v)) } + pub fn inplace_unwrap(&mut self) -> Result<()> { + while let Val::Lazy(lazy) = self { + *self = lazy.evaluate()?; + } + Ok(()) + } pub fn unwrap_if_lazy(&self) -> Result { Ok(if let Val::Lazy(v) = self { v.evaluate()?.unwrap_if_lazy()? @@ -208,7 +273,7 @@ Val::Obj(..) => ValType::Obj, Val::Bool(_) => ValType::Bool, Val::Null => ValType::Null, - Val::Func(..) | Val::Intristic(_, _) | Val::NativeExt(_, _) => ValType::Func, + Val::Func(..) => ValType::Func, Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?, }) } @@ -374,7 +439,7 @@ } fn is_function_like(val: &Val) -> bool { - matches!(val, Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _)) + matches!(val, Val::Func(_)) } /// Implements std.primitiveEquals builtin --- a/crates/jrsonnet-stdlib/src/std.jsonnet +++ b/crates/jrsonnet-stdlib/src/std.jsonnet @@ -2,7 +2,7 @@ __intristic_namespace__:: 'std', local std = self, - local id = function(x) x, + local id = std.id, isString(v):: std.type(v) == 'string', isNumber(v):: std.type(v) == 'number',