git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

source

crates/jrsonnet-evaluator/src/function/mod.rs5.8 KiBsourcehistory
1use std::{fmt::Debug, rc::Rc};23use educe::Educe;4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6use jrsonnet_ir::Span;7pub use jrsonnet_macros::builtin;89use self::{10	builtin::Builtin,11	prepared::{parse_prepared_builtin_call, PreparedCall},12};13use crate::{14	analyze::{LDestruct, LExpr, LFunction},15	evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},16	function::builtin::BuiltinFunc,17	Context, ContextBuilder, Result, Thunk, Val,18};1920pub mod builtin;21mod native;22mod parse;23pub(crate) mod prepared;2425pub use jrsonnet_ir::function::*;26pub use native::NativeFn;27pub(crate) use prepared::PreparedFuncVal;2829/// Function callsite location.30/// Either from other jsonnet code, specified by expression location, or from native (without location).31#[derive(Clone, Copy)]32pub struct CallLocation<'l>(pub Option<&'l Span>);33impl<'l> CallLocation<'l> {34	/// Construct new location for calls coming from specified jsonnet expression location.35	pub const fn new(loc: &'l Span) -> Self {36		Self(Some(loc))37	}38}39impl CallLocation<'static> {40	/// Construct new location for calls coming from native code.41	pub const fn native() -> Self {42		Self(None)43	}44}4546/// Represents Jsonnet function defined in code.47#[derive(Trace, Educe)]48#[educe(Debug, PartialEq)]49pub struct FuncDesc {50	/// # Example51	///52	/// In expressions like this, deducted to `a`, unspecified otherwise.53	/// ```jsonnet54	/// local a = function() ...55	/// local a() ...56	/// { a: function() ... }57	/// { a() = ... }58	/// ```59	pub name: IStr,60	/// Context, in which this function was evaluated.61	///62	/// # Example63	/// In64	/// ```jsonnet65	/// local a = 2;66	/// function() ...67	/// ```68	/// context will contain `a`.69	pub ctx: Context,7071	#[educe(PartialEq(method = Rc::ptr_eq))]72	pub func: Rc<LFunction>,73}7475impl FuncDesc {76	pub fn signature(&self) -> FunctionSignature {77		self.func.signature.clone()78	}7980	pub fn call(81		&self,82		unnamed: &[Thunk<Val>],83		named: &[Thunk<Val>],84		prepared: &PreparedCall,85	) -> Result<Val> {86		let has_defaults = !prepared.defaults().is_empty();87		let mut builder = ContextBuilder::extend(self.ctx.clone(), self.func.params.len());8889		let fctx = Context::new_future();90		for (param_idx, thunk) in unnamed.iter().enumerate() {91			destruct(92				&self.func.params[param_idx].destruct,93				thunk.clone(),94				fctx.clone(),95				&mut builder,96			);97		}9899		for &(param_idx, arg_idx) in prepared.named() {100			destruct(101				&self.func.params[param_idx].destruct,102				named[arg_idx].clone(),103				fctx.clone(),104				&mut builder,105			);106		}107108		if has_defaults {109			for &param_idx in prepared.defaults() {110				let param = &self.func.params[param_idx];111				if let Some(default_expr) = &param.default {112					let default_expr = default_expr.clone();113					let fctxc = fctx.clone();114					let thunk = Thunk!(move || {115						let ctx = fctxc.unwrap();116						evaluate(ctx, &default_expr)117					});118					destruct(&param.destruct, thunk, fctx.clone(), &mut builder);119				}120			}121		};122		let ctx = builder.build().into_future(fctx);123		ensure_sufficient_stack(|| evaluate(ctx, &self.func.body))124	}125126	pub fn evaluate_trivial(&self) -> Option<Val> {127		evaluate_trivial(&self.func.body)128	}129}130131/// Represents a Jsonnet function value, including plain functions and user-provided builtins.132#[allow(clippy::module_name_repetitions)]133#[derive(Trace, Clone)]134pub enum FuncVal {135	/// Plain function implemented in jsonnet.136	Normal(Cc<FuncDesc>),137	/// User-provided function.138	Builtin(BuiltinFunc),139}140141impl Debug for FuncVal {142	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {143		match self {144			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),145			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),146		}147	}148}149150#[allow(clippy::unnecessary_wraps)]151#[builtin]152pub const fn builtin_id(x: Thunk<Val>) -> Thunk<Val> {153	x154}155156impl FuncVal {157	pub fn builtin(builtin: impl Builtin) -> Self {158		Self::Builtin(BuiltinFunc::new(builtin))159	}160161	pub fn params(&self) -> FunctionSignature {162		match self {163			Self::Builtin(i) => i.params(),164			Self::Normal(p) => p.signature(),165		}166	}167	/// Amount of non-default required arguments168	pub fn params_len(&self) -> u32 {169		self.params().iter().filter(|p| !p.has_default()).count() as u32170	}171	/// Function name, as defined in code.172	pub fn name(&self) -> IStr {173		match self {174			Self::Normal(normal) => normal.name.clone(),175			Self::Builtin(builtin) => builtin.name().into(),176		}177	}178179	pub(crate) fn evaluate_prepared(180		&self,181		prepared: &PreparedCall,182		loc: CallLocation<'_>,183		unnamed: &[Thunk<Val>],184		named: &[Thunk<Val>],185		_tailstrict: bool,186	) -> Result<Val> {187		match self {188			FuncVal::Normal(func) => func.call(unnamed, named, prepared),189			FuncVal::Builtin(b) => {190				let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);191				b.call(loc, &args)192			}193		}194	}195196	/// Is this function an identity function.197	///198	/// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.199	///200	/// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too201	pub fn is_identity(&self) -> bool {202		match self {203			Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),204			Self::Normal(desc) => {205				if desc.func.params.len() != 1 {206					return false;207				}208				let param = &desc.func.params[0];209				if param.default.is_some() {210					return false;211				}212				#[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]213				let LDestruct::Full(id) = &param.destruct214				else {215					return false;216				};217				matches!(&*desc.func.body, LExpr::Local(v) if v == id)218			}219		}220	}221222	pub fn evaluate_trivial(&self) -> Option<Val> {223		match self {224			Self::Normal(n) => n.evaluate_trivial(),225			Self::Builtin(_) => None,226		}227	}228}229230impl<T> From<T> for FuncVal231where232	T: Builtin,233{234	fn from(value: T) -> Self {235		Self::builtin(value)236	}237}