1use std::fmt::Debug;23pub use arglike::{ArgLike, ArgsLike, TlaArg};4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6pub use jrsonnet_macros::builtin;7use jrsonnet_parser::{Destruct, Expr, LocExpr, ParamsDesc, Span};89use self::{10 arglike::OptionalContext,11 builtin::{Builtin, BuiltinParam, ParamDefault, ParamName, StaticBuiltin},12 native::NativeDesc,13 parse::{parse_default_function_call, parse_function_call},14};15use crate::{16 bail, error::ErrorKind::*, evaluate, evaluate_trivial, gc::TraceBox, tb, Context,17 ContextBuilder, Result, Thunk, Val,18};1920pub mod arglike;21pub mod builtin;22pub mod native;23pub mod parse;24252627#[derive(Clone, Copy)]28pub struct CallLocation<'l>(pub Option<&'l Span>);29impl<'l> CallLocation<'l> {30 31 pub const fn new(loc: &'l Span) -> Self {32 Self(Some(loc))33 }34}35impl CallLocation<'static> {36 37 pub const fn native() -> Self {38 Self(None)39 }40}414243#[derive(Debug, PartialEq, Trace)]44pub struct FuncDesc {45 46 47 48 49 50 51 52 53 54 pub name: IStr,55 56 57 58 59 60 61 62 63 64 pub ctx: Context,6566 67 pub params: ParamsDesc,68 69 pub body: LocExpr,70}71impl FuncDesc {72 73 pub fn default_body_context(&self) -> Result<Context> {74 parse_default_function_call(self.ctx.clone(), &self.params)75 }7677 78 pub fn call_body_context(79 &self,80 call_ctx: Context,81 args: &dyn ArgsLike,82 tailstrict: bool,83 ) -> Result<Context> {84 parse_function_call(call_ctx, self.ctx.clone(), &self.params, args, tailstrict)85 }8687 pub fn evaluate_trivial(&self) -> Option<Val> {88 evaluate_trivial(&self.body)89 }90}919293#[allow(clippy::module_name_repetitions)]94#[derive(Trace, Clone)]95pub enum FuncVal {96 97 Id,98 99 Normal(Cc<FuncDesc>),100 101 Thunk(Thunk<Val>),102 103 StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin),104 105 Builtin(Cc<TraceBox<dyn Builtin>>),106}107108impl Debug for FuncVal {109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {110 match self {111 Self::Id => f.debug_tuple("Id").finish(),112 Self::Thunk(arg0) => f.debug_tuple("Thunk").field(arg0).finish(),113 Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),114 Self::StaticBuiltin(arg0) => {115 f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()116 }117 Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),118 }119 }120}121122#[allow(clippy::unnecessary_wraps)]123#[builtin]124const fn builtin_id(x: Val) -> Val {125 x126}127static ID: &builtin_id = &builtin_id {};128129impl FuncVal {130 pub fn builtin(builtin: impl Builtin) -> Self {131 Self::Builtin(Cc::new(tb!(builtin)))132 }133 pub fn static_builtin(static_builtin: &'static dyn StaticBuiltin) -> Self {134 Self::StaticBuiltin(static_builtin)135 }136137 pub fn params(&self) -> Vec<BuiltinParam> {138 match self {139 Self::Id => ID.params().to_vec(),140 Self::StaticBuiltin(i) => i.params().to_vec(),141 Self::Builtin(i) => i.params().to_vec(),142 Self::Normal(p) => p143 .params144 .iter()145 .map(|p| {146 BuiltinParam::new(147 p.0.name()148 .as_ref()149 .map(IStr::to_string)150 .map_or(ParamName::ANONYMOUS, ParamName::new_dynamic),151 ParamDefault::exists(p.1.is_some()),152 )153 })154 .collect(),155 Self::Thunk(_) => vec![],156 }157 }158 159 pub fn params_len(&self) -> usize {160 match self {161 Self::Id => 1,162 Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),163 Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default()).count(),164 Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default()).count(),165 Self::Thunk(_) => 0,166 }167 }168 169 pub fn name(&self) -> IStr {170 match self {171 Self::Id => "id".into(),172 Self::Normal(normal) => normal.name.clone(),173 Self::StaticBuiltin(builtin) => builtin.name().into(),174 Self::Builtin(builtin) => builtin.name().into(),175 Self::Thunk(_) => "thunk".into(),176 }177 }178 179 180 181 pub fn evaluate(182 &self,183 call_ctx: Context,184 loc: CallLocation<'_>,185 args: &dyn ArgsLike,186 tailstrict: bool,187 ) -> Result<Val> {188 match self {189 Self::Id => ID.call(call_ctx, loc, args),190 Self::Normal(func) => {191 let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;192 evaluate(body_ctx, &func.body)193 }194 Self::Thunk(thunk) => {195 if args.is_empty() {196 bail!(TooManyArgsFunctionHas(0, vec![],))197 }198 thunk.evaluate()199 }200 Self::StaticBuiltin(b) => b.call(call_ctx, loc, args),201 Self::Builtin(b) => b.call(call_ctx, loc, args),202 }203 }204 pub fn evaluate_simple<A: ArgsLike + OptionalContext>(205 &self,206 args: &A,207 tailstrict: bool,208 ) -> Result<Val> {209 self.evaluate(210 ContextBuilder::dangerous_empty_state().build(),211 CallLocation::native(),212 args,213 tailstrict,214 )215 }216 217 pub fn into_native<D: NativeDesc>(self) -> D::Value {218 D::into_native(self)219 }220221 222 223 224 225 226 pub fn is_identity(&self) -> bool {227 match self {228 Self::Id => true,229 Self::Normal(desc) => {230 if desc.params.len() != 1 {231 return false;232 }233 let param = &desc.params[0];234 if param.1.is_some() {235 return false;236 }237 #[allow(clippy::infallible_destructuring_match)]238 let id = match ¶m.0 {239 Destruct::Full(id) => id,240 #[cfg(feature = "exp-destruct")]241 _ => return false,242 };243 desc.body.expr() == &Expr::Var(id.clone())244 }245 _ => false,246 }247 }248 249 pub const fn identity() -> Self {250 Self::Id251 }252253 pub fn evaluate_trivial(&self) -> Option<Val> {254 match self {255 Self::Normal(n) => n.evaluate_trivial(),256 _ => None,257 }258 }259}260261impl<T> From<T> for FuncVal262where263 T: Builtin,264{265 fn from(value: T) -> Self {266 Self::builtin(value)267 }268}269impl From<&'static dyn StaticBuiltin> for FuncVal {270 fn from(value: &'static dyn StaticBuiltin) -> Self {271 Self::static_builtin(value)272 }273}