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

difftreelog

source

crates/jrsonnet-evaluator/src/function.rs11.8 KiBsourcehistory
1use crate::{2	error::{Error::*, LocError},3	evaluate, evaluate_named,4	gc::TraceBox,5	throw,6	typed::Typed,7	Context, FutureWrapper, GcHashMap, LazyVal, LazyValValue, Result, Val,8};9use gcmodule::Trace;10use jrsonnet_interner::IStr;11pub use jrsonnet_macros::builtin;12use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};13use std::{borrow::Cow, collections::HashMap, convert::TryFrom};1415#[derive(Trace)]16struct EvaluateLazyVal {17	context: Context,18	expr: LocExpr,19}20impl LazyValValue for EvaluateLazyVal {21	fn get(self: Box<Self>) -> Result<Val> {22		evaluate(self.context, &self.expr)23	}24}2526#[derive(Trace)]27struct EvaluateNamedLazyVal {28	future_context: FutureWrapper<Context>,29	name: IStr,30	value: LocExpr,31}32impl LazyValValue for EvaluateNamedLazyVal {33	fn get(self: Box<Self>) -> Result<Val> {34		evaluate_named(self.future_context.unwrap(), &self.value, self.name)35	}36}3738pub trait ArgLike {39	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal>;40}41impl ArgLike for &LocExpr {42	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {43		Ok(if tailstrict {44			LazyVal::new_resolved(evaluate(ctx, self)?)45		} else {46			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {47				context: ctx,48				expr: (*self).clone(),49			})))50		})51	}52}53impl<T> ArgLike for T54where55	T: Typed + Clone,56	Val: TryFrom<T, Error = LocError>,57{58	fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {59		let val: Val = Val::try_from(self.clone())?;60		Ok(LazyVal::new_resolved(val))61	}62}63pub enum TlaArg {64	String(IStr),65	Code(LocExpr),66	Val(Val),67}68impl ArgLike for TlaArg {69	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {70		match self {71			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),72			TlaArg::Code(code) => Ok(if tailstrict {73				LazyVal::new_resolved(evaluate(ctx, code)?)74			} else {75				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {76					context: ctx,77					expr: code.clone(),78				})))79			}),80			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),81		}82	}83}8485pub trait ArgsLike {86	fn unnamed_len(&self) -> usize;87	fn unnamed_iter(88		&self,89		ctx: Context,90		tailstrict: bool,91		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,92	) -> Result<()>;93	fn named_iter(94		&self,95		ctx: Context,96		tailstrict: bool,97		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,98	) -> Result<()>;99	fn named_names(&self, handler: &mut dyn FnMut(&IStr));100}101102impl ArgsLike for ArgsDesc {103	fn unnamed_len(&self) -> usize {104		self.unnamed.len()105	}106107	fn unnamed_iter(108		&self,109		ctx: Context,110		tailstrict: bool,111		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,112	) -> Result<()> {113		for (id, arg) in self.unnamed.iter().enumerate() {114			handler(115				id,116				if tailstrict {117					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)118				} else {119					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {120						context: ctx.clone(),121						expr: arg.clone(),122					})))123				},124			)?;125		}126		Ok(())127	}128129	fn named_iter(130		&self,131		ctx: Context,132		tailstrict: bool,133		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,134	) -> Result<()> {135		for (name, arg) in self.named.iter() {136			handler(137				name,138				if tailstrict {139					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)140				} else {141					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {142						context: ctx.clone(),143						expr: arg.clone(),144					})))145				},146			)?;147		}148		Ok(())149	}150151	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {152		for (name, _) in self.named.iter() {153			handler(name)154		}155	}156}157158impl<A: ArgLike> ArgsLike for [(IStr, A)] {159	fn unnamed_len(&self) -> usize {160		0161	}162163	fn unnamed_iter(164		&self,165		_ctx: Context,166		_tailstrict: bool,167		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,168	) -> Result<()> {169		Ok(())170	}171172	fn named_iter(173		&self,174		ctx: Context,175		tailstrict: bool,176		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,177	) -> Result<()> {178		for (name, val) in self.iter() {179			handler(name, val.evaluate_arg(ctx.clone(), tailstrict)?)?;180		}181		Ok(())182	}183184	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {185		for (name, _) in self.iter() {186			handler(name);187		}188	}189}190191impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {192	fn unnamed_len(&self) -> usize {193		0194	}195196	fn unnamed_iter(197		&self,198		_ctx: Context,199		_tailstrict: bool,200		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,201	) -> Result<()> {202		Ok(())203	}204205	fn named_iter(206		&self,207		ctx: Context,208		tailstrict: bool,209		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,210	) -> Result<()> {211		for (name, value) in self.iter() {212			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;213		}214		Ok(())215	}216217	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {218		for (name, _) in self.iter() {219			handler(name);220		}221	}222}223224impl<A: ArgLike> ArgsLike for [A] {225	fn unnamed_len(&self) -> usize {226		self.len()227	}228229	fn unnamed_iter(230		&self,231		ctx: Context,232		tailstrict: bool,233		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,234	) -> Result<()> {235		for (i, arg) in self.iter().enumerate() {236			handler(i, arg.evaluate_arg(ctx.clone(), tailstrict)?)?;237		}238		Ok(())239	}240241	fn named_iter(242		&self,243		_ctx: Context,244		_tailstrict: bool,245		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,246	) -> Result<()> {247		Ok(())248	}249250	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}251}252impl<A: ArgLike> ArgsLike for &[A] {253	fn unnamed_len(&self) -> usize {254		(*self).unnamed_len()255	}256257	fn unnamed_iter(258		&self,259		ctx: Context,260		tailstrict: bool,261		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,262	) -> Result<()> {263		(*self).unnamed_iter(ctx, tailstrict, handler)264	}265266	fn named_iter(267		&self,268		ctx: Context,269		tailstrict: bool,270		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,271	) -> Result<()> {272		(*self).named_iter(ctx, tailstrict, handler)273	}274275	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {276		(*self).named_names(handler)277	}278}279280/// Creates correct [context](Context) for function body evaluation returning error on invalid call.281///282/// ## Parameters283/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)284/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)285/// * `params`: function parameters' definition286/// * `args`: passed function arguments287/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily288pub fn parse_function_call(289	ctx: Context,290	body_ctx: Context,291	params: &ParamsDesc,292	args: &dyn ArgsLike,293	tailstrict: bool,294) -> Result<Context> {295	let mut passed_args = GcHashMap::with_capacity(params.len());296	if args.unnamed_len() > params.len() {297		throw!(TooManyArgsFunctionHas(params.len()))298	}299300	let mut filled_args = 0;301302	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {303		let name = params[id].0.clone();304		passed_args.insert(name, arg);305		filled_args += 1;306		Ok(())307	})?;308309	args.named_iter(ctx, tailstrict, &mut |name, value| {310		// FIXME: O(n) for arg existence check311		if !params.iter().any(|p| &p.0 == name) {312			throw!(UnknownFunctionParameter((name as &str).to_owned()));313		}314		if passed_args.insert(name.clone(), value).is_some() {315			throw!(BindingParameterASecondTime(name.clone()));316		}317		filled_args += 1;318		Ok(())319	})?;320321	if filled_args < params.len() {322		// Some args are unset, but maybe we have defaults for them323		// Default values should be created in newly created context324		let future_context = Context::new_future();325		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);326327		for param in params.iter().filter(|p| p.1.is_some()) {328			if passed_args.contains_key(&param.0.clone()) {329				continue;330			}331			LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {332				future_context: future_context.clone(),333				name: param.0.clone(),334				value: param.1.clone().unwrap(),335			})));336337			defaults.insert(338				param.0.clone(),339				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {340					future_context: future_context.clone(),341					name: param.0.clone(),342					value: param.1.clone().unwrap(),343				}))),344			);345			filled_args += 1;346		}347348		// Some args still wasn't filled349		if filled_args != params.len() {350			for param in params.iter().skip(args.unnamed_len()) {351				let mut found = false;352				args.named_names(&mut |name| {353					if name == &param.0 {354						found = true;355					}356				});357				if !found {358					throw!(FunctionParameterNotBoundInCall(param.0.clone()));359				}360			}361			unreachable!();362		}363364		Ok(body_ctx365			.extend(passed_args, None, None, None)366			.extend_bound(defaults)367			.into_future(future_context))368	} else {369		let body_ctx = body_ctx.extend(passed_args, None, None, None);370		Ok(body_ctx)371	}372}373374type BuiltinParamName = Cow<'static, str>;375376#[derive(Clone, Trace)]377pub struct BuiltinParam {378	pub name: BuiltinParamName,379	pub has_default: bool,380}381382/// Do not implement it directly, instead use #[builtin] macro383pub trait Builtin: Trace {384	fn name(&self) -> &str;385	fn params(&self) -> &[BuiltinParam];386	fn call(387		&self,388		context: Context,389		loc: Option<&ExprLocation>,390		args: &dyn ArgsLike,391	) -> Result<Val>;392}393394pub trait StaticBuiltin: Builtin + Send + Sync395where396	Self: 'static,397{398	// In impl, to make it object safe:399	// const INST: &'static Self;400}401402/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead403///404/// ## Parameters405/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)406/// * `params`: function parameters' definition407/// * `args`: passed function arguments408/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily409pub fn parse_builtin_call(410	ctx: Context,411	params: &[BuiltinParam],412	args: &dyn ArgsLike,413	tailstrict: bool,414) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {415	let mut passed_args = GcHashMap::with_capacity(params.len());416	if args.unnamed_len() > params.len() {417		throw!(TooManyArgsFunctionHas(params.len()))418	}419420	let mut filled_args = 0;421422	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {423		let name = params[id].name.clone();424		passed_args.insert(name, arg);425		filled_args += 1;426		Ok(())427	})?;428429	args.named_iter(ctx, tailstrict, &mut |name, arg| {430		// FIXME: O(n) for arg existence check431		let p = params432			.iter()433			.find(|p| p.name == name as &str)434			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;435		if passed_args.insert(p.name.clone(), arg).is_some() {436			throw!(BindingParameterASecondTime(name.clone()));437		}438		filled_args += 1;439		Ok(())440	})?;441442	if filled_args < params.len() {443		for param in params.iter().filter(|p| p.has_default) {444			if passed_args.contains_key(&param.name) {445				continue;446			}447			filled_args += 1;448		}449450		// Some args still wasn't filled451		if filled_args != params.len() {452			for param in params.iter().skip(args.unnamed_len()) {453				let mut found = false;454				args.named_names(&mut |name| {455					if name as &str == &param.name as &str {456						found = true;457					}458				});459				if !found {460					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));461				}462			}463			unreachable!();464		}465	}466	Ok(passed_args)467}468469/// Creates Context, which has all argument default values applied470/// and with unbound values causing error to be returned471pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {472	let ctx = Context::new_future();473474	let mut bindings = GcHashMap::new();475476	#[derive(Trace)]477	struct DependsOnUnbound(IStr);478	impl LazyValValue for DependsOnUnbound {479		fn get(self: Box<Self>) -> Result<Val> {480			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())481		}482	}483484	for param in params.iter() {485		if let Some(v) = &param.1 {486			bindings.insert(487				param.0.clone(),488				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {489					future_context: ctx.clone(),490					name: param.0.clone(),491					value: v.clone(),492				}))),493			);494		} else {495			bindings.insert(496				param.0.clone(),497				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),498			);499		}500	}501502	body_ctx.extend(bindings, None, None, None).into_future(ctx)503}