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
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,6 +2,7 @@
 	cell::RefCell,
 	fmt::{self, Debug, Display},
 	mem::replace,
+	num::{NonZeroU32, NonZeroUsize},
 	rc::Rc,
 };
 
@@ -277,26 +278,11 @@
 					.into(),
 				))
 			}
-			Self::Arr(arr) => {
-				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, arr.len(), 0);
-				let end = get_idx(end, arr.len(), arr.len());
-				let step = step.as_deref().copied().unwrap_or(1);
-
-				if index >= end {
-					return Ok(Self::Arr(ArrValue::empty()));
-				}
-
-				Ok(Self::Arr(
-					arr.clone()
-						.slice(Some(index), Some(end), Some(step))
-						.expect("arguments checked"),
-				))
-			}
+			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(
+				index,
+				end,
+				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),
+			))),
 		}
 	}
 }
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-macros/src/lib.rs
1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6	parenthesized,7	parse::{Parse, ParseStream},8	parse_macro_input,9	punctuated::Punctuated,10	spanned::Spanned,11	token::{self, Comma},12	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13	PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18	Ident: PartialEq<I>,19{20	let attrs = attrs21		.iter()22		.filter(|a| a.path().is_ident(&ident))23		.collect::<Vec<_>>();24	if attrs.len() > 1 {25		return Err(Error::new(26			attrs[1].span(),27			"this attribute may be specified only once",28		));29	} else if attrs.is_empty() {30		return Ok(None);31	}32	let attr = attrs[0];33	let attr = attr.parse_args::<A>()?;3435	Ok(Some(attr))36}3738fn path_is(path: &Path, needed: &str) -> bool {39	path.leading_colon.is_none()40		&& !path.segments.is_empty()41		&& path.segments.iter().last().unwrap().ident == needed42}4344fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {45	match ty {46		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {47			let args = &path.path.segments.iter().last().unwrap().arguments;48			Some(args)49		}50		_ => None,51	}52}5354fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {55	let Some(args) = type_is_path(ty, "Option") else {56		return Ok(None);57	};58	// It should have only on angle-bracketed param ("<String>"):59	let PathArguments::AngleBracketed(params) = args else {60		return Err(Error::new(args.span(), "missing option generic"));61	};62	let generic_arg = params.args.iter().next().unwrap();63	// This argument must be a type:64	let GenericArgument::Type(ty) = generic_arg else {65		return Err(Error::new(66			generic_arg.span(),67			"option generic should be a type",68		));69	};70	Ok(Some(ty))71}7273struct Field {74	attrs: Vec<Attribute>,75	name: Ident,76	_colon: Token![:],77	ty: Type,78}79impl Parse for Field {80	fn parse(input: ParseStream) -> syn::Result<Self> {81		Ok(Self {82			attrs: input.call(Attribute::parse_outer)?,83			name: input.parse()?,84			_colon: input.parse()?,85			ty: input.parse()?,86		})87	}88}8990mod kw {91	syn::custom_keyword!(fields);92	syn::custom_keyword!(rename);93	syn::custom_keyword!(flatten);94	syn::custom_keyword!(add);95	syn::custom_keyword!(hide);96	syn::custom_keyword!(ok);97}9899struct EmptyAttr;100impl Parse for EmptyAttr {101	fn parse(_input: ParseStream) -> Result<Self> {102		Ok(Self)103	}104}105106struct BuiltinAttrs {107	fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110	fn parse(input: ParseStream) -> syn::Result<Self> {111		if input.is_empty() {112			return Ok(Self { fields: Vec::new() });113		}114		input.parse::<kw::fields>()?;115		let fields;116		parenthesized!(fields in input);117		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118		Ok(Self {119			fields: p.into_iter().collect(),120		})121	}122}123124enum ArgInfo {125	Normal {126		ty: Box<Type>,127		is_option: bool,128		name: Option<String>,129		cfg_attrs: Vec<Attribute>,130	},131	Lazy {132		is_option: bool,133		name: Option<String>,134	},135	Context,136	Location,137	This,138}139140impl ArgInfo {141	fn parse(name: &str, arg: &FnArg) -> Result<Self> {142		let FnArg::Typed(arg) = arg else {143			unreachable!()144		};145		let ident = match &arg.pat as &Pat {146			Pat::Ident(i) => Some(i.ident.clone()),147			_ => None,148		};149		let ty = &arg.ty;150		if type_is_path(ty, "Context").is_some() {151			return Ok(Self::Context);152		} else if type_is_path(ty, "CallLocation").is_some() {153			return Ok(Self::Location);154		} else if type_is_path(ty, "Thunk").is_some() {155			return Ok(Self::Lazy {156				is_option: false,157				name: ident.map(|v| v.to_string()),158			});159		}160161		match ty as &Type {162			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),163			_ => {}164		}165166		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {167			if type_is_path(ty, "Thunk").is_some() {168				return Ok(Self::Lazy {169					is_option: true,170					name: ident.map(|v| v.to_string()),171				});172			}173174			(true, Box::new(ty.clone()))175		} else {176			(false, ty.clone())177		};178179		let cfg_attrs = arg180			.attrs181			.iter()182			.filter(|a| a.path().is_ident("cfg"))183			.cloned()184			.collect();185186		Ok(Self::Normal {187			ty,188			is_option,189			name: ident.map(|v| v.to_string()),190			cfg_attrs,191		})192	}193}194195#[proc_macro_attribute]196pub fn builtin(197	attr: proc_macro::TokenStream,198	item: proc_macro::TokenStream,199) -> proc_macro::TokenStream {200	let attr = parse_macro_input!(attr as BuiltinAttrs);201	let item_fn = item.clone();202	let item_fn: ItemFn = parse_macro_input!(item_fn);203204	match builtin_inner(attr, item_fn, item.into()) {205		Ok(v) => v.into(),206		Err(e) => e.into_compile_error().into(),207	}208}209210#[allow(clippy::too_many_lines)]211fn builtin_inner(212	attr: BuiltinAttrs,213	fun: ItemFn,214	item: proc_macro2::TokenStream,215) -> syn::Result<TokenStream> {216	let ReturnType::Type(_, result) = &fun.sig.output else {217		return Err(Error::new(218			fun.sig.span(),219			"builtin should return something",220		));221	};222223	let name = fun.sig.ident.to_string();224	let args = fun225		.sig226		.inputs227		.iter()228		.map(|arg| ArgInfo::parse(&name, arg))229		.collect::<Result<Vec<_>>>()?;230231	let params_desc = args.iter().filter_map(|a| match a {232		ArgInfo::Normal {233			is_option,234			name,235			cfg_attrs,236			..237		} => {238			let name = name239				.as_ref()240				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});241			Some(quote! {242				#(#cfg_attrs)*243				BuiltinParam::new(#name, #is_option),244			})245		}246		ArgInfo::Lazy { is_option, name } => {247			let name = name248				.as_ref()249				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});250			Some(quote! {251				BuiltinParam::new(#name, #is_option),252			})253		}254		ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,255	});256257	let mut id = 0usize;258	let pass = args259		.iter()260		.map(|a| match a {261			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {262				let cid = id;263				id += 1;264				(quote! {#cid}, a)265			}266			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {267				(quote! {compile_error!("should not use id")}, a)268			}269		})270		.map(|(id, a)| match a {271			ArgInfo::Normal {272				ty,273				is_option,274				name,275				cfg_attrs,276			} => {277				let name = name.as_ref().map_or("<unnamed>", String::as_str);278				let eval = quote! {jrsonnet_evaluator::State::push_description(279					|| format!("argument <{}> evaluation", #name),280					|| <#ty>::from_untyped(value.evaluate()?),281				)?};282				let value = if *is_option {283					quote! {if let Some(value) = &parsed[#id] {284						Some(#eval)285					} else {286						None287					},}288				} else {289					quote! {{290						let value = parsed[#id].as_ref().expect("args shape is checked");291						#eval292					},}293				};294				quote! {295					#(#cfg_attrs)*296					#value297				}298			}299			ArgInfo::Lazy { is_option, .. } => {300				if *is_option {301					quote! {if let Some(value) = &parsed[#id] {302						Some(value.clone())303					} else {304						None305					}}306				} else {307					quote! {308						parsed[#id].as_ref().expect("args shape is correct").clone(),309					}310				}311			}312			ArgInfo::Context => quote! {ctx.clone(),},313			ArgInfo::Location => quote! {location,},314			ArgInfo::This => quote! {self,},315		});316317	let fields = attr.fields.iter().map(|field| {318		let attrs = &field.attrs;319		let name = &field.name;320		let ty = &field.ty;321		quote! {322			#(#attrs)*323			pub #name: #ty,324		}325	});326327	let name = &fun.sig.ident;328	let vis = &fun.vis;329	let static_ext = if attr.fields.is_empty() {330		quote! {331			impl #name {332				pub const INST: &'static dyn StaticBuiltin = &#name {};333			}334			impl StaticBuiltin for #name {}335		}336	} else {337		quote! {}338	};339	let static_derive_copy = if attr.fields.is_empty() {340		quote! {, Copy}341	} else {342		quote! {}343	};344345	Ok(quote! {346		#item347348		#[doc(hidden)]349		#[allow(non_camel_case_types)]350		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]351		#vis struct #name {352			#(#fields)*353		}354		const _: () = {355			use ::jrsonnet_evaluator::{356				State, Val,357				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},358				Result, Context, typed::Typed,359				parser::ExprLocation,360			};361			const PARAMS: &'static [BuiltinParam] = &[362				#(#params_desc)*363			];364365			#static_ext366			impl Builtin for #name367			where368				Self: 'static369			{370				fn name(&self) -> &str {371					stringify!(#name)372				}373				fn params(&self) -> &[BuiltinParam] {374					PARAMS375				}376				#[allow(unused_variable)]377				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {378					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;379380					let result: #result = #name(#(#pass)*);381					<_ as Typed>::into_result(result)382				}383				fn as_any(&self) -> &dyn ::std::any::Any {384					self385				}386			}387		};388	})389}390391#[derive(Default)]392#[allow(clippy::struct_excessive_bools)]393struct TypedAttr {394	rename: Option<String>,395	flatten: bool,396	/// flatten(ok) strategy for flattened optionals397	/// field would be None in case of any parsing error (as in serde)398	flatten_ok: bool,399	// Should it be `field+:` instead of `field:`400	add: bool,401	// Should it be `field::` instead of `field:`402	hide: bool,403}404impl Parse for TypedAttr {405	fn parse(input: ParseStream) -> syn::Result<Self> {406		let mut out = Self::default();407		loop {408			let lookahead = input.lookahead1();409			if lookahead.peek(kw::rename) {410				input.parse::<kw::rename>()?;411				input.parse::<Token![=]>()?;412				let name = input.parse::<LitStr>()?;413				if out.rename.is_some() {414					return Err(Error::new(415						name.span(),416						"rename attribute may only be specified once",417					));418				}419				out.rename = Some(name.value());420			} else if lookahead.peek(kw::flatten) {421				input.parse::<kw::flatten>()?;422				out.flatten = true;423				if input.peek(token::Paren) {424					let content;425					parenthesized!(content in input);426					let lookahead = content.lookahead1();427					if lookahead.peek(kw::ok) {428						content.parse::<kw::ok>()?;429						out.flatten_ok = true;430					} else {431						return Err(lookahead.error());432					}433				}434			} else if lookahead.peek(kw::add) {435				input.parse::<kw::add>()?;436				out.add = true;437			} else if lookahead.peek(kw::hide) {438				input.parse::<kw::hide>()?;439				out.hide = true;440			} else if input.is_empty() {441				break;442			} else {443				return Err(lookahead.error());444			}445			if input.peek(Token![,]) {446				input.parse::<Token![,]>()?;447			} else {448				break;449			}450		}451		Ok(out)452	}453}454455struct TypedField {456	attr: TypedAttr,457	ident: Ident,458	ty: Type,459	is_option: bool,460}461impl TypedField {462	fn parse(field: &syn::Field) -> Result<Self> {463		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();464		let Some(ident) = field.ident.clone() else {465			return Err(Error::new(466				field.span(),467				"this field should appear in output object, but it has no visible name",468			));469		};470		let (is_option, ty) = extract_type_from_option(&field.ty)?471			.map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));472		if is_option && attr.flatten {473			if !attr.flatten_ok {474				return Err(Error::new(475					field.span(),476					"strategy should be set when flattening Option",477				));478			}479		} else if attr.flatten_ok {480			return Err(Error::new(481				field.span(),482				"flatten(ok) is only useable on optional fields",483			));484		}485486		Ok(Self {487			attr,488			ident,489			ty,490			is_option,491		})492	}493	/// None if this field is flattened in jsonnet output494	fn name(&self) -> Option<String> {495		if self.attr.flatten {496			return None;497		}498		Some(499			self.attr500				.rename501				.clone()502				.unwrap_or_else(|| self.ident.to_string()),503		)504	}505506	fn expand_field(&self) -> Option<TokenStream> {507		if self.is_option {508			return None;509		}510		let name = self.name()?;511		let ty = &self.ty;512		Some(quote! {513			(#name, <#ty as Typed>::TYPE)514		})515	}516	fn expand_parse(&self) -> TokenStream {517		let ident = &self.ident;518		let ty = &self.ty;519		if self.attr.flatten {520			// optional flatten is handled in same way as serde521			return if self.is_option {522				quote! {523					#ident: <#ty as TypedObj>::parse(&obj).ok(),524				}525			} else {526				quote! {527					#ident: <#ty as TypedObj>::parse(&obj)?,528				}529			};530		};531532		let name = self.name().unwrap();533		let value = if self.is_option {534			quote! {535				if let Some(value) = obj.get(#name.into())? {536					Some(<#ty as Typed>::from_untyped(value)?)537				} else {538					None539				}540			}541		} else {542			quote! {543				<#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?544			}545		};546547		quote! {548			#ident: #value,549		}550	}551	fn expand_serialize(&self) -> TokenStream {552		let ident = &self.ident;553		let ty = &self.ty;554		self.name().map_or_else(555			|| {556				if self.is_option {557					quote! {558						if let Some(value) = self.#ident {559							<#ty as TypedObj>::serialize(value, out)?;560						}561					}562				} else {563					quote! {564						<#ty as TypedObj>::serialize(self.#ident, out)?;565					}566				}567			},568			|name| {569				let hide = if self.attr.hide {570					quote! {.hide()}571				} else {572					quote! {}573				};574				let add = if self.attr.add {575					quote! {.add()}576				} else {577					quote! {}578				};579				if self.is_option {580					quote! {581						if let Some(value) = self.#ident {582							out.field(#name)583								#hide584								#add585								.try_value(<#ty as Typed>::into_untyped(value)?)?;586						}587					}588				} else {589					quote! {590						out.field(#name)591							#hide592							#add593							.try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;594					}595				}596			},597		)598	}599}600601#[proc_macro_derive(Typed, attributes(typed))]602pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {603	let input = parse_macro_input!(item as DeriveInput);604605	match derive_typed_inner(input) {606		Ok(v) => v.into(),607		Err(e) => e.to_compile_error().into(),608	}609}610611fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {612	let syn::Data::Struct(data) = &input.data else {613		return Err(Error::new(input.span(), "only structs supported"));614	};615616	let ident = &input.ident;617	let fields = data618		.fields619		.iter()620		.map(TypedField::parse)621		.collect::<Result<Vec<_>>>()?;622623	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();624625	let typed = {626		let fields = fields627			.iter()628			.filter_map(TypedField::expand_field)629			.collect::<Vec<_>>();630		quote! {631			impl #impl_generics Typed for #ident #ty_generics #where_clause {632				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[633					#(#fields,)*634				]);635636				fn from_untyped(value: Val) -> JrResult<Self> {637					let obj = value.as_obj().expect("shape is correct");638					Self::parse(&obj)639				}640641				fn into_untyped(value: Self) -> JrResult<Val> {642					let mut out = ObjValueBuilder::new();643					value.serialize(&mut out)?;644					Ok(Val::Obj(out.build()))645				}646647			}648		}649	};650651	let fields_parse = fields.iter().map(TypedField::expand_parse);652	let fields_serialize = fields653		.iter()654		.map(TypedField::expand_serialize)655		.collect::<Vec<_>>();656657	Ok(quote! {658		const _: () = {659			use ::jrsonnet_evaluator::{660				typed::{ComplexValType, Typed, TypedObj, CheckType},661				Val, State,662				error::{ErrorKind, Result as JrResult},663				ObjValueBuilder, ObjValue,664			};665666			#typed667668			impl #impl_generics TypedObj for #ident #ty_generics #where_clause {669				fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {670					#(#fields_serialize)*671672					Ok(())673				}674				fn parse(obj: &ObjValue) -> JrResult<Self> {675					Ok(Self {676						#(#fields_parse)*677					})678				}679			}680		};681	})682}683684struct FormatInput {685	formatting: LitStr,686	arguments: Vec<Expr>,687}688impl Parse for FormatInput {689	fn parse(input: ParseStream) -> Result<Self> {690		let formatting = input.parse()?;691		let mut arguments = Vec::new();692693		while input.peek(Token![,]) {694			input.parse::<Token![,]>()?;695			if input.is_empty() {696				// Trailing comma697				break;698			}699			let expr = input.parse()?;700			arguments.push(expr);701		}702703		if !input.is_empty() {704			return Err(syn::Error::new(input.span(), "unexpected trailing input"));705		}706707		Ok(Self {708			formatting,709			arguments,710		})711	}712}713fn is_format_str(i: &str) -> bool {714	let mut is_plain = true;715	// -1 = {716	// +1 = }717	let mut is_bracket = 0i8;718	for ele in i.chars() {719		match ele {720			'{' if is_bracket == -1 => {721				is_bracket = 0;722			}723			'}' if is_bracket == -1 => {724				is_plain = false;725				break;726			}727			'}' if is_bracket == 1 => {728				is_bracket = 0;729			}730			'{' if is_bracket == 1 => {731				is_plain = false;732				break;733			}734			'{' => {735				is_bracket = -1;736			}737			'}' => {738				is_bracket = 1;739			}740			_ if is_bracket != 0 => {741				is_plain = false;742				break;743			}744			_ => {}745		}746	}747	!is_plain || is_bracket != 0748}749impl FormatInput {750	fn expand(self) -> TokenStream {751		let format = self.formatting;752		if is_format_str(&format.value()) {753			let args = self.arguments;754			quote! {755				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))756			}757		} else {758			if let Some(first) = self.arguments.first() {759				return syn::Error::new(760					first.span(),761					"string has no formatting codes, it should not have the arguments",762				)763				.into_compile_error();764			}765			quote! {766				::jrsonnet_evaluator::IStr::from(#format)767			}768		}769	}770}771772/// `IStr` formatting helper773///774/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`775/// This macro looks for formatting codes in the input string, and uses776/// `format!()` only when necessary777#[proc_macro]778pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {779	let input = parse_macro_input!(input as FormatInput);780	input.expand().into()781}
after · crates/jrsonnet-macros/src/lib.rs
1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6	parenthesized,7	parse::{Parse, ParseStream},8	parse_macro_input,9	punctuated::Punctuated,10	spanned::Spanned,11	token::{self, Comma},12	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13	PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18	Ident: PartialEq<I>,19{20	let attrs = attrs21		.iter()22		.filter(|a| a.path().is_ident(&ident))23		.collect::<Vec<_>>();24	if attrs.len() > 1 {25		return Err(Error::new(26			attrs[1].span(),27			"this attribute may be specified only once",28		));29	} else if attrs.is_empty() {30		return Ok(None);31	}32	let attr = attrs[0];33	let attr = attr.parse_args::<A>()?;3435	Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39	Ident: PartialEq<I>,40{41	attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45	path.leading_colon.is_none()46		&& !path.segments.is_empty()47		&& path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51	match ty {52		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53			let args = &path.path.segments.iter().last().unwrap().arguments;54			Some(args)55		}56		_ => None,57	}58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61	let Some(args) = type_is_path(ty, "Option") else {62		return Ok(None);63	};64	// It should have only on angle-bracketed param ("<String>"):65	let PathArguments::AngleBracketed(params) = args else {66		return Err(Error::new(args.span(), "missing option generic"));67	};68	let generic_arg = params.args.iter().next().unwrap();69	// This argument must be a type:70	let GenericArgument::Type(ty) = generic_arg else {71		return Err(Error::new(72			generic_arg.span(),73			"option generic should be a type",74		));75	};76	Ok(Some(ty))77}7879struct Field {80	attrs: Vec<Attribute>,81	name: Ident,82	_colon: Token![:],83	ty: Type,84}85impl Parse for Field {86	fn parse(input: ParseStream) -> syn::Result<Self> {87		Ok(Self {88			attrs: input.call(Attribute::parse_outer)?,89			name: input.parse()?,90			_colon: input.parse()?,91			ty: input.parse()?,92		})93	}94}9596mod kw {97	syn::custom_keyword!(fields);98	syn::custom_keyword!(rename);99	syn::custom_keyword!(flatten);100	syn::custom_keyword!(add);101	syn::custom_keyword!(hide);102	syn::custom_keyword!(ok);103}104105struct EmptyAttr;106impl Parse for EmptyAttr {107	fn parse(_input: ParseStream) -> Result<Self> {108		Ok(Self)109	}110}111112struct BuiltinAttrs {113	fields: Vec<Field>,114}115impl Parse for BuiltinAttrs {116	fn parse(input: ParseStream) -> syn::Result<Self> {117		if input.is_empty() {118			return Ok(Self { fields: Vec::new() });119		}120		input.parse::<kw::fields>()?;121		let fields;122		parenthesized!(fields in input);123		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;124		Ok(Self {125			fields: p.into_iter().collect(),126		})127	}128}129130enum Optionality {131	Required,132	Optional,133	Default(Expr),134}135impl Optionality {136	fn is_optional(&self) -> bool {137		!matches!(self, Self::Required)138	}139}140141enum ArgInfo {142	Normal {143		ty: Box<Type>,144		optionality: Optionality,145		name: Option<String>,146		cfg_attrs: Vec<Attribute>,147	},148	Lazy {149		is_option: bool,150		name: Option<String>,151	},152	Context,153	Location,154	This,155}156157impl ArgInfo {158	fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {159		let FnArg::Typed(arg) = arg else {160			unreachable!()161		};162		let ident = match &arg.pat as &Pat {163			Pat::Ident(i) => Some(i.ident.clone()),164			_ => None,165		};166		let ty = &arg.ty;167		if type_is_path(ty, "Context").is_some() {168			return Ok(Self::Context);169		} else if type_is_path(ty, "CallLocation").is_some() {170			return Ok(Self::Location);171		} else if type_is_path(ty, "Thunk").is_some() {172			return Ok(Self::Lazy {173				is_option: false,174				name: ident.map(|v| v.to_string()),175			});176		}177178		match ty as &Type {179			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),180			_ => {}181		}182183		let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {184			remove_attr(&mut arg.attrs, "default");185			(Optionality::Default(default), ty.clone())186		} else if let Some(ty) = extract_type_from_option(ty)? {187			if type_is_path(ty, "Thunk").is_some() {188				return Ok(Self::Lazy {189					is_option: true,190					name: ident.map(|v| v.to_string()),191				});192			}193194			(Optionality::Optional, Box::new(ty.clone()))195		} else {196			(Optionality::Required, ty.clone())197		};198199		let cfg_attrs = arg200			.attrs201			.iter()202			.filter(|a| a.path().is_ident("cfg"))203			.cloned()204			.collect();205206		Ok(Self::Normal {207			ty,208			optionality,209			name: ident.map(|v| v.to_string()),210			cfg_attrs,211		})212	}213}214215#[proc_macro_attribute]216pub fn builtin(217	attr: proc_macro::TokenStream,218	item: proc_macro::TokenStream,219) -> proc_macro::TokenStream {220	let attr = parse_macro_input!(attr as BuiltinAttrs);221	let item_fn = item.clone();222	let item_fn: ItemFn = parse_macro_input!(item_fn);223224	match builtin_inner(attr, item_fn) {225		Ok(v) => v.into(),226		Err(e) => e.into_compile_error().into(),227	}228}229230#[allow(clippy::too_many_lines)]231fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {232	let ReturnType::Type(_, result) = &fun.sig.output else {233		return Err(Error::new(234			fun.sig.span(),235			"builtin should return something",236		));237	};238239	let name = fun.sig.ident.to_string();240	let args = fun241		.sig242		.inputs243		.iter_mut()244		.map(|arg| ArgInfo::parse(&name, arg))245		.collect::<Result<Vec<_>>>()?;246247	let params_desc = args.iter().filter_map(|a| match a {248		ArgInfo::Normal {249			optionality,250			name,251			cfg_attrs,252			..253		} => {254			let name = name255				.as_ref()256				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});257			let is_optional = optionality.is_optional();258			Some(quote! {259				#(#cfg_attrs)*260				BuiltinParam::new(#name, #is_optional),261			})262		}263		ArgInfo::Lazy { is_option, name } => {264			let name = name265				.as_ref()266				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});267			Some(quote! {268				BuiltinParam::new(#name, #is_option),269			})270		}271		ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,272	});273274	let mut id = 0usize;275	let pass = args276		.iter()277		.map(|a| match a {278			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {279				let cid = id;280				id += 1;281				(quote! {#cid}, a)282			}283			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {284				(quote! {compile_error!("should not use id")}, a)285			}286		})287		.map(|(id, a)| match a {288			ArgInfo::Normal {289				ty,290				optionality,291				name,292				cfg_attrs,293			} => {294				let name = name.as_ref().map_or("<unnamed>", String::as_str);295				let eval = quote! {jrsonnet_evaluator::State::push_description(296					|| format!("argument <{}> evaluation", #name),297					|| <#ty>::from_untyped(value.evaluate()?),298				)?};299				let value = match optionality {300					Optionality::Required => quote! {{301						let value = parsed[#id].as_ref().expect("args shape is checked");302						#eval303					},},304					Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {305						Some(#eval)306					} else {307						None308					},},309					Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {310						#eval311					} else {312						let v: #ty = #expr;313						v314					},},315				};316				quote! {317					#(#cfg_attrs)*318					#value319				}320			}321			ArgInfo::Lazy { is_option, .. } => {322				if *is_option {323					quote! {if let Some(value) = &parsed[#id] {324						Some(value.clone())325					} else {326						None327					},}328				} else {329					quote! {330						parsed[#id].as_ref().expect("args shape is correct").clone(),331					}332				}333			}334			ArgInfo::Context => quote! {ctx.clone(),},335			ArgInfo::Location => quote! {location,},336			ArgInfo::This => quote! {self,},337		});338339	let fields = attr.fields.iter().map(|field| {340		let attrs = &field.attrs;341		let name = &field.name;342		let ty = &field.ty;343		quote! {344			#(#attrs)*345			pub #name: #ty,346		}347	});348349	let name = &fun.sig.ident;350	let vis = &fun.vis;351	let static_ext = if attr.fields.is_empty() {352		quote! {353			impl #name {354				pub const INST: &'static dyn StaticBuiltin = &#name {};355			}356			impl StaticBuiltin for #name {}357		}358	} else {359		quote! {}360	};361	let static_derive_copy = if attr.fields.is_empty() {362		quote! {, Copy}363	} else {364		quote! {}365	};366367	Ok(quote! {368		#fun369370		#[doc(hidden)]371		#[allow(non_camel_case_types)]372		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]373		#vis struct #name {374			#(#fields)*375		}376		const _: () = {377			use ::jrsonnet_evaluator::{378				State, Val,379				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},380				Result, Context, typed::Typed,381				parser::ExprLocation,382			};383			const PARAMS: &'static [BuiltinParam] = &[384				#(#params_desc)*385			];386387			#static_ext388			impl Builtin for #name389			where390				Self: 'static391			{392				fn name(&self) -> &str {393					stringify!(#name)394				}395				fn params(&self) -> &[BuiltinParam] {396					PARAMS397				}398				#[allow(unused_variables)]399				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {400					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;401402					let result: #result = #name(#(#pass)*);403					<_ as Typed>::into_result(result)404				}405				fn as_any(&self) -> &dyn ::std::any::Any {406					self407				}408			}409		};410	})411}412413#[derive(Default)]414#[allow(clippy::struct_excessive_bools)]415struct TypedAttr {416	rename: Option<String>,417	flatten: bool,418	/// flatten(ok) strategy for flattened optionals419	/// field would be None in case of any parsing error (as in serde)420	flatten_ok: bool,421	// Should it be `field+:` instead of `field:`422	add: bool,423	// Should it be `field::` instead of `field:`424	hide: bool,425}426impl Parse for TypedAttr {427	fn parse(input: ParseStream) -> syn::Result<Self> {428		let mut out = Self::default();429		loop {430			let lookahead = input.lookahead1();431			if lookahead.peek(kw::rename) {432				input.parse::<kw::rename>()?;433				input.parse::<Token![=]>()?;434				let name = input.parse::<LitStr>()?;435				if out.rename.is_some() {436					return Err(Error::new(437						name.span(),438						"rename attribute may only be specified once",439					));440				}441				out.rename = Some(name.value());442			} else if lookahead.peek(kw::flatten) {443				input.parse::<kw::flatten>()?;444				out.flatten = true;445				if input.peek(token::Paren) {446					let content;447					parenthesized!(content in input);448					let lookahead = content.lookahead1();449					if lookahead.peek(kw::ok) {450						content.parse::<kw::ok>()?;451						out.flatten_ok = true;452					} else {453						return Err(lookahead.error());454					}455				}456			} else if lookahead.peek(kw::add) {457				input.parse::<kw::add>()?;458				out.add = true;459			} else if lookahead.peek(kw::hide) {460				input.parse::<kw::hide>()?;461				out.hide = true;462			} else if input.is_empty() {463				break;464			} else {465				return Err(lookahead.error());466			}467			if input.peek(Token![,]) {468				input.parse::<Token![,]>()?;469			} else {470				break;471			}472		}473		Ok(out)474	}475}476477struct TypedField {478	attr: TypedAttr,479	ident: Ident,480	ty: Type,481	is_option: bool,482}483impl TypedField {484	fn parse(field: &syn::Field) -> Result<Self> {485		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();486		let Some(ident) = field.ident.clone() else {487			return Err(Error::new(488				field.span(),489				"this field should appear in output object, but it has no visible name",490			));491		};492		let (is_option, ty) = extract_type_from_option(&field.ty)?493			.map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));494		if is_option && attr.flatten {495			if !attr.flatten_ok {496				return Err(Error::new(497					field.span(),498					"strategy should be set when flattening Option",499				));500			}501		} else if attr.flatten_ok {502			return Err(Error::new(503				field.span(),504				"flatten(ok) is only useable on optional fields",505			));506		}507508		Ok(Self {509			attr,510			ident,511			ty,512			is_option,513		})514	}515	/// None if this field is flattened in jsonnet output516	fn name(&self) -> Option<String> {517		if self.attr.flatten {518			return None;519		}520		Some(521			self.attr522				.rename523				.clone()524				.unwrap_or_else(|| self.ident.to_string()),525		)526	}527528	fn expand_field(&self) -> Option<TokenStream> {529		if self.is_option {530			return None;531		}532		let name = self.name()?;533		let ty = &self.ty;534		Some(quote! {535			(#name, <#ty as Typed>::TYPE)536		})537	}538	fn expand_parse(&self) -> TokenStream {539		let ident = &self.ident;540		let ty = &self.ty;541		if self.attr.flatten {542			// optional flatten is handled in same way as serde543			return if self.is_option {544				quote! {545					#ident: <#ty as TypedObj>::parse(&obj).ok(),546				}547			} else {548				quote! {549					#ident: <#ty as TypedObj>::parse(&obj)?,550				}551			};552		};553554		let name = self.name().unwrap();555		let value = if self.is_option {556			quote! {557				if let Some(value) = obj.get(#name.into())? {558					Some(<#ty as Typed>::from_untyped(value)?)559				} else {560					None561				}562			}563		} else {564			quote! {565				<#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?566			}567		};568569		quote! {570			#ident: #value,571		}572	}573	fn expand_serialize(&self) -> TokenStream {574		let ident = &self.ident;575		let ty = &self.ty;576		self.name().map_or_else(577			|| {578				if self.is_option {579					quote! {580						if let Some(value) = self.#ident {581							<#ty as TypedObj>::serialize(value, out)?;582						}583					}584				} else {585					quote! {586						<#ty as TypedObj>::serialize(self.#ident, out)?;587					}588				}589			},590			|name| {591				let hide = if self.attr.hide {592					quote! {.hide()}593				} else {594					quote! {}595				};596				let add = if self.attr.add {597					quote! {.add()}598				} else {599					quote! {}600				};601				if self.is_option {602					quote! {603						if let Some(value) = self.#ident {604							out.field(#name)605								#hide606								#add607								.try_value(<#ty as Typed>::into_untyped(value)?)?;608						}609					}610				} else {611					quote! {612						out.field(#name)613							#hide614							#add615							.try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;616					}617				}618			},619		)620	}621}622623#[proc_macro_derive(Typed, attributes(typed))]624pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {625	let input = parse_macro_input!(item as DeriveInput);626627	match derive_typed_inner(input) {628		Ok(v) => v.into(),629		Err(e) => e.to_compile_error().into(),630	}631}632633fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {634	let syn::Data::Struct(data) = &input.data else {635		return Err(Error::new(input.span(), "only structs supported"));636	};637638	let ident = &input.ident;639	let fields = data640		.fields641		.iter()642		.map(TypedField::parse)643		.collect::<Result<Vec<_>>>()?;644645	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();646647	let typed = {648		let fields = fields649			.iter()650			.filter_map(TypedField::expand_field)651			.collect::<Vec<_>>();652		quote! {653			impl #impl_generics Typed for #ident #ty_generics #where_clause {654				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[655					#(#fields,)*656				]);657658				fn from_untyped(value: Val) -> JrResult<Self> {659					let obj = value.as_obj().expect("shape is correct");660					Self::parse(&obj)661				}662663				fn into_untyped(value: Self) -> JrResult<Val> {664					let mut out = ObjValueBuilder::new();665					value.serialize(&mut out)?;666					Ok(Val::Obj(out.build()))667				}668669			}670		}671	};672673	let fields_parse = fields.iter().map(TypedField::expand_parse);674	let fields_serialize = fields675		.iter()676		.map(TypedField::expand_serialize)677		.collect::<Vec<_>>();678679	Ok(quote! {680		const _: () = {681			use ::jrsonnet_evaluator::{682				typed::{ComplexValType, Typed, TypedObj, CheckType},683				Val, State,684				error::{ErrorKind, Result as JrResult},685				ObjValueBuilder, ObjValue,686			};687688			#typed689690			impl #impl_generics TypedObj for #ident #ty_generics #where_clause {691				fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {692					#(#fields_serialize)*693694					Ok(())695				}696				fn parse(obj: &ObjValue) -> JrResult<Self> {697					Ok(Self {698						#(#fields_parse)*699					})700				}701			}702		};703	})704}705706struct FormatInput {707	formatting: LitStr,708	arguments: Vec<Expr>,709}710impl Parse for FormatInput {711	fn parse(input: ParseStream) -> Result<Self> {712		let formatting = input.parse()?;713		let mut arguments = Vec::new();714715		while input.peek(Token![,]) {716			input.parse::<Token![,]>()?;717			if input.is_empty() {718				// Trailing comma719				break;720			}721			let expr = input.parse()?;722			arguments.push(expr);723		}724725		if !input.is_empty() {726			return Err(syn::Error::new(input.span(), "unexpected trailing input"));727		}728729		Ok(Self {730			formatting,731			arguments,732		})733	}734}735fn is_format_str(i: &str) -> bool {736	let mut is_plain = true;737	// -1 = {738	// +1 = }739	let mut is_bracket = 0i8;740	for ele in i.chars() {741		match ele {742			'{' if is_bracket == -1 => {743				is_bracket = 0;744			}745			'}' if is_bracket == -1 => {746				is_plain = false;747				break;748			}749			'}' if is_bracket == 1 => {750				is_bracket = 0;751			}752			'{' if is_bracket == 1 => {753				is_plain = false;754				break;755			}756			'{' => {757				is_bracket = -1;758			}759			'}' => {760				is_bracket = 1;761			}762			_ if is_bracket != 0 => {763				is_plain = false;764				break;765			}766			_ => {}767		}768	}769	!is_plain || is_bracket != 0770}771impl FormatInput {772	fn expand(self) -> TokenStream {773		let format = self.formatting;774		if is_format_str(&format.value()) {775			let args = self.arguments;776			quote! {777				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))778			}779		} else {780			if let Some(first) = self.arguments.first() {781				return syn::Error::new(782					first.span(),783					"string has no formatting codes, it should not have the arguments",784				)785				.into_compile_error();786			}787			quote! {788				::jrsonnet_evaluator::IStr::from(#format)789			}790		}791	}792}793794/// `IStr` formatting helper795///796/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`797/// This macro looks for formatting codes in the input string, and uses798/// `format!()` only when necessary799#[proc_macro]800pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {801	let input = parse_macro_input!(input as FormatInput);802	input.expand().into()803}
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()
 }