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};1use crate::{2 error::Error::*, evaluate, evaluate_named, throw, Context, FutureWrapper, LazyVal,3 LazyValValue, Result, Val,4};2use jrsonnet_gc::Trace;5use jrsonnet_gc::Trace;3use jrsonnet_interner::IStr;6use jrsonnet_interner::IStr;8const NO_DEFAULT_CONTEXT: &str =11const NO_DEFAULT_CONTEXT: &str =9 "no default context set for call with defined default parameter value";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}102511/// Creates correct [context](Context) for function body evaluation returning error on invalid call.26/// Creates correct [context](Context) for function body evaluation returning error on invalid call.12///27///18/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily33/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily19pub fn parse_function_call(34pub fn parse_function_call(20 ctx: Context,35 ctx: Context,21 body_ctx: Option<Context>,36 body_ctx: Context,22 params: &ParamsDesc,37 params: &ParamsDesc,23 args: &ArgsDesc,38 args: &ArgsDesc,24 tailstrict: bool,39 tailstrict: bool,25) -> Result<Context> {40) -> Result<Context> {26 let mut out = HashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());41 let mut passed_args =42 HashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());43 if args.unnamed.len() > params.len() {27 let mut positioned_args = vec![None; params.0.len()];44 throw!(TooManyArgsFunctionHas(params.len()))45 }4647 let mut filled_args = 0;4828 for (id, arg) in args.iter().enumerate() {49 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 }6429 let idx = if let Some(name) = &arg.0 {65 for (name, value) in args.named.iter() {66 // FIXME: O(n) for arg existence check30 params67 if !params.iter().any(|p| &p.0 == name) {31 .iter()68 throw!(UnknownFunctionParameter((name as &str).to_owned()));32 .position(|p| *p.0 == *name)69 }33 .ok_or_else(|| UnknownFunctionParameter(name.clone()))?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;34 } else {87 }35 id36 };378838 if idx >= params.len() {89 if filled_args < params.len() {39 throw!(TooManyArgsFunctionHas(params.len()));90 // Some args are unset, but maybe we have defaults for them40 }91 // Default values should be created in newly created context41 if positioned_args[idx].is_some() {92 let future_context = FutureWrapper::<Context>::new();42 throw!(BindingParameterASecondTime(params[idx].0.clone()));43 }44 positioned_args[idx] = Some(arg.1.clone());93 let mut defaults = HashMap::with_capacity_and_hasher(45 }94 params.len() - filled_args,46 // Fill defaults95 BuildHasherDefault::default(),96 );9747 for (id, p) in params.iter().enumerate() {98 for param in params.iter().filter(|p| p.1.is_some()) {48 let (ctx, expr) = if let Some(arg) = &positioned_args[id] {99 if passed_args.contains_key(¶m.0.clone()) {49 (ctx.clone(), arg)100 continue;50 } else if let Some(default) = &p.1 {101 }51 (body_ctx.clone().expect(NO_DEFAULT_CONTEXT), default)102 #[derive(Trace)]52 } else {103 #[trivially_drop]53 throw!(FunctionParameterNotBoundInCall(p.0.clone()));54 };55 let val = if tailstrict {104 struct LazyNamedBinding {56 LazyVal::new_resolved(evaluate(ctx, expr)?)105 future_context: FutureWrapper<Context>,106 name: IStr,107 value: LocExpr,57 } else {108 }58 #[derive(Trace)]59 #[trivially_drop]60 struct EvaluateLazyVal {61 context: Context,62 expr: LocExpr,63 }64 impl LazyValValue for EvaluateLazyVal {109 impl LazyValValue for LazyNamedBinding {65 fn get(self: Box<Self>) -> Result<Val> {110 fn get(self: Box<Self>) -> Result<Val> {66 evaluate(self.context, &self.expr)111 evaluate_named(self.future_context.unwrap(), &self.value, self.name)67 }112 }68 }113 }6970 LazyVal::new(Box::new(EvaluateLazyVal {114 LazyVal::new(Box::new(LazyNamedBinding {71 context: ctx.clone(),115 future_context: future_context.clone(),72 expr: expr.clone(),116 name: param.0.clone(),117 value: param.1.clone().unwrap(),73 }))118 }));74 };11975 out.insert(p.0.clone(), val);120 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;76 }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 }7714078 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))141 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 }79}149}8015081pub fn parse_function_call_map(151pub fn parse_function_call_map(176 use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType};246 use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType};177247178 let args = $args;248 let args = $args;179 if args.len() > $total_args {249 if args.unnamed.len() + args.named.len() > $total_args {180 throw!(TooManyArgsFunctionHas($total_args));250 throw!(TooManyArgsFunctionHas($total_args));181 }251 }182 $(252 $(183 if args.len() <= $id {253 if args.unnamed.len() + args.named.len() <= $id {184 throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));254 throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));185 }255 }256 // Is named186 let $name = &args[$id];257 let $name = if $id >= $args.unnamed.len() {187 if $name.0.is_some() {258 let named = &args.named[$id - $args.unnamed.len()];188 if $name.0.as_ref().unwrap() != stringify!($name) {259 if &named.0 != stringify!($name) {189 throw!(IntrinsicArgumentReorderingIsNotSupportedYet);260 throw!(IntrinsicArgumentReorderingIsNotSupportedYet);190 }261 }191 }262 &named.1263 } else {264 &$args.unnamed[$id]265 };192 let $name = push_stack_frame(None, || format!("evaluating argument"), || {266 let $name = push_stack_frame(None, || format!("evaluating argument"), || {193 let value = evaluate($ctx.clone(), &$name.1)?;267 let value = evaluate($ctx.clone(), &$name)?;194 $ty.check(&value)?;268 $ty.check(&value)?;195 Ok(value)269 Ok(value)196 })?;270 })?;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(