1use std::{cell::RefCell, collections::BTreeSet};23use jrsonnet_evaluator::{4 bail,5 error::{ErrorKind::*, Result},6 function::{builtin, CallLocation, FuncVal},7 manifest::JsonFormat,8 typed::{Either2, Either4},9 val::{equals, ArrValue},10 Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,11};12use jrsonnet_gcmodule::Cc;1314use crate::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, x: IStr) -> Result<Val> {54 this.settings55 .borrow()56 .ext_vars57 .get(&x)58 .cloned()59 .ok_or_else(|| UndefinedExternalVariable(x))?60 .evaluate_tailstrict()61}6263#[builtin(fields(64 settings: Cc<RefCell<Settings>>,65))]66pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {67 this.settings68 .borrow()69 .ext_natives70 .get(&x)71 .cloned()72 .map_or(Val::Null, Val::Func)73}7475#[builtin(fields(76 settings: Cc<RefCell<Settings>>,77))]78pub fn builtin_trace(79 this: &builtin_trace,80 loc: CallLocation,81 str: Val,82 rest: Option<Thunk<Val>>,83) -> Result<Val> {84 this.settings.borrow().trace_printer.print_trace(85 loc,86 match &str {87 Val::Str(s) => s.clone().into_flat(),88 Val::Func(f) => format!("{f:?}").into(),89 v => v.manifest(JsonFormat::debug())?.into(),90 },91 );92 rest.map_or_else(|| Ok(str), |rest| rest.evaluate())93}9495#[allow(clippy::comparison_chain)]96#[builtin]97pub fn builtin_starts_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {98 Ok(match (a, b) {99 (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),100 (Either2::B(a), Either2::B(b)) => {101 if b.len() > a.len() {102 return Ok(false);103 } else if b.len() == a.len() {104 return equals(&Val::Arr(a), &Val::Arr(b));105 }106 for (a, b) in a.iter().take(b.len()).zip(b.iter()) {107 let a = a?;108 let b = b?;109 if !equals(&a, &b)? {110 return Ok(false);111 }112 }113 true114 }115 _ => bail!("both arguments should be of the same type"),116 })117}118119#[allow(clippy::comparison_chain)]120#[builtin]121pub fn builtin_ends_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {122 Ok(match (a, b) {123 (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),124 (Either2::B(a), Either2::B(b)) => {125 if b.len() > a.len() {126 return Ok(false);127 } else if b.len() == a.len() {128 return equals(&Val::Arr(a), &Val::Arr(b));129 }130 let a_len = a.len();131 for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {132 let a = a?;133 let b = b?;134 if !equals(&a, &b)? {135 return Ok(false);136 }137 }138 true139 }140 _ => bail!("both arguments should be of the same type"),141 })142}143144#[builtin]145pub fn builtin_assert_equal(a: Val, b: Val) -> Result<bool> {146 if equals(&a, &b)? {147 return Ok(true);148 }149 150 let format = JsonFormat::std_to_json(151 " ".to_owned(),152 "\n",153 ": ",154 #[cfg(feature = "exp-preserve-order")]155 true,156 );157 let a = if let Some(a) = a.as_str() {158 format!("<A>\n{a}\n</A>")159 } else {160 a.manifest(&format).description("<a> manifestification")?161 };162 let b = if let Some(b) = b.as_str() {163 format!("<B>\n{b}\n</B>")164 } else {165 b.manifest(&format).description("<b> manifestification")?166 };167 bail!("assertion failed: A != B\nA: {a}\nB: {b}")168}169170#[builtin]171pub fn builtin_merge_patch(target: Val, patch: Val) -> Result<Val> {172 let Some(patch) = patch.as_obj() else {173 return Ok(patch);174 };175 let target = target.as_obj().unwrap_or_else(ObjValue::empty);176 let target_fields = target177 .fields(178 179 180 181 182 183 184 #[cfg(feature = "exp-preserve-order")]185 false,186 )187 .into_iter()188 .collect::<BTreeSet<IStr>>();189 let patch_fields = patch190 .fields(191 192 193 194 195 #[cfg(feature = "exp-preserve-order")]196 false,197 )198 .into_iter()199 .collect::<BTreeSet<IStr>>();200201 let mut out = ObjValueBuilder::new();202 for field in target_fields.union(&patch_fields) {203 let Some(field_patch) = patch.get(field.clone())? else {204 205 let target_field = target.get_lazy(field.clone()).expect("we're iterating over fields union, if field is missing in patch - it exists in target");206 out.field(field.clone()).thunk(target_field);207 continue;208 };209 if matches!(field_patch, Val::Null) {210 continue;211 }212 let field_target = target.get(field.clone())?.unwrap_or(Val::Null);213 out.field(field.clone())214 .value(builtin_merge_patch(field_target, field_patch)?);215 }216 Ok(out.build().into())217}