git.delta.rocks / jrsonnet / refs/commits / 0111266c91b4

difftreelog

source

crates/jrsonnet-stdlib/src/misc.rs5.6 KiBsourcehistory
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	// Happy path for invisible fields41	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	// TODO: Use debug output format150	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			// FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here?179			// But IndexSet won't allow fast ordered union...180			// // Makes sense to preserve source ordering where possible.181			// // May affect evaluation order, but it is not specified by jsonnet spec.182			// #[cfg(feature = "exp-preserve-order")]183			// true,184			#[cfg(feature = "exp-preserve-order")]185			false,186		)187		.into_iter()188		.collect::<BTreeSet<IStr>>();189	let patch_fields = patch190		.fields(191			// No need to look at the patch field order, I think?192			// New fields (that will be appended at the end) will be alphabeticaly-ordered,193			// but it is fine for jsonpatch, I don't think people write jsonpatch in jsonnet,194			// when they can use mixins.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			// All lazy fields might be unified into a single filtered object core instead of creating a thunk per, but this implementation is good enough.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}