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

difftreelog

source

crates/jrsonnet-evaluator/src/function.rs9.6 KiBsourcehistory
1use crate::{2	error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, Context, FutureWrapper,3	GcHashMap, LazyVal, LazyValValue, Result, Val,4};5use gcmodule::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};8use std::collections::HashMap;910const NO_DEFAULT_CONTEXT: &str =11	"no default context set for call with defined default parameter value";1213#[derive(Trace)]14struct EvaluateLazyVal {15	context: Context,16	expr: LocExpr,17}18impl LazyValValue for EvaluateLazyVal {19	fn get(self: Box<Self>) -> Result<Val> {20		evaluate(self.context, &self.expr)21	}22}2324/// Creates correct [context](Context) for function body evaluation returning error on invalid call.25///26/// ## Parameters27/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)28/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)29/// * `params`: function parameters' definition30/// * `args`: passed function arguments31/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily32pub fn parse_function_call(33	ctx: Context,34	body_ctx: Context,35	params: &ParamsDesc,36	args: &ArgsDesc,37	tailstrict: bool,38) -> Result<Context> {39	let mut passed_args = GcHashMap::with_capacity(params.len());40	if args.unnamed.len() > params.len() {41		throw!(TooManyArgsFunctionHas(params.len()))42	}4344	let mut filled_args = 0;4546	for (id, arg) in args.unnamed.iter().enumerate() {47		let name = params[id].0.clone();48		passed_args.insert(49			name,50			if tailstrict {51				LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)52			} else {53				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {54					context: ctx.clone(),55					expr: arg.clone(),56				})))57			},58		);59		filled_args += 1;60	}6162	for (name, value) in args.named.iter() {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_args68			.insert(69				name.clone(),70				if tailstrict {71					LazyVal::new_resolved(evaluate(ctx.clone(), value)?)72				} else {73					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {74						context: ctx.clone(),75						expr: value.clone(),76					})))77				},78			)79			.is_some()80		{81			throw!(BindingParameterASecondTime(name.clone()));82		}83		filled_args += 1;84	}8586	if filled_args < params.len() {87		// Some args are unset, but maybe we have defaults for them88		// Default values should be created in newly created context89		let future_context = FutureWrapper::<Context>::new();90		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);9192		for param in params.iter().filter(|p| p.1.is_some()) {93			if passed_args.contains_key(&param.0.clone()) {94				continue;95			}96			#[derive(Trace)]97			struct LazyNamedBinding {98				future_context: FutureWrapper<Context>,99				name: IStr,100				value: LocExpr,101			}102			impl LazyValValue for LazyNamedBinding {103				fn get(self: Box<Self>) -> Result<Val> {104					evaluate_named(self.future_context.unwrap(), &self.value, self.name)105				}106			}107			LazyVal::new(TraceBox(Box::new(LazyNamedBinding {108				future_context: future_context.clone(),109				name: param.0.clone(),110				value: param.1.clone().unwrap(),111			})));112113			defaults.insert(114				param.0.clone(),115				LazyVal::new(TraceBox(Box::new(LazyNamedBinding {116					future_context: future_context.clone(),117					name: param.0.clone(),118					value: param.1.clone().unwrap(),119				}))),120			);121			filled_args += 1;122		}123124		// Some args still wasn't filled125		if filled_args != params.len() {126			for param in params.iter().skip(args.unnamed.len()) {127				if !args.named.iter().any(|a| a.0 == param.0) {128					throw!(FunctionParameterNotBoundInCall(param.0.clone()));129				}130			}131			unreachable!();132		}133134		Ok(body_ctx135			.extend(passed_args, None, None, None)136			.extend_bound(defaults)137			.into_future(future_context))138	} else {139		let body_ctx = body_ctx.extend(passed_args, None, None, None);140		Ok(body_ctx)141	}142}143144#[derive(Clone, Copy)]145pub struct BuiltinParam {146	pub name: &'static str,147	pub has_default: bool,148}149150/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead151///152/// ## Parameters153/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)154/// * `params`: function parameters' definition155/// * `args`: passed function arguments156/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily157pub fn parse_builtin_call<'k>(158	ctx: Context,159	params: &'static [BuiltinParam],160	args: &'k ArgsDesc,161	tailstrict: bool,162) -> Result<GcHashMap<&'k str, LazyVal>> {163	let mut passed_args = GcHashMap::with_capacity(params.len());164	if args.unnamed.len() > params.len() {165		throw!(TooManyArgsFunctionHas(params.len()))166	}167168	let mut filled_args = 0;169170	for (id, arg) in args.unnamed.iter().enumerate() {171		let name = params[id].name;172		passed_args.insert(173			name,174			if tailstrict {175				LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)176			} else {177				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {178					context: ctx.clone(),179					expr: arg.clone(),180				})))181			},182		);183		filled_args += 1;184	}185186	for (name, value) in args.named.iter() {187		// FIXME: O(n) for arg existence check188		if !params.iter().any(|p| p.name == name as &str) {189			throw!(UnknownFunctionParameter((name as &str).to_owned()));190		}191		if passed_args192			.insert(193				name,194				if tailstrict {195					LazyVal::new_resolved(evaluate(ctx.clone(), value)?)196				} else {197					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {198						context: ctx.clone(),199						expr: value.clone(),200					})))201				},202			)203			.is_some()204		{205			throw!(BindingParameterASecondTime(name.clone()));206		}207		filled_args += 1;208	}209210	if filled_args < params.len() {211		for param in params.iter().filter(|p| p.has_default) {212			if passed_args.contains_key(&param.name) {213				continue;214			}215			filled_args += 1;216		}217218		// Some args still wasn't filled219		if filled_args != params.len() {220			for param in params.iter().skip(args.unnamed.len()) {221				if !args.named.iter().any(|a| &a.0 as &str == param.name) {222					throw!(FunctionParameterNotBoundInCall(param.name.into()));223				}224			}225			unreachable!();226		}227	}228	Ok(passed_args)229}230231pub fn parse_function_call_map(232	ctx: Context,233	body_ctx: Option<Context>,234	params: &ParamsDesc,235	args: &HashMap<IStr, Val>,236	tailstrict: bool,237) -> Result<Context> {238	let mut out = GcHashMap::with_capacity(params.len());239	let mut positioned_args = vec![None; params.0.len()];240	for (name, val) in args.iter() {241		let idx = params242			.iter()243			.position(|p| *p.0 == **name)244			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;245246		if idx >= params.len() {247			throw!(TooManyArgsFunctionHas(params.len()));248		}249		if positioned_args[idx].is_some() {250			throw!(BindingParameterASecondTime(params[idx].0.clone()));251		}252		positioned_args[idx] = Some(val.clone());253	}254	// Fill defaults255	for (id, p) in params.iter().enumerate() {256		let val = if let Some(arg) = positioned_args[id].take() {257			LazyVal::new_resolved(arg)258		} else if let Some(default) = &p.1 {259			if tailstrict {260				LazyVal::new_resolved(evaluate(261					body_ctx.clone().expect(NO_DEFAULT_CONTEXT),262					default,263				)?)264			} else {265				let body_ctx = body_ctx.clone();266				let default = default.clone();267				#[derive(Trace)]268				struct EvaluateLazyVal {269					body_ctx: Option<Context>,270					default: LocExpr,271				}272				impl LazyValValue for EvaluateLazyVal {273					fn get(self: Box<Self>) -> Result<Val> {274						evaluate(275							self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT),276							&self.default,277						)278					}279				}280				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { body_ctx, default })))281			}282		} else {283			throw!(FunctionParameterNotBoundInCall(p.0.clone()));284		};285		out.insert(p.0.clone(), val);286	}287288	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))289}290291pub fn place_args(body_ctx: Context, params: &ParamsDesc, args: &[Val]) -> Result<Context> {292	let mut out = GcHashMap::with_capacity(params.len());293	let mut positioned_args = vec![None; params.0.len()];294	for (id, arg) in args.iter().enumerate() {295		if id >= params.len() {296			throw!(TooManyArgsFunctionHas(params.len()));297		}298		positioned_args[id] = Some(arg);299	}300	// Fill defaults301	for (id, p) in params.iter().enumerate() {302		let val = if let Some(arg) = &positioned_args[id] {303			(*arg).clone()304		} else if let Some(default) = &p.1 {305			evaluate(body_ctx.clone(), default)?306		} else {307			throw!(FunctionParameterNotBoundInCall(p.0.clone()));308		};309		out.insert(p.0.clone(), LazyVal::new_resolved(val));310	}311312	Ok(body_ctx.extend(out, None, None, None))313}314315#[macro_export]316macro_rules! parse_args {317	($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [318		$($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)?319	], $handler:block) => {{320		use $crate::{error::Error::*, throw, evaluate, push_description_frame, typed::CheckType};321322		let args = $args;323		if args.unnamed.len() + args.named.len() > $total_args {324			throw!(TooManyArgsFunctionHas($total_args));325		}326		$(327			if args.unnamed.len() + args.named.len() <= $id {328				throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));329			}330			// Is named331			let $name = if $id >= $args.unnamed.len() {332				let named = &args.named[$id - $args.unnamed.len()];333				if &named.0 != stringify!($name) {334					throw!(IntrinsicArgumentReorderingIsNotSupportedYet);335				}336				&named.1337			} else {338				&$args.unnamed[$id]339			};340			let $name = push_description_frame(|| format!("evaluating builtin argument {}", stringify!($name)), || {341				let value = evaluate($ctx.clone(), &$name)?;342				$ty.check(&value)?;343				Ok(value)344			})?;345			$(346				let $name = if let $match(v) = $name {347					v348				} else {349					unreachable!();350				};351			)?352		)+353		($handler as crate::Result<_>)354	}};355}