git.delta.rocks / jrsonnet / refs/commits / f0883bbdc9f6

difftreelog

refactor move more stdlib functions to builtins

Yaroslav Bolyukin2024-06-18parent: #f319c35.patch.diff
in: master

16 files changed

modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -4,7 +4,7 @@
 use jrsonnet_evaluator::manifest::{
 	JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat,
 };
-use jrsonnet_stdlib::{TomlFormat, YamlFormat};
+use jrsonnet_stdlib::{TomlFormat, XmlJsonmlFormat, YamlFormat};
 
 #[derive(Clone, Copy, ValueEnum)]
 pub enum ManifestFormatName {
@@ -13,6 +13,7 @@
 	Json,
 	Yaml,
 	Toml,
+	XmlJsonml,
 }
 
 #[derive(Parser)]
@@ -70,10 +71,11 @@
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
 				)),
+				ManifestFormatName::XmlJsonml => Box::new(XmlJsonmlFormat::cli()),
 			}
 		};
 		if self.yaml_stream {
-			Box::new(YamlStreamFormat(format))
+			Box::new(YamlStreamFormat::cli(format))
 		} else {
 			format
 		}
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -1,4 +1,7 @@
-use std::any::Any;
+use std::{
+	any::Any,
+	num::{NonZeroU32, NonZeroUsize},
+};
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IBytes;
@@ -99,28 +102,29 @@
 		Self::new(RangeArray::new_inclusive(a, b))
 	}
 
+	/// # Panics
+	/// If step == 0
 	#[must_use]
-	pub fn slice(
-		self,
-		from: Option<usize>,
-		to: Option<usize>,
-		step: Option<usize>,
-	) -> Option<Self> {
-		let len = self.len();
-		let from = from.unwrap_or(0);
-		let to = to.unwrap_or(len).min(len);
-		let step = step.unwrap_or(1);
+	pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {
+		let get_idx = |pos: Option<i32>, len: usize, default| match pos {
+			Some(v) if v < 0 => len.saturating_sub((-v) as usize),
+			Some(v) => (v as usize).min(len),
+			None => default,
+		};
+		let index = get_idx(index, self.len(), 0);
+		let end = get_idx(end, self.len(), self.len());
+		let step = step.unwrap_or_else(|| NonZeroU32::new(1).expect("1 != 0"));
 
-		if from >= to || step == 0 {
-			return None;
+		if index >= end {
+			return Self::empty();
 		}
 
-		Some(Self::new(SliceArray {
+		Self::new(SliceArray {
 			inner: self,
-			from: from as u32,
-			to: to as u32,
-			step: step as u32,
-		}))
+			from: index as u32,
+			to: end as u32,
+			step: step.get(),
+		})
 	}
 
 	/// Array length.
modifiedcrates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -340,7 +340,28 @@
 	}
 }
 
