git.delta.rocks / jrsonnet / refs/commits / 6bf55d21bf51

difftreelog

source

crates/jrsonnet-evaluator/src/function/parse.rs7.2 KiBsourcehistory
1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9	bail,10	destructure::destruct,11	error::{ErrorKind::*, Result},12	evaluate_named,13	function::builtin::ParamDefault,14	gc::GcHashMap,15	val::ThunkValue,16	Context, Pending, 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>) -> Result<Val> {29		evaluate_named(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	ctx: Context,43	body_ctx: Context,44	params: &ParamsDesc,45	args: &dyn ArgsLike,46	tailstrict: bool,47) -> Result<Context> {48	let mut passed_args =49		GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());50	if args.unnamed_len() > params.len() {51		bail!(TooManyArgsFunctionHas(52			params.len(),53			params54				.iter()55				.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))56				.collect()57		))58	}5960	let mut filled_named = 0;61	let mut filled_positionals = 0;6263	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {64		let name = params[id].0.clone();65		destruct(66			&name,67			arg,68			Pending::new_filled(ctx.clone()),69			&mut passed_args,70		)?;71		filled_positionals += 1;72		Ok(())73	})?;7475	args.named_iter(ctx, tailstrict, &mut |name, value| {76		// FIXME: O(n) for arg existence check77		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {78			bail!(UnknownFunctionParameter((name as &str).to_owned()));79		}80		if passed_args.insert(name.clone(), value).is_some() {81			bail!(BindingParameterASecondTime(name.clone()));82		}83		filled_named += 1;84		Ok(())85	})?;8687	if filled_named + filled_positionals < params.len() {88		// Some args are unset, but maybe we have defaults for them89		// Default values should be created in newly created context90		let fctx = Context::new_future();91		let mut defaults = GcHashMap::with_capacity(92			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()93				- filled_named - filled_positionals,94		);9596		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {97			if let Some(name) = param.0.name() {98				if passed_args.contains_key(&name) {99					continue;100				}101			} else if idx < filled_positionals {102				continue;103			}104105			destruct(106				&param.0,107				Thunk::new(EvaluateNamedThunk {108					ctx: fctx.clone(),109					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),110					value: param.1.clone().expect("default exists"),111				}),112				fctx.clone(),113				&mut defaults,114			)?;115			if param.0.name().is_some() {116				filled_named += 1;117			} else {118				filled_positionals += 1;119			}120		}121122		// Some args still weren't filled123		if filled_named + filled_positionals != params.len() {124			for param in params.iter().skip(args.unnamed_len()) {125				let mut found = false;126				args.named_names(&mut |name| {127					if Some(name) == param.0.name().as_ref() {128						found = true;129					}130				});131				if !found {132					bail!(FunctionParameterNotBoundInCall(133						param.0.clone().name(),134						params135							.iter()136							.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))137							.collect()138					));139				}140			}141			unreachable!();142		}143144		Ok(body_ctx145			.extend(passed_args, None, None, None)146			.extend(defaults, None, None, None)147			.into_future(fctx))148	} else {149		let body_ctx = body_ctx.extend(passed_args, None, None, None);150		Ok(body_ctx)151	}152}153154/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead155///156/// ## Parameters157/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)158/// * `params`: function parameters' definition159/// * `args`: passed function arguments160/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily161pub fn parse_builtin_call(162	ctx: Context,163	params: &[BuiltinParam],164	args: &dyn ArgsLike,165	tailstrict: bool,166) -> Result<Vec<Option<Thunk<Val>>>> {167	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];168	if args.unnamed_len() > params.len() {169		bail!(TooManyArgsFunctionHas(170			params.len(),171			params172				.iter()173				.map(|p| (p.name().as_str().map(IStr::from), p.default()))174				.collect()175		))176	}177178	let mut filled_args = 0;179180	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {181		passed_args[id] = Some(arg);182		filled_args += 1;183		Ok(())184	})?;185186	args.named_iter(ctx, tailstrict, &mut |name, arg| {187		// FIXME: O(n) for arg existence check188		let id = params189			.iter()190			.position(|p| p.name() == name)191			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;192		if replace(&mut passed_args[id], Some(arg)).is_some() {193			bail!(BindingParameterASecondTime(name.clone()));194		}195		filled_args += 1;196		Ok(())197	})?;198199	if filled_args < params.len() {200		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default()) {201			if passed_args[id].is_some() {202				continue;203			}204			filled_args += 1;205		}206207		// Some args still wasn't filled208		if filled_args != params.len() {209			for param in params.iter().skip(args.unnamed_len()) {210				let mut found = false;211				args.named_names(&mut |name| {212					if param.name() == name {213						found = true;214					}215				});216				if !found {217					bail!(FunctionParameterNotBoundInCall(218						param.name().as_str().map(IStr::from),219						params220							.iter()221							.map(|p| (p.name().as_str().map(IStr::from), p.default()))222							.collect()223					));224				}225			}226			unreachable!();227		}228	}229	Ok(passed_args)230}231232/// Creates Context, which has all argument default values applied233/// and with unbound values causing error to be returned234pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {235	#[derive(Trace)]236	struct DependsOnUnbound(IStr, ParamsDesc);237	impl ThunkValue for DependsOnUnbound {238		type Output = Val;239		fn get(self: Box<Self>) -> Result<Val> {240			Err(FunctionParameterNotBoundInCall(241				Some(self.0.clone()),242				self.1243					.iter()244					.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))245					.collect(),246			)247			.into())248		}249	}250251	let fctx = Context::new_future();252253	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());254255	for param in params.iter() {256		if let Some(v) = &param.1 {257			destruct(258				&param.0.clone(),259				Thunk::new(EvaluateNamedThunk {260					ctx: fctx.clone(),261					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),262					value: v.clone(),263				}),264				fctx.clone(),265				&mut bindings,266			)?;267		} else {268			destruct(269				&param.0,270				Thunk::new(DependsOnUnbound(271					param.0.name().unwrap_or_else(|| "<destruct>".into()),272					params.clone(),273				)),274				fctx.clone(),275				&mut bindings,276			)?;277		}278	}279280	Ok(body_ctx281		.extend(bindings, None, None, None)282		.into_future(fctx))283}