--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -19,7 +19,7 @@ destructure::evaluate_dest, error::{ErrorKind::*, suggest_object_fields}, evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op}, - function::{CallLocation, FuncDesc, FuncVal}, + function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal}, gc::WithCapacityExt as _, in_frame, typed::{FromUntyped, IntoUntyped as _, Typed}, @@ -430,11 +430,25 @@ let value = evaluate(ctx.clone(), value)?; Ok(match value { Val::Func(f) => { - let body = || f.evaluate(ctx, loc, args, tailstrict); + let name = f.name(); + let prepare = PreparedFuncVal::new(f, args.unnamed.len(), &args.names)?; + let unnamed = args + .unnamed + .iter() + .cloned() + .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict)) + .collect::>>()?; + let named = args + .values + .iter() + .cloned() + .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict)) + .collect::>>()?; + let body = || prepare.call(loc, &unnamed, &named); if tailstrict { body()? } else { - in_frame(loc, || format!("function <{}> call", f.name()), body)? + in_frame(loc, || format!("function <{name}> call"), body)? } } v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())), @@ -479,6 +493,13 @@ }) } +pub fn evaluate_thunk(ctx: Context, expr: Rc, tailstrict: bool) -> Result> { + Ok(if tailstrict { + Thunk::evaluated(evaluate(ctx, &expr)?) + } else { + Thunk!(move || { evaluate(ctx, &expr) }) + }) +} #[allow(clippy::too_many_lines)] pub fn evaluate(ctx: Context, expr: &Expr) -> Result { use Expr::*; --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -3,12 +3,12 @@ use educe::Educe; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_ir::{ArgsDesc, Destruct, Expr, ExprParams, Span}; +use jrsonnet_ir::{Destruct, Expr, ExprParams, Span}; pub use jrsonnet_macros::builtin; use self::{ builtin::Builtin, - parse::{parse_builtin_call, parse_default_function_call, parse_function_call}, + parse::parse_default_function_call, prepared::{PreparedCall, parse_prepared_builtin_call, parse_prepared_function_call}, }; use crate::{ @@ -22,7 +22,7 @@ pub use jrsonnet_ir::function::*; pub use native::NativeFn; -pub use prepared::PreparedFuncVal; +pub(crate) use prepared::PreparedFuncVal; /// Function callsite location. /// Either from other jsonnet code, specified by expression location, or from native (without location). @@ -77,16 +77,6 @@ parse_default_function_call(self.ctx.clone(), &self.params) } - /// Create context, with which body code will run - pub(crate) fn call_body_context( - &self, - call_ctx: Context, - args: &ArgsDesc, - tailstrict: bool, - ) -> Result { - parse_function_call(call_ctx, self.ctx.clone(), &self.params, args, tailstrict) - } - pub fn evaluate_trivial(&self) -> Option { evaluate_trivial(&self.body) } @@ -137,27 +127,6 @@ match self { Self::Normal(normal) => normal.name.clone(), Self::Builtin(builtin) => builtin.name().into(), - } - } - /// Call function using arguments evaluated in specified `call_ctx` [`Context`]. - /// - /// If `tailstrict` is specified - then arguments will be evaluated before being passed to function body. - pub fn evaluate( - &self, - call_ctx: Context, - loc: CallLocation<'_>, - args: &ArgsDesc, - tailstrict: bool, - ) -> Result { - match self { - Self::Normal(func) => { - let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?; - evaluate(body_ctx, &func.body) - } - Self::Builtin(b) => { - let args = parse_builtin_call(call_ctx, b.params(), args, tailstrict)?; - b.call(loc, &args) - } } } --- a/crates/jrsonnet-evaluator/src/function/native.rs +++ b/crates/jrsonnet-evaluator/src/function/native.rs @@ -39,12 +39,19 @@ impl<$($gen,)* O> FromUntyped for NativeFn<($($gen,)* O,)> { fn from_untyped(untyped: Val) -> Result { let func = FuncVal::from_untyped(untyped)?; + Self::try_from(func) + } + } + impl<$($gen,)* O> TryFrom for NativeFn<($($gen,)* O,)> { + type Error = crate::Error; + fn try_from(v: FuncVal) -> Result { Ok(Self( - PreparedFuncVal::new(func, $i, &[])?, + PreparedFuncVal::new(v, $i, &[])?, PhantomData, )) } } + }; ($i:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => { impl_native_desc!($i; $($cur)*); --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -1,215 +1,13 @@ -use std::rc::Rc; - -use jrsonnet_ir::{ - ArgsDesc, Expr, ExprParams, - function::{FunctionSignature, ParamName}, -}; +use jrsonnet_ir::ExprParams; use rustc_hash::FxHashMap; use crate::{ - Context, Pending, Thunk, Val, bail, + Context, Thunk, destructure::destruct, error::{ErrorKind::*, Result}, - evaluate, evaluate_named_param, + evaluate_named_param, gc::WithCapacityExt as _, }; - -fn eval_arg(ctx: Context, arg: &Rc, tailstrict: bool) -> Result> { - if tailstrict { - Ok(Thunk::evaluated(evaluate(ctx, arg)?)) - } else { - let arg = arg.clone(); - Ok(Thunk!(move || evaluate(ctx, &arg))) - } -} - -/// Creates correct [context](Context) for function body evaluation returning error on invalid call. -/// -/// ## Parameters -/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set) -/// * `body_ctx`: used for default parameter values' execution and for body execution (if set) -/// * `params`: function parameters' definition -/// * `args`: passed function arguments -/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily -pub(crate) fn parse_function_call( - ctx: Context, - body_ctx: Context, - params: &ExprParams, - args: &ArgsDesc, - tailstrict: bool, -) -> Result { - let mut passed_args = FxHashMap::with_capacity(params.binds_len()); - if args.unnamed.len() > params.signature.len() { - bail!(TooManyArgsFunctionHas( - params.signature.len(), - params.signature.clone(), - )) - } - - let mut filled_named = 0; - let mut filled_positionals = 0; - - for (id, arg) in args.unnamed.iter().enumerate() { - destruct( - ¶ms.exprs[id].destruct, - eval_arg(ctx.clone(), arg, tailstrict)?, - Pending::new_filled(ctx.clone()), - &mut passed_args, - )?; - filled_positionals += 1; - } - - for (name, value) in &args.named { - // FIXME: O(n) for arg existence check - if !params.exprs.iter().any(|p| &p.destruct.name() == name) { - bail!(UnknownFunctionParameter(name.clone())); - } - if passed_args - .insert(name.clone(), eval_arg(ctx.clone(), value, tailstrict)?) - .is_some() - { - bail!(BindingParameterASecondTime(name.clone())); - } - filled_named += 1; - } - - if filled_named + filled_positionals < params.len() { - // Some args are unset, but maybe we have defaults for them - // Default values should be created in newly created context - let fctx = Context::new_future(); - let mut defaults = - FxHashMap::with_capacity(params.binds_len() - filled_named - filled_positionals); - - for (idx, into, default) in params - .exprs - .iter() - .enumerate() - .filter_map(|(i, p)| Some((i, &p.destruct, p.default.as_ref()?))) - { - if let ParamName::Named(name) = into.name() { - if passed_args.contains_key(&name) { - continue; - } - } else if idx < filled_positionals { - continue; - } - - destruct( - into, - { - let ctx = fctx.clone(); - let name = into.name(); - let value = default.clone(); - Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name)) - }, - fctx.clone(), - &mut defaults, - )?; - if into.name().is_named() { - filled_named += 1; - } else { - filled_positionals += 1; - } - } - - // Some args still weren't filled - if filled_named + filled_positionals != params.len() { - for param in params.exprs.iter().skip(args.unnamed.len()) { - let mut found = false; - for (name, _) in &args.named { - if ¶m.destruct.name() == name { - found = true; - } - } - if !found { - bail!(FunctionParameterNotBoundInCall( - param.destruct.name(), - params.signature.clone() - )); - } - } - unreachable!(); - } - - Ok(body_ctx - .extend_bindings(passed_args) - .extend_bindings(defaults) - .into_future(fctx)) - } else { - let body_ctx = body_ctx.extend_bindings(passed_args); - Ok(body_ctx) - } -} - -/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead -/// -/// ## Parameters -/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set) -/// * `params`: function parameters' definition -/// * `args`: passed function arguments -/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily -pub fn parse_builtin_call( - ctx: Context, - params: FunctionSignature, - args: &ArgsDesc, - tailstrict: bool, -) -> Result>>> { - let mut passed_args: Vec>> = vec![None; params.len()]; - if args.unnamed.len() > params.len() { - bail!(TooManyArgsFunctionHas(params.len(), params,)) - } - - let mut filled_args = 0; - - for (id, arg) in args.unnamed.iter().enumerate() { - passed_args[id] = Some(eval_arg(ctx.clone(), arg, tailstrict)?); - filled_args += 1; - } - - for (name, arg) in &args.named { - // FIXME: O(n) for arg existence check - let id = params - .iter() - .position(|p| p.name() == name) - .ok_or_else(|| UnknownFunctionParameter(name.clone()))?; - if passed_args[id] - .replace(eval_arg(ctx.clone(), arg, tailstrict)?) - .is_some() - { - bail!(BindingParameterASecondTime(name.clone())); - } - filled_args += 1; - } - - if filled_args < params.len() { - for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default()) { - if passed_args[id].is_some() { - continue; - } - filled_args += 1; - } - - // Some args still wasn't filled - if filled_args != params.len() { - for param in params.iter().skip(args.unnamed.len()) { - let mut found = false; - for (name, _) in &args.named { - if param.name() == name { - found = true; - } - } - if !found { - bail!(FunctionParameterNotBoundInCall( - param.name().clone(), - params, - )); - } - } - unreachable!(); - } - } - Ok(passed_args) -} /// Creates Context, which has all argument default values applied /// and with unbound values causing error to be returned --- a/crates/jrsonnet-ir-parser/src/lib.rs +++ b/crates/jrsonnet-ir-parser/src/lib.rs @@ -416,10 +416,11 @@ fn args(p: &mut Parser<'_>) -> Result { if p.at(T![')']) { - return Ok(ArgsDesc::new(Vec::new(), Vec::new())); + return Ok(ArgsDesc::new(Vec::new(), Vec::new(), Vec::new())); } let mut unnamed = Vec::new(); - let mut named = Vec::new(); + let mut names = Vec::new(); + let mut values = Vec::new(); let mut named_started = false; loop { let is_named = p.at(SyntaxKind::IDENT) && { @@ -430,7 +431,9 @@ let name: IStr = ident(p)?; p.eat(T![=])?; let value = Rc::new(expr(p)?); - named.push((name, value)); + + names.push(name); + values.push(value); named_started = true; } else { if named_started { @@ -445,7 +448,7 @@ break; } } - Ok(ArgsDesc::new(unnamed, named)) + Ok(ArgsDesc::new(unnamed, names, values)) } fn bind(p: &mut Parser<'_>) -> Result { --- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap +++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap @@ -66,12 +66,12 @@ 2.0, ), ], - named: [ - ( - "y", - Num( - 3.0, - ), + names: [ + "y", + ], + values: [ + Num( + 3.0, ), ], } from virtual::26-34, --- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snap +++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snap @@ -23,7 +23,8 @@ 2.0, ), ], - named: [], + names: [], + values: [], } from virtual::8-11, false, ), --- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snap +++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snap @@ -26,7 +26,8 @@ "x" from virtual::16-17, ), ], - named: [], + names: [], + values: [], } from virtual::15-18, false, ), --- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snap +++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snap @@ -24,7 +24,8 @@ "null_fields" from virtual::20-31, ), ], - named: [], + names: [], + values: [], } from virtual::16-32, false, ), --- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snap +++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snap @@ -28,7 +28,8 @@ 2.0, ), ], - named: [], + names: [], + values: [], } from virtual::15-18, false, ), @@ -52,7 +53,8 @@ 2.0, ), ], - named: [], + names: [], + values: [], } from virtual::28-31, false, ), --- a/crates/jrsonnet-ir/src/expr.rs +++ b/crates/jrsonnet-ir/src/expr.rs @@ -195,11 +195,16 @@ #[derive(Debug, PartialEq, Acyclic)] pub struct ArgsDesc { pub unnamed: Vec>, - pub named: Vec<(IStr, Rc)>, + pub names: Vec, + pub values: Vec>, } impl ArgsDesc { - pub fn new(unnamed: Vec>, named: Vec<(IStr, Rc)>) -> Self { - Self { unnamed, named } + pub fn new(unnamed: Vec>, names: Vec, values: Vec>) -> Self { + Self { + unnamed, + names, + values, + } } } --- a/crates/jrsonnet-ir/src/visit.rs +++ b/crates/jrsonnet-ir/src/visit.rs @@ -216,11 +216,15 @@ } Expr::Apply(expr, spanned, _) => { v.visit_expr(expr); - let ArgsDesc { unnamed, named } = &**spanned; + let ArgsDesc { + unnamed, + names: _, + values, + } = &**spanned; for unnamed in unnamed { v.visit_expr(unnamed); } - for (_name, named) in named { + for named in values { v.visit_expr(named); } } --- a/crates/jrsonnet-peg-parser/src/lib.rs +++ b/crates/jrsonnet-peg-parser/src/lib.rs @@ -73,12 +73,14 @@ = args:arg(s)**comma() comma()? {? let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count(); let mut unnamed = Vec::with_capacity(unnamed_count); - let mut named = Vec::with_capacity(args.len() - unnamed_count); + let mut names = Vec::with_capacity(args.len() - unnamed_count); + let mut values = Vec::with_capacity(args.len() - unnamed_count); let mut named_started = false; for (name, value) in args { if let Some(name) = name { named_started = true; - named.push((name, value)); + names.push(name); + values.push(value); } else { if named_started { return Err("") @@ -86,7 +88,7 @@ unnamed.push(value); } } - Ok(ArgsDesc::new(unnamed, named)) + Ok(ArgsDesc{unnamed, names, values}) } pub rule destruct_rest() -> DestructRest --- a/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@array_comp.jsonnet.snap +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@array_comp.jsonnet.snap @@ -26,7 +26,8 @@ "x" from virtual::16-17, ), ], - named: [], + names: [], + values: [], } from virtual::15-18, false, ), --- a/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@reserved.jsonnet.snap +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@reserved.jsonnet.snap @@ -24,7 +24,8 @@ "null_fields" from virtual::20-31, ), ], - named: [], + names: [], + values: [], } from virtual::16-32, false, ), --- a/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@suffix.jsonnet.snap +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@suffix.jsonnet.snap @@ -28,7 +28,8 @@ 2.0, ), ], - named: [], + names: [], + values: [], } from virtual::15-18, false, ), @@ -52,7 +53,8 @@ 2.0, ), ], - named: [], + names: [], + values: [], } from virtual::28-31, false, ), --- a/crates/jrsonnet-stdlib/src/keyf.rs +++ b/crates/jrsonnet-stdlib/src/keyf.rs @@ -1,14 +1,16 @@ use jrsonnet_evaluator::{ Error, Result, Thunk, Val, - function::{CallLocation, FuncVal, PreparedFuncVal}, + function::{FuncVal, NativeFn}, typed::{ComplexValType, FromUntyped, Typed, ValType}, }; +type PreparedKeyF = NativeFn!((Thunk) -> Val); + #[derive(Default, Clone)] pub enum KeyF { #[default] Identity, - Prepared(PreparedFuncVal), + Prepared(PreparedKeyF), PrepareFailure(Error), } impl KeyF { @@ -19,13 +21,13 @@ if val.is_identity() { Self::Identity } else { - PreparedFuncVal::new(val, 1, &[]).map_or_else(Self::PrepareFailure, Self::Prepared) + PreparedKeyF::try_from(val).map_or_else(Self::PrepareFailure, Self::Prepared) } } pub fn eval(&self, val: impl Into>) -> Result { match self { KeyF::Identity => val.into().evaluate(), - KeyF::Prepared(p) => p.call(CallLocation::native(), &[val.into()], &[]), + KeyF::Prepared(p) => p.call(val.into()), KeyF::PrepareFailure(e) => Err(e.clone()), } }