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

difftreelog

source

crates/jrsonnet-evaluator/src/function.rs12.6 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}48pub trait ArgLike {49	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;50}51impl ArgLike for &LocExpr {52	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {53		Ok(if tailstrict {54			LazyVal::new_resolved(evaluate(s, ctx, self)?)55		} else {56			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {57				ctx,58				expr: (*self).clone(),59			})))60		})61	}62}63impl<T> ArgLike for T64where65	T: Typed + Clone,66{67	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {68		let val = T::into_untyped(self.clone(), s)?;69		Ok(LazyVal::new_resolved(val))70	}71}72pub enum TlaArg {73	String(IStr),74	Code(LocExpr),75	Val(Val),76}77impl ArgLike for TlaArg {78	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {79		match self {80			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),81			TlaArg::Code(code) => Ok(if tailstrict {82				LazyVal::new_resolved(evaluate(s, ctx, code)?)83			} else {84				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {85					ctx,86					expr: code.clone(),87				})))88			}),89			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),90		}91	}92}9394pub trait ArgsLike {95	fn unnamed_len(&self) -> usize;96	fn unnamed_iter(97		&self,98		s: State,99		ctx: Context,100		tailstrict: bool,101		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,102	) -> Result<()>;103	fn named_iter(104		&self,105		s: State,106		ctx: Context,107		tailstrict: bool,108		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,109	) -> Result<()>;110	fn named_names(&self, handler: &mut dyn FnMut(&IStr));111}112113impl ArgsLike for ArgsDesc {114	fn unnamed_len(&self) -> usize {115		self.unnamed.len()116	}117118	fn unnamed_iter(119		&self,120		s: State,121		ctx: Context,122		tailstrict: bool,123		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,124	) -> Result<()> {125		for (id, arg) in self.unnamed.iter().enumerate() {126			handler(127				id,128				if tailstrict {129					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)130				} else {131					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {132						ctx: ctx.clone(),133						expr: arg.clone(),134					})))135				},136			)?;137		}138		Ok(())139	}140141	fn named_iter(142		&self,143		s: State,144		ctx: Context,145		tailstrict: bool,146		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,147	) -> Result<()> {148		for (name, arg) in &self.named {149			handler(150				name,151				if tailstrict {152					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)153				} else {154					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {155						ctx: 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 {166			handler(name);167		}168	}169}170171impl ArgsLike for [(); 0] {172	fn unnamed_len(&self) -> usize {173		0174	}175176	fn unnamed_iter(177		&self,178		_s: State,179		_ctx: Context,180		_tailstrict: bool,181		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,182	) -> Result<()> {183		Ok(())184	}185186	fn named_iter(187		&self,188		_s: State,189		_ctx: Context,190		_tailstrict: bool,191		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,192	) -> Result<()> {193		Ok(())194	}195196	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}197}198199impl<A: ArgLike> ArgsLike for [(IStr, A)] {200	fn unnamed_len(&self) -> usize {201		0202	}203204	fn unnamed_iter(205		&self,206		_s: State,207		_ctx: Context,208		_tailstrict: bool,209		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,210	) -> Result<()> {211		Ok(())212	}213214	fn named_iter(215		&self,216		s: State,217		ctx: Context,218		tailstrict: bool,219		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,220	) -> Result<()> {221		for (name, val) in self.iter() {222			handler(name, val.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;223		}224		Ok(())225	}226227	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {228		for (name, _) in self.iter() {229			handler(name);230		}231	}232}233234impl<A: ArgLike, S> ArgsLike for HashMap<IStr, A, S> {235	fn unnamed_len(&self) -> usize {236		0237	}238239	fn unnamed_iter(240		&self,241		_s: State,242		_ctx: Context,243		_tailstrict: bool,244		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,245	) -> Result<()> {246		Ok(())247	}248249	fn named_iter(250		&self,251		s: State,252		ctx: Context,253		tailstrict: bool,254		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,255	) -> Result<()> {256		for (name, value) in self.iter() {257			handler(258				name,259				value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,260			)?;261		}262		Ok(())263	}264265	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {266		for (name, _) in self.iter() {267			handler(name);268		}269	}270}271272impl<A: ArgLike> ArgsLike for [A] {273	fn unnamed_len(&self) -> usize {274		self.len()275	}276277	fn unnamed_iter(278		&self,279		s: State,280		ctx: Context,281		tailstrict: bool,282		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,283	) -> Result<()> {284		for (i, arg) in self.iter().enumerate() {285			handler(i, arg.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;286		}287		Ok(())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		Ok(())298	}299300	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}301}302impl<A: ArgLike> ArgsLike for &[A] {303	fn unnamed_len(&self) -> usize {304		(*self).unnamed_len()305	}306307	fn unnamed_iter(308		&self,309		s: State,310		ctx: Context,311		tailstrict: bool,312		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,313	) -> Result<()> {314		(*self).unnamed_iter(s, ctx, tailstrict, handler)315	}316317	fn named_iter(318		&self,319		s: State,320		ctx: Context,321		tailstrict: bool,322		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,323	) -> Result<()> {324		(*self).named_iter(s, ctx, tailstrict, handler)325	}326327	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {328		(*self).named_names(handler);329	}330}331332/// Creates correct [context](Context) for function body evaluation returning error on invalid call.333///334/// ## Parameters335/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)336/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)337/// * `params`: function parameters' definition338/// * `args`: passed function arguments339/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily340pub fn parse_function_call(341	s: State,342	ctx: Context,343	body_ctx: Context,344	params: &ParamsDesc,345	args: &dyn ArgsLike,346	tailstrict: bool,347) -> Result<Context> {348	let mut passed_args = GcHashMap::with_capacity(params.len());349	if args.unnamed_len() > params.len() {350		throw!(TooManyArgsFunctionHas(params.len()))351	}352353	let mut filled_args = 0;354355	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {356		let name = params[id].0.clone();357		passed_args.insert(name, arg);358		filled_args += 1;359		Ok(())360	})?;361362	args.named_iter(s, ctx, tailstrict, &mut |name, value| {363		// FIXME: O(n) for arg existence check364		if !params.iter().any(|p| &p.0 == name) {365			throw!(UnknownFunctionParameter((name as &str).to_owned()));366		}367		if passed_args.insert(name.clone(), value).is_some() {368			throw!(BindingParameterASecondTime(name.clone()));369		}370		filled_args += 1;371		Ok(())372	})?;373374	if filled_args < params.len() {375		// Some args are unset, but maybe we have defaults for them376		// Default values should be created in newly created context377		let fctx = Context::new_future();378		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);379380		for param in params.iter().filter(|p| p.1.is_some()) {381			if passed_args.contains_key(&param.0.clone()) {382				continue;383			}384385			defaults.insert(386				param.0.clone(),387				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {388					ctx: fctx.clone(),389					name: param.0.clone(),390					value: param.1.clone().expect("default exists"),391				}))),392			);393			filled_args += 1;394		}395396		// Some args still wasn't filled397		if filled_args != params.len() {398			for param in params.iter().skip(args.unnamed_len()) {399				let mut found = false;400				args.named_names(&mut |name| {401					if name == &param.0 {402						found = true;403					}404				});405				if !found {406					throw!(FunctionParameterNotBoundInCall(param.0.clone()));407				}408			}409			unreachable!();410		}411412		Ok(body_ctx413			.extend(passed_args, None, None, None)414			.extend_bound(defaults)415			.into_future(fctx))416	} else {417		let body_ctx = body_ctx.extend(passed_args, None, None, None);418		Ok(body_ctx)419	}420}421422type BuiltinParamName = Cow<'static, str>;423424#[derive(Clone, Trace)]425pub struct BuiltinParam {426	pub name: BuiltinParamName,427	pub has_default: bool,428}429430/// Do not implement it directly, instead use #[builtin] macro431pub trait Builtin: Trace {432	fn name(&self) -> &str;433	fn params(&self) -> &[BuiltinParam];434	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;435}436437pub trait StaticBuiltin: Builtin + Send + Sync438where439	Self: 'static,440{441	// In impl, to make it object safe:442	// const INST: &'static Self;443}444445/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead446///447/// ## Parameters448/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)449/// * `params`: function parameters' definition450/// * `args`: passed function arguments451/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily452pub fn parse_builtin_call(453	s: State,454	ctx: Context,455	params: &[BuiltinParam],456	args: &dyn ArgsLike,457	tailstrict: bool,458) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {459	let mut passed_args = GcHashMap::with_capacity(params.len());460	if args.unnamed_len() > params.len() {461		throw!(TooManyArgsFunctionHas(params.len()))462	}463464	let mut filled_args = 0;465466	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {467		let name = params[id].name.clone();468		passed_args.insert(name, arg);469		filled_args += 1;470		Ok(())471	})?;472473	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {474		// FIXME: O(n) for arg existence check475		let p = params476			.iter()477			.find(|p| p.name == name as &str)478			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;479		if passed_args.insert(p.name.clone(), arg).is_some() {480			throw!(BindingParameterASecondTime(name.clone()));481		}482		filled_args += 1;483		Ok(())484	})?;485486	if filled_args < params.len() {487		for param in params.iter().filter(|p| p.has_default) {488			if passed_args.contains_key(&param.name) {489				continue;490			}491			filled_args += 1;492		}493494		// Some args still wasn't filled495		if filled_args != params.len() {496			for param in params.iter().skip(args.unnamed_len()) {497				let mut found = false;498				args.named_names(&mut |name| {499					if name as &str == &param.name as &str {500						found = true;501					}502				});503				if !found {504					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));505				}506			}507			unreachable!();508		}509	}510	Ok(passed_args)511}512513/// Creates Context, which has all argument default values applied514/// and with unbound values causing error to be returned515pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {516	#[derive(Trace)]517	struct DependsOnUnbound(IStr);518	impl LazyValValue for DependsOnUnbound {519		fn get(self: Box<Self>, _: State) -> Result<Val> {520			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())521		}522	}523524	let fctx = Context::new_future();525526	let mut bindings = GcHashMap::new();527528	for param in params.iter() {529		if let Some(v) = &param.1 {530			bindings.insert(531				param.0.clone(),532				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {533					ctx: fctx.clone(),534					name: param.0.clone(),535					value: v.clone(),536				}))),537			);538		} else {539			bindings.insert(540				param.0.clone(),541				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),542			);543		}544	}545546	body_ctx547		.extend(bindings, None, None, None)548		.into_future(fctx)549}