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

difftreelog

source

crates/jrsonnet-evaluator/src/function.rs12.2 KiBsourcehistory
1use std::{borrow::Cow, collections::HashMap};23use gcmodule::Trace;4use jrsonnet_interner::IStr;5pub use jrsonnet_macros::builtin;6use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};78use crate::{9	error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, typed::Typed,10	val::LazyValValue, Context, FutureWrapper, GcHashMap, LazyVal, Result, State, Val,11};1213#[derive(Clone, Copy)]14pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);15impl<'l> CallLocation<'l> {16	pub const fn new(loc: &'l ExprLocation) -> Self {17		Self(Some(loc))18	}19}20impl CallLocation<'static> {21	pub const fn native() -> Self {22		Self(None)23	}24}2526#[derive(Trace)]27struct EvaluateLazyVal {28	ctx: Context,29	expr: LocExpr,30}31impl LazyValValue for EvaluateLazyVal {32	fn get(self: Box<Self>, s: State) -> Result<Val> {33		evaluate(s, self.ctx, &self.expr)34	}35}3637#[derive(Trace)]38struct EvaluateNamedLazyVal {39	ctx: FutureWrapper<Context>,40	name: IStr,41	value: LocExpr,42}43impl LazyValValue for EvaluateNamedLazyVal {44	fn get(self: Box<Self>, s: State) -> Result<Val> {45		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)46	}47}4849pub trait ArgLike {50	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;51}52impl ArgLike for &LocExpr {53	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {54		Ok(if tailstrict {55			LazyVal::new_resolved(evaluate(s, ctx, self)?)56		} else {57			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {58				ctx,59				expr: (*self).clone(),60			})))61		})62	}63}64impl<T> ArgLike for T65where66	T: Typed + Clone,67{68	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {69		let val = T::into_untyped(self.clone(), s)?;70		Ok(LazyVal::new_resolved(val))71	}72}73pub enum TlaArg {74	String(IStr),75	Code(LocExpr),76	Val(Val),77}78impl ArgLike for TlaArg {79	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {80		match self {81			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),82			TlaArg::Code(code) => Ok(if tailstrict {83				LazyVal::new_resolved(evaluate(s, ctx, code)?)84			} else {85				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {86					ctx,87					expr: code.clone(),88				})))89			}),90			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),91		}92	}93}9495pub trait ArgsLike {96	fn unnamed_len(&self) -> usize;97	fn unnamed_iter(98		&self,99		s: State,100		ctx: Context,101		tailstrict: bool,102		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,103	) -> Result<()>;104	fn named_iter(105		&self,106		s: State,107		ctx: Context,108		tailstrict: bool,109		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,110	) -> Result<()>;111	fn named_names(&self, handler: &mut dyn FnMut(&IStr));112}113114impl ArgsLike for ArgsDesc {115	fn unnamed_len(&self) -> usize {116		self.unnamed.len()117	}118119	fn unnamed_iter(120		&self,121		s: State,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(s.clone(), ctx.clone(), arg)?)131				} else {132					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {133						ctx: ctx.clone(),134						expr: arg.clone(),135					})))136				},137			)?;138		}139		Ok(())140	}141142	fn named_iter(143		&self,144		s: State,145		ctx: Context,146		tailstrict: bool,147		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,148	) -> Result<()> {149		for (name, arg) in self.named.iter() {150			handler(151				name,152				if tailstrict {153					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)154				} else {155					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {156						ctx: ctx.clone(),157						expr: arg.clone(),158					})))159				},160			)?;161		}162		Ok(())163	}164165	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {166		for (name, _) in self.named.iter() {167			handler(name)168		}169	}170}171172impl<A: ArgLike> ArgsLike for [(IStr, A)] {173	fn unnamed_len(&self) -> usize {174		0175	}176177	fn unnamed_iter(178		&self,179		_s: State,180		_ctx: Context,181		_tailstrict: bool,182		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,183	) -> Result<()> {184		Ok(())185	}186187	fn named_iter(188		&self,189		s: State,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(s.clone(), 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		_s: State,215		_ctx: Context,216		_tailstrict: bool,217		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,218	) -> Result<()> {219		Ok(())220	}221222	fn named_iter(223		&self,224		s: State,225		ctx: Context,226		tailstrict: bool,227		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,228	) -> Result<()> {229		for (name, value) in self.iter() {230			handler(231				name,232				value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,233			)?;234		}235		Ok(())236	}237238	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {239		for (name, _) in self.iter() {240			handler(name);241		}242	}243}244245impl<A: ArgLike> ArgsLike for [A] {246	fn unnamed_len(&self) -> usize {247		self.len()248	}249250	fn unnamed_iter(251		&self,252		s: State,253		ctx: Context,254		tailstrict: bool,255		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,256	) -> Result<()> {257		for (i, arg) in self.iter().enumerate() {258			handler(i, arg.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;259		}260		Ok(())261	}262263	fn named_iter(264		&self,265		_s: State,266		_ctx: Context,267		_tailstrict: bool,268		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,269	) -> Result<()> {270		Ok(())271	}272273	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}274}275impl<A: ArgLike> ArgsLike for &[A] {276	fn unnamed_len(&self) -> usize {277		(*self).unnamed_len()278	}279280	fn unnamed_iter(281		&self,282		s: State,283		ctx: Context,284		tailstrict: bool,285		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,286	) -> Result<()> {287		(*self).unnamed_iter(s, ctx, tailstrict, handler)288	}289290	fn named_iter(291		&self,292		s: State,293		ctx: Context,294		tailstrict: bool,295		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,296	) -> Result<()> {297		(*self).named_iter(s, ctx, tailstrict, handler)298	}299300	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {301		(*self).named_names(handler)302	}303}304305/// Creates correct [context](Context) for function body evaluation returning error on invalid call.306///307/// ## Parameters308/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)309/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)310/// * `params`: function parameters' definition311/// * `args`: passed function arguments312/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily313pub fn parse_function_call(314	s: State,315	ctx: Context,316	body_ctx: Context,317	params: &ParamsDesc,318	args: &dyn ArgsLike,319	tailstrict: bool,320) -> Result<Context> {321	let mut passed_args = GcHashMap::with_capacity(params.len());322	if args.unnamed_len() > params.len() {323		throw!(TooManyArgsFunctionHas(params.len()))324	}325326	let mut filled_args = 0;327328	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {329		let name = params[id].0.clone();330		passed_args.insert(name, arg);331		filled_args += 1;332		Ok(())333	})?;334335	args.named_iter(s, ctx, tailstrict, &mut |name, value| {336		// FIXME: O(n) for arg existence check337		if !params.iter().any(|p| &p.0 == name) {338			throw!(UnknownFunctionParameter((name as &str).to_owned()));339		}340		if passed_args.insert(name.clone(), value).is_some() {341			throw!(BindingParameterASecondTime(name.clone()));342		}343		filled_args += 1;344		Ok(())345	})?;346347	if filled_args < params.len() {348		// Some args are unset, but maybe we have defaults for them349		// Default values should be created in newly created context350		let fctx = Context::new_future();351		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);352353		for param in params.iter().filter(|p| p.1.is_some()) {354			if passed_args.contains_key(&param.0.clone()) {355				continue;356			}357			LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {358				ctx: fctx.clone(),359				name: param.0.clone(),360				value: param.1.clone().unwrap(),361			})));362363			defaults.insert(364				param.0.clone(),365				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {366					ctx: fctx.clone(),367					name: param.0.clone(),368					value: param.1.clone().unwrap(),369				}))),370			);371			filled_args += 1;372		}373374		// Some args still wasn't filled375		if filled_args != params.len() {376			for param in params.iter().skip(args.unnamed_len()) {377				let mut found = false;378				args.named_names(&mut |name| {379					if name == &param.0 {380						found = true;381					}382				});383				if !found {384					throw!(FunctionParameterNotBoundInCall(param.0.clone()));385				}386			}387			unreachable!();388		}389390		Ok(body_ctx391			.extend(passed_args, None, None, None)392			.extend_bound(defaults)393			.into_future(fctx))394	} else {395		let body_ctx = body_ctx.extend(passed_args, None, None, None);396		Ok(body_ctx)397	}398}399400type BuiltinParamName = Cow<'static, str>;401402#[derive(Clone, Trace)]403pub struct BuiltinParam {404	pub name: BuiltinParamName,405	pub has_default: bool,406}407408/// Do not implement it directly, instead use #[builtin] macro409pub trait Builtin: Trace {410	fn name(&self) -> &str;411	fn params(&self) -> &[BuiltinParam];412	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;413}414415pub trait StaticBuiltin: Builtin + Send + Sync416where417	Self: 'static,418{419	// In impl, to make it object safe:420	// const INST: &'static Self;421}422423/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead424///425/// ## Parameters426/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)427/// * `params`: function parameters' definition428/// * `args`: passed function arguments429/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily430pub fn parse_builtin_call(431	s: State,432	ctx: Context,433	params: &[BuiltinParam],434	args: &dyn ArgsLike,435	tailstrict: bool,436) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {437	let mut passed_args = GcHashMap::with_capacity(params.len());438	if args.unnamed_len() > params.len() {439		throw!(TooManyArgsFunctionHas(params.len()))440	}441442	let mut filled_args = 0;443444	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {445		let name = params[id].name.clone();446		passed_args.insert(name, arg);447		filled_args += 1;448		Ok(())449	})?;450451	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {452		// FIXME: O(n) for arg existence check453		let p = params454			.iter()455			.find(|p| p.name == name as &str)456			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;457		if passed_args.insert(p.name.clone(), arg).is_some() {458			throw!(BindingParameterASecondTime(name.clone()));459		}460		filled_args += 1;461		Ok(())462	})?;463464	if filled_args < params.len() {465		for param in params.iter().filter(|p| p.has_default) {466			if passed_args.contains_key(&param.name) {467				continue;468			}469			filled_args += 1;470		}471472		// Some args still wasn't filled473		if filled_args != params.len() {474			for param in params.iter().skip(args.unnamed_len()) {475				let mut found = false;476				args.named_names(&mut |name| {477					if name as &str == &param.name as &str {478						found = true;479					}480				});481				if !found {482					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));483				}484			}485			unreachable!();486		}487	}488	Ok(passed_args)489}490491/// Creates Context, which has all argument default values applied492/// and with unbound values causing error to be returned493pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {494	let fctx = Context::new_future();495496	let mut bindings = GcHashMap::new();497498	#[derive(Trace)]499	struct DependsOnUnbound(IStr);500	impl LazyValValue for DependsOnUnbound {501		fn get(self: Box<Self>, _: State) -> Result<Val> {502			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())503		}504	}505506	for param in params.iter() {507		if let Some(v) = &param.1 {508			bindings.insert(509				param.0.clone(),510				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {511					ctx: fctx.clone(),512					name: param.0.clone(),513					value: v.clone(),514				}))),515			);516		} else {517			bindings.insert(518				param.0.clone(),519				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),520			);521		}522	}523524	body_ctx525		.extend(bindings, None, None, None)526		.into_future(fctx)527}