1use crate::{2 equals,3 error::{Error::*, Result},4 parse_args, primitive_equals, push, throw, with_state, ArrValue, Context, EvaluationState,5 FuncVal, LazyVal, Val,6};7use format::{format_arr, format_obj};8use jrsonnet_interner::IStr;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: IStr, 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 ("map".into(), builtin_map),61 ("foldl".into(), builtin_foldl),62 ("foldr".into(), builtin_foldr),63 ("sortImpl".into(), builtin_sort_impl),64 ("format".into(), builtin_format),65 ("range".into(), builtin_range),66 ("char".into(), builtin_char),67 ("encodeUTF8".into(), builtin_encode_utf8),68 ("md5".into(), builtin_md5),69 ("base64".into(), builtin_base64),70 ("trace".into(), builtin_trace),71 ("join".into(), builtin_join),72 ("escapeStringJson".into(), builtin_escape_string_json),73 ("manifestJsonEx".into(), builtin_manifest_json_ex),74 ("reverse".into(), builtin_reverse),75 ("id".into(), builtin_id),76 ("strReplace".into(), builtin_str_replace),77 ("parseJson".into(), builtin_parse_json),78 ].iter().cloned().collect()79 };80}8182fn builtin_length(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {83 parse_args!(context, "length", args, 1, [84 0, x: ty!((string | object | array));85 ], {86 Ok(match x {87 Val::Str(n) => Val::Num(n.chars().count() as f64),88 Val::Arr(a) => Val::Num(a.len() as f64),89 Val::Obj(o) => Val::Num(90 o.fields_visibility()91 .into_iter()92 .filter(|(_k, v)| *v)93 .count() as f64,94 ),95 _ => unreachable!(),96 })97 })98}99100fn builtin_type(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {101 parse_args!(context, "type", args, 1, [102 0, x: ty!(any);103 ], {104 Ok(Val::Str(x.value_type().name().into()))105 })106}107108fn builtin_make_array(109 context: Context,110 _loc: Option<&ExprLocation>,111 args: &ArgsDesc,112) -> Result<Val> {113 parse_args!(context, "makeArray", args, 2, [114 0, sz: ty!(BoundedNumber<(Some(0.0)), (None)>) => Val::Num;115 1, func: ty!(function) => Val::Func;116 ], {117 let mut out = Vec::with_capacity(sz as usize);118 for i in 0..sz as usize {119 out.push(LazyVal::new_resolved(func.evaluate_values(120 context.clone(),121 &[Val::Num(i as f64)]122 )?))123 }124 Ok(Val::Arr(out.into()))125 })126}127128fn builtin_codepoint(129 context: Context,130 _loc: Option<&ExprLocation>,131 args: &ArgsDesc,132) -> Result<Val> {133 parse_args!(context, "codepoint", args, 1, [134 0, str: ty!(char) => Val::Str;135 ], {136 Ok(Val::Num(str.chars().next().unwrap() as u32 as f64))137 })138}139140fn builtin_object_fields_ex(141 context: Context,142 _loc: Option<&ExprLocation>,143 args: &ArgsDesc,144) -> Result<Val> {145 parse_args!(context, "objectFieldsEx", args, 2, [146 0, obj: ty!(object) => Val::Obj;147 1, inc_hidden: ty!(boolean) => Val::Bool;148 ], {149 let out = obj.fields_ex(inc_hidden);150 Ok(Val::Arr(out.into_iter().map(Val::Str).collect::<Vec<_>>().into()))151 })152}153154fn builtin_object_has_ex(155 context: Context,156 _loc: Option<&ExprLocation>,157 args: &ArgsDesc,158) -> Result<Val> {159 parse_args!(context, "objectHasEx", args, 3, [160 0, obj: ty!(object) => Val::Obj;161 1, f: ty!(string) => Val::Str;162 2, inc_hidden: ty!(boolean) => Val::Bool;163 ], {164 Ok(Val::Bool(obj.has_field_ex(f, inc_hidden)))165 })166}167168fn builtin_parse_json(169 context: Context,170 _loc: Option<&ExprLocation>,171 args: &ArgsDesc,172) -> Result<Val> {173 parse_args!(context, "parseJson", args, 1, [174 0, s: ty!(string) => Val::Str;175 ], {176 let state = EvaluationState::default();177 let path = Rc::new(PathBuf::from("std.parseJson"));178 state.evaluate_snippet_raw(path ,s)179 })180}181182183fn builtin_slice(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {184 parse_args!(context, "slice", args, 4, [185 0, indexable: ty!((string | array));186 1, index: ty!((number | null));187 2, end: ty!((number | null));188 3, step: ty!((number | null));189 ], {190 let index = match index {191 Val::Num(v) => v as usize,192 Val::Null => 0,193 _ => unreachable!(),194 };195 let end = match end {196 Val::Num(v) => v as usize,197 Val::Null => match &indexable {198 Val::Str(s) => s.chars().count(),199 Val::Arr(v) => v.len(),200 _ => unreachable!()201 },202 _ => unreachable!()203 };204 let step = match step {205 Val::Num(v) => v as usize,206 Val::Null => 1,207 _ => unreachable!()208 };209 match &indexable {210 Val::Str(s) => {211 Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::<String>()).into()))212 }213 Val::Arr(arr) => {214 Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).collect::<Result<Vec<Val>>>()?).into()))215 }216 _ => unreachable!()217 }218 })219}220221222fn builtin_primitive_equals(223 context: Context,224 _loc: Option<&ExprLocation>,225 args: &ArgsDesc,226) -> Result<Val> {227 parse_args!(context, "primitiveEquals", args, 2, [228 0, a: ty!(any);229 1, b: ty!(any);230 ], {231 Ok(Val::Bool(primitive_equals(&a, &b)?))232 })233}234235236fn builtin_equals(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {237 parse_args!(context, "equals", args, 2, [238 0, a: ty!(any);239 1, b: ty!(any);240 ], {241 Ok(Val::Bool(equals(&a, &b)?))242 })243}244245fn builtin_modulo(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {246 parse_args!(context, "modulo", args, 2, [247 0, a: ty!(number) => Val::Num;248 1, b: ty!(number) => Val::Num;249 ], {250 Ok(Val::Num(a % b))251 })252}253254fn builtin_mod(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {255 parse_args!(context, "mod", args, 2, [256 0, a: ty!((number | string));257 1, b: ty!(any);258 ], {259 match (a, b) {260 (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)),261 (Val::Str(str), vals) => std_format(str, vals),262 (a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(BinaryOpType::Mod, a.value_type(), b.value_type()))263 }264 })265}266267fn builtin_floor(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {268 parse_args!(context, "floor", args, 1, [269 0, x: ty!(number) => Val::Num;270 ], {271 Ok(Val::Num(x.floor()))272 })273}274275fn builtin_log(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {276 parse_args!(context, "log", args, 1, [277 0, n: ty!(number) => Val::Num;278 ], {279 Ok(Val::Num(n.ln()))280 })281}282283fn builtin_pow(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {284 parse_args!(context, "pow", args, 2, [285 0, x: ty!(number) => Val::Num;286 1, n: ty!(number) => Val::Num;287 ], {288 Ok(Val::Num(x.powf(n)))289 })290}291292fn builtin_ext_var(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {293 parse_args!(context, "extVar", args, 1, [294 0, x: ty!(string) => Val::Str;295 ], {296 Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or(UndefinedExternalVariable(x))?)297 })298}299300fn builtin_native(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {301 parse_args!(context, "native", args, 1, [302 0, x: ty!(string) => Val::Str;303 ], {304 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))?)305 })306}307308fn builtin_filter(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {309 parse_args!(context, "filter", args, 2, [310 0, func: ty!(function) => Val::Func;311 1, arr: ty!(array) => Val::Arr;312 ], {313 Ok(Val::Arr(arr.filter(|val| func314 .evaluate_values(context.clone(), &[val.clone()])?315 .try_cast_bool("filter predicate"))?))316 })317}318319fn builtin_map(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {320 parse_args!(context, "map", args, 2, [321 0, func: ty!(function) => Val::Func;322 1, arr: ty!(array) => Val::Arr;323 ], {324 Ok(Val::Arr(arr.map(|val| func325 .evaluate_values(context.clone(), &[val]))?))326 })327}328329fn builtin_foldl(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {330 parse_args!(context, "foldl", args, 3, [331 0, func: ty!(function) => Val::Func;332 1, arr: ty!(array) => Val::Arr;333 2, init: ty!(any);334 ], {335 let mut acc = init;336 for i in arr.iter() {337 acc = func.evaluate_values(context.clone(), &[acc, i?])?;338 }339 Ok(acc)340 })341}342343fn builtin_foldr(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {344 parse_args!(context, "foldr", args, 3, [345 0, func: ty!(function) => Val::Func;346 1, arr: ty!(array) => Val::Arr;347 2, init: ty!(any);348 ], {349 let mut acc = init;350 for i in arr.iter().rev() {351 acc = func.evaluate_values(context.clone(), &[acc, i?])?;352 }353 Ok(acc)354 })355}356357#[allow(non_snake_case)]358fn builtin_sort_impl(359 context: Context,360 _loc: Option<&ExprLocation>,361 args: &ArgsDesc,362) -> Result<Val> {363 parse_args!(context, "sort", args, 2, [364 0, arr: ty!(array) => Val::Arr;365 1, keyF: ty!(function) => Val::Func;366 ], {367 if arr.len() <= 1 {368 return Ok(Val::Arr(arr))369 }370 Ok(Val::Arr(ArrValue::Eager(sort::sort(context, arr.evaluated()?, &keyF)?)))371 })372}373374375fn builtin_format(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {376 parse_args!(context, "format", args, 2, [377 0, str: ty!(string) => Val::Str;378 1, vals: ty!(any)379 ], {380 std_format(str, vals)381 })382}383384fn builtin_range(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {385 parse_args!(context, "range", args, 2, [386 0, from: ty!(number) => Val::Num;387 1, to: ty!(number) => Val::Num;388 ], {389 if to < from {390 return Ok(Val::Arr(ArrValue::new_eager()))391 }392 let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));393 for i in from as usize..=to as usize {394 out.push(Val::Num(i as f64));395 }396 Ok(Val::Arr(out.into()))397 })398}399400fn builtin_char(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {401 parse_args!(context, "char", args, 1, [402 0, n: ty!(number) => Val::Num;403 ], {404 let mut out = String::new();405 out.push(std::char::from_u32(n as u32).ok_or_else(||406 InvalidUnicodeCodepointGot(n as u32)407 )?);408 Ok(Val::Str(out.into()))409 })410}411412fn builtin_encode_utf8(413 context: Context,414 _loc: Option<&ExprLocation>,415 args: &ArgsDesc,416) -> Result<Val> {417 parse_args!(context, "encodeUTF8", args, 1, [418 0, str: ty!(string) => Val::Str;419 ], {420 Ok(Val::Arr((str.bytes().map(|b| Val::Num(b as f64)).collect::<Vec<Val>>()).into()))421 })422}423424fn builtin_md5(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {425 parse_args!(context, "md5", args, 1, [426 0, str: ty!(string) => Val::Str;427 ], {428 Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))429 })430}431432fn builtin_trace(context: Context, loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {433 parse_args!(context, "trace", args, 2, [434 0, str: ty!(string) => Val::Str;435 1, rest: ty!(any);436 ], {437 eprint!("TRACE:");438 if let Some(loc) = loc {439 with_state(|s|{440 let locs = s.map_source_locations(&loc.0, &[loc.1]);441 eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);442 });443 }444 eprintln!(" {}", str);445 Ok(rest)446 })447}448449fn builtin_base64(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {450 parse_args!(context, "base64", args, 1, [451 0, input: ty!((string | (Array<number>)));452 ], {453 Ok(Val::Str(match input {454 Val::Str(s) => {455 base64::encode(s.bytes().collect::<Vec<_>>()).into()456 },457 Val::Arr(a) => {458 base64::encode(a.iter().map(|v| {459 Ok(v?.unwrap_num()? as u8)460 }).collect::<Result<Vec<_>>>()?).into()461 },462 _ => unreachable!()463 }))464 })465}466467fn builtin_join(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {468 parse_args!(context, "join", args, 2, [469 0, sep: ty!((string | array));470 1, arr: ty!(array) => Val::Arr;471 ], {472 Ok(match sep {473 Val::Arr(joiner_items) => {474 let mut out = Vec::new();475476 let mut first = true;477 for item in arr.iter() {478 let item = item?.clone();479 if let Val::Arr(items) = item {480 if !first {481 out.reserve(joiner_items.len());482 483 for item in joiner_items.iter() {484 out.push(item?);485 }486 }487 first = false;488 out.reserve(items.len());489 490 for item in items.iter() {491 out.push(item?);492 }493 } else {494 throw!(RuntimeError("in std.join all items should be arrays".into()));495 }496 }497498 Val::Arr(out.into())499 },500 Val::Str(sep) => {501 let mut out = String::new();502503 let mut first = true;504 for item in arr.iter() {505 let item = item?.clone();506 if let Val::Str(item) = item {507 if !first {508 out += &sep;509 }510 first = false;511 out += &item;512 } else {513 throw!(RuntimeError("in std.join all items should be strings".into()));514 }515 }516517 Val::Str(out.into())518 },519 _ => unreachable!()520 })521 })522}523524525fn builtin_escape_string_json(526 context: Context,527 _loc: Option<&ExprLocation>,528 args: &ArgsDesc,529) -> Result<Val> {530 parse_args!(context, "escapeStringJson", args, 1, [531 0, str_: ty!(string) => Val::Str;532 ], {533 Ok(Val::Str(escape_string_json(&str_).into()))534 })535}536537538fn builtin_manifest_json_ex(539 context: Context,540 _loc: Option<&ExprLocation>,541 args: &ArgsDesc,542) -> Result<Val> {543 parse_args!(context, "manifestJsonEx", args, 2, [544 0, value: ty!(any);545 1, indent: ty!(string) => Val::Str;546 ], {547 Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {548 padding: &indent,549 mtype: ManifestType::Std,550 })?.into()))551 })552}553554555fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {556 parse_args!(context, "reverse", args, 1, [557 0, value: ty!(array) => Val::Arr;558 ], {559 Ok(Val::Arr(value.reversed()))560 })561}562563fn builtin_id(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {564 parse_args!(context, "id", args, 1, [565 0, v: ty!(any);566 ], {567 Ok(v)568 })569}570571572fn builtin_str_replace(573 context: Context,574 _loc: Option<&ExprLocation>,575 args: &ArgsDesc,576) -> Result<Val> {577 parse_args!(context, "strReplace", args, 3, [578 0, str: ty!(string) => Val::Str;579 1, from: ty!(string) => Val::Str;580 2, to: ty!(string) => Val::Str;581 ], {582 let mut out = String::new();583 let mut last_idx = 0;584 while let Some(idx) = (&str[last_idx..]).find(&from as &str) {585 out.push_str(&str[last_idx..last_idx+idx]);586 out.push_str(&to);587 last_idx += idx + from.len();588 }589 if last_idx == 0 {590 return Ok(Val::Str(str))591 }592 out.push_str(&str[last_idx..]);593 Ok(Val::Str(out.into()))594 })595}596597pub fn call_builtin(598 context: Context,599 loc: Option<&ExprLocation>,600 name: &str,601 args: &ArgsDesc,602) -> Result<Val> {603 BUILTINS604 .with(|builtins| builtins.get(name).copied())605 .ok_or_else(|| IntrinsicNotFound(name.into()))?(context, loc, args)606}