1use crate::{error::Error::*, evaluate, throw, Context, LazyVal, LazyValValue, Result, Val};2use jrsonnet_gc::Trace;3use jrsonnet_interner::IStr;4use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};5use rustc_hash::FxHashMap;6use std::{collections::HashMap, hash::BuildHasherDefault};78const NO_DEFAULT_CONTEXT: &str =9 "no default context set for call with defined default parameter value";10111213141516171819pub fn parse_function_call(20 ctx: Context,21 body_ctx: Option<Context>,22 params: &ParamsDesc,23 args: &ArgsDesc,24 tailstrict: bool,25) -> Result<Context> {26 let mut out = HashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());27 let mut positioned_args = vec![None; params.0.len()];28 for (id, arg) in args.iter().enumerate() {29 let idx = if let Some(name) = &arg.0 {30 params31 .iter()32 .position(|p| *p.0 == *name)33 .ok_or_else(|| UnknownFunctionParameter(name.clone()))?34 } else {35 id36 };3738 if idx >= params.len() {39 throw!(TooManyArgsFunctionHas(params.len()));40 }41 if positioned_args[idx].is_some() {42 throw!(BindingParameterASecondTime(params[idx].0.clone()));43 }44 positioned_args[idx] = Some(arg.1.clone());45 }46 47 for (id, p) in params.iter().enumerate() {48 let (ctx, expr) = if let Some(arg) = &positioned_args[id] {49 (ctx.clone(), arg)50 } else if let Some(default) = &p.1 {51 (body_ctx.clone().expect(NO_DEFAULT_CONTEXT), default)52 } else {53 throw!(FunctionParameterNotBoundInCall(p.0.clone()));54 };55 let val = if tailstrict {56 LazyVal::new_resolved(evaluate(ctx, expr)?)57 } else {58 #[derive(Trace)]59 #[trivially_drop]60 struct EvaluateLazyVal {61 context: Context,62 expr: LocExpr,63 }64 impl LazyValValue for EvaluateLazyVal {65 fn get(self: Box<Self>) -> Result<Val> {66 evaluate(self.context, &self.expr)67 }68 }6970 LazyVal::new(Box::new(EvaluateLazyVal {71 context: ctx.clone(),72 expr: expr.clone(),73 }))74 };75 out.insert(p.0.clone(), val);76 }7778 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))79}8081pub fn parse_function_call_map(82 ctx: Context,83 body_ctx: Option<Context>,84 params: &ParamsDesc,85 args: &HashMap<IStr, Val>,86 tailstrict: bool,87) -> Result<Context> {88 let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());89 let mut positioned_args = vec![None; params.0.len()];90 for (name, val) in args.iter() {91 let idx = params92 .iter()93 .position(|p| *p.0 == **name)94 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;9596 if idx >= params.len() {97 throw!(TooManyArgsFunctionHas(params.len()));98 }99 if positioned_args[idx].is_some() {100 throw!(BindingParameterASecondTime(params[idx].0.clone()));101 }102 positioned_args[idx] = Some(val.clone());103 }104 105 for (id, p) in params.iter().enumerate() {106 let val = if let Some(arg) = positioned_args[id].take() {107 LazyVal::new_resolved(arg)108 } else if let Some(default) = &p.1 {109 if tailstrict {110 LazyVal::new_resolved(evaluate(111 body_ctx.clone().expect(NO_DEFAULT_CONTEXT),112 default,113 )?)114 } else {115 let body_ctx = body_ctx.clone();116 let default = default.clone();117 #[derive(Trace)]118 #[trivially_drop]119 struct EvaluateLazyVal {120 body_ctx: Option<Context>,121 default: LocExpr,122 }123 impl LazyValValue for EvaluateLazyVal {124 fn get(self: Box<Self>) -> Result<Val> {125 evaluate(126 self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT),127 &self.default,128 )129 }130 }131 LazyVal::new(Box::new(EvaluateLazyVal { body_ctx, default }))132 }133 } else {134 throw!(FunctionParameterNotBoundInCall(p.0.clone()));135 };136 out.insert(p.0.clone(), val);137 }138139 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))140}141142pub fn place_args(143 ctx: Context,144 body_ctx: Option<Context>,145 params: &ParamsDesc,146 args: &[Val],147) -> Result<Context> {148 let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());149 let mut positioned_args = vec![None; params.0.len()];150 for (id, arg) in args.iter().enumerate() {151 if id >= params.len() {152 throw!(TooManyArgsFunctionHas(params.len()));153 }154 positioned_args[id] = Some(arg);155 }156 157 for (id, p) in params.iter().enumerate() {158 let val = if let Some(arg) = &positioned_args[id] {159 (*arg).clone()160 } else if let Some(default) = &p.1 {161 evaluate(ctx.clone(), default)?162 } else {163 throw!(FunctionParameterNotBoundInCall(p.0.clone()));164 };165 out.insert(p.0.clone(), LazyVal::new_resolved(val));166 }167168 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))169}170171#[macro_export]172macro_rules! parse_args {173 ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [174 $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)?175 ], $handler:block) => {{176 use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType};177178 let args = $args;179 if args.len() > $total_args {180 throw!(TooManyArgsFunctionHas($total_args));181 }182 $(183 if args.len() <= $id {184 throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));185 }186 let $name = &args[$id];187 if $name.0.is_some() {188 if $name.0.as_ref().unwrap() != stringify!($name) {189 throw!(IntrinsicArgumentReorderingIsNotSupportedYet);190 }191 }192 let $name = push_stack_frame(None, || format!("evaluating argument"), || {193 let value = evaluate($ctx.clone(), &$name.1)?;194 $ty.check(&value)?;195 Ok(value)196 })?;197 $(198 let $name = if let $match(v) = $name {199 v200 } else {201 unreachable!();202 };203 )?204 )+205 ($handler as crate::Result<_>)206 }};207}