git.delta.rocks / jrsonnet / refs/commits / d76beb28c515

difftreelog

source

crates/jrsonnet-evaluator/src/function/parse.rs6.7 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(60			&name,61			arg,62			Pending::new_filled(ctx.clone()),63			&mut passed_args,64		)?;65		filled_positionals += 1;66		Ok(())67	})?;6869	args.named_iter(s, ctx, tailstrict, &mut |name, value| {70		// FIXME: O(n) for arg existence check71		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {72			throw!(UnknownFunctionParameter((name as &str).to_owned()));73		}74		if passed_args.insert(name.clone(), value).is_some() {75			throw!(BindingParameterASecondTime(name.clone()));76		}77		filled_named += 1;78		Ok(())79	})?;8081	if filled_named + filled_positionals < params.len() {82		// Some args are unset, but maybe we have defaults for them83		// Default values should be created in newly created context84		let fctx = Context::new_future();85		let mut defaults =86			GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8788		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {89			if let Some(name) = param.0.name() {90				if passed_args.contains_key(&name) {91					continue;92				}93			} else if idx < filled_positionals {94				continue;95			}9697			destruct(98				&param.0,99				Thunk::new(tb!(EvaluateNamedThunk {100					ctx: fctx.clone(),101					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),102					value: param.1.clone().expect("default exists"),103				})),104				fctx.clone(),105				&mut defaults,106			)?;107			if param.0.name().is_some() {108				filled_named += 1;109			} else {110				filled_positionals += 1;111			}112		}113114		// Some args still weren't filled115		if filled_named + filled_positionals != params.len() {116			for param in params.iter().skip(args.unnamed_len()) {117				let mut found = false;118				args.named_names(&mut |name| {119					if Some(name) == param.0.name().as_ref() {120						found = true;121					}122				});123				if !found {124					throw!(FunctionParameterNotBoundInCall(125						param126							.0127							.clone()128							.name()129							.unwrap_or_else(|| "<destruct>".into())130					));131				}132			}133			unreachable!();134		}135136		Ok(body_ctx137			.extend(passed_args, None, None, None)138			.extend(defaults, None, None, None)139			.into_future(fctx))140	} else {141		let body_ctx = body_ctx.extend(passed_args, None, None, None);142		Ok(body_ctx)143	}144}145146/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead147///148/// ## Parameters149/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)150/// * `params`: function parameters' definition151/// * `args`: passed function arguments152/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily153pub fn parse_builtin_call(154	s: State,155	ctx: Context,156	params: &[BuiltinParam],157	args: &dyn ArgsLike,158	tailstrict: bool,159) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {160	let mut passed_args = GcHashMap::with_capacity(params.len());161	if args.unnamed_len() > params.len() {162		throw!(TooManyArgsFunctionHas(params.len()))163	}164165	let mut filled_args = 0;166167	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {168		let name = params[id].name.clone();169		passed_args.insert(name, arg);170		filled_args += 1;171		Ok(())172	})?;173174	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {175		// FIXME: O(n) for arg existence check176		let p = params177			.iter()178			.find(|p| p.name == name as &str)179			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;180		if passed_args.insert(p.name.clone(), arg).is_some() {181			throw!(BindingParameterASecondTime(name.clone()));182		}183		filled_args += 1;184		Ok(())185	})?;186187	if filled_args < params.len() {188		for param in params.iter().filter(|p| p.has_default) {189			if passed_args.contains_key(&param.name) {190				continue;191			}192			filled_args += 1;193		}194195		// Some args still wasn't filled196		if filled_args != params.len() {197			for param in params.iter().skip(args.unnamed_len()) {198				let mut found = false;199				args.named_names(&mut |name| {200					if name as &str == &param.name as &str {201						found = true;202					}203				});204				if !found {205					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));206				}207			}208			unreachable!();209		}210	}211	Ok(passed_args)212}213214/// Creates Context, which has all argument default values applied215/// and with unbound values causing error to be returned216pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {217	#[derive(Trace)]218	struct DependsOnUnbound(IStr);219	impl ThunkValue for DependsOnUnbound {220		type Output = Val;221		fn get(self: Box<Self>, _: State) -> Result<Val> {222			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())223		}224	}225226	let fctx = Context::new_future();227228	let mut bindings = GcHashMap::new();229230	for param in params.iter() {231		if let Some(v) = &param.1 {232			destruct(233				&param.0.clone(),234				Thunk::new(tb!(EvaluateNamedThunk {235					ctx: fctx.clone(),236					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),237					value: v.clone(),238				})),239				fctx.clone(),240				&mut bindings,241			)?;242		} else {243			destruct(244				&param.0,245				Thunk::new(tb!(DependsOnUnbound(246					param.0.name().unwrap_or_else(|| "<destruct>".into())247				))),248				fctx.clone(),249				&mut bindings,250			)?;251		}252	}253254	Ok(body_ctx255		.extend(bindings, None, None, None)256		.into_future(fctx))257}