-pub struct YamlStreamFormat<I>(pub I);
+pub struct YamlStreamFormat<I> {
+	inner: I,
+	c_document_end: bool,
+	end_newline: bool,
+}
+impl<I> YamlStreamFormat<I> {
+	pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {
+		Self {
+			inner,
+			c_document_end,
+			// Stdlib format always inserts newline at the end
+			end_newline: true,
+		}
+	}
+	pub fn cli(inner: I) -> Self {
+		Self {
+			inner,
+			c_document_end: true,
+			end_newline: false,
+		}
+	}
+}
 impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {
 	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {
 		let Val::Arr(arr) = val else {
@@ -353,11 +374,16 @@
 			for v in arr.iter() {
 				let v = v?;
 				out.push_str("---\n");
-				self.0.manifest_buf(v, out)?;
+				self.inner.manifest_buf(v, out)?;
 				out.push('\n');
 			}
+		}
+		if self.c_document_end {
 			out.push_str("...");
 		}
+		if self.end_newline {
+			out.push('\n');
+		}
 		Ok(())
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::{ArrValue, ArrayLike};13use crate::{14	bail,15	error::{Error, ErrorKind::*},16	function::FuncVal,17	gc::{GcHashMap, TraceBox},18	manifest::{ManifestFormat, ToStringFormat},19	tb,20	typed::BoundedUsize,21	ObjValue, Result, Unbound, WeakObjValue,22};2324pub trait ThunkValue: Trace {25	type Output;26	fn get(self: Box<Self>) -> Result<Self::Output>;27}2829#[derive(Trace)]30enum ThunkInner<T: Trace> {31	Computed(T),32	Errored(Error),33	Waiting(TraceBox<dyn ThunkValue<Output = T>>),34	Pending,35}3637/// Lazily evaluated value38#[allow(clippy::module_name_repetitions)]39#[derive(Clone, Trace)]40pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4142impl<T: Trace> Thunk<T> {43	pub fn evaluated(val: T) -> Self {44		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))45	}46	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {47		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))48	}49	pub fn errored(e: Error) -> Self {50		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))51	}52	pub fn result(res: Result<T, Error>) -> Self {53		match res {54			Ok(o) => Self::evaluated(o),55			Err(e) => Self::errored(e),56		}57	}58}5960impl<T> Thunk<T>61where62	T: Clone + Trace,63{64	pub fn force(&self) -> Result<()> {65		self.evaluate()?;66		Ok(())67	}6869	/// Evaluate thunk, or return cached value70	///71	/// # Errors72	///73	/// - Lazy value evaluation returned error74	/// - This method was called during inner value evaluation75	pub fn evaluate(&self) -> Result<T> {76		match &*self.0.borrow() {77			ThunkInner::Computed(v) => return Ok(v.clone()),78			ThunkInner::Errored(e) => return Err(e.clone()),79			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),80			ThunkInner::Waiting(..) => (),81		};82		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)83		else {84			unreachable!();85		};86		let new_value = match value.0.get() {87			Ok(v) => v,88			Err(e) => {89				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());90				return Err(e);91			}92		};93		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());94		Ok(new_value)95	}96}9798pub trait ThunkMapper<Input>: Trace {99	type Output;100	fn map(self, from: Input) -> Result<Self::Output>;101}102impl<Input> Thunk<Input>103where104	Input: Trace + Clone,105{106	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>107	where108		M: ThunkMapper<Input>,109		M::Output: Trace,110	{111		#[derive(Trace)]112		struct Mapped<Input: Trace, Mapper: Trace> {113			inner: Thunk<Input>,114			mapper: Mapper,115		}116		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>117		where118			Input: Trace + Clone,119			Mapper: ThunkMapper<Input>,120		{121			type Output = Mapper::Output;122123			fn get(self: Box<Self>) -> Result<Self::Output> {124				let value = self.inner.evaluate()?;125				let mapped = self.mapper.map(value)?;126				Ok(mapped)127			}128		}129130		Thunk::new(Mapped::<Input, M> {131			inner: self,132			mapper,133		})134	}135}136137impl<T: Trace> From<Result<T>> for Thunk<T> {138	fn from(value: Result<T>) -> Self {139		match value {140			Ok(o) => Self::evaluated(o),141			Err(e) => Self::errored(e),142		}143	}144}145impl<T, V: Trace> From<T> for Thunk<V>146where147	T: ThunkValue<Output = V>,148{149	fn from(value: T) -> Self {150		Self::new(value)151	}152}153154impl<T: Trace + Default> Default for Thunk<T> {155	fn default() -> Self {156		Self::evaluated(T::default())157	}158}159160type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);161162#[derive(Trace, Clone)]163pub struct CachedUnbound<I, T>164where165	I: Unbound<Bound = T>,166	T: Trace,167{168	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,169	value: I,170}171impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {172	pub fn new(value: I) -> Self {173		Self {174			cache: Cc::new(RefCell::new(GcHashMap::new())),175			value,176		}177	}178}179impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {180	type Bound = T;181	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {182		let cache_key = (183			sup.as_ref().map(|s| s.clone().downgrade()),184			this.as_ref().map(|t| t.clone().downgrade()),185		);186		{187			if let Some(t) = self.cache.borrow().get(&cache_key) {188				return Ok(t.clone());189			}190		}191		let bound = self.value.bind(sup, this)?;192193		{194			let mut cache = self.cache.borrow_mut();195			cache.insert(cache_key, bound.clone());196		}197198		Ok(bound)199	}200}201202impl<T: Debug + Trace> Debug for Thunk<T> {203	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {204		write!(f, "Lazy")205	}206}207impl<T: Trace> PartialEq for Thunk<T> {208	fn eq(&self, other: &Self) -> bool {209		Cc::ptr_eq(&self.0, &other.0)210	}211}212213/// Represents a Jsonnet value, which can be sliced or indexed (string or array).214#[allow(clippy::module_name_repetitions)]215pub enum IndexableVal {216	/// String.217	Str(IStr),218	/// Array.219	Arr(ArrValue),220}221impl IndexableVal {222	pub fn to_array(self) -> ArrValue {223		match self {224			Self::Str(s) => ArrValue::chars(s.chars()),225			Self::Arr(arr) => arr,226		}227	}228	/// Slice the value.229	///230	/// # Implementation231	///232	/// For strings, will create a copy of specified interval.233	///234	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.235	pub fn slice(236		self,237		index: Option<i32>,238		end: Option<i32>,239		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,240	) -> Result<Self> {241		match &self {242			Self::Str(s) => {243				let mut computed_len = None;244				let mut get_len = || {245					computed_len.map_or_else(246						|| {247							let len = s.chars().count();248							let _ = computed_len.insert(len);249							len250						},251						|len| len,252					)253				};254				let mut get_idx = |pos: Option<i32>, default| {255					match pos {256						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),257						// No need to clamp, as iterator interface is used258						Some(v) => v as usize,259						None => default,260					}261				};262263				let index = get_idx(index, 0);264				let end = get_idx(end, usize::MAX);265				let step = step.as_deref().copied().unwrap_or(1);266267				if index >= end {268					return Ok(Self::Str("".into()));269				}270271				Ok(Self::Str(272					(s.chars()273						.skip(index)274						.take(end - index)275						.step_by(step)276						.collect::<String>())277					.into(),278				))279			}280			Self::Arr(arr) => {281				let get_idx = |pos: Option<i32>, len: usize, default| match pos {282					Some(v) if v < 0 => len.saturating_sub((-v) as usize),283					Some(v) => (v as usize).min(len),284					None => default,285				};286				let index = get_idx(index, arr.len(), 0);287				let end = get_idx(end, arr.len(), arr.len());288				let step = step.as_deref().copied().unwrap_or(1);289290				if index >= end {291					return Ok(Self::Arr(ArrValue::empty()));292				}293294				Ok(Self::Arr(295					arr.clone()296						.slice(Some(index), Some(end), Some(step))297						.expect("arguments checked"),298				))299			}300		}301	}302}303304#[derive(Debug, Clone, Trace)]305pub enum StrValue {306	Flat(IStr),307	Tree(Rc<(StrValue, StrValue, usize)>),308}309impl StrValue {310	pub fn concat(a: Self, b: Self) -> Self {311		// TODO: benchmark for an optimal value, currently just a arbitrary choice312		const STRING_EXTEND_THRESHOLD: usize = 100;313314		if a.is_empty() {315			b316		} else if b.is_empty() {317			a318		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {319			Self::Flat(format!("{a}{b}").into())320		} else {321			let len = a.len() + b.len();322			Self::Tree(Rc::new((a, b, len)))323		}324	}325	pub fn into_flat(self) -> IStr {326		#[cold]327		fn write_buf(s: &StrValue, out: &mut String) {328			match s {329				StrValue::Flat(f) => out.push_str(f),330				StrValue::Tree(t) => {331					write_buf(&t.0, out);332					write_buf(&t.1, out);333				}334			}335		}336		match self {337			Self::Flat(f) => f,338			Self::Tree(_) => {339				let mut buf = String::with_capacity(self.len());340				write_buf(&self, &mut buf);341				buf.into()342			}343		}344	}345	pub fn len(&self) -> usize {346		match self {347			Self::Flat(v) => v.len(),348			Self::Tree(t) => t.2,349		}350	}351	pub fn is_empty(&self) -> bool {352		match self {353			Self::Flat(v) => v.is_empty(),354			// Can't create non-flat empty string355			Self::Tree(_) => false,356		}357	}358}359impl<T> From<T> for StrValue360where361	IStr: From<T>,362{363	fn from(value: T) -> Self {364		Self::Flat(IStr::from(value))365	}366}367impl Display for StrValue {368	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {369		match self {370			Self::Flat(v) => write!(f, "{v}"),371			Self::Tree(t) => {372				write!(f, "{}", t.0)?;373				write!(f, "{}", t.1)374			}375		}376	}377}378impl PartialEq for StrValue {379	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.380	#[allow(clippy::unconditional_recursion)]381	fn eq(&self, other: &Self) -> bool {382		let a = self.clone().into_flat();383		let b = other.clone().into_flat();384		a == b385	}386}387impl Eq for StrValue {}388impl PartialOrd for StrValue {389	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {390		Some(self.cmp(other))391	}392}393impl Ord for StrValue {394	fn cmp(&self, other: &Self) -> std::cmp::Ordering {395		let a = self.clone().into_flat();396		let b = other.clone().into_flat();397		a.cmp(&b)398	}399}400401/// Represents any valid Jsonnet value.402#[derive(Debug, Clone, Trace, Default)]403pub enum Val {404	/// Represents a Jsonnet boolean.405	Bool(bool),406	/// Represents a Jsonnet null value.407	#[default]408	Null,409	/// Represents a Jsonnet string.410	Str(StrValue),411	/// Represents a Jsonnet number.412	/// Should be finite, and not NaN413	/// This restriction isn't enforced by enum, as enum field can't be marked as private414	Num(f64),415	/// Experimental bigint416	#[cfg(feature = "exp-bigint")]417	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),418	/// Represents a Jsonnet array.419	Arr(ArrValue),420	/// Represents a Jsonnet object.421	Obj(ObjValue),422	/// Represents a Jsonnet function.423	Func(FuncVal),424}425426#[cfg(target_pointer_width = "64")]427static_assertions::assert_eq_size!(Val, [u8; 24]);428429impl From<IndexableVal> for Val {430	fn from(v: IndexableVal) -> Self {431		match v {432			IndexableVal::Str(s) => Self::string(s),433			IndexableVal::Arr(a) => Self::Arr(a),434		}435	}436}437438impl Val {439	pub const fn as_bool(&self) -> Option<bool> {440		match self {441			Self::Bool(v) => Some(*v),442			_ => None,443		}444	}445	pub const fn as_null(&self) -> Option<()> {446		match self {447			Self::Null => Some(()),448			_ => None,449		}450	}451	pub fn as_str(&self) -> Option<IStr> {452		match self {453			Self::Str(s) => Some(s.clone().into_flat()),454			_ => None,455		}456	}457	pub const fn as_num(&self) -> Option<f64> {458		match self {459			Self::Num(n) => Some(*n),460			_ => None,461		}462	}463	pub fn as_arr(&self) -> Option<ArrValue> {464		match self {465			Self::Arr(a) => Some(a.clone()),466			_ => None,467		}468	}469	pub fn as_obj(&self) -> Option<ObjValue> {470		match self {471			Self::Obj(o) => Some(o.clone()),472			_ => None,473		}474	}475	pub fn as_func(&self) -> Option<FuncVal> {476		match self {477			Self::Func(f) => Some(f.clone()),478			_ => None,479		}480	}481482	/// Creates `Val::Num` after checking for numeric overflow.483	/// As numbers are `f64`, we can just check for their finity.484	pub fn new_checked_num(num: f64) -> Result<Self> {485		if num.is_finite() {486			Ok(Self::Num(num))487		} else {488			bail!("overflow")489		}490	}491492	pub const fn value_type(&self) -> ValType {493		match self {494			Self::Str(..) => ValType::Str,495			Self::Num(..) => ValType::Num,496			#[cfg(feature = "exp-bigint")]497			Self::BigInt(..) => ValType::BigInt,498			Self::Arr(..) => ValType::Arr,499			Self::Obj(..) => ValType::Obj,500			Self::Bool(_) => ValType::Bool,501			Self::Null => ValType::Null,502			Self::Func(..) => ValType::Func,503		}504	}505506	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {507		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {508			manifest.manifest(val.clone())509		}510		manifest_dyn(self, &format)511	}512513	pub fn to_string(&self) -> Result<IStr> {514		Ok(match self {515			Self::Bool(true) => "true".into(),516			Self::Bool(false) => "false".into(),517			Self::Null => "null".into(),518			Self::Str(s) => s.clone().into_flat(),519			_ => self.manifest(ToStringFormat).map(IStr::from)?,520		})521	}522523	pub fn into_indexable(self) -> Result<IndexableVal> {524		Ok(match self {525			Self::Str(s) => IndexableVal::Str(s.into_flat()),526			Self::Arr(arr) => IndexableVal::Arr(arr),527			_ => bail!(ValueIsNotIndexable(self.value_type())),528		})529	}530531	pub fn function(function: impl Into<FuncVal>) -> Self {532		Self::Func(function.into())533	}534	pub fn string(string: impl Into<StrValue>) -> Self {535		Self::Str(string.into())536	}537}538539impl From<IStr> for Val {540	fn from(value: IStr) -> Self {541		Self::string(value)542	}543}544impl From<String> for Val {545	fn from(value: String) -> Self {546		Self::string(value)547	}548}549impl From<&str> for Val {550	fn from(value: &str) -> Self {551		Self::string(value)552	}553}554impl From<ObjValue> for Val {555	fn from(value: ObjValue) -> Self {556		Self::Obj(value)557	}558}559560const fn is_function_like(val: &Val) -> bool {561	matches!(val, Val::Func(_))562}563564/// Native implementation of `std.primitiveEquals`565pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {566	Ok(match (val_a, val_b) {567		(Val::Bool(a), Val::Bool(b)) => a == b,568		(Val::Null, Val::Null) => true,569		(Val::Str(a), Val::Str(b)) => a == b,570		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,571		#[cfg(feature = "exp-bigint")]572		(Val::BigInt(a), Val::BigInt(b)) => a == b,573		(Val::Arr(_), Val::Arr(_)) => {574			bail!("primitiveEquals operates on primitive types, got array")575		}576		(Val::Obj(_), Val::Obj(_)) => {577			bail!("primitiveEquals operates on primitive types, got object")578		}579		(a, b) if is_function_like(a) && is_function_like(b) => {580			bail!("cannot test equality of functions")581		}582		(_, _) => false,583	})584}585586/// Native implementation of `std.equals`587pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {588	if val_a.value_type() != val_b.value_type() {589		return Ok(false);590	}591	match (val_a, val_b) {592		(Val::Arr(a), Val::Arr(b)) => {593			if ArrValue::ptr_eq(a, b) {594				return Ok(true);595			}596			if a.len() != b.len() {597				return Ok(false);598			}599			for (a, b) in a.iter().zip(b.iter()) {600				if !equals(&a?, &b?)? {601					return Ok(false);602				}603			}604			Ok(true)605		}606		(Val::Obj(a), Val::Obj(b)) => {607			if ObjValue::ptr_eq(a, b) {608				return Ok(true);609			}610			let fields = a.fields(611				#[cfg(feature = "exp-preserve-order")]612				false,613			);614			if fields615				!= b.fields(616					#[cfg(feature = "exp-preserve-order")]617					false,618				) {619				return Ok(false);620			}621			for field in fields {622				if !equals(623					&a.get(field.clone())?.expect("field exists"),624					&b.get(field)?.expect("field exists"),625				)? {626					return Ok(false);627				}628			}629			Ok(true)630		}631		(a, b) => Ok(primitive_equals(a, b)?),632	}633}
after · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	num::{NonZeroU32, NonZeroUsize},6	rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::IStr;11use jrsonnet_types::ValType;1213pub use crate::arr::{ArrValue, ArrayLike};14use crate::{15	bail,16	error::{Error, ErrorKind::*},17	function::FuncVal,18	gc::{GcHashMap, TraceBox},19	manifest::{ManifestFormat, ToStringFormat},20	tb,21	typed::BoundedUsize,22	ObjValue, Result, Unbound, WeakObjValue,23};2425pub trait ThunkValue: Trace {26	type Output;27	fn get(self: Box<Self>) -> Result<Self::Output>;28}2930#[derive(Trace)]31enum ThunkInner<T: Trace> {32	Computed(T),33	Errored(Error),34	Waiting(TraceBox<dyn ThunkValue<Output = T>>),35	Pending,36}3738/// Lazily evaluated value39#[allow(clippy::module_name_repetitions)]40#[derive(Clone, Trace)]41pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4243impl<T: Trace> Thunk<T> {44	pub fn evaluated(val: T) -> Self {45		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))46	}47	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {48		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))49	}50	pub fn errored(e: Error) -> Self {51		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))52	}53	pub fn result(res: Result<T, Error>) -> Self {54		match res {55			Ok(o) => Self::evaluated(o),56			Err(e) => Self::errored(e),57		}58	}59}6061impl<T> Thunk<T>62where63	T: Clone + Trace,64{65	pub fn force(&self) -> Result<()> {66		self.evaluate()?;67		Ok(())68	}6970	/// Evaluate thunk, or return cached value71	///72	/// # Errors73	///74	/// - Lazy value evaluation returned error75	/// - This method was called during inner value evaluation76	pub fn evaluate(&self) -> Result<T> {77		match &*self.0.borrow() {78			ThunkInner::Computed(v) => return Ok(v.clone()),79			ThunkInner::Errored(e) => return Err(e.clone()),80			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),81			ThunkInner::Waiting(..) => (),82		};83		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)84		else {85			unreachable!();86		};87		let new_value = match value.0.get() {88			Ok(v) => v,89			Err(e) => {90				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());91				return Err(e);92			}93		};94		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());95		Ok(new_value)96	}97}9899pub trait ThunkMapper<Input>: Trace {100	type Output;101	fn map(self, from: Input) -> Result<Self::Output>;102}103impl<Input> Thunk<Input>104where105	Input: Trace + Clone,106{107	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>108	where109		M: ThunkMapper<Input>,110		M::Output: Trace,111	{112		#[derive(Trace)]113		struct Mapped<Input: Trace, Mapper: Trace> {114			inner: Thunk<Input>,115			mapper: Mapper,116		}117		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>118		where119			Input: Trace + Clone,120			Mapper: ThunkMapper<Input>,121		{122			type Output = Mapper::Output;123124			fn get(self: Box<Self>) -> Result<Self::Output> {125				let value = self.inner.evaluate()?;126				let mapped = self.mapper.map(value)?;127				Ok(mapped)128			}129		}130131		Thunk::new(Mapped::<Input, M> {132			inner: self,133			mapper,134		})135	}136}137138impl<T: Trace> From<Result<T>> for Thunk<T> {139	fn from(value: Result<T>) -> Self {140		match value {141			Ok(o) => Self::evaluated(o),142			Err(e) => Self::errored(e),143		}144	}145}146impl<T, V: Trace> From<T> for Thunk<V>147where148	T: ThunkValue<Output = V>,149{150	fn from(value: T) -> Self {151		Self::new(value)152	}153}154155impl<T: Trace + Default> Default for Thunk<T> {156	fn default() -> Self {157		Self::evaluated(T::default())158	}159}160161type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);162163#[derive(Trace, Clone)]164pub struct CachedUnbound<I, T>165where166	I: Unbound<Bound = T>,167	T: Trace,168{169	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,170	value: I,171}172impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {173	pub fn new(value: I) -> Self {174		Self {175			cache: Cc::new(RefCell::new(GcHashMap::new())),176			value,177		}178	}179}180impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {181	type Bound = T;182	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {183		let cache_key = (184			sup.as_ref().map(|s| s.clone().downgrade()),185			this.as_ref().map(|t| t.clone().downgrade()),186		);187		{188			if let Some(t) = self.cache.borrow().get(&cache_key) {189				return Ok(t.clone());190			}191		}192		let bound = self.value.bind(sup, this)?;193194		{195			let mut cache = self.cache.borrow_mut();196			cache.insert(cache_key, bound.clone());197		}198199		Ok(bound)200	}201}202203impl<T: Debug + Trace> Debug for Thunk<T> {204	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {205		write!(f, "Lazy")206	}207}208impl<T: Trace> PartialEq for Thunk<T> {209	fn eq(&self, other: &Self) -> bool {210		Cc::ptr_eq(&self.0, &other.0)211	}212}213214/// Represents a Jsonnet value, which can be sliced or indexed (string or array).215#[allow(clippy::module_name_repetitions)]216pub enum IndexableVal {217	/// String.218	Str(IStr),219	/// Array.220	Arr(ArrValue),221}222impl IndexableVal {223	pub fn to_array(self) -> ArrValue {224		match self {225			Self::Str(s) => ArrValue::chars(s.chars()),226			Self::Arr(arr) => arr,227		}228	}229	/// Slice the value.230	///231	/// # Implementation232	///233	/// For strings, will create a copy of specified interval.234	///235	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.236	pub fn slice(237		self,238		index: Option<i32>,239		end: Option<i32>,240		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,241	) -> Result<Self> {242		match &self {243			Self::Str(s) => {244				let mut computed_len = None;245				let mut get_len = || {246					computed_len.map_or_else(247						|| {248							let len = s.chars().count();249							let _ = computed_len.insert(len);250							len251						},252						|len| len,253					)254				};255				let mut get_idx = |pos: Option<i32>, default| {256					match pos {257						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),258						// No need to clamp, as iterator interface is used259						Some(v) => v as usize,260						None => default,261					}262				};263264				let index = get_idx(index, 0);265				let end = get_idx(end, usize::MAX);266				let step = step.as_deref().copied().unwrap_or(1);267268				if index >= end {269					return Ok(Self::Str("".into()));270				}271272				Ok(Self::Str(273					(s.chars()274						.skip(index)275						.take(end - index)276						.step_by(step)277						.collect::<String>())278					.into(),279				))280			}281			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(282				index,283				end,284				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),285			))),286		}287	}288}289290#[derive(Debug, Clone, Trace)]291pub enum StrValue {292	Flat(IStr),293	Tree(Rc<(StrValue, StrValue, usize)>),294}295impl StrValue {296	pub fn concat(a: Self, b: Self) -> Self {297		// TODO: benchmark for an optimal value, currently just a arbitrary choice298		const STRING_EXTEND_THRESHOLD: usize = 100;299300		if a.is_empty() {301			b302		} else if b.is_empty() {303			a304		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {305			Self::Flat(format!("{a}{b}").into())306		} else {307			let len = a.len() + b.len();308			Self::Tree(Rc::new((a, b, len)))309		}310	}311	pub fn into_flat(self) -> IStr {312		#[cold]313		fn write_buf(s: &StrValue, out: &mut String) {314			match s {315				StrValue::Flat(f) => out.push_str(f),316				StrValue::Tree(t) => {317					write_buf(&t.0, out);318					write_buf(&t.1, out);319				}320			}321		}322		match self {323			Self::Flat(f) => f,324			Self::Tree(_) => {325				let mut buf = String::with_capacity(self.len());326				write_buf(&self, &mut buf);327				buf.into()328			}329		}330	}331	pub fn len(&self) -> usize {332		match self {333			Self::Flat(v) => v.len(),334			Self::Tree(t) => t.2,335		}336	}337	pub fn is_empty(&self) -> bool {338		match self {339			Self::Flat(v) => v.is_empty(),340			// Can't create non-flat empty string341			Self::Tree(_) => false,342		}343	}344}345impl<T> From<T> for StrValue346where347	IStr: From<T>,348{349	fn from(value: T) -> Self {350		Self::Flat(IStr::from(value))351	}352}353impl Display for StrValue {354	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {355		match self {356			Self::Flat(v) => write!(f, "{v}"),357			Self::Tree(t) => {358				write!(f, "{}", t.0)?;359				write!(f, "{}", t.1)360			}361		}362	}363}364impl PartialEq for StrValue {365	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.366	#[allow(clippy::unconditional_recursion)]367	fn eq(&self, other: &Self) -> bool {368		let a = self.clone().into_flat();369		let b = other.clone().into_flat();370		a == b371	}372}373impl Eq for StrValue {}374impl PartialOrd for StrValue {375	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {376		Some(self.cmp(other))377	}378}379impl Ord for StrValue {380	fn cmp(&self, other: &Self) -> std::cmp::Ordering {381		let a = self.clone().into_flat();382		let b = other.clone().into_flat();383		a.cmp(&b)384	}385}386387/// Represents any valid Jsonnet value.388#[derive(Debug, Clone, Trace, Default)]389pub enum Val {390	/// Represents a Jsonnet boolean.391	Bool(bool),392	/// Represents a Jsonnet null value.393	#[default]394	Null,395	/// Represents a Jsonnet string.396	Str(StrValue),397	/// Represents a Jsonnet number.398	/// Should be finite, and not NaN399	/// This restriction isn't enforced by enum, as enum field can't be marked as private400	Num(f64),401	/// Experimental bigint402	#[cfg(feature = "exp-bigint")]403	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),404	/// Represents a Jsonnet array.405	Arr(ArrValue),406	/// Represents a Jsonnet object.407	Obj(ObjValue),408	/// Represents a Jsonnet function.409	Func(FuncVal),410}411412#[cfg(target_pointer_width = "64")]413static_assertions::assert_eq_size!(Val, [u8; 24]);414415impl From<IndexableVal> for Val {416	fn from(v: IndexableVal) -> Self {417		match v {418			IndexableVal::Str(s) => Self::string(s),419			IndexableVal::Arr(a) => Self::Arr(a),420		}421	}422}423424impl Val {425	pub const fn as_bool(&self) -> Option<bool> {426		match self {427			Self::Bool(v) => Some(*v),428			_ => None,429		}430	}431	pub const fn as_null(&self) -> Option<()> {432		match self {433			Self::Null => Some(()),434			_ => None,435		}436	}437	pub fn as_str(&self) -> Option<IStr> {438		match self {439			Self::Str(s) => Some(s.clone().into_flat()),440			_ => None,441		}442	}443	pub const fn as_num(&self) -> Option<f64> {444		match self {445			Self::Num(n) => Some(*n),446			_ => None,447		}448	}449	pub fn as_arr(&self) -> Option<ArrValue> {450		match self {451			Self::Arr(a) => Some(a.clone()),452			_ => None,453		}454	}455	pub fn as_obj(&self) -> Option<ObjValue> {456		match self {457			Self::Obj(o) => Some(o.clone()),458			_ => None,459		}460	}461	pub fn as_func(&self) -> Option<FuncVal> {462		match self {463			Self::Func(f) => Some(f.clone()),464			_ => None,465		}466	}467468	/// Creates `Val::Num` after checking for numeric overflow.469	/// As numbers are `f64`, we can just check for their finity.470	pub fn new_checked_num(num: f64) -> Result<Self> {471		if num.is_finite() {472			Ok(Self::Num(num))473		} else {474			bail!("overflow")475		}476	}477478	pub const fn value_type(&self) -> ValType {479		match self {480			Self::Str(..) => ValType::Str,481			Self::Num(..) => ValType::Num,482			#[cfg(feature = "exp-bigint")]483			Self::BigInt(..) => ValType::BigInt,484			Self::Arr(..) => ValType::Arr,485			Self::Obj(..) => ValType::Obj,486			Self::Bool(_) => ValType::Bool,487			Self::Null => ValType::Null,488			Self::Func(..) => ValType::Func,489		}490	}491492	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {493		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {494			manifest.manifest(val.clone())495		}496		manifest_dyn(self, &format)497	}498499	pub fn to_string(&self) -> Result<IStr> {500		Ok(match self {501			Self::Bool(true) => "true".into(),502			Self::Bool(false) => "false".into(),503			Self::Null => "null".into(),504			Self::Str(s) => s.clone().into_flat(),505			_ => self.manifest(ToStringFormat).map(IStr::from)?,506		})507	}508509	pub fn into_indexable(self) -> Result<IndexableVal> {510		Ok(match self {511			Self::Str(s) => IndexableVal::Str(s.into_flat()),512			Self::Arr(arr) => IndexableVal::Arr(arr),513			_ => bail!(ValueIsNotIndexable(self.value_type())),514		})515	}516517	pub fn function(function: impl Into<FuncVal>) -> Self {518		Self::Func(function.into())519	}520	pub fn string(string: impl Into<StrValue>) -> Self {521		Self::Str(string.into())522	}523}524525impl From<IStr> for Val {526	fn from(value: IStr) -> Self {527		Self::string(value)528	}529}530impl From<String> for Val {531	fn from(value: String) -> Self {532		Self::string(value)533	}534}535impl From<&str> for Val {536	fn from(value: &str) -> Self {537		Self::string(value)538	}539}540impl From<ObjValue> for Val {541	fn from(value: ObjValue) -> Self {542		Self::Obj(value)543	}544}545546const fn is_function_like(val: &Val) -> bool {547	matches!(val, Val::Func(_))548}549550/// Native implementation of `std.primitiveEquals`551pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {552	Ok(match (val_a, val_b) {553		(Val::Bool(a), Val::Bool(b)) => a == b,554		(Val::Null, Val::Null) => true,555		(Val::Str(a), Val::Str(b)) => a == b,556		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,557		#[cfg(feature = "exp-bigint")]558		(Val::BigInt(a), Val::BigInt(b)) => a == b,559		(Val::Arr(_), Val::Arr(_)) => {560			bail!("primitiveEquals operates on primitive types, got array")561		}562		(Val::Obj(_), Val::Obj(_)) => {563			bail!("primitiveEquals operates on primitive types, got object")564		}565		(a, b) if is_function_like(a) && is_function_like(b) => {566			bail!("cannot test equality of functions")567		}568		(_, _) => false,569	})570}571572/// Native implementation of `std.equals`573pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {574	if val_a.value_type() != val_b.value_type() {575		return Ok(false);576	}577	match (val_a, val_b) {578		(Val::Arr(a), Val::Arr(b)) => {579			if ArrValue::ptr_eq(a, b) {580				return Ok(true);581			}582			if a.len() != b.len() {583				return Ok(false);584			}585			for (a, b) in a.iter().zip(b.iter()) {586				if !equals(&a?, &b?)? {587					return Ok(false);588				}589			}590			Ok(true)591		}592		(Val::Obj(a), Val::Obj(b)) => {593			if ObjValue::ptr_eq(a, b) {594				return Ok(true);595			}596			let fields = a.fields(597				#[cfg(feature = "exp-preserve-order")]598				false,599			);600			if fields601				!= b.fields(602					#[cfg(feature = "exp-preserve-order")]603					false,604				) {605				return Ok(false);606			}607			for field in fields {608				if !equals(609					&a.get(field.clone())?.expect("field exists"),610					&b.get(field)?.expect("field exists"),611				)? {612					return Ok(false);613				}614			}615			Ok(true)616		}617		(a, b) => Ok(primitive_equals(a, b)?),618	}619}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -34,6 +34,12 @@
 
 	Ok(Some(attr))
 }
+fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)
+where
+	Ident: PartialEq<I>,
+{
+	attrs.retain(|a| !a.path().is_ident(&ident));
+}
 
 fn path_is(path: &Path, needed: &str) -> bool {
 	path.leading_colon.is_none()
@@ -121,10 +127,21 @@
 	}
 }
 
