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

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	destructure::destruct,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(51			params.len(),52			params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()53		))54	}5556	let mut filled_named = 0;57	let mut filled_positionals = 0;5859	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {60		let name = params[id].0.clone();61		destruct(62			&name,63			arg,64			Pending::new_filled(ctx.clone()),65			&mut passed_args,66		)?;67		filled_positionals += 1;68		Ok(())69	})?;7071	args.named_iter(s, ctx, tailstrict, &mut |name, value| {72		// FIXME: O(n) for arg existence check73		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {74			throw!(UnknownFunctionParameter((name as &str).to_owned()));75		}76		if passed_args.insert(name.clone(), value).is_some() {77			throw!(BindingParameterASecondTime(name.clone()));78		}79		filled_named += 1;80		Ok(())81	})?;8283	if filled_named + filled_positionals < params.len() {84		// Some args are unset, but maybe we have defaults for them85		// Default values should be created in newly created context86		let fctx = Context::new_future();87		let mut defaults =88			GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8990		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {91			if let Some(name) = param.0.name() {92				if passed_args.contains_key(&name) {93					continue;94				}95			} else if idx < filled_positionals {96				continue;97			}9899			destruct(100				&param.0,101				Thunk::new(tb!(EvaluateNamedThunk {102					ctx: fctx.clone(),103					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),104					value: param.1.clone().expect("default exists"),105				})),106				fctx.clone(),107				&mut defaults,108			)?;109			if param.0.name().is_some() {110				filled_named += 1;111			} else {112				filled_positionals += 1;113			}114		}115116		// Some args still weren't filled117		if filled_named + filled_positionals != params.len() {118			for param in params.iter().skip(args.unnamed_len()) {119				let mut found = false;120				args.named_names(&mut |name| {121					if Some(name) == param.0.name().as_ref() {122						found = true;123					}124				});125				if !found {126					throw!(FunctionParameterNotBoundInCall(127						param.0.clone().name(),128						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()129					));130				}131			}132			unreachable!();133		}134135		Ok(body_ctx136			.extend(passed_args, None, None, None)137			.extend(defaults, None, None, None)138			.into_future(fctx))139	} else {140		let body_ctx = body_ctx.extend(passed_args, None, None, None);141		Ok(body_ctx)142	}143}144145/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead146///147/// ## Parameters148/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)149/// * `params`: function parameters' definition150/// * `args`: passed function arguments151/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily152pub fn parse_builtin_call(153	s: State,154	ctx: Context,155	params: &[BuiltinParam],156	args: &dyn ArgsLike,157	tailstrict: bool,158) -> Result<Vec<Option<Thunk<Val>>>> {159	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];160	if args.unnamed_len() > params.len() {161		throw!(TooManyArgsFunctionHas(162			params.len(),163			params164				.iter()165				.map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))166				.collect()167		))168	}169170	let mut filled_args = 0;171172	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {173		passed_args[id] = Some(arg);174		filled_args += 1;175		Ok(())176	})?;177178	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {179		// FIXME: O(n) for arg existence check180		let id = params181			.iter()182			.position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))183			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;184		if replace(&mut passed_args[id], Some(arg)).is_some() {185			throw!(BindingParameterASecondTime(name.clone()));186		}187		filled_args += 1;188		Ok(())189	})?;190191	if filled_args < params.len() {192		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {193			if passed_args[id].is_some() {194				continue;195			}196			filled_args += 1;197		}198199		// Some args still wasn't filled200		if filled_args != params.len() {201			for param in params.iter().skip(args.unnamed_len()) {202				let mut found = false;203				args.named_names(&mut |name| {204					if param205						.name206						.as_ref()207						.map_or(false, |v| v as &str == name as &str)208					{209						found = true;210					}211				});212				if !found {213					throw!(FunctionParameterNotBoundInCall(214						param.name.as_ref().map(|v| v.as_ref().into()),215						params216							.iter()217							.map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))218							.collect()219					));220				}221			}222			unreachable!();223		}224	}225	Ok(passed_args)226}227228/// Creates Context, which has all argument default values applied229/// and with unbound values causing error to be returned230pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {231	#[derive(Trace)]232	struct DependsOnUnbound(IStr, ParamsDesc);233	impl ThunkValue for DependsOnUnbound {234		type Output = Val;235		fn get(self: Box<Self>, _: State) -> Result<Val> {236			Err(FunctionParameterNotBoundInCall(237				Some(self.0.clone()),238				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),239			)240			.into())241		}242	}243244	let fctx = Context::new_future();245246	let mut bindings = GcHashMap::new();247248	for param in params.iter() {249		if let Some(v) = &param.1 {250			destruct(251				&param.0.clone(),252				Thunk::new(tb!(EvaluateNamedThunk {253					ctx: fctx.clone(),254					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),255					value: v.clone(),256				})),257				fctx.clone(),258				&mut bindings,259			)?;260		} else {261			destruct(262				&param.0,263				Thunk::new(tb!(DependsOnUnbound(264					param.0.name().unwrap_or_else(|| "<destruct>".into()),265					params.clone()266				))),267				fctx.clone(),268				&mut bindings,269			)?;270		}271	}272273	Ok(body_ctx274		.extend(bindings, None, None, None)275		.into_future(fctx))276}