1use crate::{2 error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, Context, FutureWrapper,3 GcHashMap, LazyVal, LazyValValue, Result, Val,4};5use gcmodule::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};8use std::collections::HashMap;910const NO_DEFAULT_CONTEXT: &str =11 "no default context set for call with defined default parameter value";1213#[derive(Trace)]14struct EvaluateLazyVal {15 context: Context,16 expr: LocExpr,17}18impl LazyValValue for EvaluateLazyVal {19 fn get(self: Box<Self>) -> Result<Val> {20 evaluate(self.context, &self.expr)21 }22}23242526272829303132pub fn parse_function_call(33 ctx: Context,34 body_ctx: Context,35 params: &ParamsDesc,36 args: &ArgsDesc,37 tailstrict: bool,38) -> Result<Context> {39 let mut passed_args = GcHashMap::with_capacity(params.len());40 if args.unnamed.len() > params.len() {41 throw!(TooManyArgsFunctionHas(params.len()))42 }4344 let mut filled_args = 0;4546 for (id, arg) in args.unnamed.iter().enumerate() {47 let name = params[id].0.clone();48 passed_args.insert(49 name,50 if tailstrict {51 LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)52 } else {53 LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {54 context: ctx.clone(),55 expr: arg.clone(),56 })))57 },58 );59 filled_args += 1;60 }6162 for (name, value) in args.named.iter() {63 64 if !params.iter().any(|p| &p.0 == name) {65 throw!(UnknownFunctionParameter((name as &str).to_owned()));66 }67 if passed_args68 .insert(69 name.clone(),70 if tailstrict {71 LazyVal::new_resolved(evaluate(ctx.clone(), value)?)72 } else {73 LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {74 context: ctx.clone(),75 expr: value.clone(),76 })))77 },78 )79 .is_some()80 {81 throw!(BindingParameterASecondTime(name.clone()));82 }83 filled_args += 1;84 }8586 if filled_args < params.len() {87 88 89 let future_context = FutureWrapper::<Context>::new();90 let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);9192 for param in params.iter().filter(|p| p.1.is_some()) {93 if passed_args.contains_key(¶m.0.clone()) {94 continue;95 }96 #[derive(Trace)]97 struct LazyNamedBinding {98 future_context: FutureWrapper<Context>,99 name: IStr,100 value: LocExpr,101 }102 impl LazyValValue for LazyNamedBinding {103 fn get(self: Box<Self>) -> Result<Val> {104 evaluate_named(self.future_context.unwrap(), &self.value, self.name)105 }106 }107 LazyVal::new(TraceBox(Box::new(LazyNamedBinding {108 future_context: future_context.clone(),109 name: param.0.clone(),110 value: param.1.clone().unwrap(),111 })));112113 defaults.insert(114 param.0.clone(),115 LazyVal::new(TraceBox(Box::new(LazyNamedBinding {116 future_context: future_context.clone(),117 name: param.0.clone(),118 value: param.1.clone().unwrap(),119 }))),120 );121 filled_args += 1;122 }123124 125 if filled_args != params.len() {126 for param in params.iter().skip(args.unnamed.len()) {127 if !args.named.iter().any(|a| a.0 == param.0) {128 throw!(FunctionParameterNotBoundInCall(param.0.clone()));129 }130 }131 unreachable!();132 }133134 Ok(body_ctx135 .extend(passed_args, None, None, None)136 .extend_bound(defaults)137 .into_future(future_context))138 } else {139 let body_ctx = body_ctx.extend(passed_args, None, None, None);140 Ok(body_ctx)141 }142}143144#[derive(Clone, Copy)]145pub struct BuiltinParam {146 pub name: &'static str,147 pub has_default: bool,148}149150151152153154155156157pub fn parse_builtin_call<'k>(158 ctx: Context,159 params: &'static [BuiltinParam],160 args: &'k ArgsDesc,161 tailstrict: bool,162) -> Result<GcHashMap<&'k str, LazyVal>> {163 let mut passed_args = GcHashMap::with_capacity(params.len());164 if args.unnamed.len() > params.len() {165 throw!(TooManyArgsFunctionHas(params.len()))166 }167168 let mut filled_args = 0;169170 for (id, arg) in args.unnamed.iter().enumerate() {171 let name = params[id].name;172 passed_args.insert(173 name,174 if tailstrict {175 LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)176 } else {177 LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {178 context: ctx.clone(),179 expr: arg.clone(),180 })))181 },182 );183 filled_args += 1;184 }185186 for (name, value) in args.named.iter() {187 188 if !params.iter().any(|p| p.name == name as &str) {189 throw!(UnknownFunctionParameter((name as &str).to_owned()));190 }191 if passed_args192 .insert(193 name,194 if tailstrict {195 LazyVal::new_resolved(evaluate(ctx.clone(), value)?)196 } else {197 LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {198 context: ctx.clone(),199 expr: value.clone(),200 })))201 },202 )203 .is_some()204 {205 throw!(BindingParameterASecondTime(name.clone()));206 }207 filled_args += 1;208 }209210 if filled_args < params.len() {211 for param in params.iter().filter(|p| p.has_default) {212 if passed_args.contains_key(¶m.name) {213 continue;214 }215 filled_args += 1;216 }217218 219 if filled_args != params.len() {220 for param in params.iter().skip(args.unnamed.len()) {221 if !args.named.iter().any(|a| &a.0 as &str == param.name) {222 throw!(FunctionParameterNotBoundInCall(param.name.into()));223 }224 }225 unreachable!();226 }227 }228 Ok(passed_args)229}230231pub fn parse_function_call_map(232 ctx: Context,233 body_ctx: Option<Context>,234 params: &ParamsDesc,235 args: &HashMap<IStr, Val>,236 tailstrict: bool,237) -> Result<Context> {238 let mut out = GcHashMap::with_capacity(params.len());239 let mut positioned_args = vec![None; params.0.len()];240 for (name, val) in args.iter() {241 let idx = params242 .iter()243 .position(|p| *p.0 == **name)244 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;245246 if idx >= params.len() {247 throw!(TooManyArgsFunctionHas(params.len()));248 }249 if positioned_args[idx].is_some() {250 throw!(BindingParameterASecondTime(params[idx].0.clone()));251 }252 positioned_args[idx] = Some(val.clone());253 }254 255 for (id, p) in params.iter().enumerate() {256 let val = if let Some(arg) = positioned_args[id].take() {257 LazyVal::new_resolved(arg)258 } else if let Some(default) = &p.1 {259 if tailstrict {260 LazyVal::new_resolved(evaluate(261 body_ctx.clone().expect(NO_DEFAULT_CONTEXT),262 default,263 )?)264 } else {265 let body_ctx = body_ctx.clone();266 let default = default.clone();267 #[derive(Trace)]268 struct EvaluateLazyVal {269 body_ctx: Option<Context>,270 default: LocExpr,271 }272 impl LazyValValue for EvaluateLazyVal {273 fn get(self: Box<Self>) -> Result<Val> {274 evaluate(275 self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT),276 &self.default,277 )278 }279 }280 LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { body_ctx, default })))281 }282 } else {283 throw!(FunctionParameterNotBoundInCall(p.0.clone()));284 };285 out.insert(p.0.clone(), val);286 }287288 Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))289}290291pub fn place_args(body_ctx: Context, params: &ParamsDesc, args: &[Val]) -> Result<Context> {292 let mut out = GcHashMap::with_capacity(params.len());293 let mut positioned_args = vec![None; params.0.len()];294 for (id, arg) in args.iter().enumerate() {295 if id >= params.len() {296 throw!(TooManyArgsFunctionHas(params.len()));297 }298 positioned_args[id] = Some(arg);299 }300 301 for (id, p) in params.iter().enumerate() {302 let val = if let Some(arg) = &positioned_args[id] {303 (*arg).clone()304 } else if let Some(default) = &p.1 {305 evaluate(body_ctx.clone(), default)?306 } else {307 throw!(FunctionParameterNotBoundInCall(p.0.clone()));308 };309 out.insert(p.0.clone(), LazyVal::new_resolved(val));310 }311312 Ok(body_ctx.extend(out, None, None, None))313}314315#[macro_export]316macro_rules! parse_args {317 ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [318 $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)?319 ], $handler:block) => {{320 use $crate::{error::Error::*, throw, evaluate, push_description_frame, typed::CheckType};321322 let args = $args;323 if args.unnamed.len() + args.named.len() > $total_args {324 throw!(TooManyArgsFunctionHas($total_args));325 }326 $(327 if args.unnamed.len() + args.named.len() <= $id {328 throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));329 }330 331 let $name = if $id >= $args.unnamed.len() {332 let named = &args.named[$id - $args.unnamed.len()];333 if &named.0 != stringify!($name) {334 throw!(IntrinsicArgumentReorderingIsNotSupportedYet);335 }336 &named.1337 } else {338 &$args.unnamed[$id]339 };340 let $name = push_description_frame(|| format!("evaluating builtin argument {}", stringify!($name)), || {341 let value = evaluate($ctx.clone(), &$name)?;342 $ty.check(&value)?;343 Ok(value)344 })?;345 $(346 let $name = if let $match(v) = $name {347 v348 } else {349 unreachable!();350 };351 )?352 )+353 ($handler as crate::Result<_>)354 }};355}