+enum Optionality {
+	Required,
+	Optional,
+	Default(Expr),
+}
+impl Optionality {
+	fn is_optional(&self) -> bool {
+		!matches!(self, Self::Required)
+	}
+}
+
 enum ArgInfo {
 	Normal {
 		ty: Box<Type>,
-		is_option: bool,
+		optionality: Optionality,
 		name: Option<String>,
 		cfg_attrs: Vec<Attribute>,
 	},
@@ -138,7 +155,7 @@
 }
 
 impl ArgInfo {
-	fn parse(name: &str, arg: &FnArg) -> Result<Self> {
+	fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {
 		let FnArg::Typed(arg) = arg else {
 			unreachable!()
 		};
@@ -163,7 +180,10 @@
 			_ => {}
 		}
 
-		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
+		let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {
+			remove_attr(&mut arg.attrs, "default");
+			(Optionality::Default(default), ty.clone())
+		} else if let Some(ty) = extract_type_from_option(ty)? {
 			if type_is_path(ty, "Thunk").is_some() {
 				return Ok(Self::Lazy {
 					is_option: true,
@@ -171,9 +191,9 @@
 				});
 			}
 
-			(true, Box::new(ty.clone()))
+			(Optionality::Optional, Box::new(ty.clone()))
 		} else {
-			(false, ty.clone())
+			(Optionality::Required, ty.clone())
 		};
 
 		let cfg_attrs = arg
@@ -185,7 +205,7 @@
 
 		Ok(Self::Normal {
 			ty,
-			is_option,
+			optionality,
 			name: ident.map(|v| v.to_string()),
 			cfg_attrs,
 		})
@@ -201,18 +221,14 @@
 	let item_fn = item.clone();
 	let item_fn: ItemFn = parse_macro_input!(item_fn);
 
-	match builtin_inner(attr, item_fn, item.into()) {
+	match builtin_inner(attr, item_fn) {
 		Ok(v) => v.into(),
 		Err(e) => e.into_compile_error().into(),
 	}
 }
 
 #[allow(clippy::too_many_lines)]
-fn builtin_inner(
-	attr: BuiltinAttrs,
-	fun: ItemFn,
-	item: proc_macro2::TokenStream,
-) -> syn::Result<TokenStream> {
+fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {
 	let ReturnType::Type(_, result) = &fun.sig.output else {
 		return Err(Error::new(
 			fun.sig.span(),
@@ -224,13 +240,13 @@
 	let args = fun
 		.sig
 		.inputs
-		.iter()
+		.iter_mut()
 		.map(|arg| ArgInfo::parse(&name, arg))
 		.collect::<Result<Vec<_>>>()?;
 
 	let params_desc = args.iter().filter_map(|a| match a {
 		ArgInfo::Normal {
-			is_option,
+			optionality,
 			name,
 			cfg_attrs,
 			..
@@ -238,9 +254,10 @@
 			let name = name
 				.as_ref()
 				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
+			let is_optional = optionality.is_optional();
 			Some(quote! {
 				#(#cfg_attrs)*
-				BuiltinParam::new(#name, #is_option),
+				BuiltinParam::new(#name, #is_optional),
 			})
 		}
 		ArgInfo::Lazy { is_option, name } => {
@@ -270,7 +287,7 @@
 		.map(|(id, a)| match a {
 			ArgInfo::Normal {
 				ty,
-				is_option,
+				optionality,
 				name,
 				cfg_attrs,
 			} => {
@@ -279,17 +296,22 @@
 					|| format!("argument <{}> evaluation", #name),
 					|| <#ty>::from_untyped(value.evaluate()?),
 				)?};
-				let value = if *is_option {
-					quote! {if let Some(value) = &parsed[#id] {
+				let value = match optionality {
+					Optionality::Required => quote! {{
+						let value = parsed[#id].as_ref().expect("args shape is checked");
+						#eval
+					},},
+					Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {
 						Some(#eval)
 					} else {
 						None
-					},}
-				} else {
-					quote! {{
-						let value = parsed[#id].as_ref().expect("args shape is checked");
+					},},
+					Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {
 						#eval
-					},}
+					} else {
+						let v: #ty = #expr;
+						v
+					},},
 				};
 				quote! {
 					#(#cfg_attrs)*
@@ -302,7 +324,7 @@
 						Some(value.clone())
 					} else {
 						None
-					}}
+					},}
 				} else {
 					quote! {
 						parsed[#id].as_ref().expect("args shape is correct").clone(),
@@ -343,7 +365,7 @@
 	};
 
 	Ok(quote! {
-		#item
+		#fun
 
 		#[doc(hidden)]
 		#[allow(non_camel_case_types)]
@@ -373,7 +395,7 @@
 				fn params(&self) -> &[BuiltinParam] {
 					PARAMS
 				}
-				#[allow(unused_variable)]
+				#[allow(unused_variables)]
 				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
 					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;
 
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -265,21 +265,18 @@
 }
 
 #[builtin]
-pub fn builtin_remove_at(arr: ArrValue, at: usize) -> Result<ArrValue> {
+pub fn builtin_remove_at(arr: ArrValue, at: i32) -> Result<ArrValue> {
 	let newArrLeft = arr.clone().slice(None, Some(at), None);
 	let newArrRight = arr.slice(Some(at + 1), None, None);
 
-	Ok(ArrValue::extended(
-		newArrLeft.unwrap_or_else(ArrValue::empty),
-		newArrRight.unwrap_or_else(ArrValue::empty),
-	))
+	Ok(ArrValue::extended(newArrLeft, newArrRight))
 }
 
 #[builtin]
 pub fn builtin_remove(arr: ArrValue, elem: Val) -> Result<ArrValue> {
 	for (index, item) in arr.iter().enumerate() {
 		if equals(&item?, &elem)? {
-			return builtin_remove_at(arr.clone(), index);
+			return builtin_remove_at(arr.clone(), index as i32);
 		}
 	}
 	Ok(arr)
@@ -325,7 +322,9 @@
 #[builtin]
 pub fn builtin_prune(
 	a: Val,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> Result<Val> {
 	fn is_content(val: &Val) -> bool {
 		match val {
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -102,6 +102,7 @@
 		("sign", builtin_sign::INST),
 		("max", builtin_max::INST),
 		("min", builtin_min::INST),
+		("clamp", builtin_clamp::INST),
 		("sum", builtin_sum::INST),
 		("modulo", builtin_modulo::INST),
 		("floor", builtin_floor::INST),
@@ -163,11 +164,20 @@
 		("objectRemoveKey", builtin_object_remove_key::INST),
 		// Manifest
 		("escapeStringJson", builtin_escape_string_json::INST),
+		("escapeStringPython", builtin_escape_string_json::INST),
+		("escapeStringXML", builtin_escape_string_xml::INST),
 		("manifestJsonEx", builtin_manifest_json_ex::INST),
+		("manifestJson", builtin_manifest_json::INST),
+		("manifestJsonMinified", builtin_manifest_json_minified::INST),
 		("manifestYamlDoc", builtin_manifest_yaml_doc::INST),
+		("manifestYamlStream", builtin_manifest_yaml_stream::INST),
 		("manifestTomlEx", builtin_manifest_toml_ex::INST),
+		("manifestToml", builtin_manifest_toml::INST),
 		("toString", builtin_to_string::INST),
-		// Parsing
+		("manifestPython", builtin_manifest_python::INST),
+		("manifestPythonVars", builtin_manifest_python_vars::INST),
+		("manifestXmlJsonml", builtin_manifest_xml_jsonml::INST),
+		// Parse
 		("parseJson", builtin_parse_json::INST),
 		("parseYaml", builtin_parse_yaml::INST),
 		// Strings
@@ -175,10 +185,13 @@
 		("substr", builtin_substr::INST),
 		("char", builtin_char::INST),
 		("strReplace", builtin_str_replace::INST),
+		("escapeStringBash", builtin_escape_string_bash::INST),
+		("escapeStringDollars", builtin_escape_string_dollars::INST),
 		("isEmpty", builtin_is_empty::INST),
 		("equalsIgnoreCase", builtin_equals_ignore_case::INST),
 		("splitLimit", builtin_splitlimit::INST),
 		("splitLimitR", builtin_splitlimitr::INST),
+		("split", builtin_split::INST),
 		("asciiUpper", builtin_ascii_upper::INST),
 		("asciiLower", builtin_ascii_lower::INST),
 		("findSubstr", builtin_find_substr::INST),
@@ -190,6 +203,7 @@
 		("stringChars", builtin_string_chars::INST),
 		// Misc
 		("length", builtin_length::INST),
+		("get", builtin_get::INST),
 		("startsWith", builtin_starts_with::INST),
 		("endsWith", builtin_ends_with::INST),
 		// Sets
modifiedcrates/jrsonnet-stdlib/src/manifest/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/mod.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/mod.rs
@@ -1,13 +1,17 @@
+mod python;
 mod toml;
+mod xml;
 mod yaml;
 
 use jrsonnet_evaluator::{
 	function::builtin,
-	manifest::{escape_string_json, JsonFormat},
+	manifest::{escape_string_json, JsonFormat, YamlStreamFormat},
 	IStr, ObjValue, Result, Val,
 };
+pub use python::{PythonFormat, PythonVarsFormat};
 pub use toml::TomlFormat;
 pub use yaml::YamlFormat;
+pub use xml::XmlJsonmlFormat;
 
 #[builtin]
 pub fn builtin_escape_string_json(str_: IStr) -> Result<String> {
@@ -17,51 +21,149 @@
 #[builtin]
 pub fn builtin_manifest_json_ex(
 	value: Val,
-	indent: IStr,
+	indent: String,
 	newline: Option<IStr>,
 	key_val_sep: Option<IStr>,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> Result<String> {
 	let newline = newline.as_deref().unwrap_or("\n");
 	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
 	value.manifest(JsonFormat::std_to_json(
-		indent.to_string(),
+		indent,
 		newline,
 		key_val_sep,
 		#[cfg(feature = "exp-preserve-order")]
-		preserve_order.unwrap_or(false),
+		preserve_order,
+	))
+}
+
+#[builtin]
+pub fn builtin_manifest_json(
+	value: Val,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+) -> Result<String> {
+	builtin_manifest_json_ex(
+		value,
+		"    ".to_owned(),
+		None,
+		None,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+
+#[builtin]
+pub fn builtin_manifest_json_minified(
+	value: Val,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+) -> Result<String> {
+	value.manifest(JsonFormat::minify(
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
 	))
 }
 
 #[builtin]
 pub fn builtin_manifest_yaml_doc(
 	value: Val,
-	indent_array_in_object: Option<bool>,
-	quote_keys: Option<bool>,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+	#[default(false)] indent_array_in_object: bool,
+	#[default(true)] quote_keys: bool,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> Result<String> {
 	value.manifest(YamlFormat::std_to_yaml(
-		indent_array_in_object.unwrap_or(false),
-		quote_keys.unwrap_or(true),
+		indent_array_in_object,
+		quote_keys,
 		#[cfg(feature = "exp-preserve-order")]
-		preserve_order.unwrap_or(false),
+		preserve_order,
+	))
+}
+
+#[builtin]
+pub fn builtin_manifest_yaml_stream(
+	value: Val,
+	#[default(false)] indent_array_in_object: bool,
+	#[default(true)] c_document_end: bool,
+	#[default(true)] quote_keys: bool,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+) -> Result<String> {
+	value.manifest(YamlStreamFormat::std_yaml_stream(
+		YamlFormat::std_to_yaml(
+			indent_array_in_object,
+			quote_keys,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		),
+		c_document_end,
 	))
 }
 
 #[builtin]
 pub fn builtin_manifest_toml_ex(
 	value: ObjValue,
-	indent: IStr,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+	indent: String,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> Result<String> {
 	Val::Obj(value).manifest(TomlFormat::std_to_toml(
-		indent.to_string(),
+		indent,
 		#[cfg(feature = "exp-preserve-order")]
-		preserve_order.unwrap_or(false),
+		preserve_order,
 	))
 }
 
 #[builtin]
+pub fn builtin_manifest_toml(
+	value: ObjValue,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+) -> Result<String> {
+	builtin_manifest_toml_ex(
+		value,
+		"  ".to_owned(),
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+
+#[builtin]
 pub fn builtin_to_string(a: Val) -> Result<IStr> {
 	a.to_string()
 }
+
+#[builtin]
+pub fn builtin_manifest_python(v: Val) -> Result<String> {
+	v.manifest(PythonFormat {})
+}
+#[builtin]
+pub fn builtin_manifest_python_vars(v: Val) -> Result<String> {
+	v.manifest(PythonVarsFormat {})
+}
+
+#[builtin]
+pub fn builtin_escape_string_xml(str_: String) -> String {
+	xml::escape_string_xml(str_.as_str())
+}
+
+#[builtin]
+pub fn builtin_manifest_xml_jsonml(value: Val) -> Result<String> {
+	value.manifest(XmlJsonmlFormat::std_to_xml())
+}
addedcrates/jrsonnet-stdlib/src/manifest/python.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/python.rs
@@ -0,0 +1,87 @@
+use jrsonnet_evaluator::{
+	bail,
+	manifest::{escape_string_json_buf, ManifestFormat, ToStringFormat},
+	Result, Val,
+};
+
+pub struct PythonFormat {
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+}
+
+impl ManifestFormat for PythonFormat {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		match val {
+			Val::Bool(true) => buf.push_str("True"),
+			Val::Bool(false) => buf.push_str("False"),
+			Val::Null => buf.push_str("None"),
+			Val::Str(s) => escape_string_json_buf(&s.to_string(), buf),
+			Val::Num(_) => ToStringFormat.manifest_buf(val, buf)?,
+			Val::Arr(arr) => {
+				buf.push('[');
+				for (i, el) in arr.iter().enumerate() {
+					let el = el?;
+					if i != 0 {
+						buf.push_str(", ");
+					}
+					self.manifest_buf(el, buf)?;
+				}
+				buf.push(']');
+			}
+			Val::Obj(obj) => {
+				obj.run_assertions()?;
+				buf.push('{');
+				let fields = obj.fields(
+					#[cfg(feature = "exp-preserve-order")]
+					self.preserve_order,
+				);
+				for (i, field) in fields.into_iter().enumerate() {
+					if i != 0 {
+						buf.push_str(", ");
+					}
+					escape_string_json_buf(&field, buf);
+					buf.push_str(": ");
+					let value = obj.get(field)?.expect("field exists");
+					self.manifest_buf(value, buf)?;
+				}
+				buf.push('}');
+			}
+			Val::Func(_) => bail!("tried to manifest function"),
+		}
+		Ok(())
+	}
+}
+
+pub struct PythonVarsFormat {
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+}
+
+impl PythonVarsFormat {}
+
+impl ManifestFormat for PythonVarsFormat {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		let inner = PythonFormat {
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: self.preserve_order,
+		};
+		let Val::Obj(obj) = val else {
+			bail!("python vars root should be object");
+		};
+		obj.run_assertions()?;
+
+		let fields = obj.fields(
+			#[cfg(feature = "exp-preserve-order")]
+			self.preserve_order,
+		);
+
+		for field in fields {
+			// Yep, no escaping
+			buf.push_str(&field);
+			buf.push_str(" = ");
+			inner.manifest_buf(obj.get(field)?.expect("field exists"), buf)?;
+			buf.push('\n');
+		}
+		Ok(())
+	}
+}
addedcrates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -0,0 +1,173 @@
+use jrsonnet_evaluator::{
+	bail,
+	manifest::{ManifestFormat, ToStringFormat},
+	typed::{ComplexValType, Either4, Typed, ValType},
+	val::{ArrValue, IndexableVal},
+	Either, ObjValue, Result, ResultExt, Val,
+};
+
+pub struct XmlJsonmlFormat {
+	force_closing: bool,
+}
+impl XmlJsonmlFormat {
+	pub fn std_to_xml() -> Self {
+		Self {
+			force_closing: true,
+		}
+	}
+	pub fn cli() -> Self {
+		Self {
+			force_closing: false,
+		}
+	}
+}
+
+enum JSONMLValue {
+	Tag {
+		tag: String,
+		attrs: ObjValue,
+		children: Vec<JSONMLValue>,
+	},
+	String(String),
+}
+impl Typed for JSONMLValue {
+	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);
+
+	fn into_untyped(_typed: Self) -> Result<Val> {
+		unreachable!("not used, reserved for parseXML?")
+	}
+
+	fn from_untyped(untyped: Val) -> Result<Self> {
+		let Val::Arr(arr) = untyped else {
+			if let Val::Str(s) = untyped {
+				return Ok(Self::String(s.to_string()));
+			};
+			bail!("expected JSONML value (an array or string)");
+		};
+		if arr.len() < 1 {
+			bail!("JSONML value should have tag");
+		};
+		let tag = String::from_untyped(
+			arr.get(0)
+				.with_description(|| "getting JSONML tag")?
+				.expect("length checked"),
+		)?;
+		let (has_attrs, attrs) = if arr.len() >= 2 {
+			let maybe_attrs = arr
+				.get(1)
+				.with_description(|| "getting JSONML attrs")?
+				.expect("length checked");
+			if let Val::Obj(attrs) = maybe_attrs {
+				(true, attrs)
+			} else {
+				(false, ObjValue::new_empty())
+			}
+		} else {
+			(false, ObjValue::new_empty())
+		};
+		Ok(Self::Tag {
+			tag,
+			attrs,
+			children: Typed::from_untyped(Val::Arr(arr.slice(
+				Some(if has_attrs { 2 } else { 1 }),
+				None,
+				None,
+			)))?,
+		})
+	}
+}
+
+impl ManifestFormat for XmlJsonmlFormat {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;
+		manifest_jsonml(&val, buf, self)
+	}
+}
+
+fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {
+	match v {
+		JSONMLValue::Tag {
+			tag,
+			attrs,
+			children,
+		} => {
+			let has_children = !children.is_empty();
+			buf.push('<');
+			buf.push_str(&tag);
+			attrs.run_assertions()?;
+			for (key, value) in attrs.iter() {
+				buf.push(' ');
+				buf.push_str(&key);
+				buf.push('=');
+				buf.push('"');
+				let value = value?;
+				let value = if let Val::Str(s) = value {
+					s.to_string()
+				} else {
+					ToStringFormat.manifest(value)?
+				};
+				escape_string_xml_buf(&value, buf);
+				buf.push('"');
+			}
+			if !has_children && !opts.force_closing {
+				buf.push('/');
+			}
+			buf.push('>');
+			for child in children {
+				manifest_jsonml(&child, buf, opts)?;
+			}
+			if has_children || opts.force_closing {
+				buf.push('<');
+				buf.push('/');
+				buf.push_str(&tag);
+				buf.push('>');
+			}
+			Ok(())
+		}
+		JSONMLValue::String(s) => {
+			escape_string_xml_buf(s, buf);
+			Ok(())
+		}
+	}
+}
+
+pub fn escape_string_xml(str: &str) -> String {
+	let mut out = String::new();
+	escape_string_xml_buf(str, &mut out);
+	out
+}
+
+fn escape_string_xml_buf(str: &str, out: &mut String) {
+	if str.is_empty() {
+		return;
+	}
+	let mut remaining = str;
+
+	let mut found = false;
+	while let Some(position) = remaining
+		.bytes()
+		.position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))
+	{
+		found = true;
+
+		let (plain, rem) = remaining.split_at(position);
+		out.push_str(plain);
+
+		out.push_str(match rem.as_bytes()[0] {
+			b'<' => "&lt;",
+			b'>' => "&gt;",
+			b'&' => "&amp;",
+			b'"' => "&quot;",
+			b'\'' => "&apos;",
+			_ => unreachable!("position() searches for those matches"),
+		});
+
+		remaining = &rem[1..];
+	}
+	if !found {
+		// No match - no escapes required
+		out.push_str(&str);
+		return;
+	}
+	out.push_str(&remaining);
+}
modifiedcrates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/math.rs
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -24,6 +24,12 @@
 	a.min(b)
 }
 
+#[allow(non_snake_case)]
+#[builtin]
+pub fn builtin_clamp(x: f64, minVal: f64, maxVal: f64) -> f64 {
+	x.clamp(minVal, maxVal)
+}
+
 #[builtin]
 pub fn builtin_sum(arr: Vec<f64>) -> f64 {
 	arr.iter().sum()
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -23,6 +23,30 @@
 	}
 }
 
+#[builtin]
+pub fn builtin_get(
+	o: ObjValue,
+	f: IStr,
+	default: Option<Thunk<Val>>,
+	#[default(true)]
+	inc_hidden: bool,
+) -> Result<Val> {
+	let do_default = move || {
+		let Some(default) = default else {
+			return Ok(Val::Null);
+		};
+		default.evaluate()
+	};
+	// Happy path for invisible fields
+	if !inc_hidden && !o.has_field_ex(f.clone(), false) {
+		return do_default();
+	}
+	let Some(v) = o.get(f)? else {
+		return do_default();
+	};
+	Ok(v)
+}
+
 #[builtin(fields(
 	settings: Rc<RefCell<Settings>>,
 ))]
modifiedcrates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -8,10 +8,11 @@
 pub fn builtin_object_fields_ex(
 	obj: ObjValue,
 	hidden: bool,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Vec<Val> {
+
+	#[default(false)]
 	#[cfg(feature = "exp-preserve-order")]
-	let preserve_order = preserve_order.unwrap_or(false);
+	preserve_order: bool,
+) -> Vec<Val> {
 	let out = obj.fields_ex(
 		hidden,
 		#[cfg(feature = "exp-preserve-order")]
@@ -23,7 +24,10 @@
 #[builtin]
 pub fn builtin_object_fields(
 	o: ObjValue,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> Vec<Val> {
 	builtin_object_fields_ex(
 		o,
@@ -36,7 +40,10 @@
 #[builtin]
 pub fn builtin_object_fields_all(
 	o: ObjValue,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> Vec<Val> {
 	builtin_object_fields_ex(
 		o,
@@ -49,10 +56,9 @@
 pub fn builtin_object_values_ex(
 	o: ObjValue,
 	include_hidden: bool,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
 ) -> ArrValue {
-	#[cfg(feature = "exp-preserve-order")]
-	let preserve_order = preserve_order.unwrap_or(false);
 	o.values_ex(
 		include_hidden,
 		#[cfg(feature = "exp-preserve-order")]
@@ -62,7 +68,10 @@
 #[builtin]
 pub fn builtin_object_values(
 	o: ObjValue,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> ArrValue {
 	builtin_object_values_ex(
 		o,
@@ -74,7 +83,10 @@
 #[builtin]
 pub fn builtin_object_values_all(
 	o: ObjValue,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> ArrValue {
 	builtin_object_values_ex(
 		o,
@@ -87,10 +99,8 @@
 pub fn builtin_object_keys_values_ex(
 	o: ObjValue,
 	include_hidden: bool,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
 ) -> ArrValue {
-	#[cfg(feature = "exp-preserve-order")]
-	let preserve_order = preserve_order.unwrap_or(false);
 	o.key_values_ex(
 		include_hidden,
 		#[cfg(feature = "exp-preserve-order")]
@@ -100,7 +110,10 @@
 #[builtin]
 pub fn builtin_object_keys_values(
 	o: ObjValue,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> ArrValue {
 	builtin_object_keys_values_ex(
 		o,
@@ -112,7 +125,10 @@
 #[builtin]
 pub fn builtin_object_keys_values_all(
 	o: ObjValue,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> ArrValue {
 	builtin_object_keys_values_ex(
 		o,
@@ -141,12 +157,13 @@
 pub fn builtin_object_remove_key(
 	obj: ObjValue,
 	key: IStr,
+
 	// Standard implementation uses std.objectFields without such argument, we can't
 	// assume order preservation should always be enabled/disabled
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+	#[default(false)]
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
 ) -> ObjValue {
-	#[cfg(feature = "exp-preserve-order")]
-	let preserve_order = preserve_order.unwrap_or(false);
 	let mut new_obj = ObjValueBuilder::with_capacity(obj.len() - 1);
 	for (k, v) in obj.iter(
 		#[cfg(feature = "exp-preserve-order")]
modifiedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -139,8 +139,12 @@
 }
 
 #[builtin]
-pub fn builtin_sort(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
-	super::sort::sort(arr, keyF.unwrap_or_else(FuncVal::identity))
+pub fn builtin_sort(
+	arr: ArrValue,
+
+	#[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
+	super::sort::sort(arr, keyF)
 }
 
 fn uniq_identity(arr: Vec<Val>) -> Result<Vec<Val>> {
@@ -174,11 +178,14 @@
 
 #[builtin]
 #[allow(non_snake_case)]
-pub fn builtin_uniq(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_uniq(
+	arr: ArrValue,
+
+	#[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
 	if arr.len() <= 1 {
 		return Ok(arr);
 	}
-	let keyF = keyF.unwrap_or(FuncVal::identity());
 	if keyF.is_identity() {
 		Ok(ArrValue::eager(uniq_identity(
 			arr.iter().collect::<Result<Vec<Val>>>()?,
@@ -190,11 +197,14 @@
 
 #[builtin]
 #[allow(non_snake_case)]
-pub fn builtin_set(arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_set(
+	arr: ArrValue,
+
+	#[default(FuncVal::identity())] keyF: FuncVal,
+) -> Result<ArrValue> {
 	if arr.len() <= 1 {
 		return Ok(arr);
 	}
-	let keyF = keyF.unwrap_or(FuncVal::identity());
 	if keyF.is_identity() {
 		let arr = arr.iter().collect::<Result<Vec<Val>>>()?;
 		let arr = sort_identity(arr)?;
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -1,6 +1,5 @@
 {
   local std = self,
-  local id = std.id,
 
   thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support.\nThis will slow down stdlib caching a bit, though',
 
@@ -19,8 +18,6 @@
 
   stripChars(str, chars)::
     std.lstripChars(std.rstripChars(str, chars), chars),
-
-  split(str, c):: std.splitLimit(str, c, -1),
 
   mapWithIndex(func, arr)::
     if !std.isFunction(func) then
@@ -55,11 +52,6 @@
     else
       error 'Assertion failed. ' + a + ' != ' + b,
 
-  clamp(x, minVal, maxVal)::
-    if x < minVal then minVal
-    else if x > maxVal then maxVal
-    else x,
-
   manifestIni(ini)::
     local body_lines(body) =
       std.join([], [
@@ -79,98 +71,7 @@
       for k in std.objectFields(ini.sections)
     ];
     std.join('\n', main_body + std.flattenArrays(all_sections) + ['']),
-
-  manifestToml(value):: std.manifestTomlEx(value, '  '),
-
-  escapeStringPython(str)::
-    std.escapeStringJson(str),
-
-  escapeStringBash(str_)::
-    local str = std.toString(str_);
-    local trans(ch) =
-      if ch == "'" then
-        "'\"'\"'"
-      else
-        ch;
-    "'%s'" % std.join('', [trans(ch) for ch in std.stringChars(str)]),
-
-  escapeStringDollars(str_)::
-    local str = std.toString(str_);
-    local trans(ch) =
-      if ch == '$' then
-        '$$'
-      else
-        ch;
-    std.foldl(function(a, b) a + trans(b), std.stringChars(str), ''),
-
-  local xml_escapes = {
-    '<': '&lt;',
-    '>': '&gt;',
-    '&': '&amp;',
-    '"': '&quot;',
-    "'": '&apos;',
-  },
-
-  escapeStringXML(str_)::
-    local str = std.toString(str_);
-    std.join('', [std.get(xml_escapes, ch, ch) for ch in std.stringChars(str)]),
-
-  manifestJson(value):: std.manifestJsonEx(value, '    ') tailstrict,
-
-  manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),
-
-  manifestYamlStream(value, indent_array_in_object=false, c_document_end=true, quote_keys=true)::
-    if !std.isArray(value) then
-      error 'manifestYamlStream only takes arrays, got ' + std.type(value)
-    else
-      '---\n' + std.join(
-        '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object, quote_keys) for e in value]
-      ) + if c_document_end then '\n...\n' else '\n',
-
-  manifestPython(v)::
-    if std.isObject(v) then
-      local fields = [
-        '%s: %s' % [std.escapeStringPython(k), std.manifestPython(v[k])]
-        for k in std.objectFields(v)
-      ];
-      '{%s}' % [std.join(', ', fields)]
-    else if std.isArray(v) then
-      '[%s]' % [std.join(', ', [std.manifestPython(v2) for v2 in v])]
-    else if std.isString(v) then
-      '%s' % [std.escapeStringPython(v)]
-    else if std.isFunction(v) then
-      error 'cannot manifest function'
-    else if std.isNumber(v) then
-      std.toString(v)
-    else if v == true then
-      'True'
-    else if v == false then
-      'False'
-    else if v == null then
-      'None',
 
-  manifestPythonVars(conf)::
-    local vars = ['%s = %s' % [k, std.manifestPython(conf[k])] for k in std.objectFields(conf)];
-    std.join('\n', vars + ['']),
-
-  manifestXmlJsonml(value)::
-    if !std.isArray(value) then
-      error 'Expected a JSONML value (an array), got %s' % std.type(value)
-    else
-      local aux(v) =
-        if std.isString(v) then
-          v
-        else
-          local tag = v[0];
-          local has_attrs = std.length(v) > 1 && std.isObject(v[1]);
-          local attrs = if has_attrs then v[1] else {};
-          local children = if has_attrs then v[2:] else v[1:];
-          local attrs_str =
-            std.join('', [' %s="%s"' % [k, attrs[k]] for k in std.objectFields(attrs)]);
-          std.deepJoin(['<', tag, attrs_str, '>', [aux(x) for x in children], '</', tag, '>']);
-
-      aux(value),
-
   mergePatch(target, patch)::
     if std.isObject(patch) then
       local target_object =
@@ -194,9 +95,6 @@
       }
     else
       patch,
-
-  get(o, f, default=null, inc_hidden=true)::
-    if std.objectHasEx(o, f, inc_hidden) then o[f] else default,
 
   resolvePath(f, r)::
     local arr = std.split(f, '/');
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -28,6 +28,20 @@
 }
 
 #[builtin]
+pub fn builtin_escape_string_bash(str: String) -> String {
+	const QUOTE: char = '\'';
+	let mut out = str.replace(QUOTE, "'\"'\"'");
+	out.insert(0, QUOTE);
+	out.push(QUOTE);
+	out
+}
+
+#[builtin]
+pub fn builtin_escape_string_dollars(str: String) -> String {
+	str.replace('$', "$$")
+}
+
+#[builtin]
 pub fn builtin_is_empty(str: String) -> bool {
 	str.is_empty()
 }
@@ -66,6 +80,12 @@
 }
 
 #[builtin]
+pub fn builtin_split(str: IStr, c: IStr) -> ArrValue {
+	use Either2::*;
+	builtin_splitlimit(str, c, B(M1))
+}
+
+#[builtin]
 pub fn builtin_ascii_upper(str: IStr) -> String {
 	str.to_ascii_uppercase()
 }