git.delta.rocks / jrsonnet / refs/commits / 3961a530b796

difftreelog

feat destruct function arguments

Yaroslav Bolyukin2022-06-03parent: #c35e2bd.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -12,7 +12,7 @@
 };
 
 #[allow(clippy::too_many_lines)]
-fn destruct(
+pub fn destruct(
 	d: &Destruct,
 	parent: Thunk<Val>,
 	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -59,7 +59,7 @@
 }
 impl FuncDesc {
 	/// Create body context, but fill arguments without defaults with lazy error
-	pub fn default_body_context(&self) -> Context {
+	pub fn default_body_context(&self) -> Result<Context> {
 		parse_default_function_call(self.ctx.clone(), &self.params)
 	}
 
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/parse.rs
1use gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{LocExpr, ParamsDesc};45use super::{6	arglike::ArgsLike,7	builtin::{BuiltinParam, BuiltinParamName},8};9use crate::{10	error::{Error::*, Result},11	evaluate_named,12	gc::GcHashMap,13	tb, throw,14	val::ThunkValue,15	Context, Pending, State, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20	ctx: Pending<Context>,21	name: IStr,22	value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26	type Output = Val;27	fn get(self: Box<Self>, s: State) -> Result<Val> {28		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)29	}30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41	s: State,42	ctx: Context,43	body_ctx: Context,44	params: &ParamsDesc,45	args: &dyn ArgsLike,46	tailstrict: bool,47) -> Result<Context> {48	let mut passed_args = GcHashMap::with_capacity(params.len());49	if args.unnamed_len() > params.len() {50		throw!(TooManyArgsFunctionHas(params.len()))51	}5253	let mut filled_args = 0;5455	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {56		let name = params[id].0.clone();57		passed_args.insert(name, arg);58		filled_args += 1;59		Ok(())60	})?;6162	args.named_iter(s, ctx, tailstrict, &mut |name, value| {63		// FIXME: O(n) for arg existence check64		if !params.iter().any(|p| &p.0 == name) {65			throw!(UnknownFunctionParameter((name as &str).to_owned()));66		}67		if passed_args.insert(name.clone(), value).is_some() {68			throw!(BindingParameterASecondTime(name.clone()));69		}70		filled_args += 1;71		Ok(())72	})?;7374	if filled_args < params.len() {75		// Some args are unset, but maybe we have defaults for them76		// Default values should be created in newly created context77		let fctx = Context::new_future();78		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);7980		for param in params.iter().filter(|p| p.1.is_some()) {81			if passed_args.contains_key(&param.0.clone()) {82				continue;83			}8485			defaults.insert(86				param.0.clone(),87				Thunk::new(tb!(EvaluateNamedThunk {88					ctx: fctx.clone(),89					name: param.0.clone(),90					value: param.1.clone().expect("default exists"),91				})),92			);93			filled_args += 1;94		}9596		// Some args still wasn't filled97		if filled_args != params.len() {98			for param in params.iter().skip(args.unnamed_len()) {99				let mut found = false;100				args.named_names(&mut |name| {101					if name == &param.0 {102						found = true;103					}104				});105				if !found {106					throw!(FunctionParameterNotBoundInCall(param.0.clone()));107				}108			}109			unreachable!();110		}111112		Ok(body_ctx113			.extend(passed_args, None, None, None)114			.extend(defaults, None, None, None)115			.into_future(fctx))116	} else {117		let body_ctx = body_ctx.extend(passed_args, None, None, None);118		Ok(body_ctx)119	}120}121122/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead123///124/// ## Parameters125/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)126/// * `params`: function parameters' definition127/// * `args`: passed function arguments128/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily129pub fn parse_builtin_call(130	s: State,131	ctx: Context,132	params: &[BuiltinParam],133	args: &dyn ArgsLike,134	tailstrict: bool,135) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {136	let mut passed_args = GcHashMap::with_capacity(params.len());137	if args.unnamed_len() > params.len() {138		throw!(TooManyArgsFunctionHas(params.len()))139	}140141	let mut filled_args = 0;142143	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {144		let name = params[id].name.clone();145		passed_args.insert(name, arg);146		filled_args += 1;147		Ok(())148	})?;149150	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {151		// FIXME: O(n) for arg existence check152		let p = params153			.iter()154			.find(|p| p.name == name as &str)155			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;156		if passed_args.insert(p.name.clone(), arg).is_some() {157			throw!(BindingParameterASecondTime(name.clone()));158		}159		filled_args += 1;160		Ok(())161	})?;162163	if filled_args < params.len() {164		for param in params.iter().filter(|p| p.has_default) {165			if passed_args.contains_key(&param.name) {166				continue;167			}168			filled_args += 1;169		}170171		// Some args still wasn't filled172		if filled_args != params.len() {173			for param in params.iter().skip(args.unnamed_len()) {174				let mut found = false;175				args.named_names(&mut |name| {176					if name as &str == &param.name as &str {177						found = true;178					}179				});180				if !found {181					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));182				}183			}184			unreachable!();185		}186	}187	Ok(passed_args)188}189190/// Creates Context, which has all argument default values applied191/// and with unbound values causing error to be returned192pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {193	#[derive(Trace)]194	struct DependsOnUnbound(IStr);195	impl ThunkValue for DependsOnUnbound {196		type Output = Val;197		fn get(self: Box<Self>, _: State) -> Result<Val> {198			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())199		}200	}201202	let fctx = Context::new_future();203204	let mut bindings = GcHashMap::new();205206	for param in params.iter() {207		if let Some(v) = &param.1 {208			bindings.insert(209				param.0.clone(),210				Thunk::new(tb!(EvaluateNamedThunk {211					ctx: fctx.clone(),212					name: param.0.clone(),213					value: v.clone(),214				})),215			);216		} else {217			bindings.insert(218				param.0.clone(),219				Thunk::new(tb!(DependsOnUnbound(param.0.clone()))),220			);221		}222	}223224	body_ctx225		.extend(bindings, None, None, None)226		.into_future(fctx)227}
after · crates/jrsonnet-evaluator/src/function/parse.rs
1use gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{LocExpr, ParamsDesc};45use super::{6	arglike::ArgsLike,7	builtin::{BuiltinParam, BuiltinParamName},8};9use crate::{10	destructure::destruct,11	error::{Error::*, Result},12	evaluate_named,13	gc::GcHashMap,14	tb, throw,15	val::ThunkValue,16	Context, Pending, State, Thunk, Val,17};1819#[derive(Trace)]20struct EvaluateNamedThunk {21	ctx: Pending<Context>,22	name: IStr,23	value: LocExpr,24}2526impl ThunkValue for EvaluateNamedThunk {27	type Output = Val;28	fn get(self: Box<Self>, s: State) -> Result<Val> {29		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)30	}31}3233/// Creates correct [context](Context) for function body evaluation returning error on invalid call.34///35/// ## Parameters36/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)37/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)38/// * `params`: function parameters' definition39/// * `args`: passed function arguments40/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily41pub fn parse_function_call(42	s: State,43	ctx: Context,44	body_ctx: Context,45	params: &ParamsDesc,46	args: &dyn ArgsLike,47	tailstrict: bool,48) -> Result<Context> {49	let mut passed_args = GcHashMap::with_capacity(params.len());50	if args.unnamed_len() > params.len() {51		throw!(TooManyArgsFunctionHas(params.len()))52	}5354	let mut filled_named = 0;55	let mut filled_positionals = 0;5657	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {58		let name = params[id].0.clone();59		destruct(&name, arg, &mut passed_args)?;60		filled_positionals += 1;61		Ok(())62	})?;6364	args.named_iter(s, ctx, tailstrict, &mut |name, value| {65		// FIXME: O(n) for arg existence check66		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {67			throw!(UnknownFunctionParameter((name as &str).to_owned()));68		}69		if passed_args.insert(name.clone(), value).is_some() {70			throw!(BindingParameterASecondTime(name.clone()));71		}72		filled_named += 1;73		Ok(())74	})?;7576	if filled_named + filled_positionals < params.len() {77		// Some args are unset, but maybe we have defaults for them78		// Default values should be created in newly created context79		let fctx = Context::new_future();80		let mut defaults =81			GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8283		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {84			if let Some(name) = param.0.name() {85				if passed_args.contains_key(&name) {86					continue;87				}88			} else if idx < filled_positionals {89				continue;90			}9192			destruct(93				&param.0,94				Thunk::new(tb!(EvaluateNamedThunk {95					ctx: fctx.clone(),96					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),97					value: param.1.clone().expect("default exists"),98				})),99				&mut defaults,100			)?;101			if param.0.name().is_some() {102				filled_named += 1;103			} else {104				filled_positionals += 1;105			}106		}107108		// Some args still weren't filled109		if filled_named + filled_positionals != params.len() {110			for param in params.iter().skip(args.unnamed_len()) {111				let mut found = false;112				args.named_names(&mut |name| {113					if Some(name) == param.0.name().as_ref() {114						found = true;115					}116				});117				if !found {118					throw!(FunctionParameterNotBoundInCall(119						param120							.0121							.clone()122							.name()123							.unwrap_or_else(|| "<destruct>".into())124					));125				}126			}127			unreachable!();128		}129130		Ok(body_ctx131			.extend(passed_args, None, None, None)132			.extend(defaults, None, None, None)133			.into_future(fctx))134	} else {135		let body_ctx = body_ctx.extend(passed_args, None, None, None);136		Ok(body_ctx)137	}138}139140/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead141///142/// ## Parameters143/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)144/// * `params`: function parameters' definition145/// * `args`: passed function arguments146/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily147pub fn parse_builtin_call(148	s: State,149	ctx: Context,150	params: &[BuiltinParam],151	args: &dyn ArgsLike,152	tailstrict: bool,153) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {154	let mut passed_args = GcHashMap::with_capacity(params.len());155	if args.unnamed_len() > params.len() {156		throw!(TooManyArgsFunctionHas(params.len()))157	}158159	let mut filled_args = 0;160161	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {162		let name = params[id].name.clone();163		passed_args.insert(name, arg);164		filled_args += 1;165		Ok(())166	})?;167168	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {169		// FIXME: O(n) for arg existence check170		let p = params171			.iter()172			.find(|p| p.name == name as &str)173			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;174		if passed_args.insert(p.name.clone(), arg).is_some() {175			throw!(BindingParameterASecondTime(name.clone()));176		}177		filled_args += 1;178		Ok(())179	})?;180181	if filled_args < params.len() {182		for param in params.iter().filter(|p| p.has_default) {183			if passed_args.contains_key(&param.name) {184				continue;185			}186			filled_args += 1;187		}188189		// Some args still wasn't filled190		if filled_args != params.len() {191			for param in params.iter().skip(args.unnamed_len()) {192				let mut found = false;193				args.named_names(&mut |name| {194					if name as &str == &param.name as &str {195						found = true;196					}197				});198				if !found {199					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));200				}201			}202			unreachable!();203		}204	}205	Ok(passed_args)206}207208/// Creates Context, which has all argument default values applied209/// and with unbound values causing error to be returned210pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {211	#[derive(Trace)]212	struct DependsOnUnbound(IStr);213	impl ThunkValue for DependsOnUnbound {214		type Output = Val;215		fn get(self: Box<Self>, _: State) -> Result<Val> {216			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())217		}218	}219220	let fctx = Context::new_future();221222	let mut bindings = GcHashMap::new();223224	for param in params.iter() {225		if let Some(v) = &param.1 {226			destruct(227				&param.0.clone(),228				Thunk::new(tb!(EvaluateNamedThunk {229					ctx: fctx.clone(),230					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),231					value: v.clone(),232				})),233				&mut bindings,234			)?;235		} else {236			destruct(237				&param.0,238				Thunk::new(tb!(DependsOnUnbound(239					param.0.name().unwrap_or_else(|| "<destruct>".into())240				))),241				&mut bindings,242			)?;243		}244	}245246	Ok(body_ctx247		.extend(bindings, None, None, None)248		.into_future(fctx))249}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -152,7 +152,7 @@
 /// name, default value
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
-pub struct Param(pub IStr, pub Option<LocExpr>);
+pub struct Param(pub Destruct, pub Option<LocExpr>);
 
 /// Defined function parameters
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -206,6 +206,7 @@
 	},
 }
 impl Destruct {
+	/// Name of destructure, used for function parameter names
 	pub fn name(&self) -> Option<IStr> {
 		match self {
 			Self::Full(name) => Some(name.clone()),
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -59,7 +59,7 @@
 		rule keyword(id: &'static str) -> ()
 			= ##parse_string_literal(id) end_of_ident()
 
-		pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }
+		pub rule param(s: &ParserSettings) -> expr::Param = name:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }
 		pub rule params(s: &ParserSettings) -> expr::ParamsDesc
 			= params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) }
 			/ { expr::ParamsDesc(Rc::new(Vec::new())) }