1use std::{cell::RefCell, collections::BTreeSet};23use jrsonnet_evaluator::{4 bail,5 error::{ErrorKind::*, Result},6 function::{builtin, ArgLike, CallLocation, FuncVal},7 manifest::JsonFormat,8 typed::{Either2, Either4},9 val::{equals, ArrValue},10 Context, Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,11};12use jrsonnet_gcmodule::Cc;1314use crate::{extvar_source, Settings};1516#[builtin]17pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {18 use Either4::*;19 match x {20 A(x) => x.chars().count(),21 B(x) => x.len(),22 C(x) => x.len(),23 D(f) => f.params_len(),24 }25}2627#[builtin]28pub fn builtin_get(29 o: ObjValue,30 f: IStr,31 default: Option<Thunk<Val>>,32 #[default(true)] inc_hidden: bool,33) -> Result<Val> {34 let do_default = move || {35 let Some(default) = default else {36 return Ok(Val::Null);37 };38 default.evaluate()39 };40 41 if !inc_hidden && !o.has_field_ex(f.clone(), false) {42 return do_default();43 }44 let Some(v) = o.get(f)? else {45 return do_default();46 };47 Ok(v)48}4950#[builtin(fields(51 settings: Cc<RefCell<Settings>>,52))]53pub fn builtin_ext_var(this: &builtin_ext_var, ctx: Context, x: IStr) -> Result<Val> {54 let ctx = ctx.state().create_default_context(extvar_source(&x, ""));55 this.settings56 .borrow()57 .ext_vars58 .get(&x)59 .cloned()60 .ok_or_else(|| UndefinedExternalVariable(x))?61 .evaluate_arg(ctx, true)?62 .evaluate()63}6465#[builtin(fields(66 settings: Cc<RefCell<Settings>>,67))]68pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {69 this.settings70 .borrow()71 .ext_natives72 .get(&x)73 .cloned()74 .map_or(Val::Null, Val::Func)75}7677#[builtin(fields(78 settings: Cc<RefCell<Settings>>,79))]80pub fn builtin_trace(81 this: &builtin_trace,82 loc: CallLocation,83 str: Val,84 rest: Option<Thunk<Val>>,85) -> Result<Val> {86 this.settings.borrow().trace_printer.print_trace(87 loc,88 match &str {89 Val::Str(s) => s.clone().into_flat(),90 Val::Func(f) => format!("{f:?}").into(),91 v => v.manifest(JsonFormat::debug())?.into(),92 },93 );94 rest.map_or_else(|| Ok(str), |rest| rest.evaluate())95}9697#[allow(clippy::comparison_chain)]98#[builtin]99pub fn builtin_starts_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {100 Ok(match (a, b) {101 (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),102 (Either2::B(a), Either2::B(b)) => {103 if b.len() > a.len() {104 return Ok(false);105 } else if b.len() == a.len() {106 return equals(&Val::Arr(a), &Val::Arr(b));107 }108 for (a, b) in a.iter().take(b.len()).zip(b.iter()) {109 let a = a?;110 let b = b?;111 if !equals(&a, &b)? {112 return Ok(false);113 }114 }115 true116 }117 _ => bail!("both arguments should be of the same type"),118 })119}120121#[allow(clippy::comparison_chain)]122#[builtin]123pub fn builtin_ends_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {124 Ok(match (a, b) {125 (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),126 (Either2::B(a), Either2::B(b)) => {127 if b.len() > a.len() {128 return Ok(false);129 } else if b.len() == a.len() {130 return equals(&Val::Arr(a), &Val::Arr(b));131 }132 let a_len = a.len();133 for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {134 let a = a?;135 let b = b?;136 if !equals(&a, &b)? {137 return Ok(false);138 }139 }140 true141 }142 _ => bail!("both arguments should be of the same type"),143 })144}145146#[builtin]147pub fn builtin_assert_equal(a: Val, b: Val) -> Result<bool> {148 if equals(&a, &b)? {149 return Ok(true);150 }151 152 let format = JsonFormat::std_to_json(153 " ".to_owned(),154 "\n",155 ": ",156 #[cfg(feature = "exp-preserve-order")]157 true,158 );159 let a = a.manifest(&format).description("<a> manifestification")?;160 let b = b.manifest(&format).description("<b> manifestification")?;161 bail!("assertion failed: A != B\nA: {a}\nB: {b}")162}163164#[builtin]165pub fn builtin_merge_patch(target: Val, patch: Val) -> Result<Val> {166 let Some(patch) = patch.as_obj() else {167 return Ok(patch);168 };169 let Some(target) = target.as_obj() else {170 return Ok(Val::Obj(patch));171 };172 let target_fields = target173 .fields(174 175 176 177 178 179 180 #[cfg(feature = "exp-preserve-order")]181 false,182 )183 .into_iter()184 .collect::<BTreeSet<IStr>>();185 let patch_fields = patch186 .fields(187 188 189 190 191 #[cfg(feature = "exp-preserve-order")]192 false,193 )194 .into_iter()195 .collect::<BTreeSet<IStr>>();196197 let mut out = ObjValueBuilder::new();198 for field in target_fields.union(&patch_fields) {199 let Some(field_patch) = patch.get(field.clone())? else {200 out.field(field.clone()).value(target.get(field.clone())?.expect("we're iterating over fields union, if field is missing in patch - it exists in target"));201 continue;202 };203 if matches!(field_patch, Val::Null) {204 continue;205 }206 let Some(field_target) = target.get(field.clone())? else {207 out.field(field.clone()).value(field_patch);208 continue;209 };210 out.field(field.clone())211 .value(builtin_merge_patch(field_target, field_patch)?);212 }213 Ok(out.build().into())214}