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

difftreelog

source

crates/jrsonnet-evaluator/src/function/parse.rs6.5 KiBsourcehistory
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}