1use std::{cell::RefCell, collections::BTreeSet};23use jrsonnet_evaluator::{4 Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val, bail,5 error::{ErrorKind::*, Result},6 function::{CallLocation, FuncVal, builtin},7 manifest::JsonFormat,8 typed::{Either2, Either4},9 val::{ArrValue, equals},10};11use jrsonnet_gcmodule::Cc;1213use crate::Settings;1415#[builtin]16pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> u32 {17 use Either4::*;18 match x {19 A(x) => x.chars().count() as u32,20 B(x) => x.len(),21 C(x) => x.len(),22 D(f) => f.params_len(),23 }24}2526#[builtin]27pub fn builtin_get(28 o: ObjValue,29 f: IStr,30 default: Option<Thunk<Val>>,31 #[default(true)] inc_hidden: bool,32) -> Result<Val> {33 let do_default = move || {34 let Some(default) = default else {35 return Ok(Val::Null);36 };37 default.evaluate()38 };39 40 if !inc_hidden && !o.has_field_ex(f.clone(), false) {41 return do_default();42 }43 let Some(v) = o.get(f)? else {44 return do_default();45 };46 Ok(v)47}4849#[builtin(fields(50 settings: Cc<RefCell<Settings>>,51))]52pub fn builtin_ext_var(this: &builtin_ext_var, x: IStr) -> Result<Val> {53 this.settings54 .borrow()55 .ext_vars56 .get(&x)57 .cloned()58 .ok_or_else(|| UndefinedExternalVariable(x))?59 .evaluate_tailstrict()60}6162#[builtin(fields(63 settings: Cc<RefCell<Settings>>,64))]65pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {66 this.settings67 .borrow()68 .ext_natives69 .get(&x)70 .cloned()71 .map_or(Val::Null, Val::Func)72}7374#[builtin(fields(75 settings: Cc<RefCell<Settings>>,76))]77pub fn builtin_trace(78 this: &builtin_trace,79 loc: CallLocation,80 str: Val,81 rest: Option<Thunk<Val>>,82) -> Result<Val> {83 this.settings.borrow().trace_printer.print_trace(84 loc,85 match &str {86 Val::Str(s) => s.clone().into_flat(),87 Val::Func(f) => format!("{f:?}").into(),88 v => v.manifest(JsonFormat::debug())?.into(),89 },90 );91 rest.map_or_else(|| Ok(str), |rest| rest.evaluate())92}9394#[allow(clippy::comparison_chain)]95#[builtin]96pub fn builtin_starts_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {97 Ok(match (a, b) {98 (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),99 (Either2::B(a), Either2::B(b)) => {100 if b.len() > a.len() {101 return Ok(false);102 } else if b.len() == a.len() {103 return equals(&Val::Arr(a), &Val::Arr(b));104 }105 for (a, b) in a.iter().take(b.len() as usize).zip(b.iter()) {106 let a = a?;107 let b = b?;108 if !equals(&a, &b)? {109 return Ok(false);110 }111 }112 true113 }114 _ => bail!("both arguments should be of the same type"),115 })116}117118#[allow(clippy::comparison_chain)]119#[builtin]120pub fn builtin_ends_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {121 Ok(match (a, b) {122 (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),123 (Either2::B(a), Either2::B(b)) => {124 if b.len() > a.len() {125 return Ok(false);126 } else if b.len() == a.len() {127 return equals(&Val::Arr(a), &Val::Arr(b));128 }129 let a_len = a.len();130 for (a, b) in a.iter().skip((a_len - b.len()) as usize).zip(b.iter()) {131 let a = a?;132 let b = b?;133 if !equals(&a, &b)? {134 return Ok(false);135 }136 }137 true138 }139 _ => bail!("both arguments should be of the same type"),140 })141}142143#[builtin]144pub fn builtin_assert_equal(a: Val, b: Val) -> Result<bool> {145 if equals(&a, &b)? {146 return Ok(true);147 }148 149 let format = JsonFormat::std_to_json(150 " ".to_owned(),151 "\n",152 ": ",153 #[cfg(feature = "exp-preserve-order")]154 true,155 );156 let a = if let Some(a) = a.as_str() {157 format!("<A>\n{a}\n</A>")158 } else {159 a.manifest(&format).description("<a> manifestification")?160 };161 let b = if let Some(b) = b.as_str() {162 format!("<B>\n{b}\n</B>")163 } else {164 b.manifest(&format).description("<b> manifestification")?165 };166 bail!("assertion failed: A != B\nA: {a}\nB: {b}")167}168169#[builtin]170pub fn builtin_merge_patch(target: Val, patch: Val) -> Result<Val> {171 let Some(patch) = patch.as_obj() else {172 return Ok(patch);173 };174 let target = target.as_obj().unwrap_or_else(ObjValue::empty);175 let target_fields = target176 .fields(177 178 179 180 181 182 183 #[cfg(feature = "exp-preserve-order")]184 false,185 )186 .into_iter()187 .collect::<BTreeSet<IStr>>();188 let patch_fields = patch189 .fields(190 191 192 193 194 #[cfg(feature = "exp-preserve-order")]195 false,196 )197 .into_iter()198 .collect::<BTreeSet<IStr>>();199200 let mut out = ObjValueBuilder::new();201 for field in target_fields.union(&patch_fields) {202 let Some(field_patch) = patch.get(field.clone())? else {203 204 let target_field = target.get_lazy(field.clone()).expect(205 "we're iterating over fields union, if field is missing in patch - it exists in target",206 );207 out.field(field.clone()).thunk(target_field);208 continue;209 };210 if matches!(field_patch, Val::Null) {211 continue;212 }213 let field_target = target.get(field.clone())?.unwrap_or(Val::Null);214 out.field(field.clone())215 .value(builtin_merge_patch(field_target, field_patch)?);216 }217 Ok(out.build().into())218}