git.delta.rocks / jrsonnet / refs/commits / 4f4be44d138e

difftreelog

source

crates/jrsonnet-evaluator/src/function.rs12.1 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(Clone, Copy)]16pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);17impl<'l> CallLocation<'l> {18	pub const fn new(loc: &'l ExprLocation) -> Self {19		Self(Some(loc))20	}21}22impl CallLocation<'static> {23	pub const fn native() -> Self {24		Self(None)25	}26}2728#[derive(Trace)]29struct EvaluateLazyVal {30	context: Context,31	expr: LocExpr,32}33impl LazyValValue for EvaluateLazyVal {34	fn get(self: Box<Self>) -> Result<Val> {35		evaluate(self.context, &self.expr)36	}37}3839#[derive(Trace)]40struct EvaluateNamedLazyVal {41	future_context: FutureWrapper<Context>,42	name: IStr,43	value: LocExpr,44}45impl LazyValValue for EvaluateNamedLazyVal {46	fn get(self: Box<Self>) -> Result<Val> {47		evaluate_named(self.future_context.unwrap(), &self.value, self.name)48	}49}5051pub trait ArgLike {52	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal>;53}54impl ArgLike for &LocExpr {55	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {56		Ok(if tailstrict {57			LazyVal::new_resolved(evaluate(ctx, self)?)58		} else {59			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {60				context: ctx,61				expr: (*self).clone(),62			})))63		})64	}65}66impl<T> ArgLike for T67where68	T: Typed + Clone,69	Val: TryFrom<T, Error = LocError>,70{71	fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {72		let val: Val = Val::try_from(self.clone())?;73		Ok(LazyVal::new_resolved(val))74	}75}76pub enum TlaArg {77	String(IStr),78	Code(LocExpr),79	Val(Val),80}81impl ArgLike for TlaArg {82	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {83		match self {84			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),85			TlaArg::Code(code) => Ok(if tailstrict {86				LazyVal::new_resolved(evaluate(ctx, code)?)87			} else {88				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {89					context: ctx,90					expr: code.clone(),91				})))92			}),93			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),94		}95	}96}9798pub trait ArgsLike {99	fn unnamed_len(&self) -> usize;100	fn unnamed_iter(101		&self,102		ctx: Context,103		tailstrict: bool,104		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,105	) -> Result<()>;106	fn named_iter(107		&self,108		ctx: Context,109		tailstrict: bool,110		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,111	) -> Result<()>;112	fn named_names(&self, handler: &mut dyn FnMut(&IStr));113}114115impl ArgsLike for ArgsDesc {116	fn unnamed_len(&self) -> usize {117		self.unnamed.len()118	}119120	fn unnamed_iter(121		&self,122		ctx: Context,123		tailstrict: bool,124		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,125	) -> Result<()> {126		for (id, arg) in self.unnamed.iter().enumerate() {127			handler(128				id,129				if tailstrict {130					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)131				} else {132					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {133						context: ctx.clone(),134						expr: arg.clone(),135					})))136				},137			)?;138		}139		Ok(())140	}141142	fn named_iter(143		&self,144		ctx: Context,145		tailstrict: bool,146		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,147	) -> Result<()> {148		for (name, arg) in self.named.iter() {149			handler(150				name,151				if tailstrict {152					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)153				} else {154					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {155						context: ctx.clone(),156						expr: arg.clone(),157					})))158				},159			)?;160		}161		Ok(())162	}163164	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {165		for (name, _) in self.named.iter() {166			handler(name)167		}168	}169}170171impl<A: ArgLike> ArgsLike for [(IStr, A)] {172	fn unnamed_len(&self) -> usize {173		0174	}175176	fn unnamed_iter(177		&self,178		_ctx: Context,179		_tailstrict: bool,180		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,181	) -> Result<()> {182		Ok(())183	}184185	fn named_iter(186		&self,187		ctx: Context,188		tailstrict: bool,189		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,190	) -> Result<()> {191		for (name, val) in self.iter() {192			handler(name, val.evaluate_arg(ctx.clone(), tailstrict)?)?;193		}194		Ok(())195	}196197	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {198		for (name, _) in self.iter() {199			handler(name);200		}201	}202}203204impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {205	fn unnamed_len(&self) -> usize {206		0207	}208209	fn unnamed_iter(210		&self,211		_ctx: Context,212		_tailstrict: bool,213		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,214	) -> Result<()> {215		Ok(())216	}217218	fn named_iter(219		&self,220		ctx: Context,221		tailstrict: bool,222		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,223	) -> Result<()> {224		for (name, value) in self.iter() {225			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;226		}227		Ok(())228	}229230	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {231		for (name, _) in self.iter() {232			handler(name);233		}234	}235}236237impl<A: ArgLike> ArgsLike for [A] {238	fn unnamed_len(&self) -> usize {239		self.len()240	}241242	fn unnamed_iter(243		&self,244		ctx: Context,245		tailstrict: bool,246		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,247	) -> Result<()> {248		for (i, arg) in self.iter().enumerate() {249			handler(i, arg.evaluate_arg(ctx.clone(), tailstrict)?)?;250		}251		Ok(())252	}253254	fn named_iter(255		&self,256		_ctx: Context,257		_tailstrict: bool,258		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,259	) -> Result<()> {260		Ok(())261	}262263	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}264}265impl<A: ArgLike> ArgsLike for &[A] {266	fn unnamed_len(&self) -> usize {267		(*self).unnamed_len()268	}269270	fn unnamed_iter(271		&self,272		ctx: Context,273		tailstrict: bool,274		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,275	) -> Result<()> {276		(*self).unnamed_iter(ctx, tailstrict, handler)277	}278279	fn named_iter(280		&self,281		ctx: Context,282		tailstrict: bool,283		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,284	) -> Result<()> {285		(*self).named_iter(ctx, tailstrict, handler)286	}287288	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {289		(*self).named_names(handler)290	}291}292293/// Creates correct [context](Context) for function body evaluation returning error on invalid call.294///295/// ## Parameters296/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)297/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)298/// * `params`: function parameters' definition299/// * `args`: passed function arguments300/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily301pub fn parse_function_call(302	ctx: Context,303	body_ctx: Context,304	params: &ParamsDesc,305	args: &dyn ArgsLike,306	tailstrict: bool,307) -> Result<Context> {308	let mut passed_args = GcHashMap::with_capacity(params.len());309	if args.unnamed_len() > params.len() {310		throw!(TooManyArgsFunctionHas(params.len()))311	}312313	let mut filled_args = 0;314315	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {316		let name = params[id].0.clone();317		passed_args.insert(name, arg);318		filled_args += 1;319		Ok(())320	})?;321322	args.named_iter(ctx, tailstrict, &mut |name, value| {323		// FIXME: O(n) for arg existence check324		if !params.iter().any(|p| &p.0 == name) {325			throw!(UnknownFunctionParameter((name as &str).to_owned()));326		}327		if passed_args.insert(name.clone(), value).is_some() {328			throw!(BindingParameterASecondTime(name.clone()));329		}330		filled_args += 1;331		Ok(())332	})?;333334	if filled_args < params.len() {335		// Some args are unset, but maybe we have defaults for them336		// Default values should be created in newly created context337		let future_context = Context::new_future();338		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);339340		for param in params.iter().filter(|p| p.1.is_some()) {341			if passed_args.contains_key(&param.0.clone()) {342				continue;343			}344			LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {345				future_context: future_context.clone(),346				name: param.0.clone(),347				value: param.1.clone().unwrap(),348			})));349350			defaults.insert(351				param.0.clone(),352				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {353					future_context: future_context.clone(),354					name: param.0.clone(),355					value: param.1.clone().unwrap(),356				}))),357			);358			filled_args += 1;359		}360361		// Some args still wasn't filled362		if filled_args != params.len() {363			for param in params.iter().skip(args.unnamed_len()) {364				let mut found = false;365				args.named_names(&mut |name| {366					if name == &param.0 {367						found = true;368					}369				});370				if !found {371					throw!(FunctionParameterNotBoundInCall(param.0.clone()));372				}373			}374			unreachable!();375		}376377		Ok(body_ctx378			.extend(passed_args, None, None, None)379			.extend_bound(defaults)380			.into_future(future_context))381	} else {382		let body_ctx = body_ctx.extend(passed_args, None, None, None);383		Ok(body_ctx)384	}385}386387type BuiltinParamName = Cow<'static, str>;388389#[derive(Clone, Trace)]390pub struct BuiltinParam {391	pub name: BuiltinParamName,392	pub has_default: bool,393}394395/// Do not implement it directly, instead use #[builtin] macro396pub trait Builtin: Trace {397	fn name(&self) -> &str;398	fn params(&self) -> &[BuiltinParam];399	fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;400}401402pub trait StaticBuiltin: Builtin + Send + Sync403where404	Self: 'static,405{406	// In impl, to make it object safe:407	// const INST: &'static Self;408}409410/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead411///412/// ## Parameters413/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)414/// * `params`: function parameters' definition415/// * `args`: passed function arguments416/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily417pub fn parse_builtin_call(418	ctx: Context,419	params: &[BuiltinParam],420	args: &dyn ArgsLike,421	tailstrict: bool,422) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {423	let mut passed_args = GcHashMap::with_capacity(params.len());424	if args.unnamed_len() > params.len() {425		throw!(TooManyArgsFunctionHas(params.len()))426	}427428	let mut filled_args = 0;429430	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {431		let name = params[id].name.clone();432		passed_args.insert(name, arg);433		filled_args += 1;434		Ok(())435	})?;436437	args.named_iter(ctx, tailstrict, &mut |name, arg| {438		// FIXME: O(n) for arg existence check439		let p = params440			.iter()441			.find(|p| p.name == name as &str)442			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;443		if passed_args.insert(p.name.clone(), arg).is_some() {444			throw!(BindingParameterASecondTime(name.clone()));445		}446		filled_args += 1;447		Ok(())448	})?;449450	if filled_args < params.len() {451		for param in params.iter().filter(|p| p.has_default) {452			if passed_args.contains_key(&param.name) {453				continue;454			}455			filled_args += 1;456		}457458		// Some args still wasn't filled459		if filled_args != params.len() {460			for param in params.iter().skip(args.unnamed_len()) {461				let mut found = false;462				args.named_names(&mut |name| {463					if name as &str == &param.name as &str {464						found = true;465					}466				});467				if !found {468					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));469				}470			}471			unreachable!();472		}473	}474	Ok(passed_args)475}476477/// Creates Context, which has all argument default values applied478/// and with unbound values causing error to be returned479pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {480	let ctx = Context::new_future();481482	let mut bindings = GcHashMap::new();483484	#[derive(Trace)]485	struct DependsOnUnbound(IStr);486	impl LazyValValue for DependsOnUnbound {487		fn get(self: Box<Self>) -> Result<Val> {488			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())489		}490	}491492	for param in params.iter() {493		if let Some(v) = &param.1 {494			bindings.insert(495				param.0.clone(),496				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {497					future_context: ctx.clone(),498					name: param.0.clone(),499					value: v.clone(),500				}))),501			);502		} else {503			bindings.insert(504				param.0.clone(),505				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),506			);507		}508	}509510	body_ctx.extend(bindings, None, None, None).into_future(ctx)511}