difftreelog
feat lazy evaluation of function default params
in: master
Fixes #59
7 files changed
crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -73,6 +73,9 @@
.cloned()
.ok_or(VariableIsNotDefined(name))?)
}
+ pub fn contains_binding(&self, name: IStr) -> bool {
+ self.0.bindings.contains_key(&name)
+ }
pub fn into_future(self, ctx: FutureWrapper<Self>) -> Self {
{
ctx.0.borrow_mut().replace(self);
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -56,7 +56,7 @@
BindingParameterASecondTime(IStr),
#[error("too many args, function has {0}")]
TooManyArgsFunctionHas(usize),
- #[error("founction argument is not passed: {0}")]
+ #[error("function argument is not passed: {0}")]
FunctionParameterNotBoundInCall(IStr),
#[error("external variable is not defined: {0}")]
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth1use crate::{error::Error::*, evaluate, throw, Context, LazyVal, LazyValValue, Result, Val};2use jrsonnet_gc::Trace;3use jrsonnet_interner::IStr;4use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};5use rustc_hash::FxHashMap;6use std::{collections::HashMap, hash::BuildHasherDefault};78const NO_DEFAULT_CONTEXT: &str =9 "no default context set for call with defined default parameter value";1011/// Creates correct [context](Context) for function body evaluation returning error on invalid call.12///13/// ## Parameters14/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)15/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)16/// * `params`: function parameters' definition17/// * `args`: passed function arguments18/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily19pub fn parse_function_call(20 ctx: Context,21 body_ctx: Option<Context>,22 params: &ParamsDesc,23 args: &ArgsDesc,24 tailstrict: bool,25) -> Result<Context> {26 let mut out = HashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());27 let mut positioned_args = vec![None; params.0.len()];28 for (id, arg) in args.iter().enumerate() {29 let idx = if let Some(name) = &arg.0 {30 params31 .iter()32 .position(|p| *p.0 == *name)33 .ok_or_else(|| UnknownFunctionParameter(name.clone()))?34 } else {35 id36 };3738 if idx >= params.len() {39 throw!(TooManyArgsFunctionHas(params.len()));40 }41 if positioned_args[idx].is_some() {42 throw!(BindingParameterASecondTime(params[idx].0.clone()));43 }44 positioned_args[idx] = Some(arg.1.clone());45 }46 // Fill defaults47 for (id, p) in params.iter().enumerate() {48 let (ctx, expr) = if let Some(arg) = &positioned_args[id] {49 (ctx.clone(), arg)50 } else if let Some(default) = &p.1 {51 (body_ctx.clone().expect(NO_DEFAULT_CONTEXT), default)52 } else {53 throw!(FunctionParameterNotBoundInCall(p.0.clone()));54 };55 let val = if tailstrict {56 LazyVal::new_resolved(evaluate(ctx, expr)?)57 } else {58 #[derive(Trace)]59 #[trivially_drop]60 struct EvaluateLazyVal {61 context: Context,62 expr: LocExpr,63 }64 impl LazyValValue for EvaluateLazyVal {65 fn get(self: Box<Self>) -> Result<Val> {66 evaluate(self.context, &self.expr)67 }68 }6970 LazyVal::new(Box::new(EvaluateLazyVal {71 context: ctx.clone(),72 expr: expr.clone(),73 }))74 };75 out.insert(p.0.clone(), val);76 }7778 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))79}8081pub fn parse_function_call_map(82 ctx: Context,83 body_ctx: Option<Context>,84 params: &ParamsDesc,85 args: &HashMap<IStr, Val>,86 tailstrict: bool,87) -> Result<Context> {88 let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());89 let mut positioned_args = vec![None; params.0.len()];90 for (name, val) in args.iter() {91 let idx = params92 .iter()93 .position(|p| *p.0 == **name)94 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;9596 if idx >= params.len() {97 throw!(TooManyArgsFunctionHas(params.len()));98 }99 if positioned_args[idx].is_some() {100 throw!(BindingParameterASecondTime(params[idx].0.clone()));101 }102 positioned_args[idx] = Some(val.clone());103 }104 // Fill defaults105 for (id, p) in params.iter().enumerate() {106 let val = if let Some(arg) = positioned_args[id].take() {107 LazyVal::new_resolved(arg)108 } else if let Some(default) = &p.1 {109 if tailstrict {110 LazyVal::new_resolved(evaluate(111 body_ctx.clone().expect(NO_DEFAULT_CONTEXT),112 default,113 )?)114 } else {115 let body_ctx = body_ctx.clone();116 let default = default.clone();117 #[derive(Trace)]118 #[trivially_drop]119 struct EvaluateLazyVal {120 body_ctx: Option<Context>,121 default: LocExpr,122 }123 impl LazyValValue for EvaluateLazyVal {124 fn get(self: Box<Self>) -> Result<Val> {125 evaluate(126 self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT),127 &self.default,128 )129 }130 }131 LazyVal::new(Box::new(EvaluateLazyVal { body_ctx, default }))132 }133 } else {134 throw!(FunctionParameterNotBoundInCall(p.0.clone()));135 };136 out.insert(p.0.clone(), val);137 }138139 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))140}141142pub fn place_args(143 ctx: Context,144 body_ctx: Option<Context>,145 params: &ParamsDesc,146 args: &[Val],147) -> Result<Context> {148 let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());149 let mut positioned_args = vec![None; params.0.len()];150 for (id, arg) in args.iter().enumerate() {151 if id >= params.len() {152 throw!(TooManyArgsFunctionHas(params.len()));153 }154 positioned_args[id] = Some(arg);155 }156 // Fill defaults157 for (id, p) in params.iter().enumerate() {158 let val = if let Some(arg) = &positioned_args[id] {159 (*arg).clone()160 } else if let Some(default) = &p.1 {161 evaluate(ctx.clone(), default)?162 } else {163 throw!(FunctionParameterNotBoundInCall(p.0.clone()));164 };165 out.insert(p.0.clone(), LazyVal::new_resolved(val));166 }167168 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))169}170171#[macro_export]172macro_rules! parse_args {173 ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [174 $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)?175 ], $handler:block) => {{176 use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType};177178 let args = $args;179 if args.len() > $total_args {180 throw!(TooManyArgsFunctionHas($total_args));181 }182 $(183 if args.len() <= $id {184 throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));185 }186 let $name = &args[$id];187 if $name.0.is_some() {188 if $name.0.as_ref().unwrap() != stringify!($name) {189 throw!(IntrinsicArgumentReorderingIsNotSupportedYet);190 }191 }192 let $name = push_stack_frame(None, || format!("evaluating argument"), || {193 let value = evaluate($ctx.clone(), &$name.1)?;194 $ty.check(&value)?;195 Ok(value)196 })?;197 $(198 let $name = if let $match(v) = $name {199 v200 } else {201 unreachable!();202 };203 )?204 )+205 ($handler as crate::Result<_>)206 }};207}1use crate::{2 error::Error::*, evaluate, evaluate_named, throw, Context, FutureWrapper, LazyVal,3 LazyValValue, Result, Val,4};5use jrsonnet_gc::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};8use rustc_hash::FxHashMap;9use std::{collections::HashMap, hash::BuildHasherDefault};1011const NO_DEFAULT_CONTEXT: &str =12 "no default context set for call with defined default parameter value";1314#[derive(Trace)]15#[trivially_drop]16struct EvaluateLazyVal {17 context: Context,18 expr: LocExpr,19}20impl LazyValValue for EvaluateLazyVal {21 fn get(self: Box<Self>) -> Result<Val> {22 evaluate(self.context, &self.expr)23 }24}2526/// Creates correct [context](Context) for function body evaluation returning error on invalid call.27///28/// ## Parameters29/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)30/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)31/// * `params`: function parameters' definition32/// * `args`: passed function arguments33/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily34pub fn parse_function_call(35 ctx: Context,36 body_ctx: Context,37 params: &ParamsDesc,38 args: &ArgsDesc,39 tailstrict: bool,40) -> Result<Context> {41 let mut passed_args =42 HashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());43 if args.unnamed.len() > params.len() {44 throw!(TooManyArgsFunctionHas(params.len()))45 }4647 let mut filled_args = 0;4849 for (id, arg) in args.unnamed.iter().enumerate() {50 let name = params[id].0.clone();51 passed_args.insert(52 name,53 if tailstrict {54 LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)55 } else {56 LazyVal::new(Box::new(EvaluateLazyVal {57 context: ctx.clone(),58 expr: arg.clone(),59 }))60 },61 );62 filled_args += 1;63 }6465 for (name, value) in args.named.iter() {66 // FIXME: O(n) for arg existence check67 if !params.iter().any(|p| &p.0 == name) {68 throw!(UnknownFunctionParameter((name as &str).to_owned()));69 }70 if passed_args71 .insert(72 name.clone(),73 if tailstrict {74 LazyVal::new_resolved(evaluate(ctx.clone(), value)?)75 } else {76 LazyVal::new(Box::new(EvaluateLazyVal {77 context: ctx.clone(),78 expr: value.clone(),79 }))80 },81 )82 .is_some()83 {84 throw!(BindingParameterASecondTime(name.clone()));85 }86 filled_args += 1;87 }8889 if filled_args < params.len() {90 // Some args are unset, but maybe we have defaults for them91 // Default values should be created in newly created context92 let future_context = FutureWrapper::<Context>::new();93 let mut defaults = HashMap::with_capacity_and_hasher(94 params.len() - filled_args,95 BuildHasherDefault::default(),96 );9798 for param in params.iter().filter(|p| p.1.is_some()) {99 if passed_args.contains_key(¶m.0.clone()) {100 continue;101 }102 #[derive(Trace)]103 #[trivially_drop]104 struct LazyNamedBinding {105 future_context: FutureWrapper<Context>,106 name: IStr,107 value: LocExpr,108 }109 impl LazyValValue for LazyNamedBinding {110 fn get(self: Box<Self>) -> Result<Val> {111 evaluate_named(self.future_context.unwrap(), &self.value, self.name)112 }113 }114 LazyVal::new(Box::new(LazyNamedBinding {115 future_context: future_context.clone(),116 name: param.0.clone(),117 value: param.1.clone().unwrap(),118 }));119120 defaults.insert(121 param.0.clone(),122 LazyVal::new(Box::new(LazyNamedBinding {123 future_context: future_context.clone(),124 name: param.0.clone(),125 value: param.1.clone().unwrap(),126 })),127 );128 filled_args += 1;129 }130131 // Some args still wasn't filled132 if filled_args != params.len() {133 for param in params.iter().skip(args.unnamed.len()) {134 if !args.named.iter().any(|a| a.0 == param.0) {135 throw!(FunctionParameterNotBoundInCall(param.0.clone()));136 }137 }138 unreachable!();139 }140141 Ok(body_ctx142 .extend(passed_args, None, None, None)143 .extend_bound(defaults)144 .into_future(future_context))145 } else {146 let body_ctx = body_ctx.extend(passed_args, None, None, None);147 Ok(body_ctx)148 }149}150151pub fn parse_function_call_map(152 ctx: Context,153 body_ctx: Option<Context>,154 params: &ParamsDesc,155 args: &HashMap<IStr, Val>,156 tailstrict: bool,157) -> Result<Context> {158 let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());159 let mut positioned_args = vec![None; params.0.len()];160 for (name, val) in args.iter() {161 let idx = params162 .iter()163 .position(|p| *p.0 == **name)164 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;165166 if idx >= params.len() {167 throw!(TooManyArgsFunctionHas(params.len()));168 }169 if positioned_args[idx].is_some() {170 throw!(BindingParameterASecondTime(params[idx].0.clone()));171 }172 positioned_args[idx] = Some(val.clone());173 }174 // Fill defaults175 for (id, p) in params.iter().enumerate() {176 let val = if let Some(arg) = positioned_args[id].take() {177 LazyVal::new_resolved(arg)178 } else if let Some(default) = &p.1 {179 if tailstrict {180 LazyVal::new_resolved(evaluate(181 body_ctx.clone().expect(NO_DEFAULT_CONTEXT),182 default,183 )?)184 } else {185 let body_ctx = body_ctx.clone();186 let default = default.clone();187 #[derive(Trace)]188 #[trivially_drop]189 struct EvaluateLazyVal {190 body_ctx: Option<Context>,191 default: LocExpr,192 }193 impl LazyValValue for EvaluateLazyVal {194 fn get(self: Box<Self>) -> Result<Val> {195 evaluate(196 self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT),197 &self.default,198 )199 }200 }201 LazyVal::new(Box::new(EvaluateLazyVal { body_ctx, default }))202 }203 } else {204 throw!(FunctionParameterNotBoundInCall(p.0.clone()));205 };206 out.insert(p.0.clone(), val);207 }208209 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))210}211212pub fn place_args(213 ctx: Context,214 body_ctx: Option<Context>,215 params: &ParamsDesc,216 args: &[Val],217) -> Result<Context> {218 let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());219 let mut positioned_args = vec![None; params.0.len()];220 for (id, arg) in args.iter().enumerate() {221 if id >= params.len() {222 throw!(TooManyArgsFunctionHas(params.len()));223 }224 positioned_args[id] = Some(arg);225 }226 // Fill defaults227 for (id, p) in params.iter().enumerate() {228 let val = if let Some(arg) = &positioned_args[id] {229 (*arg).clone()230 } else if let Some(default) = &p.1 {231 evaluate(ctx.clone(), default)?232 } else {233 throw!(FunctionParameterNotBoundInCall(p.0.clone()));234 };235 out.insert(p.0.clone(), LazyVal::new_resolved(val));236 }237238 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))239}240241#[macro_export]242macro_rules! parse_args {243 ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [244 $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)?245 ], $handler:block) => {{246 use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType};247248 let args = $args;249 if args.unnamed.len() + args.named.len() > $total_args {250 throw!(TooManyArgsFunctionHas($total_args));251 }252 $(253 if args.unnamed.len() + args.named.len() <= $id {254 throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));255 }256 // Is named257 let $name = if $id >= $args.unnamed.len() {258 let named = &args.named[$id - $args.unnamed.len()];259 if &named.0 != stringify!($name) {260 throw!(IntrinsicArgumentReorderingIsNotSupportedYet);261 }262 &named.1263 } else {264 &$args.unnamed[$id]265 };266 let $name = push_stack_frame(None, || format!("evaluating argument"), || {267 let value = evaluate($ctx.clone(), &$name)?;268 $ty.check(&value)?;269 Ok(value)270 })?;271 $(272 let $name = if let $match(v) = $name {273 v274 } else {275 unreachable!();276 };277 )?278 )+279 ($handler as crate::Result<_>)280 }};281}crates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -29,6 +29,16 @@
.get(key)
.or_else(|| self.0.parent.as_ref().and_then(|p| p.get(key)))
}
+
+ pub fn contains_key(&self, key: &IStr) -> bool {
+ (self.0).current.contains_key(key)
+ || self
+ .0
+ .parent
+ .as_ref()
+ .map(|p| p.contains_key(key))
+ .unwrap_or(false)
+ }
}
impl Clone for LayeredHashMap {
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -11,7 +11,7 @@
};
use jrsonnet_gc::{Gc, GcCell, Trace};
use jrsonnet_interner::IStr;
-use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};
+use jrsonnet_parser::{el, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};
use jrsonnet_types::ValType;
use std::{collections::HashMap, fmt::Debug, rc::Rc};
@@ -127,7 +127,7 @@
Self::Normal(func) => {
let ctx = parse_function_call(
call_ctx,
- Some(func.ctx.clone()),
+ func.ctx.clone(),
&func.params,
args,
tailstrict,
@@ -136,7 +136,8 @@
}
Self::Intrinsic(name) => call_builtin(call_ctx, loc, name, args),
Self::NativeExt(_name, handler) => {
- let args = parse_function_call(call_ctx, None, &handler.params, args, true)?;
+ let args =
+ parse_function_call(call_ctx, Context::new(), &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()?);
@@ -554,17 +555,17 @@
el!(Expr::Var("std".into())),
el!(Expr::Str("manifestYamlDoc".into()))
)),
- ArgsDesc(vec![
- Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),
- Arg(
- None,
+ ArgsDesc::new(
+ vec![
+ el!(Expr::Var("__tmp__to_json__".into())),
el!(Expr::Literal(if padding != 0 {
LiteralType::True
} else {
LiteralType::False
- }))
- )
- ]),
+ })),
+ ],
+ vec![]
+ ),
false
)),
)?
crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -194,18 +194,13 @@
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[derive(Debug, PartialEq, Trace)]
#[trivially_drop]
-pub struct Arg(pub Option<String>, pub LocExpr);
-
-#[cfg_attr(feature = "serialize", derive(Serialize))]
-#[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq, Trace)]
-#[trivially_drop]
-pub struct ArgsDesc(pub Vec<Arg>);
-
-impl Deref for ArgsDesc {
- type Target = Vec<Arg>;
- fn deref(&self) -> &Self::Target {
- &self.0
+pub struct ArgsDesc {
+ pub unnamed: Vec<LocExpr>,
+ pub named: Vec<(IStr, LocExpr)>,
+}
+impl ArgsDesc {
+ pub fn new(unnamed: Vec<LocExpr>, named: Vec<(IStr, LocExpr)>) -> Self {
+ Self { unnamed, named }
}
}
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -7,6 +7,7 @@
};
mod expr;
pub use expr::*;
+pub use jrsonnet_interner::IStr;
pub use peg;
pub struct ParserSettings {
@@ -70,19 +71,29 @@
}
/ { expr::ParamsDesc(Rc::new(Vec::new())) }
- pub rule arg(s: &ParserSettings) -> expr::Arg
- = name:$(id()) _ "=" _ expr:expr(s) {expr::Arg(Some(name.into()), expr)}
- / expr:expr(s) {expr::Arg(None, expr)}
+ pub rule arg(s: &ParserSettings) -> (Option<IStr>, LocExpr)
+ = quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} }
+ / expected!("<argument>")
+
pub rule args(s: &ParserSettings) -> expr::ArgsDesc
- = args:arg(s) ** comma() comma()? {
+ = 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 named_started = false;
- for arg in &args {
- named_started = named_started || arg.0.is_some();
- assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");
+ for (name, value) in args {
+ if let Some(name) = name {
+ named_started = true;
+ named.push((name, value));
+ } else {
+ if named_started {
+ return Err("<named argument>")
+ }
+ unnamed.push(value);
+ }
}
- expr::ArgsDesc(args)
+ Ok(expr::ArgsDesc::new(unnamed, named))
}
- / { expr::ArgsDesc(Vec::new()) }
pub rule bind(s: &ParserSettings) -> expr::BindSpec
= name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}
@@ -493,7 +504,7 @@
el!(ArrComp(
el!(Apply(
el!(Index(el!(Var("std".into())), el!(Str("deepJoin".into())))),
- ArgsDesc(vec![Arg(None, el!(Var("x".into())))]),
+ ArgsDesc::new(vec![el!(Var("x".into()))], vec![]),
false,
)),
vec![CompSpec::ForSpec(ForSpecData(