1use crate::{2 equals,3 error::{Error::*, Result},4 evaluate, parse_args, primitive_equals, push, throw,5 typed::CheckType,6 with_state, ArrValue, Context, FuncVal, LazyVal, Val,7};8use format::{format_arr, format_obj};9use jrsonnet_parser::{ArgsDesc, BinaryOpType, ExprLocation};10use jrsonnet_types::ty;11use std::{collections::HashMap, path::PathBuf, rc::Rc};1213pub mod stdlib;14pub use stdlib::*;1516use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};1718pub mod format;19pub mod manifest;20pub mod sort;2122fn std_format(str: Rc<str>, vals: Val) -> Result<Val> {23 push(24 &Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)),25 || format!("std.format of {}", str),26 || {27 Ok(match vals {28 Val::Arr(vals) => Val::Str(format_arr(&str, &vals.evaluated()?)?.into()),29 Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),30 o => Val::Str(format_arr(&str, &[o])?.into()),31 })32 },33 )34}3536type Builtin = fn(context: Context, loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val>;3738type BuiltinsType = HashMap<Box<str>, Builtin>;3940thread_local! {41 static BUILTINS: BuiltinsType = {42 [43 ("length".into(), builtin_length as Builtin),44 ("type".into(), builtin_type),45 ("makeArray".into(), builtin_make_array),46 ("codepoint".into(), builtin_codepoint),47 ("objectFieldsEx".into(), builtin_object_fields_ex),48 ("objectHasEx".into(), builtin_object_has_ex),49 ("slice".into(), builtin_slice),50 ("primitiveEquals".into(), builtin_primitive_equals),51 ("equals".into(), builtin_equals),52 ("modulo".into(), builtin_modulo),53 ("mod".into(), builtin_mod),54 ("floor".into(), builtin_floor),55 ("log".into(), builtin_log),56 ("pow".into(), builtin_pow),57 ("extVar".into(), builtin_ext_var),58 ("native".into(), builtin_native),59 ("filter".into(), builtin_filter),60 ("foldl".into(), builtin_foldl),61 ("foldr".into(), builtin_foldr),62 ("sortImpl".into(), builtin_sort_impl),63 ("format".into(), builtin_format),64 ("range".into(), builtin_range),65 ("char".into(), builtin_char),66 ("encodeUTF8".into(), builtin_encode_utf8),67 ("md5".into(), builtin_md5),68 ("base64".into(), builtin_base64),69 ("trace".into(), builtin_trace),70 ("join".into(), builtin_join),71 ("escapeStringJson".into(), builtin_escape_string_json),72 ("manifestJsonEx".into(), builtin_manifest_json_ex),73 ("reverse".into(), builtin_reverse),74 ("id".into(), builtin_id),75 ].iter().cloned().collect()76 };77}7879fn builtin_length(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {80 parse_args!(context, "length", args, 1, [81 0, x: ty!((str | obj | [any]));82 ], {83 Ok(match x {84 Val::Str(n) => Val::Num(n.chars().count() as f64),85 Val::Arr(a) => Val::Num(a.len() as f64),86 Val::Obj(o) => Val::Num(87 o.fields_visibility()88 .into_iter()89 .filter(|(_k, v)| *v)90 .count() as f64,91 ),92 _ => unreachable!(),93 })94 })95}9697fn builtin_type(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {98 parse_args!(context, "type", args, 1, [99 0, x: ty!(any);100 ], {101 Ok(Val::Str(x.value_type().name().into()))102 })103}104105fn builtin_make_array(106 context: Context,107 _loc: &Option<ExprLocation>,108 args: &ArgsDesc,109) -> Result<Val> {110 parse_args!(context, "makeArray", args, 2, [111 0, sz: ty!(number((Some(0.0))..(None))) => Val::Num;112 1, func: ty!(fn.any) => Val::Func;113 ], {114 let mut out = Vec::with_capacity(sz as usize);115 for i in 0..sz as usize {116 out.push(LazyVal::new_resolved(func.evaluate_values(117 Context::new(),118 &[Val::Num(i as f64)]119 )?))120 }121 Ok(Val::Arr(out.into()))122 })123}124125fn builtin_codepoint(126 context: Context,127 _loc: &Option<ExprLocation>,128 args: &ArgsDesc,129) -> Result<Val> {130 parse_args!(context, "codepoint", args, 1, [131 0, str: ty!(char) => Val::Str;132 ], {133 Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))134 })135}136137fn builtin_object_fields_ex(138 context: Context,139 _loc: &Option<ExprLocation>,140 args: &ArgsDesc,141) -> Result<Val> {142 parse_args!(context, "objectFieldsEx", args, 2, [143 0, obj: ty!(obj) => Val::Obj;144 1, inc_hidden: ty!(bool) => Val::Bool;145 ], {146 let mut out = obj.fields_visibility()147 .into_iter()148 .filter(|(_k, v)| *v || inc_hidden)149 .map(|(k, _v)|k)150 .collect::<Vec<_>>();151 out.sort();152 Ok(Val::Arr(out.into_iter().map(Val::Str).collect::<Vec<_>>().into()))153 })154}155156fn builtin_object_has_ex(157 context: Context,158 _loc: &Option<ExprLocation>,159 args: &ArgsDesc,160) -> Result<Val> {161 parse_args!(context, "objectHasEx", args, 3, [162 0, obj: ty!(obj) => Val::Obj;163 1, f: ty!(str) => Val::Str;164 2, inc_hidden: ty!(bool) => Val::Bool;165 ], {166 Ok(Val::Bool(167 obj.fields_visibility()168 .into_iter()169 .filter(|(_k, v)| *v || inc_hidden)170 .any(|(k, _v)| *k == *f),171 ))172 })173}174175176fn builtin_slice(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {177 parse_args!(context, "slice", args, 4, [178 0, indexable: ty!((str | [any]));179 1, index: ty!((num | null));180 2, end: ty!((num | null));181 3, step: ty!((num | null));182 ], {183 let index = match index {184 Val::Num(v) => v as usize,185 Val::Null => 0,186 _ => unreachable!(),187 };188 let end = match end {189 Val::Num(v) => v as usize,190 Val::Null => match &indexable {191 Val::Str(s) => s.chars().count(),192 Val::Arr(v) => v.len(),193 _ => unreachable!()194 },195 _ => unreachable!()196 };197 let step = match step {198 Val::Num(v) => v as usize,199 Val::Null => 1,200 _ => unreachable!()201 };202 match &indexable {203 Val::Str(s) => {204 Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::<String>()).into()))205 }206 Val::Arr(arr) => {207 Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).collect::<Result<Vec<Val>>>()?).into()))208 }209 _ => unreachable!()210 }211 })212}213214215fn builtin_primitive_equals(216 context: Context,217 _loc: &Option<ExprLocation>,218 args: &ArgsDesc,219) -> Result<Val> {220 parse_args!(context, "primitiveEquals", args, 2, [221 0, a: ty!(any);222 1, b: ty!(any);223 ], {224 Ok(Val::Bool(primitive_equals(&a, &b)?))225 })226}227228229fn builtin_equals(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {230 parse_args!(context, "equals", args, 2, [231 0, a: ty!(any);232 1, b: ty!(any);233 ], {234 Ok(Val::Bool(equals(&a, &b)?))235 })236}237238fn builtin_modulo(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {239 parse_args!(context, "modulo", args, 2, [240 0, a: ty!(num) => Val::Num;241 1, b: ty!(num) => Val::Num;242 ], {243 Ok(Val::Num(a % b))244 })245}246247fn builtin_mod(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {248 parse_args!(context, "mod", args, 2, [249 0, a: ty!((num | str));250 1, b: ty!(any);251 ], {252 match (a, b) {253 (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)),254 (Val::Str(str), vals) => std_format(str, vals),255 (a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(BinaryOpType::Mod, a.value_type(), b.value_type()))256 }257 })258}259260fn builtin_floor(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {261 parse_args!(context, "floor", args, 1, [262 0, x: ty!(num) => Val::Num;263 ], {264 Ok(Val::Num(x.floor()))265 })266}267268fn builtin_log(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {269 parse_args!(context, "log", args, 1, [270 0, n: ty!(num) => Val::Num;271 ], {272 Ok(Val::Num(n.ln()))273 })274}275276fn builtin_pow(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {277 parse_args!(context, "pow", args, 2, [278 0, x: ty!(num) => Val::Num;279 1, n: ty!(num) => Val::Num;280 ], {281 Ok(Val::Num(x.powf(n)))282 })283}284285fn builtin_ext_var(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {286 parse_args!(context, "extVar", args, 1, [287 0, x: ty!(str) => Val::Str;288 ], {289 Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or(UndefinedExternalVariable(x))?)290 })291}292293fn builtin_native(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {294 parse_args!(context, "native", args, 1, [295 0, x: ty!(str) => Val::Str;296 ], {297 Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?)298 })299}300301fn builtin_filter(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {302 parse_args!(context, "filter", args, 2, [303 0, func: ty!(fn.any) => Val::Func;304 1, arr: ty!([any]) => Val::Arr;305 ], {306 let mut out = Vec::new();307 for item in arr.iter() {308 let item = item?;309 if func310 .evaluate_values(context.clone(), &[item.clone()])?311 .try_cast_bool("filter predicate")? {312 out.push(item);313 }314 }315 Ok(Val::Arr(out.into()))316 })317}318319fn builtin_foldl(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {320 parse_args!(context, "foldl", args, 3, [321 0, func: ty!(fn.any) => Val::Func;322 1, arr: ty!([any]) => Val::Arr;323 2, init: ty!(any);324 ], {325 let mut acc = init;326 for i in arr.iter() {327 acc = func.evaluate_values(context.clone(), &[acc, i?])?;328 }329 Ok(acc)330 })331}332333fn builtin_foldr(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {334 parse_args!(context, "foldr", args, 3, [335 0, func: ty!(fn.any) => Val::Func;336 1, arr: ty!([any]) => Val::Arr;337 2, init: ty!(any);338 ], {339 let mut acc = init;340 for i in arr.iter().rev() {341 acc = func.evaluate_values(context.clone(), &[acc, i?])?;342 }343 Ok(acc)344 })345}346347#[allow(non_snake_case)]348fn builtin_sort_impl(349 context: Context,350 _loc: &Option<ExprLocation>,351 args: &ArgsDesc,352) -> Result<Val> {353 parse_args!(context, "sort", args, 2, [354 0, arr: ty!([any]) => Val::Arr;355 1, keyF: ty!(fn.any) => Val::Func;356 ], {357 if arr.len() <= 1 {358 return Ok(Val::Arr(arr))359 }360 Ok(Val::Arr(ArrValue::Eager(sort::sort(context, arr.evaluated()?, &keyF)?)))361 })362}363364365fn builtin_format(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {366 parse_args!(context, "format", args, 2, [367 0, str: ty!(str) => Val::Str;368 1, vals: ty!(any)369 ], {370 std_format(str, vals)371 })372}373374fn builtin_range(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {375 parse_args!(context, "range", args, 2, [376 0, from: ty!(num) => Val::Num;377 1, to: ty!(num) => Val::Num;378 ], {379 let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));380 for i in from as usize..=to as usize {381 out.push(Val::Num(i as f64));382 }383 Ok(Val::Arr(out.into()))384 })385}386387fn builtin_char(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {388 parse_args!(context, "char", args, 1, [389 0, n: ty!(num) => Val::Num;390 ], {391 let mut out = String::new();392 out.push(std::char::from_u32(n as u32).ok_or_else(||393 InvalidUnicodeCodepointGot(n as u32)394 )?);395 Ok(Val::Str(out.into()))396 })397}398399fn builtin_encode_utf8(400 context: Context,401 _loc: &Option<ExprLocation>,402 args: &ArgsDesc,403) -> Result<Val> {404 parse_args!(context, "encodeUTF8", args, 1, [405 0, str: ty!(str) => Val::Str;406 ], {407 Ok(Val::Arr((str.bytes().map(|b| Val::Num(b as f64)).collect::<Vec<Val>>()).into()))408 })409}410411fn builtin_md5(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {412 parse_args!(context, "md5", args, 1, [413 0, str: ty!(str) => Val::Str;414 ], {415 Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))416 })417}418419fn builtin_trace(context: Context, loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {420 parse_args!(context, "trace", args, 2, [421 0, str: ty!(str) => Val::Str;422 1, rest: ty!(any);423 ], {424 eprint!("TRACE:");425 if let Some(loc) = loc {426 with_state(|s|{427 let locs = s.map_source_locations(&loc.0, &[loc.1]);428 eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);429 });430 }431 eprintln!(" {}", str);432 Ok(rest)433 })434}435436fn builtin_base64(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {437 parse_args!(context, "base64", args, 1, [438 0, input: ty!((str | [num]));439 ], {440 Ok(Val::Str(match input {441 Val::Str(s) => {442 base64::encode(s.bytes().collect::<Vec<_>>()).into()443 },444 Val::Arr(a) => {445 base64::encode(a.iter().map(|v| {446 Ok(v?.clone().unwrap_num()? as u8)447 }).collect::<Result<Vec<_>>>()?).into()448 },449 _ => unreachable!()450 }))451 })452}453454fn builtin_join(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {455 parse_args!(context, "join", args, 2, [456 0, sep: ty!((str | [any]));457 1, arr: ty!([any]) => Val::Arr;458 ], {459 Ok(match sep {460 Val::Arr(joiner_items) => {461 let mut out = Vec::new();462463 let mut first = true;464 for item in arr.iter() {465 let item = item?.clone();466 if let Val::Arr(items) = item {467 if !first {468 out.reserve(joiner_items.len());469 470 for item in joiner_items.iter() {471 out.push(item?);472 }473 }474 first = false;475 out.reserve(items.len());476 477 for item in items.iter() {478 out.push(item?);479 }480 } else {481 throw!(RuntimeError("in std.join all items should be arrays".into()));482 }483 }484485 Val::Arr(out.into())486 },487 Val::Str(sep) => {488 let mut out = String::new();489490 let mut first = true;491 for item in arr.iter() {492 let item = item?.clone();493 if let Val::Str(item) = item {494 if !first {495 out += &sep;496 }497 first = false;498 out += &item;499 } else {500 throw!(RuntimeError("in std.join all items should be strings".into()));501 }502 }503504 Val::Str(out.into())505 },506 _ => unreachable!()507 })508 })509}510511512fn builtin_escape_string_json(513 context: Context,514 _loc: &Option<ExprLocation>,515 args: &ArgsDesc,516) -> Result<Val> {517 parse_args!(context, "escapeStringJson", args, 1, [518 0, str_: ty!(str) => Val::Str;519 ], {520 Ok(Val::Str(escape_string_json(&str_).into()))521 })522}523524525fn builtin_manifest_json_ex(526 context: Context,527 _loc: &Option<ExprLocation>,528 args: &ArgsDesc,529) -> Result<Val> {530 parse_args!(context, "manifestJsonEx", args, 2, [531 0, value: ty!(any);532 1, indent: ty!(str) => Val::Str;533 ], {534 Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {535 padding: &indent,536 mtype: ManifestType::Std,537 })?.into()))538 })539}540541542fn builtin_reverse(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {543 parse_args!(context, "reverse", args, 1, [544 0, value: ty!([any]) => Val::Arr;545 ], {546 Ok(Val::Arr(value.reversed()))547 })548}549550fn builtin_id(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {551 parse_args!(context, "id", args, 1, [552 0, v: ty!(any);553 ], {554 Ok(v)555 })556}557558#[allow(clippy::cognitive_complexity)]559pub fn call_builtin(560 context: Context,561 loc: &Option<ExprLocation>,562 name: &str,563 args: &ArgsDesc,564) -> Result<Val> {565 if let Some(f) = BUILTINS.with(|builtins| builtins.get(name).map(|f| *f)) {566 return Ok(f(context, loc, args)?);567 }568 throw!(IntrinsicNotFound(name.into()))569}