git.delta.rocks / jrsonnet / refs/commits / 0d5cefbba5bd

difftreelog

source

crates/jrsonnet-evaluator/src/function.rs12.1 KiBsourcehistory
1use std::{borrow::Cow, collections::HashMap, convert::TryFrom};23use gcmodule::Trace;4use jrsonnet_interner::IStr;5pub use jrsonnet_macros::builtin;6use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};78use crate::{9	error::{Error::*, LocError},10	evaluate, evaluate_named,11	gc::TraceBox,12	throw,13	typed::Typed,14	val::LazyValValue,15	Context, FutureWrapper, GcHashMap, LazyVal, Result, Val,16};1718#[derive(Clone, Copy)]19pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);20impl<'l> CallLocation<'l> {21	pub const fn new(loc: &'l ExprLocation) -> Self {22		Self(Some(loc))23	}24}25impl CallLocation<'static> {26	pub const fn native() -> Self {27		Self(None)28	}29}3031#[derive(Trace)]32struct EvaluateLazyVal {33	context: Context,34	expr: LocExpr,35}36impl LazyValValue for EvaluateLazyVal {37	fn get(self: Box<Self>) -> Result<Val> {38		evaluate(self.context, &self.expr)39	}40}4142#[derive(Trace)]43struct EvaluateNamedLazyVal {44	future_context: FutureWrapper<Context>,45	name: IStr,46	value: LocExpr,47}48impl LazyValValue for EvaluateNamedLazyVal {49	fn get(self: Box<Self>) -> Result<Val> {50		evaluate_named(self.future_context.unwrap(), &self.value, self.name)51	}52}5354pub trait ArgLike {55	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal>;56}57impl ArgLike for &LocExpr {58	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {59		Ok(if tailstrict {60			LazyVal::new_resolved(evaluate(ctx, self)?)61		} else {62			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {63				context: ctx,64				expr: (*self).clone(),65			})))66		})67	}68}69impl<T> ArgLike for T70where71	T: Typed + Clone,72	Val: TryFrom<T, Error = LocError>,73{74	fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {75		let val: Val = Val::try_from(self.clone())?;76		Ok(LazyVal::new_resolved(val))77	}78}79pub enum TlaArg {80	String(IStr),81	Code(LocExpr),82	Val(Val),83}84impl ArgLike for TlaArg {85	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {86		match self {87			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),88			TlaArg::Code(code) => Ok(if tailstrict {89				LazyVal::new_resolved(evaluate(ctx, code)?)90			} else {91				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {92					context: ctx,93					expr: code.clone(),94				})))95			}),96			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),97		}98	}99}100101pub trait ArgsLike {102	fn unnamed_len(&self) -> usize;103	fn unnamed_iter(104		&self,105		ctx: Context,106		tailstrict: bool,107		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,108	) -> Result<()>;109	fn named_iter(110		&self,111		ctx: Context,112		tailstrict: bool,113		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,114	) -> Result<()>;115	fn named_names(&self, handler: &mut dyn FnMut(&IStr));116}117118impl ArgsLike for ArgsDesc {119	fn unnamed_len(&self) -> usize {120		self.unnamed.len()121	}122123	fn unnamed_iter(124		&self,125		ctx: Context,126		tailstrict: bool,127		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,128	) -> Result<()> {129		for (id, arg) in self.unnamed.iter().enumerate() {130			handler(131				id,132				if tailstrict {133					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)134				} else {135					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {136						context: ctx.clone(),137						expr: arg.clone(),138					})))139				},140			)?;141		}142		Ok(())143	}144145	fn named_iter(146		&self,147		ctx: Context,148		tailstrict: bool,149		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,150	) -> Result<()> {151		for (name, arg) in self.named.iter() {152			handler(153				name,154				if tailstrict {155					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)156				} else {157					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {158						context: ctx.clone(),159						expr: arg.clone(),160					})))161				},162			)?;163		}164		Ok(())165	}166167	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {168		for (name, _) in self.named.iter() {169			handler(name)170		}171	}172}173174impl<A: ArgLike> ArgsLike for [(IStr, A)] {175	fn unnamed_len(&self) -> usize {176		0177	}178179	fn unnamed_iter(180		&self,181		_ctx: Context,182		_tailstrict: bool,183		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,184	) -> Result<()> {185		Ok(())186	}187188	fn named_iter(189		&self,190		ctx: Context,191		tailstrict: bool,192		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,193	) -> Result<()> {194		for (name, val) in self.iter() {195			handler(name, val.evaluate_arg(ctx.clone(), tailstrict)?)?;196		}197		Ok(())198	}199200	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {201		for (name, _) in self.iter() {202			handler(name);203		}204	}205}206207impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {208	fn unnamed_len(&self) -> usize {209		0210	}211212	fn unnamed_iter(213		&self,214		_ctx: Context,215		_tailstrict: bool,216		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,217	) -> Result<()> {218		Ok(())219	}220221	fn named_iter(222		&self,223		ctx: Context,224		tailstrict: bool,225		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,226	) -> Result<()> {227		for (name, value) in self.iter() {228			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;229		}230		Ok(())231	}232233	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {234		for (name, _) in self.iter() {235			handler(name);236		}237	}238}239240impl<A: ArgLike> ArgsLike for [A] {241	fn unnamed_len(&self) -> usize {242		self.len()243	}244245	fn unnamed_iter(246		&self,247		ctx: Context,248		tailstrict: bool,249		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,250	) -> Result<()> {251		for (i, arg) in self.iter().enumerate() {252			handler(i, arg.evaluate_arg(ctx.clone(), tailstrict)?)?;253		}254		Ok(())255	}256257	fn named_iter(258		&self,259		_ctx: Context,260		_tailstrict: bool,261		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,262	) -> Result<()> {263		Ok(())264	}265266	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}267}268impl<A: ArgLike> ArgsLike for &[A] {269	fn unnamed_len(&self) -> usize {270		(*self).unnamed_len()271	}272273	fn unnamed_iter(274		&self,275		ctx: Context,276		tailstrict: bool,277		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,278	) -> Result<()> {279		(*self).unnamed_iter(ctx, tailstrict, handler)280	}281282	fn named_iter(283		&self,284		ctx: Context,285		tailstrict: bool,286		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,287	) -> Result<()> {288		(*self).named_iter(ctx, tailstrict, handler)289	}290291	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {292		(*self).named_names(handler)293	}294}295296/// Creates correct [context](Context) for function body evaluation returning error on invalid call.297///298/// ## Parameters299/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)300/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)301/// * `params`: function parameters' definition302/// * `args`: passed function arguments303/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily304pub fn parse_function_call(305	ctx: Context,306	body_ctx: Context,307	params: &ParamsDesc,308	args: &dyn ArgsLike,309	tailstrict: bool,310) -> Result<Context> {311	let mut passed_args = GcHashMap::with_capacity(params.len());312	if args.unnamed_len() > params.len() {313		throw!(TooManyArgsFunctionHas(params.len()))314	}315316	let mut filled_args = 0;317318	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {319		let name = params[id].0.clone();320		passed_args.insert(name, arg);321		filled_args += 1;322		Ok(())323	})?;324325	args.named_iter(ctx, tailstrict, &mut |name, value| {326		// FIXME: O(n) for arg existence check327		if !params.iter().any(|p| &p.0 == name) {328			throw!(UnknownFunctionParameter((name as &str).to_owned()));329		}330		if passed_args.insert(name.clone(), value).is_some() {331			throw!(BindingParameterASecondTime(name.clone()));332		}333		filled_args += 1;334		Ok(())335	})?;336337	if filled_args < params.len() {338		// Some args are unset, but maybe we have defaults for them339		// Default values should be created in newly created context340		let future_context = Context::new_future();341		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);342343		for param in params.iter().filter(|p| p.1.is_some()) {344			if passed_args.contains_key(&param.0.clone()) {345				continue;346			}347			LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {348				future_context: future_context.clone(),349				name: param.0.clone(),350				value: param.1.clone().unwrap(),351			})));352353			defaults.insert(354				param.0.clone(),355				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {356					future_context: future_context.clone(),357					name: param.0.clone(),358					value: param.1.clone().unwrap(),359				}))),360			);361			filled_args += 1;362		}363364		// Some args still wasn't filled365		if filled_args != params.len() {366			for param in params.iter().skip(args.unnamed_len()) {367				let mut found = false;368				args.named_names(&mut |name| {369					if name == &param.0 {370						found = true;371					}372				});373				if !found {374					throw!(FunctionParameterNotBoundInCall(param.0.clone()));375				}376			}377			unreachable!();378		}379380		Ok(body_ctx381			.extend(passed_args, None, None, None)382			.extend_bound(defaults)383			.into_future(future_context))384	} else {385		let body_ctx = body_ctx.extend(passed_args, None, None, None);386		Ok(body_ctx)387	}388}389390type BuiltinParamName = Cow<'static, str>;391392#[derive(Clone, Trace)]393pub struct BuiltinParam {394	pub name: BuiltinParamName,395	pub has_default: bool,396}397398/// Do not implement it directly, instead use #[builtin] macro399pub trait Builtin: Trace {400	fn name(&self) -> &str;401	fn params(&self) -> &[BuiltinParam];402	fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;403}404405pub trait StaticBuiltin: Builtin + Send + Sync406where407	Self: 'static,408{409	// In impl, to make it object safe:410	// const INST: &'static Self;411}412413/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead414///415/// ## Parameters416/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)417/// * `params`: function parameters' definition418/// * `args`: passed function arguments419/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily420pub fn parse_builtin_call(421	ctx: Context,422	params: &[BuiltinParam],423	args: &dyn ArgsLike,424	tailstrict: bool,425) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {426	let mut passed_args = GcHashMap::with_capacity(params.len());427	if args.unnamed_len() > params.len() {428		throw!(TooManyArgsFunctionHas(params.len()))429	}430431	let mut filled_args = 0;432433	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {434		let name = params[id].name.clone();435		passed_args.insert(name, arg);436		filled_args += 1;437		Ok(())438	})?;439440	args.named_iter(ctx, tailstrict, &mut |name, arg| {441		// FIXME: O(n) for arg existence check442		let p = params443			.iter()444			.find(|p| p.name == name as &str)445			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;446		if passed_args.insert(p.name.clone(), arg).is_some() {447			throw!(BindingParameterASecondTime(name.clone()));448		}449		filled_args += 1;450		Ok(())451	})?;452453	if filled_args < params.len() {454		for param in params.iter().filter(|p| p.has_default) {455			if passed_args.contains_key(&param.name) {456				continue;457			}458			filled_args += 1;459		}460461		// Some args still wasn't filled462		if filled_args != params.len() {463			for param in params.iter().skip(args.unnamed_len()) {464				let mut found = false;465				args.named_names(&mut |name| {466					if name as &str == &param.name as &str {467						found = true;468					}469				});470				if !found {471					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));472				}473			}474			unreachable!();475		}476	}477	Ok(passed_args)478}479480/// Creates Context, which has all argument default values applied481/// and with unbound values causing error to be returned482pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {483	let ctx = Context::new_future();484485	let mut bindings = GcHashMap::new();486487	#[derive(Trace)]488	struct DependsOnUnbound(IStr);489	impl LazyValValue for DependsOnUnbound {490		fn get(self: Box<Self>) -> Result<Val> {491			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())492		}493	}494495	for param in params.iter() {496		if let Some(v) = &param.1 {497			bindings.insert(498				param.0.clone(),499				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {500					future_context: ctx.clone(),501					name: param.0.clone(),502					value: v.clone(),503				}))),504			);505		} else {506			bindings.insert(507				param.0.clone(),508				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),509			);510		}511	}512513	body_ctx.extend(bindings, None, None, None).into_future(ctx)514}