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

difftreelog

refactor move modules from root

Yaroslav Bolyukin2022-04-24parent: #cf77cb3.patch.diff
in: master

31 files changed

modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -8,9 +8,8 @@
 use gcmodule::Cc;
 use jrsonnet_evaluator::{
 	error::{Error, LocError},
-	function::BuiltinParam,
+	function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},
 	gc::TraceBox,
-	native::{NativeCallback, NativeCallbackHandler},
 	typed::Typed,
 	IStr, State, Val,
 };
deletedcrates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/format.rs
+++ /dev/null
@@ -1,796 +0,0 @@
-//! faster std.format impl
-#![allow(clippy::too_many_arguments)]
-
-use gcmodule::Trace;
-use jrsonnet_interner::IStr;
-use jrsonnet_types::ValType;
-use thiserror::Error;
-
-use crate::{error::Error::*, throw, typed::Typed, LocError, ObjValue, Result, State, Val};
-
-#[derive(Debug, Clone, Error, Trace)]
-pub enum FormatError {
-	#[error("truncated format code")]
-	TruncatedFormatCode,
-	#[error("unrecognized conversion type: {0}")]
-	UnrecognizedConversionType(char),
-
-	#[error("not enough values")]
-	NotEnoughValues,
-
-	#[error("cannot use * width with object")]
-	CannotUseStarWidthWithObject,
-	#[error("mapping keys required")]
-	MappingKeysRequired,
-	#[error("no such format field: {0}")]
-	NoSuchFormatField(IStr),
-}
-
-impl From<FormatError> for LocError {
-	fn from(e: FormatError) -> Self {
-		Self::new(Format(e))
-	}
-}
-
-use FormatError::*;
-
-type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;
-
-pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-	let bytes = str.as_bytes();
-	if bytes[0] == b'(' {
-		let mut i = 1;
-		while i < bytes.len() {
-			if bytes[i] == b')' {
-				return Ok((&str[1..i as usize], &str[i as usize + 1..]));
-			}
-			i += 1;
-		}
-		Err(TruncatedFormatCode)
-	} else {
-		Ok(("", str))
-	}
-}
-
-#[cfg(test)]
-pub mod tests_key {
-	use super::*;
-
-	#[test]
-	fn parse_key() {
-		assert_eq!(
-			try_parse_mapping_key("(hello ) world").unwrap(),
-			("hello ", " world")
-		);
-		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));
-		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));
-		assert_eq!(
-			try_parse_mapping_key(" () world").unwrap(),
-			("", " () world")
-		);
-	}
-
-	#[test]
-	#[should_panic]
-	fn parse_key_missing_start() {
-		try_parse_mapping_key("").unwrap();
-	}
-
-	#[test]
-	#[should_panic]
-	fn parse_key_missing_end() {
-		try_parse_mapping_key("(   ").unwrap();
-	}
-}
-
-#[allow(clippy::struct_excessive_bools)]
-#[derive(Default, Debug)]
-pub struct CFlags {
-	pub alt: bool,
-	pub zero: bool,
-	pub left: bool,
-	pub blank: bool,
-	pub sign: bool,
-}
-
-pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-	let bytes = str.as_bytes();
-	let mut i = 0;
-	let mut out = CFlags::default();
-	loop {
-		if bytes.len() == i {
-			return Err(TruncatedFormatCode);
-		}
-		match bytes[i] {
-			b'#' => out.alt = true,
-			b'0' => out.zero = true,
-			b'-' => out.left = true,
-			b' ' => out.blank = true,
-			b'+' => out.sign = true,
-			_ => break,
-		}
-		i += 1;
-	}
-	Ok((out, &str[i..]))
-}
-
-#[derive(Debug, PartialEq)]
-pub enum Width {
-	Star,
-	Fixed(usize),
-}
-pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-	let bytes = str.as_bytes();
-	if bytes[0] == b'*' {
-		return Ok((Width::Star, &str[1..]));
-	}
-	let mut out: usize = 0;
-	let mut digits = 0;
-	while let Some(digit) = (bytes[digits] as char).to_digit(10) {
-		out *= 10;
-		out += digit as usize;
-		digits += 1;
-		if digits == bytes.len() {
-			return Err(TruncatedFormatCode);
-		}
-	}
-	Ok((Width::Fixed(out), &str[digits..]))
-}
-
-pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-	let bytes = str.as_bytes();
-	if bytes[0] == b'.' {
-		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))
-	} else {
-		Ok((None, str))
-	}
-}
-
-// Only skips
-pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-	let bytes = str.as_bytes();
-	let mut idx = 0;
-	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {
-		idx += 1;
-		if bytes.len() == idx {
-			return Err(TruncatedFormatCode);
-		}
-	}
-	Ok(((), &str[idx..]))
-}
-
-#[derive(Debug, PartialEq)]
-pub enum ConvTypeV {
-	Decimal,
-	Octal,
-	Hexadecimal,
-	Scientific,
-	Float,
-	Shorter,
-	Char,
-	String,
-	Percent,
-}
-pub struct ConvType {
-	v: ConvTypeV,
-	caps: bool,
-}
-
-pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-
-	let code = str.as_bytes()[0];
-	let v: (ConvTypeV, bool) = match code {
-		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),
-		b'o' => (ConvTypeV::Octal, false),
-		b'x' => (ConvTypeV::Hexadecimal, false),
-		b'X' => (ConvTypeV::Hexadecimal, true),
-		b'e' => (ConvTypeV::Scientific, false),
-		b'E' => (ConvTypeV::Scientific, true),
-		b'f' => (ConvTypeV::Float, false),
-		b'F' => (ConvTypeV::Float, true),
-		b'g' => (ConvTypeV::Shorter, false),
-		b'G' => (ConvTypeV::Shorter, true),
-		b'c' => (ConvTypeV::Char, false),
-		b's' => (ConvTypeV::String, false),
-		b'%' => (ConvTypeV::Percent, false),
-		c => return Err(UnrecognizedConversionType(c as char)),
-	};
-
-	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))
-}
-
-#[derive(Debug)]
-pub struct Code<'s> {
-	mkey: &'s str,
-	cflags: CFlags,
-	width: Width,
-	precision: Option<Width>,
-	convtype: ConvTypeV,
-	caps: bool,
-}
-pub fn parse_code(str: &str) -> ParseResult<Code> {
-	if str.is_empty() {
-		return Err(TruncatedFormatCode);
-	}
-	let (mkey, str) = try_parse_mapping_key(str)?;
-	let (cflags, str) = try_parse_cflags(str)?;
-	let (width, str) = try_parse_field_width(str)?;
-	let (precision, str) = try_parse_precision(str)?;
-	let (_, str) = try_parse_length_modifier(str)?;
-	let (convtype, str) = parse_conversion_type(str)?;
-
-	Ok((
-		Code {
-			mkey,
-			cflags,
-			width,
-			precision,
-			convtype: convtype.v,
-			caps: convtype.caps,
-		},
-		str,
-	))
-}
-
-#[derive(Debug)]
-pub enum Element<'s> {
-	String(&'s str),
-	Code(Code<'s>),
-}
-pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {
-	let mut bytes = str.as_bytes();
-	let mut out = vec![];
-	let mut offset = 0;
-
-	loop {
-		while offset != bytes.len() && bytes[offset] != b'%' {
-			offset += 1;
-		}
-		if offset != 0 {
-			out.push(Element::String(&str[0..offset]));
-		}
-		if offset == bytes.len() {
-			return Ok(out);
-		}
-		str = &str[offset + 1..];
-		let code;
-		(code, str) = parse_code(str)?;
-		bytes = str.as_bytes();
-		offset = 0;
-
-		out.push(Element::Code(code));
-	}
-}
-
-const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
-
-#[inline]
-pub fn render_integer(
-	out: &mut String,
-	iv: i64,
-	padding: usize,
-	precision: usize,
-	blank: bool,
-	sign: bool,
-	radix: i64,
-	prefix: &str,
-	caps: bool,
-) {
-	// Digit char indexes in reverse order, i.e
-	// for radix = 16 and n = 12f: [15, 2, 1]
-	let digits = if iv == 0 {
-		vec![0u8]
-	} else {
-		let mut v = iv.abs();
-		let mut nums = Vec::with_capacity(1);
-		while v > 0 {
-			nums.push((v % radix) as u8);
-			v /= radix;
-		}
-		nums
-	};
-	let neg = iv < 0;
-	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
-	let zp2 = zp
-		.max(precision)
-		.saturating_sub(prefix.len() + digits.len());
-
-	if neg {
-		out.push('-');
-	} else if sign {
-		out.push('+');
-	} else if blank {
-		out.push(' ');
-	}
-
-	out.reserve(zp2);
-	for _ in 0..zp2 {
-		out.push('0');
-	}
-	out.push_str(prefix);
-
-	for digit in digits.into_iter().rev() {
-		let ch = NUMBERS[digit as usize] as char;
-		out.push(if caps { ch.to_ascii_uppercase() } else { ch });
-	}
-}
-
-pub fn render_decimal(
-	out: &mut String,
-	iv: i64,
-	padding: usize,
-	precision: usize,
-	blank: bool,
-	sign: bool,
-) {
-	render_integer(out, iv, padding, precision, blank, sign, 10, "", false);
-}
-pub fn render_octal(
-	out: &mut String,
-	iv: i64,
-	padding: usize,
-	precision: usize,
-	alt: bool,
-	blank: bool,
-	sign: bool,
-) {
-	render_integer(
-		out,
-		iv,
-		padding,
-		precision,
-		blank,
-		sign,
-		8,
-		if alt && iv != 0 { "0" } else { "" },
-		false,
-	);
-}
-
-#[allow(clippy::fn_params_excessive_bools)]
-pub fn render_hexadecimal(
-	out: &mut String,
-	iv: i64,
-	padding: usize,
-	precision: usize,
-	alt: bool,
-	blank: bool,
-	sign: bool,
-	caps: bool,
-) {
-	render_integer(
-		out,
-		iv,
-		padding,
-		precision,
-		blank,
-		sign,
-		16,
-		match (alt, caps) {
-			(true, true) => "0X",
-			(true, false) => "0x",
-			(false, _) => "",
-		},
-		caps,
-	);
-}
-
-#[allow(clippy::fn_params_excessive_bools)]
-pub fn render_float(
-	out: &mut String,
-	n: f64,
-	mut padding: usize,
-	precision: usize,
-	blank: bool,
-	sign: bool,
-	ensure_pt: bool,
-	trailing: bool,
-) {
-	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
-	padding = padding.saturating_sub(dot_size + precision);
-	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
-	if precision == 0 {
-		if ensure_pt {
-			out.push('.');
-		}
-		return;
-	}
-	let frac = n
-		.fract()
-		.mul_add(10.0_f64.powf(precision as f64), 0.5)
-		.floor();
-	if trailing || frac > 0.0 {
-		out.push('.');
-		let mut frac_str = String::new();
-		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
-		let mut trim = frac_str.len();
-		if !trailing {
-			for b in frac_str.as_bytes().iter().rev() {
-				if *b == b'0' {
-					trim -= 1;
-				}
-			}
-		}
-		out.push_str(&frac_str[..trim]);
-	} else if ensure_pt {
-		out.push('.');
-	}
-}
-
-#[allow(clippy::fn_params_excessive_bools)]
-pub fn render_float_sci(
-	out: &mut String,
-	n: f64,
-	mut padding: usize,
-	precision: usize,
-	blank: bool,
-	sign: bool,
-	ensure_pt: bool,
-	trailing: bool,
-	caps: bool,
-) {
-	let exponent = n.log10().floor();
-	let mantissa = if exponent as i16 == -324 {
-		n * 10.0 / 10.0_f64.powf(exponent + 1.0)
-	} else {
-		n / 10.0_f64.powf(exponent)
-	};
-	let mut exponent_str = String::new();
-	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
-
-	// +1 for e
-	padding = padding.saturating_sub(exponent_str.len() + 1);
-
-	render_float(
-		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,
-	);
-	out.push(if caps { 'E' } else { 'e' });
-	out.push_str(&exponent_str);
-}
-
-#[allow(clippy::too_many_lines)]
-pub fn format_code(
-	s: State,
-	out: &mut String,
-	value: &Val,
-	code: &Code,
-	width: usize,
-	precision: Option<usize>,
-) -> Result<()> {
-	let clfags = &code.cflags;
-	let (fpprec, iprec) = match precision {
-		Some(v) => (v, v),
-		None => (6, 0),
-	};
-	let padding = if clfags.zero && !clfags.left {
-		width
-	} else {
-		0
-	};
-
-	// TODO: If left padded, can optimize by writing directly to out
-	let mut tmp_out = String::new();
-
-	match code.convtype {
-		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string(s)?),
-		ConvTypeV::Decimal => {
-			let value = f64::from_untyped(value.clone(), s)?;
-			render_decimal(
-				&mut tmp_out,
-				value as i64,
-				padding,
-				iprec,
-				clfags.blank,
-				clfags.sign,
-			);
-		}
-		ConvTypeV::Octal => {
-			let value = f64::from_untyped(value.clone(), s)?;
-			render_octal(
-				&mut tmp_out,
-				value as i64,
-				padding,
-				iprec,
-				clfags.alt,
-				clfags.blank,
-				clfags.sign,
-			);
-		}
-		ConvTypeV::Hexadecimal => {
-			let value = f64::from_untyped(value.clone(), s)?;
-			render_hexadecimal(
-				&mut tmp_out,
-				value as i64,
-				padding,
-				iprec,
-				clfags.alt,
-				clfags.blank,
-				clfags.sign,
-				code.caps,
-			);
-		}
-		ConvTypeV::Scientific => {
-			let value = f64::from_untyped(value.clone(), s)?;
-			render_float_sci(
-				&mut tmp_out,
-				value,
-				padding,
-				fpprec,
-				clfags.blank,
-				clfags.sign,
-				clfags.alt,
-				true,
-				code.caps,
-			);
-		}
-		ConvTypeV::Float => {
-			let value = f64::from_untyped(value.clone(), s)?;
-			render_float(
-				&mut tmp_out,
-				value,
-				padding,
-				fpprec,
-				clfags.blank,
-				clfags.sign,
-				clfags.alt,
-				true,
-			);
-		}
-		ConvTypeV::Shorter => {
-			let value = f64::from_untyped(value.clone(), s)?;
-			let exponent = value.log10().floor();
-			if exponent < -4.0 || exponent >= fpprec as f64 {
-				render_float_sci(
-					&mut tmp_out,
-					value,
-					padding,
-					fpprec - 1,
-					clfags.blank,
-					clfags.sign,
-					clfags.alt,
-					clfags.alt,
-					code.caps,
-				);
-			} else {
-				let digits_before_pt = 1.max(exponent as usize + 1);
-				render_float(
-					&mut tmp_out,
-					value,
-					padding,
-					fpprec - digits_before_pt,
-					clfags.blank,
-					clfags.sign,
-					clfags.alt,
-					clfags.alt,
-				);
-			}
-		}
-		ConvTypeV::Char => match value.clone() {
-			Val::Num(n) => tmp_out
-				.push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),
-			Val::Str(s) => {
-				if s.chars().count() != 1 {
-					throw!(RuntimeError(
-						format!("%c expected 1 char string, got {}", s.chars().count()).into(),
-					));
-				}
-				tmp_out.push_str(&s);
-			}
-			_ => {
-				throw!(TypeMismatch(
-					"%c requires number/string",
-					vec![ValType::Num, ValType::Str],
-					value.value_type(),
-				));
-			}
-		},
-		ConvTypeV::Percent => tmp_out.push('%'),
-	};
-
-	let padding = width.saturating_sub(tmp_out.len());
-
-	if !clfags.left {
-		for _ in 0..padding {
-			out.push(' ');
-		}
-	}
-	out.push_str(&tmp_out);
-	if clfags.left {
-		for _ in 0..padding {
-			out.push(' ');
-		}
-	}
-
-	Ok(())
-}
-
-pub fn format_arr(s: State, str: &str, mut values: &[Val]) -> Result<String> {
-	let codes = parse_codes(str)?;
-	let mut out = String::new();
-
-	for code in codes {
-		match code {
-			Element::String(s) => {
-				out.push_str(s);
-			}
-			Element::Code(c) => {
-				let width = match c.width {
-					Width::Star => {
-						if values.is_empty() {
-							throw!(NotEnoughValues);
-						}
-						let value = &values[0];
-						values = &values[1..];
-						usize::from_untyped(value.clone(), s.clone())?
-					}
-					Width::Fixed(n) => n,
-				};
-				let precision = match c.precision {
-					Some(Width::Star) => {
-						if values.is_empty() {
-							throw!(NotEnoughValues);
-						}
-						let value = &values[0];
-						values = &values[1..];
-						Some(usize::from_untyped(value.clone(), s.clone())?)
-					}
-					Some(Width::Fixed(n)) => Some(n),
-					None => None,
-				};
-
-				// %% should not consume a value
-				let value = if c.convtype == ConvTypeV::Percent {
-					&Val::Null
-				} else {
-					if values.is_empty() {
-						throw!(NotEnoughValues);
-					}
-					let value = &values[0];
-					values = &values[1..];
-					value
-				};
-
-				format_code(s.clone(), &mut out, value, &c, width, precision)?;
-			}
-		}
-	}
-
-	Ok(out)
-}
-
-pub fn format_obj(s: State, str: &str, values: &ObjValue) -> Result<String> {
-	let codes = parse_codes(str)?;
-	let mut out = String::new();
-
-	for code in codes {
-		match code {
-			Element::String(s) => {
-				out.push_str(s);
-			}
-			Element::Code(c) => {
-				// TODO: Operate on ref
-				let f: IStr = c.mkey.into();
-				let width = match c.width {
-					Width::Star => {
-						throw!(CannotUseStarWidthWithObject);
-					}
-					Width::Fixed(n) => n,
-				};
-				let precision = match c.precision {
-					Some(Width::Star) => {
-						throw!(CannotUseStarWidthWithObject);
-					}
-					Some(Width::Fixed(n)) => Some(n),
-					None => None,
-				};
-
-				let value = if c.convtype == ConvTypeV::Percent {
-					Val::Null
-				} else {
-					if f.is_empty() {
-						throw!(MappingKeysRequired);
-					}
-					if let Some(v) = values.get(s.clone(), f.clone())? {
-						v
-					} else {
-						throw!(NoSuchFormatField(f));
-					}
-				};
-
-				format_code(s.clone(), &mut out, &value, &c, width, precision)?;
-			}
-		}
-	}
-
-	Ok(out)
-}
-
-#[cfg(test)]
-pub mod test_format {
-	use super::*;
-
-	#[test]
-	fn parse() {
-		assert_eq!(
-			parse_codes(
-				"How much error budget is left looking at our %.3f%% availability gurantees?"
-			)
-			.unwrap()
-			.len(),
-			4
-		);
-	}
-
-	#[test]
-	fn octals() {
-		let s = State::default();
-		assert_eq!(
-			format_arr(s.clone(), "%#o", &[Val::Num(8.0)]).unwrap(),
-			"010"
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%#4o", &[Val::Num(8.0)]).unwrap(),
-			" 010"
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%4o", &[Val::Num(8.0)]).unwrap(),
-			"  10"
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%04o", &[Val::Num(8.0)]).unwrap(),
-			"0010"
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%+4o", &[Val::Num(8.0)]).unwrap(),
-			" +10"
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%+04o", &[Val::Num(8.0)]).unwrap(),
-			"+010"
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%-4o", &[Val::Num(8.0)]).unwrap(),
-			"10  "
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%+-4o", &[Val::Num(8.0)]).unwrap(),
-			"+10 "
-		);
-		assert_eq!(
-			format_arr(s.clone(), "%+-04o", &[Val::Num(8.0)]).unwrap(),
-			"+10 "
-		);
-	}
-
-	#[test]
-	fn percent_doesnt_consumes_values() {
-		let s = State::default();
-		assert_eq!(
-			format_arr(
-				s,
-				"How much error budget is left looking at our %.3f%% availability gurantees?",
-				&[Val::Num(4.0)]
-			)
-			.unwrap(),
-			"How much error budget is left looking at our 4.000% availability gurantees?"
-		);
-	}
-}
deletedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ /dev/null
@@ -1,347 +0,0 @@
-use crate::{
-	error::{Error::*, Result},
-	throw, State, Val,
-};
-
-#[derive(PartialEq, Clone, Copy)]
-pub enum ManifestType {
-	// Applied in manifestification
-	Manifest,
-	/// Used for std.manifestJson
-	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest
-	Std,
-	/// No line breaks, used in `obj+''`
-	ToString,
-	/// Minified json
-	Minify,
-}
-
-pub struct ManifestJsonOptions<'s> {
-	pub padding: &'s str,
-	pub mtype: ManifestType,
-	pub newline: &'s str,
-	pub key_val_sep: &'s str,
-	#[cfg(feature = "exp-preserve-order")]
-	pub preserve_order: bool,
-}
-
-pub fn manifest_json_ex(s: State, val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {
-	let mut out = String::new();
-	manifest_json_ex_buf(s, val, &mut out, &mut String::new(), options)?;
-	Ok(out)
-}
-fn manifest_json_ex_buf(
-	s: State,
-	val: &Val,
-	buf: &mut String,
-	cur_padding: &mut String,
-	options: &ManifestJsonOptions<'_>,
-) -> Result<()> {
-	use std::fmt::Write;
-	let mtype = options.mtype;
-	match val {
-		Val::Bool(v) => {
-			if *v {
-				buf.push_str("true");
-			} else {
-				buf.push_str("false");
-			}
-		}
-		Val::Null => buf.push_str("null"),
-		Val::Str(s) => escape_string_json_buf(s, buf),
-		Val::Num(n) => write!(buf, "{}", n).unwrap(),
-		Val::Arr(items) => {
-			buf.push('[');
-			if !items.is_empty() {
-				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
-					buf.push_str(options.newline);
-				}
-
-				let old_len = cur_padding.len();
-				cur_padding.push_str(options.padding);
-				for (i, item) in items.iter(s.clone()).enumerate() {
-					if i != 0 {
-						buf.push(',');
-						if mtype == ManifestType::ToString {
-							buf.push(' ');
-						} else if mtype != ManifestType::Minify {
-							buf.push_str(options.newline);
-						}
-					}
-					buf.push_str(cur_padding);
-					manifest_json_ex_buf(s.clone(), &item?, buf, cur_padding, options)?;
-				}
-				cur_padding.truncate(old_len);
-
-				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
-					buf.push_str(options.newline);
-					buf.push_str(cur_padding);
-				}
-			} else if mtype == ManifestType::Std {
-				buf.push_str("\n\n");
-				buf.push_str(cur_padding);
-			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
-				buf.push(' ');
-			}
-			buf.push(']');
-		}
-		Val::Obj(obj) => {
-			obj.run_assertions(s.clone())?;
-			buf.push('{');
-			let fields = obj.fields(
-				#[cfg(feature = "exp-preserve-order")]
-				options.preserve_order,
-			);
-			if !fields.is_empty() {
-				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
-					buf.push_str(options.newline);
-				}
-
-				let old_len = cur_padding.len();
-				cur_padding.push_str(options.padding);
-				for (i, field) in fields.into_iter().enumerate() {
-					if i != 0 {
-						buf.push(',');
-						if mtype == ManifestType::ToString {
-							buf.push(' ');
-						} else if mtype != ManifestType::Minify {
-							buf.push_str(options.newline);
-						}
-					}
-					buf.push_str(cur_padding);
-					escape_string_json_buf(&field, buf);
-					buf.push_str(options.key_val_sep);
-					s.push_description(
-						|| format!("field <{}> manifestification", field.clone()),
-						|| {
-							let value = obj.get(s.clone(), field.clone())?.unwrap();
-							manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;
-							Ok(Val::Null)
-						},
-					)?;
-				}
-				cur_padding.truncate(old_len);
-
-				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
-					buf.push_str(options.newline);
-					buf.push_str(cur_padding);
-				}
-			} else if mtype == ManifestType::Std {
-				buf.push_str("\n\n");
-				buf.push_str(cur_padding);
-			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
-				buf.push(' ');
-			}
-			buf.push('}');
-		}
-		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
-	};
-	Ok(())
-}
-
-pub fn escape_string_json(s: &str) -> String {
-	let mut buf = String::new();
-	escape_string_json_buf(s, &mut buf);
-	buf
-}
-
-fn escape_string_json_buf(s: &str, buf: &mut String) {
-	use std::fmt::Write;
-	buf.push('"');
-	for c in s.chars() {
-		match c {
-			'"' => buf.push_str("\\\""),
-			'\\' => buf.push_str("\\\\"),
-			'\u{0008}' => buf.push_str("\\b"),
-			'\u{000c}' => buf.push_str("\\f"),
-			'\n' => buf.push_str("\\n"),
-			'\r' => buf.push_str("\\r"),
-			'\t' => buf.push_str("\\t"),
-			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {
-				write!(buf, "\\u{:04x}", c as u32).unwrap();
-			}
-			c => buf.push(c),
-		}
-	}
-	buf.push('"');
-}
-
-pub struct ManifestYamlOptions<'s> {
-	/// Padding before fields, i.e
-	/// ```yaml
-	/// a:
-	///   b:
-	/// ## <- this
-	/// ```
-	pub padding: &'s str,
-	/// Padding before array elements in objects
-	/// ```yaml
-	/// a:
-	///   - 1
-	/// ## <- this
-	/// ```
-	pub arr_element_padding: &'s str,
-	/// Should yaml keys appear unescaped, when possible
-	/// ```yaml
-	/// "safe_key": 1
-	/// # vs
-	/// safe_key: 1
-	/// ```
-	pub quote_keys: bool,
-	/// If true - then order of fields is preserved as written,
-	/// instead of sorting alphabetically
-	#[cfg(feature = "exp-preserve-order")]
-	pub preserve_order: bool,
-}
-
-/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>
-/// With added date check
-fn yaml_needs_quotes(string: &str) -> bool {
-	fn need_quotes_spaces(string: &str) -> bool {
-		string.starts_with(' ') || string.ends_with(' ')
-	}
-
-	string.is_empty()
-		|| need_quotes_spaces(string)
-		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))
-		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))
-		|| [
-			// http://yaml.org/type/bool.html
-			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
-			// them as string, not booleans, although it is violating the YAML 1.1 specification.
-			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.
-			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",
-			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html
-			"null", "Null", "NULL", "~",
-		].contains(&string)
-		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))
-			&& string.chars().filter(|c| *c == '-').count() == 2)
-		|| string.starts_with('.')
-		|| string.starts_with("0x")
-		|| string.parse::<i64>().is_ok()
-		|| string.parse::<f64>().is_ok()
-}
-
-pub fn manifest_yaml_ex(s: State, val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
-	let mut out = String::new();
-	manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?;
-	Ok(out)
-}
-
-#[allow(clippy::too_many_lines)]
-fn manifest_yaml_ex_buf(
-	s: State,
-	val: &Val,
-	buf: &mut String,
-	cur_padding: &mut String,
-	options: &ManifestYamlOptions<'_>,
-) -> Result<()> {
-	use std::fmt::Write;
-	match val {
-		Val::Bool(v) => {
-			if *v {
-				buf.push_str("true");
-			} else {
-				buf.push_str("false");
-			}
-		}
-		Val::Null => buf.push_str("null"),
-		Val::Str(s) => {
-			if s.is_empty() {
-				buf.push_str("\"\"");
-			} else if let Some(s) = s.strip_suffix('\n') {
-				buf.push('|');
-				for line in s.split('\n') {
-					buf.push('\n');
-					buf.push_str(cur_padding);
-					buf.push_str(options.padding);
-					buf.push_str(line);
-				}
-			} else if !options.quote_keys && !yaml_needs_quotes(s) {
-				buf.push_str(s);
-			} else {
-				escape_string_json_buf(s, buf);
-			}
-		}
-		Val::Num(n) => write!(buf, "{}", *n).unwrap(),
-		Val::Arr(a) => {
-			if a.is_empty() {
-				buf.push_str("[]");
-			} else {
-				for (i, item) in a.iter(s.clone()).enumerate() {
-					if i != 0 {
-						buf.push('\n');
-						buf.push_str(cur_padding);
-					}
-					let item = item?;
-					buf.push('-');
-					match &item {
-						Val::Arr(a) if !a.is_empty() => {
-							buf.push('\n');
-							buf.push_str(cur_padding);
-							buf.push_str(options.padding);
-						}
-						_ => buf.push(' '),
-					}
-					let extra_padding = match &item {
-						Val::Arr(a) => !a.is_empty(),
-						Val::Obj(o) => !o.is_empty(),
-						_ => false,
-					};
-					let prev_len = cur_padding.len();
-					if extra_padding {
-						cur_padding.push_str(options.padding);
-					}
-					manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
-					cur_padding.truncate(prev_len);
-				}
-			}
-		}
-		Val::Obj(o) => {
-			if o.is_empty() {
-				buf.push_str("{}");
-			} else {
-				for (i, key) in o
-					.fields(
-						#[cfg(feature = "exp-preserve-order")]
-						options.preserve_order,
-					)
-					.iter()
-					.enumerate()
-				{
-					if i != 0 {
-						buf.push('\n');
-						buf.push_str(cur_padding);
-					}
-					if !options.quote_keys && !yaml_needs_quotes(key) {
-						buf.push_str(key);
-					} else {
-						escape_string_json_buf(key, buf);
-					}
-					buf.push(':');
-					let prev_len = cur_padding.len();
-					let item = o.get(s.clone(), key.clone())?.expect("field exists");
-					match &item {
-						Val::Arr(a) if !a.is_empty() => {
-							buf.push('\n');
-							buf.push_str(cur_padding);
-							buf.push_str(options.arr_element_padding);
-							cur_padding.push_str(options.arr_element_padding);
-						}
-						Val::Obj(o) if !o.is_empty() => {
-							buf.push('\n');
-							buf.push_str(cur_padding);
-							buf.push_str(options.padding);
-							cur_padding.push_str(options.padding);
-						}
-						_ => buf.push(' '),
-					}
-					manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
-					cur_padding.truncate(prev_len);
-				}
-			}
-		}
-		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
-	}
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ /dev/null
@@ -1,749 +0,0 @@
-// All builtins should return results
-#![allow(clippy::unnecessary_wraps)]
-
-use std::collections::HashMap;
-
-use format::{format_arr, format_obj};
-use gcmodule::Cc;
-use jrsonnet_interner::IStr;
-use serde::Deserialize;
-use serde_yaml::DeserializingQuirks;
-
-use crate::{
-	builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
-	error::{Error::*, Result},
-	function::{CallLocation, StaticBuiltin},
-	operator::evaluate_mod_op,
-	throw,
-	typed::{Any, BoundedUsize, Bytes, Either2, Either4, PositiveF64, Typed, VecVal, M1},
-	val::{equals, primitive_equals, ArrValue, FuncVal, IndexableVal, Slice},
-	Either, ObjValue, State, Val,
-};
-
-pub mod stdlib;
-pub use stdlib::*;
-
-use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
-
-pub mod format;
-pub mod manifest;
-pub mod sort;
-
-pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
-	s.push(
-		CallLocation::native(),
-		|| format!("std.format of {}", str),
-		|| {
-			Ok(match vals {
-				Val::Arr(vals) => format_arr(s.clone(), &str, &vals.evaluated(s.clone())?)?,
-				Val::Obj(obj) => format_obj(s.clone(), &str, &obj)?,
-				o => format_arr(s.clone(), &str, &[o])?,
-			})
-		},
-	)
-}
-
-pub fn std_slice(
-	indexable: IndexableVal,
-	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Val> {
-	match &indexable {
-		IndexableVal::Str(s) => {
-			let index = index.as_deref().copied().unwrap_or(0);
-			let end = end.as_deref().copied().unwrap_or(usize::MAX);
-			let step = step.as_deref().copied().unwrap_or(1);
-
-			if index >= end {
-				return Ok(Val::Str("".into()));
-			}
-
-			Ok(Val::Str(
-				(s.chars()
-					.skip(index)
-					.take(end - index)
-					.step_by(step)
-					.collect::<String>())
-				.into(),
-			))
-		}
-		IndexableVal::Arr(arr) => {
-			let index = index.as_deref().copied().unwrap_or(0);
-			let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
-			let step = step.as_deref().copied().unwrap_or(1);
-
-			if index >= end {
-				return Ok(Val::Arr(ArrValue::new_eager()));
-			}
-
-			Ok(Val::Arr(ArrValue::Slice(Box::new(Slice {
-				inner: arr.clone(),
-				from: index as u32,
-				to: end as u32,
-				step: step as u32,
-			}))))
-		}
-	}
-}
-
-type BuiltinsType = HashMap<IStr, &'static dyn StaticBuiltin>;
-
-thread_local! {
-	pub static BUILTINS: BuiltinsType = {
-		[
-			("length".into(), builtin_length::INST),
-			("type".into(), builtin_type::INST),
-			("makeArray".into(), builtin_make_array::INST),
-			("codepoint".into(), builtin_codepoint::INST),
-			("objectFieldsEx".into(), builtin_object_fields_ex::INST),
-			("objectHasEx".into(), builtin_object_has_ex::INST),
-			("slice".into(), builtin_slice::INST),
-			("substr".into(), builtin_substr::INST),
-			("primitiveEquals".into(), builtin_primitive_equals::INST),
-			("equals".into(), builtin_equals::INST),
-			("modulo".into(), builtin_modulo::INST),
-			("mod".into(), builtin_mod::INST),
-			("floor".into(), builtin_floor::INST),
-			("ceil".into(), builtin_ceil::INST),
-			("log".into(), builtin_log::INST),
-			("pow".into(), builtin_pow::INST),
-			("sqrt".into(), builtin_sqrt::INST),
-			("sin".into(), builtin_sin::INST),
-			("cos".into(), builtin_cos::INST),
-			("tan".into(), builtin_tan::INST),
-			("asin".into(), builtin_asin::INST),
-			("acos".into(), builtin_acos::INST),
-			("atan".into(), builtin_atan::INST),
-			("exp".into(), builtin_exp::INST),
-			("mantissa".into(), builtin_mantissa::INST),
-			("exponent".into(), builtin_exponent::INST),
-			("extVar".into(), builtin_ext_var::INST),
-			("native".into(), builtin_native::INST),
-			("filter".into(), builtin_filter::INST),
-			("map".into(), builtin_map::INST),
-			("flatMap".into(), builtin_flatmap::INST),
-			("foldl".into(), builtin_foldl::INST),
-			("foldr".into(), builtin_foldr::INST),
-			("sort".into(), builtin_sort::INST),
-			("format".into(), builtin_format::INST),
-			("range".into(), builtin_range::INST),
-			("char".into(), builtin_char::INST),
-			("encodeUTF8".into(), builtin_encode_utf8::INST),
-			("decodeUTF8".into(), builtin_decode_utf8::INST),
-			("md5".into(), builtin_md5::INST),
-			("base64".into(), builtin_base64::INST),
-			("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST),
-			("base64Decode".into(), builtin_base64_decode::INST),
-			("trace".into(), builtin_trace::INST),
-			("join".into(), builtin_join::INST),
-			("escapeStringJson".into(), builtin_escape_string_json::INST),
-			("manifestJsonEx".into(), builtin_manifest_json_ex::INST),
-			("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST),
-			("reverse".into(), builtin_reverse::INST),
-			("id".into(), builtin_id::INST),
-			("strReplace".into(), builtin_str_replace::INST),
-			("splitLimit".into(), builtin_splitlimit::INST),
-			("parseJson".into(), builtin_parse_json::INST),
-			("parseYaml".into(), builtin_parse_yaml::INST),
-			("asciiUpper".into(), builtin_ascii_upper::INST),
-			("asciiLower".into(), builtin_ascii_lower::INST),
-			("member".into(), builtin_member::INST),
-			("count".into(), builtin_count::INST),
-			("any".into(), builtin_any::INST),
-			("all".into(), builtin_all::INST),
-		].iter().cloned().collect()
-	};
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
-	use Either4::*;
-	Ok(match x {
-		A(x) => x.chars().count(),
-		B(x) => x.len(),
-		C(x) => x.len(),
-		D(f) => f.args_len(),
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_type(x: Any) -> Result<IStr> {
-	Ok(x.0.value_type().name().into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
-	let mut out = Vec::with_capacity(sz);
-	for i in 0..sz {
-		out.push(func.evaluate_simple(s.clone(), &[i as f64].as_slice())?);
-	}
-	Ok(VecVal(Cc::new(out)))
-}
-
-#[jrsonnet_macros::builtin]
-const fn builtin_codepoint(str: char) -> Result<u32> {
-	Ok(str as u32)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_fields_ex(
-	obj: ObjValue,
-	inc_hidden: bool,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<VecVal> {
-	#[cfg(feature = "exp-preserve-order")]
-	let preserve_order = preserve_order.unwrap_or(false);
-	let out = obj.fields_ex(
-		inc_hidden,
-		#[cfg(feature = "exp-preserve-order")]
-		preserve_order,
-	);
-	Ok(VecVal(Cc::new(
-		out.into_iter().map(Val::Str).collect::<Vec<_>>(),
-	)))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
-	Ok(obj.has_field_ex(f, inc_hidden))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
-	use serde_json::Value;
-	let value: Value = serde_json::from_str(&s)
-		.map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
-	Ok(Any(Value::into_untyped(value, st)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
-	use serde_json::Value;
-	let value = serde_yaml::Deserializer::from_str_with_quirks(
-		&s,
-		DeserializingQuirks { old_octals: true },
-	);
-	let mut out = vec![];
-	for item in value {
-		let value = Value::deserialize(item)
-			.map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
-		let val = Value::into_untyped(value, st.clone())?;
-		out.push(val);
-	}
-	Ok(Any(if out.is_empty() {
-		Val::Null
-	} else if out.len() == 1 {
-		out.into_iter().next().unwrap()
-	} else {
-		Val::Arr(out.into())
-	}))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_slice(
-	indexable: IndexableVal,
-	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Any> {
-	std_slice(indexable, index, end, step).map(Any)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
-	Ok(str.chars().skip(from as usize).take(len as usize).collect())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
-	primitive_equals(&a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
-	equals(s, &a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
-	Ok(a % b)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
-	use Either2::*;
-	Ok(Any(evaluate_mod_op(
-		s,
-		&match a {
-			A(v) => Val::Num(v),
-			B(s) => Val::Str(s),
-		},
-		&b.0,
-	)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_floor(x: f64) -> Result<f64> {
-	Ok(x.floor())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ceil(x: f64) -> Result<f64> {
-	Ok(x.ceil())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_log(n: f64) -> Result<f64> {
-	Ok(n.ln())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_pow(x: f64, n: f64) -> Result<f64> {
-	Ok(x.powf(n))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
-	Ok(x.0.sqrt())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sin(x: f64) -> Result<f64> {
-	Ok(x.sin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_cos(x: f64) -> Result<f64> {
-	Ok(x.cos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_tan(x: f64) -> Result<f64> {
-	Ok(x.tan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_asin(x: f64) -> Result<f64> {
-	Ok(x.asin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_acos(x: f64) -> Result<f64> {
-	Ok(x.acos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_atan(x: f64) -> Result<f64> {
-	Ok(x.atan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exp(x: f64) -> Result<f64> {
-	Ok(x.exp())
-}
-
-fn frexp(s: f64) -> (f64, i16) {
-	if 0.0 == s {
-		(s, 0)
-	} else {
-		let lg = s.abs().log2();
-		let x = (lg - lg.floor() - 1.0).exp2();
-		let exp = lg.floor() + 1.0;
-		(s.signum() * x, exp as i16)
-	}
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mantissa(x: f64) -> Result<f64> {
-	Ok(frexp(x).0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exponent(x: f64) -> Result<i16> {
-	Ok(frexp(x).1)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ext_var(s: State, x: IStr) -> Result<Any> {
-	Ok(Any(s
-		.settings()
-		.ext_vars
-		.get(&x)
-		.cloned()
-		.ok_or(UndefinedExternalVariable(x))?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_native(s: State, name: IStr) -> Result<Any> {
-	Ok(Any(s
-		.settings()
-		.ext_natives
-		.get(&name)
-		.cloned()
-		.map_or(Val::Null, |v| {
-			Val::Func(FuncVal::Builtin(v.clone()))
-		})))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
-	arr.filter(s.clone(), |val| {
-		bool::from_untyped(
-			func.evaluate_simple(s.clone(), &[Any(val.clone())].as_slice())?,
-			s.clone(),
-		)
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
-	arr.map(s.clone(), |val| {
-		func.evaluate_simple(s.clone(), &[Any(val)].as_slice())
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
-	match arr {
-		IndexableVal::Str(str) => {
-			let mut out = String::new();
-			for c in str.chars() {
-				match func.evaluate_simple(s.clone(), &[c.to_string()].as_slice())? {
-					Val::Str(o) => out.push_str(&o),
-					Val::Null => continue,
-					_ => throw!(RuntimeError(
-						"in std.join all items should be strings".into()
-					)),
-				};
-			}
-			Ok(IndexableVal::Str(out.into()))
-		}
-		IndexableVal::Arr(a) => {
-			let mut out = Vec::new();
-			for el in a.iter(s.clone()) {
-				let el = el?;
-				match func.evaluate_simple(s.clone(), &[Any(el)].as_slice())? {
-					Val::Arr(o) => {
-						for oe in o.iter(s.clone()) {
-							out.push(oe?);
-						}
-					}
-					Val::Null => continue,
-					_ => throw!(RuntimeError(
-						"in std.join all items should be arrays".into()
-					)),
-				};
-			}
-			Ok(IndexableVal::Arr(out.into()))
-		}
-	}
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
-	let mut acc = init.0;
-	for i in arr.iter(s.clone()) {
-		acc = func.evaluate_simple(s.clone(), &[Any(acc), Any(i?)].as_slice())?;
-	}
-	Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
-	let mut acc = init.0;
-	for i in arr.iter(s.clone()).rev() {
-		acc = func.evaluate_simple(s.clone(), &[Any(i?), Any(acc)].as_slice())?;
-	}
-	Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-#[allow(non_snake_case)]
-fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
-	if arr.len() <= 1 {
-		return Ok(arr);
-	}
-	Ok(ArrValue::Eager(sort::sort(
-		s.clone(),
-		arr.evaluated(s)?,
-		keyF.as_ref(),
-	)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
-	std_format(s, str, vals.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
-	if to < from {
-		return Ok(ArrValue::new_eager());
-	}
-	Ok(ArrValue::new_range(from, to))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_char(n: u32) -> Result<char> {
-	Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_encode_utf8(str: IStr) -> Result<Bytes> {
-	Ok(Bytes(str.bytes().collect::<Vec<u8>>().into()))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_decode_utf8(arr: Bytes) -> Result<IStr> {
-	Ok(std::str::from_utf8(&arr.0)
-		.map_err(|_| RuntimeError("bad utf8".into()))?
-		.into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_md5(str: IStr) -> Result<String> {
-	Ok(format!("{:x}", md5::compute(&str.as_bytes())))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
-	eprint!("TRACE:");
-	if let Some(loc) = loc.0 {
-		let locs = s.map_source_locations(&loc.0, &[loc.1]);
-		eprint!(
-			" {}:{}",
-			loc.0.file_name().unwrap().to_str().unwrap(),
-			locs[0].line
-		);
-	}
-	eprintln!(" {}", str);
-	Ok(rest) as Result<Any>
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64(input: Either![Bytes, IStr]) -> Result<String> {
-	use Either2::*;
-	Ok(match input {
-		A(a) => base64::encode(a.0),
-		B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode_bytes(input: IStr) -> Result<Bytes> {
-	Ok(Bytes(
-		base64::decode(&input.as_bytes())
-			.map_err(|_| RuntimeError("bad base64".into()))?
-			.into(),
-	))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode(input: IStr) -> Result<String> {
-	let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
-	Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
-	Ok(match sep {
-		IndexableVal::Arr(joiner_items) => {
-			let mut out = Vec::new();
-
-			let mut first = true;
-			for item in arr.iter(s.clone()) {
-				let item = item?.clone();
-				if let Val::Arr(items) = item {
-					if !first {
-						out.reserve(joiner_items.len());
-						// TODO: extend
-						for item in joiner_items.iter(s.clone()) {
-							out.push(item?);
-						}
-					}
-					first = false;
-					out.reserve(items.len());
-					for item in items.iter(s.clone()) {
-						out.push(item?);
-					}
-				} else if matches!(item, Val::Null) {
-					continue;
-				} else {
-					throw!(RuntimeError(
-						"in std.join all items should be arrays".into()
-					));
-				}
-			}
-
-			IndexableVal::Arr(out.into())
-		}
-		IndexableVal::Str(sep) => {
-			let mut out = String::new();
-
-			let mut first = true;
-			for item in arr.iter(s) {
-				let item = item?.clone();
-				if let Val::Str(item) = item {
-					if !first {
-						out += &sep;
-					}
-					first = false;
-					out += &item;
-				} else if matches!(item, Val::Null) {
-					continue;
-				} else {
-					throw!(RuntimeError(
-						"in std.join all items should be strings".into()
-					));
-				}
-			}
-
-			IndexableVal::Str(out.into())
-		}
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_escape_string_json(str_: IStr) -> Result<String> {
-	Ok(escape_string_json(&str_))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_json_ex(
-	s: State,
-	value: Any,
-	indent: IStr,
-	newline: Option<IStr>,
-	key_val_sep: Option<IStr>,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
-	let newline = newline.as_deref().unwrap_or("\n");
-	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
-	manifest_json_ex(
-		s,
-		&value.0,
-		&ManifestJsonOptions {
-			padding: &indent,
-			mtype: ManifestType::Std,
-			newline,
-			key_val_sep,
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
-		},
-	)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_yaml_doc(
-	s: State,
-	value: Any,
-	indent_array_in_object: Option<bool>,
-	quote_keys: Option<bool>,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
-	manifest_yaml_ex(
-		s,
-		&value.0,
-		&ManifestYamlOptions {
-			padding: "  ",
-			arr_element_padding: if indent_array_in_object.unwrap_or(false) {
-				"  "
-			} else {
-				""
-			},
-			quote_keys: quote_keys.unwrap_or(true),
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
-		},
-	)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
-	Ok(value.reversed())
-}
-
-#[jrsonnet_macros::builtin]
-const fn builtin_id(v: Any) -> Result<Any> {
-	Ok(v)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
-	Ok(str.replace(&from as &str, &to as &str))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
-	use Either2::*;
-	Ok(VecVal(Cc::new(match maxsplits {
-		A(n) => str
-			.splitn(n + 1, &c as &str)
-			.map(|s| Val::Str(s.into()))
-			.collect(),
-		B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
-	})))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_upper(str: IStr) -> Result<String> {
-	Ok(str.to_ascii_uppercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_lower(str: IStr) -> Result<String> {
-	Ok(str.to_ascii_lowercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
-	match arr {
-		IndexableVal::Str(str) => {
-			let x: IStr = IStr::from_untyped(x.0, s)?;
-			Ok(!x.is_empty() && str.contains(&*x))
-		}
-		IndexableVal::Arr(a) => {
-			for item in a.iter(s.clone()) {
-				let item = item?;
-				if equals(s.clone(), &item, &x.0)? {
-					return Ok(true);
-				}
-			}
-			Ok(false)
-		}
-	}
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
-	let mut count = 0;
-	for item in &arr {
-		if equals(s.clone(), &item.0, &v.0)? {
-			count += 1;
-		}
-	}
-	Ok(count)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
-	for v in arr.iter(s.clone()) {
-		let v = bool::from_untyped(v?, s.clone())?;
-		if v {
-			return Ok(true);
-		}
-	}
-	Ok(false)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
-	for v in arr.iter(s.clone()) {
-		let v = bool::from_untyped(v?, s.clone())?;
-		if !v {
-			return Ok(false);
-		}
-	}
-	Ok(true)
-}
deletedcrates/jrsonnet-evaluator/src/builtin/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/sort.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use gcmodule::{Cc, Trace};
-
-use crate::{
-	error::{Error, LocError, Result},
-	throw,
-	typed::Any,
-	val::FuncVal,
-	State, Val,
-};
-
-#[derive(Debug, Clone, thiserror::Error, Trace)]
-pub enum SortError {
-	#[error("sort key should be string or number")]
-	SortKeyShouldBeStringOrNumber,
-	#[error("sort elements should have equal types")]
-	SortElementsShouldHaveEqualType,
-}
-
-impl From<SortError> for LocError {
-	fn from(s: SortError) -> Self {
-		Self::new(Error::Sort(s))
-	}
-}
-
-#[derive(Copy, Clone)]
-enum SortKeyType {
-	Number,
-	String,
-	Unknown,
-}
-
-#[derive(PartialEq)]
-struct NonNaNf64(f64);
-impl PartialOrd for NonNaNf64 {
-	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-		self.0.partial_cmp(&other.0)
-	}
-}
-impl Eq for NonNaNf64 {}
-impl Ord for NonNaNf64 {
-	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-		self.partial_cmp(other).expect("non nan")
-	}
-}
-
-fn get_sort_type<T>(
-	values: &mut Vec<T>,
-	key_getter: impl Fn(&mut T) -> &mut Val,
-) -> Result<SortKeyType> {
-	let mut sort_type = SortKeyType::Unknown;
-	for i in values.iter_mut() {
-		let i = key_getter(i);
-		match (i, sort_type) {
-			(Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
-			(Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
-			(Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
-			(Val::Str(_) | Val::Num(_), _) => {
-				throw!(SortError::SortElementsShouldHaveEqualType)
-			}
-			_ => throw!(SortError::SortKeyShouldBeStringOrNumber),
-		}
-	}
-	Ok(sort_type)
-}
-
-pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: Option<&FuncVal>) -> Result<Cc<Vec<Val>>> {
-	if values.len() <= 1 {
-		return Ok(values);
-	}
-	if let Some(key_getter) = key_getter {
-		// Slow path, user provided key getter
-		let mut vk = Vec::with_capacity(values.len());
-		for value in values.iter() {
-			vk.push((
-				value.clone(),
-				key_getter.evaluate_simple(s.clone(), &[Any(value.clone())].as_slice())?,
-			));
-		}
-		let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
-		match sort_type {
-			SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
-				Val::Num(n) => NonNaNf64(n),
-				_ => unreachable!(),
-			}),
-			SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
-				Val::Str(s) => s.clone(),
-				_ => unreachable!(),
-			}),
-			SortKeyType::Unknown => unreachable!(),
-		};
-		Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
-	} else {
-		// Fast path, identity key getter
-		let mut values = (*values).clone();
-		let sort_type = get_sort_type(&mut values, |k| k)?;
-		match sort_type {
-			SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
-				Val::Num(n) => NonNaNf64(*n),
-				_ => unreachable!(),
-			}),
-			SortKeyType::String => values.sort_unstable_by_key(|v| match v {
-				Val::Str(s) => s.clone(),
-				_ => unreachable!(),
-			}),
-			SortKeyType::Unknown => unreachable!(),
-		};
-		Ok(Cc::new(values))
-	}
-}
deletedcrates/jrsonnet-evaluator/src/builtin/stdlib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/stdlib.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::path::PathBuf;
-
-use jrsonnet_parser::{LocExpr, ParserSettings};
-
-thread_local! {
-	/// To avoid parsing again when issued from the same thread
-	#[allow(unreachable_code)]
-	static PARSED_STDLIB: LocExpr = {
-		#[cfg(feature = "serialized-stdlib")]
-		{
-			// Should not panic, stdlib.bincode is generated in build.rs
-			return bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))
-				.unwrap();
-		}
-
-		jrsonnet_parser::parse(
-			jrsonnet_stdlib::STDLIB_STR,
-			&ParserSettings {
-				file_name: PathBuf::from("std.jsonnet").into(),
-			},
-		)
-		.unwrap()
-	}
-}
-
-pub fn get_parsed_stdlib() -> LocExpr {
-	PARSED_STDLIB.with(Clone::clone)
-}
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -11,7 +11,7 @@
 use thiserror::Error;
 
 use crate::{
-	builtin::{format::FormatError, sort::SortError},
+	stdlib::{format::FormatError, sort::SortError},
 	typed::TypeLocError,
 };
 
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -7,14 +7,14 @@
 use jrsonnet_types::ValType;
 
 use crate::{
-	builtin::{std_slice, BUILTINS},
 	error::Error::*,
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
-	function::CallLocation,
+	function::{CallLocation, FuncDesc, FuncVal},
 	gc::TraceBox,
+	stdlib::{std_slice, BUILTINS},
 	throw,
 	typed::Typed,
-	val::{ArrValue, FuncDesc, FuncVal, LazyValValue},
+	val::{ArrValue, LazyValValue},
 	Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue,
 	ObjValueBuilder, ObjectAssertion, Result, State, Val,
 };
@@ -676,6 +676,7 @@
 				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,
 		)),
 		IntrinsicThisFile => return Err(MagicThisFileUsed.into()),
+		IntrinsicId => Val::Func(FuncVal::identity()),
 		AssertExpr(assert, returned) => {
 			evaluate_assert(s.clone(), ctx.clone(), assert)?;
 			evaluate(s, ctx, returned)?
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -3,7 +3,7 @@
 use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType};
 
 use crate::{
-	builtin::std_format, error::Error::*, evaluate, throw, typed::Typed, val::equals, Context,
+	error::Error::*, evaluate, stdlib::std_format, throw, typed::Typed, val::equals, Context,
 	Result, State, Val,
 };
 
deletedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function.rs
+++ /dev/null
@@ -1,549 +0,0 @@
-use std::{borrow::Cow, collections::HashMap};
-
-use gcmodule::Trace;
-use jrsonnet_interner::IStr;
-pub use jrsonnet_macros::builtin;
-use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
-
-use crate::{
-	error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, typed::Typed,
-	val::LazyValValue, Context, FutureWrapper, GcHashMap, LazyVal, Result, State, Val,
-};
-
-#[derive(Clone, Copy)]
-pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
-impl<'l> CallLocation<'l> {
-	pub const fn new(loc: &'l ExprLocation) -> Self {
-		Self(Some(loc))
-	}
-}
-impl CallLocation<'static> {
-	pub const fn native() -> Self {
-		Self(None)
-	}
-}
-
-#[derive(Trace)]
-struct EvaluateLazyVal {
-	ctx: Context,
-	expr: LocExpr,
-}
-impl LazyValValue for EvaluateLazyVal {
-	fn get(self: Box<Self>, s: State) -> Result<Val> {
-		evaluate(s, self.ctx, &self.expr)
-	}
-}
-
-#[derive(Trace)]
-struct EvaluateNamedLazyVal {
-	ctx: FutureWrapper<Context>,
-	name: IStr,
-	value: LocExpr,
-}
-impl LazyValValue for EvaluateNamedLazyVal {
-	fn get(self: Box<Self>, s: State) -> Result<Val> {
-		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
-	}
-}
-pub trait ArgLike {
-	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
-}
-impl ArgLike for &LocExpr {
-	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
-		Ok(if tailstrict {
-			LazyVal::new_resolved(evaluate(s, ctx, self)?)
-		} else {
-			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
-				ctx,
-				expr: (*self).clone(),
-			})))
-		})
-	}
-}
-impl<T> ArgLike for T
-where
-	T: Typed + Clone,
-{
-	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {
-		let val = T::into_untyped(self.clone(), s)?;
-		Ok(LazyVal::new_resolved(val))
-	}
-}
-pub enum TlaArg {
-	String(IStr),
-	Code(LocExpr),
-	Val(Val),
-}
-impl ArgLike for TlaArg {
-	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
-		match self {
-			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),
-			TlaArg::Code(code) => Ok(if tailstrict {
-				LazyVal::new_resolved(evaluate(s, ctx, code)?)
-			} else {
-				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
-					ctx,
-					expr: code.clone(),
-				})))
-			}),
-			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),
-		}
-	}
-}
-
-pub trait ArgsLike {
-	fn unnamed_len(&self) -> usize;
-	fn unnamed_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()>;
-	fn named_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()>;
-	fn named_names(&self, handler: &mut dyn FnMut(&IStr));
-}
-
-impl ArgsLike for ArgsDesc {
-	fn unnamed_len(&self) -> usize {
-		self.unnamed.len()
-	}
-
-	fn unnamed_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		for (id, arg) in self.unnamed.iter().enumerate() {
-			handler(
-				id,
-				if tailstrict {
-					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
-				} else {
-					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
-						ctx: ctx.clone(),
-						expr: arg.clone(),
-					})))
-				},
-			)?;
-		}
-		Ok(())
-	}
-
-	fn named_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		for (name, arg) in &self.named {
-			handler(
-				name,
-				if tailstrict {
-					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
-				} else {
-					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
-						ctx: ctx.clone(),
-						expr: arg.clone(),
-					})))
-				},
-			)?;
-		}
-		Ok(())
-	}
-
-	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
-		for (name, _) in &self.named {
-			handler(name);
-		}
-	}
-}
-
-impl ArgsLike for [(); 0] {
-	fn unnamed_len(&self) -> usize {
-		0
-	}
-
-	fn unnamed_iter(
-		&self,
-		_s: State,
-		_ctx: Context,
-		_tailstrict: bool,
-		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		Ok(())
-	}
-
-	fn named_iter(
-		&self,
-		_s: State,
-		_ctx: Context,
-		_tailstrict: bool,
-		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		Ok(())
-	}
-
-	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
-}
-
-impl<A: ArgLike> ArgsLike for [(IStr, A)] {
-	fn unnamed_len(&self) -> usize {
-		0
-	}
-
-	fn unnamed_iter(
-		&self,
-		_s: State,
-		_ctx: Context,
-		_tailstrict: bool,
-		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		Ok(())
-	}
-
-	fn named_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		for (name, val) in self.iter() {
-			handler(name, val.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
-		}
-		Ok(())
-	}
-
-	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
-		for (name, _) in self.iter() {
-			handler(name);
-		}
-	}
-}
-
-impl<A: ArgLike, S> ArgsLike for HashMap<IStr, A, S> {
-	fn unnamed_len(&self) -> usize {
-		0
-	}
-
-	fn unnamed_iter(
-		&self,
-		_s: State,
-		_ctx: Context,
-		_tailstrict: bool,
-		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		Ok(())
-	}
-
-	fn named_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		for (name, value) in self.iter() {
-			handler(
-				name,
-				value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,
-			)?;
-		}
-		Ok(())
-	}
-
-	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
-		for (name, _) in self.iter() {
-			handler(name);
-		}
-	}
-}
-
-impl<A: ArgLike> ArgsLike for [A] {
-	fn unnamed_len(&self) -> usize {
-		self.len()
-	}
-
-	fn unnamed_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		for (i, arg) in self.iter().enumerate() {
-			handler(i, arg.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
-		}
-		Ok(())
-	}
-
-	fn named_iter(
-		&self,
-		_s: State,
-		_ctx: Context,
-		_tailstrict: bool,
-		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		Ok(())
-	}
-
-	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
-}
-impl<A: ArgLike> ArgsLike for &[A] {
-	fn unnamed_len(&self) -> usize {
-		(*self).unnamed_len()
-	}
-
-	fn unnamed_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		(*self).unnamed_iter(s, ctx, tailstrict, handler)
-	}
-
-	fn named_iter(
-		&self,
-		s: State,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
-	) -> Result<()> {
-		(*self).named_iter(s, ctx, tailstrict, handler)
-	}
-
-	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
-		(*self).named_names(handler);
-	}
-}
-
-/// Creates correct [context](Context) for function body evaluation returning error on invalid call.
-///
-/// ## Parameters
-/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
-/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)
-/// * `params`: function parameters' definition
-/// * `args`: passed function arguments
-/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
-pub fn parse_function_call(
-	s: State,
-	ctx: Context,
-	body_ctx: Context,
-	params: &ParamsDesc,
-	args: &dyn ArgsLike,
-	tailstrict: bool,
-) -> Result<Context> {
-	let mut passed_args = GcHashMap::with_capacity(params.len());
-	if args.unnamed_len() > params.len() {
-		throw!(TooManyArgsFunctionHas(params.len()))
-	}
-
-	let mut filled_args = 0;
-
-	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
-		let name = params[id].0.clone();
-		passed_args.insert(name, arg);
-		filled_args += 1;
-		Ok(())
-	})?;
-
-	args.named_iter(s, ctx, tailstrict, &mut |name, value| {
-		// FIXME: O(n) for arg existence check
-		if !params.iter().any(|p| &p.0 == name) {
-			throw!(UnknownFunctionParameter((name as &str).to_owned()));
-		}
-		if passed_args.insert(name.clone(), value).is_some() {
-			throw!(BindingParameterASecondTime(name.clone()));
-		}
-		filled_args += 1;
-		Ok(())
-	})?;
-
-	if filled_args < params.len() {
-		// Some args are unset, but maybe we have defaults for them
-		// Default values should be created in newly created context
-		let fctx = Context::new_future();
-		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);
-
-		for param in params.iter().filter(|p| p.1.is_some()) {
-			if passed_args.contains_key(&param.0.clone()) {
-				continue;
-			}
-
-			defaults.insert(
-				param.0.clone(),
-				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
-					ctx: fctx.clone(),
-					name: param.0.clone(),
-					value: param.1.clone().expect("default exists"),
-				}))),
-			);
-			filled_args += 1;
-		}
-
-		// Some args still wasn't filled
-		if filled_args != params.len() {
-			for param in params.iter().skip(args.unnamed_len()) {
-				let mut found = false;
-				args.named_names(&mut |name| {
-					if name == &param.0 {
-						found = true;
-					}
-				});
-				if !found {
-					throw!(FunctionParameterNotBoundInCall(param.0.clone()));
-				}
-			}
-			unreachable!();
-		}
-
-		Ok(body_ctx
-			.extend(passed_args, None, None, None)
-			.extend_bound(defaults)
-			.into_future(fctx))
-	} else {
-		let body_ctx = body_ctx.extend(passed_args, None, None, None);
-		Ok(body_ctx)
-	}
-}
-
-type BuiltinParamName = Cow<'static, str>;
-
-#[derive(Clone, Trace)]
-pub struct BuiltinParam {
-	pub name: BuiltinParamName,
-	pub has_default: bool,
-}
-
-/// Do not implement it directly, instead use #[builtin] macro
-pub trait Builtin: Trace {
-	fn name(&self) -> &str;
-	fn params(&self) -> &[BuiltinParam];
-	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
-}
-
-pub trait StaticBuiltin: Builtin + Send + Sync
-where
-	Self: 'static,
-{
-	// In impl, to make it object safe:
-	// const INST: &'static Self;
-}
-
-/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead
-///
-/// ## Parameters
-/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
-/// * `params`: function parameters' definition
-/// * `args`: passed function arguments
-/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
-pub fn parse_builtin_call(
-	s: State,
-	ctx: Context,
-	params: &[BuiltinParam],
-	args: &dyn ArgsLike,
-	tailstrict: bool,
-) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {
-	let mut passed_args = GcHashMap::with_capacity(params.len());
-	if args.unnamed_len() > params.len() {
-		throw!(TooManyArgsFunctionHas(params.len()))
-	}
-
-	let mut filled_args = 0;
-
-	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
-		let name = params[id].name.clone();
-		passed_args.insert(name, arg);
-		filled_args += 1;
-		Ok(())
-	})?;
-
-	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
-		// FIXME: O(n) for arg existence check
-		let p = params
-			.iter()
-			.find(|p| p.name == name as &str)
-			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
-		if passed_args.insert(p.name.clone(), arg).is_some() {
-			throw!(BindingParameterASecondTime(name.clone()));
-		}
-		filled_args += 1;
-		Ok(())
-	})?;
-
-	if filled_args < params.len() {
-		for param in params.iter().filter(|p| p.has_default) {
-			if passed_args.contains_key(&param.name) {
-				continue;
-			}
-			filled_args += 1;
-		}
-
-		// Some args still wasn't filled
-		if filled_args != params.len() {
-			for param in params.iter().skip(args.unnamed_len()) {
-				let mut found = false;
-				args.named_names(&mut |name| {
-					if name as &str == &param.name as &str {
-						found = true;
-					}
-				});
-				if !found {
-					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));
-				}
-			}
-			unreachable!();
-		}
-	}
-	Ok(passed_args)
-}
-
-/// Creates Context, which has all argument default values applied
-/// and with unbound values causing error to be returned
-pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {
-	#[derive(Trace)]
-	struct DependsOnUnbound(IStr);
-	impl LazyValValue for DependsOnUnbound {
-		fn get(self: Box<Self>, _: State) -> Result<Val> {
-			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
-		}
-	}
-
-	let fctx = Context::new_future();
-
-	let mut bindings = GcHashMap::new();
-
-	for param in params.iter() {
-		if let Some(v) = &param.1 {
-			bindings.insert(
-				param.0.clone(),
-				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
-					ctx: fctx.clone(),
-					name: param.0.clone(),
-					value: v.clone(),
-				}))),
-			);
-		} else {
-			bindings.insert(
-				param.0.clone(),
-				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),
-			);
-		}
-	}
-
-	body_ctx
-		.extend(bindings, None, None, None)
-		.into_future(fctx)
-}
addedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -0,0 +1,304 @@
+use std::collections::HashMap;
+
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_parser::{ArgsDesc, LocExpr};
+
+use crate::{
+	error::Result, evaluate, gc::TraceBox, typed::Typed, val::LazyValValue, Context, LazyVal,
+	State, Val,
+};
+
+#[derive(Trace)]
+struct EvaluateLazyVal {
+	ctx: Context,
+	expr: LocExpr,
+}
+impl LazyValValue for EvaluateLazyVal {
+	fn get(self: Box<Self>, s: State) -> Result<Val> {
+		evaluate(s, self.ctx, &self.expr)
+	}
+}
+
+pub trait ArgLike {
+	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
+}
+
+impl ArgLike for &LocExpr {
+	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
+		Ok(if tailstrict {
+			LazyVal::new_resolved(evaluate(s, ctx, self)?)
+		} else {
+			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+				ctx,
+				expr: (*self).clone(),
+			})))
+		})
+	}
+}
+
+impl<T> ArgLike for T
+where
+	T: Typed + Clone,
+{
+	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {
+		let val = T::into_untyped(self.clone(), s)?;
+		Ok(LazyVal::new_resolved(val))
+	}
+}
+
+pub enum TlaArg {
+	String(IStr),
+	Code(LocExpr),
+	Val(Val),
+}
+impl ArgLike for TlaArg {
+	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
+		match self {
+			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),
+			TlaArg::Code(code) => Ok(if tailstrict {
+				LazyVal::new_resolved(evaluate(s, ctx, code)?)
+			} else {
+				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+					ctx,
+					expr: code.clone(),
+				})))
+			}),
+			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),
+		}
+	}
+}
+
+mod sealed {
+	/// Implemented for `ArgsLike`, where only unnamed arguments present
+	pub trait Unnamed {}
+	/// Implemented for `ArgsLike`, where only named arguments present
+	pub trait Named {}
+}
+
+pub trait ArgsLike {
+	fn unnamed_len(&self) -> usize;
+	fn unnamed_iter(
+		&self,
+		s: State,
+		ctx: Context,
+		tailstrict: bool,
+		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+	) -> Result<()>;
+	fn named_iter(
+		&self,
+		s: State,
+		ctx: Context,
+		tailstrict: bool,
+		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+	) -> Result<()>;
+	fn named_names(&self, handler: &mut dyn FnMut(&IStr));
+}
+
+impl ArgsLike for ArgsDesc {
+	fn unnamed_len(&self) -> usize {
+		self.unnamed.len()
+	}
+
+	fn unnamed_iter(
+		&self,
+		s: State,
+		ctx: Context,
+		tailstrict: bool,
+		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+	) -> Result<()> {
+		for (id, arg) in self.unnamed.iter().enumerate() {
+			handler(
+				id,
+				if tailstrict {
+					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
+				} else {
+					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+						ctx: ctx.clone(),
+						expr: arg.clone(),
+					})))
+				},
+			)?;
+		}
+		Ok(())
+	}
+
+	fn named_iter(
+		&self,
+		s: State,
+		ctx: Context,
+		tailstrict: bool,
+		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+	) -> Result<()> {
+		for (name, arg) in &self.named {
+			handler(
+				name,
+				if tailstrict {
+					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
+				} else {
+					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+						ctx: ctx.clone(),
+						expr: arg.clone(),
+					})))
+				},
+			)?;
+		}
+		Ok(())
+	}
+
+	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
+		for (name, _) in &self.named {
+			handler(name);
+		}
+	}
+}
+
+impl<A: ArgLike, S> sealed::Named for HashMap<IStr, A, S> {}
+impl<A: ArgLike, S> ArgsLike for HashMap<IStr, A, S> {
+	fn unnamed_len(&self) -> usize {
+		0
+	}
+
+	fn unnamed_iter(
+		&self,
+		_s: State,
+		_ctx: Context,
+		_tailstrict: bool,
+		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+	) -> Result<()> {
+		Ok(())
+	}
+
+	fn named_iter(
+		&self,
+		s: State,
+		ctx: Context,
+		tailstrict: bool,
+		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+	) -> Result<()> {
+		for (name, value) in self.iter() {
+			handler(
+				name,
+				value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,
+			)?;
+		}
+		Ok(())
+	}
+
+	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
+		for (name, _) in self.iter() {
+			handler(name);
+		}
+	}
+}
+
+macro_rules! impl_args_like {
+	($count:expr; $($gen:ident)*) => {
+		impl<$($gen: ArgLike,)*> sealed::Unnamed for ($($gen,)*) {}
+		impl<$($gen: ArgLike,)*> ArgsLike for ($($gen,)*) {
+			fn unnamed_len(&self) -> usize {
+				$count
+			}
+			#[allow(non_snake_case, unused_assignments)]
+			fn unnamed_iter(
+				&self,
+				s: State,
+				ctx: Context,
+				tailstrict: bool,
+				handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+			) -> Result<()> {
+				let mut i = 0usize;
+				let ($($gen,)*) = self;
+				$(
+					handler(i, $gen.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
+					i+=1;
+				)*
+				Ok(())
+			}
+			fn named_iter(
+				&self,
+				_s: State,
+				_ctx: Context,
+				_tailstrict: bool,
+				_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+			) -> Result<()> {
+				Ok(())
+			}
+			fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+		}
+		impl<$($gen: ArgLike,)*> sealed::Named for ($((IStr, $gen),)*) {}
+		impl<$($gen: ArgLike,)*> ArgsLike for ($((IStr, $gen),)*) {
+			fn unnamed_len(&self) -> usize {
+				0
+			}
+			fn unnamed_iter(
+				&self,
+				_s: State,
+				_ctx: Context,
+				_tailstrict: bool,
+				_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+			) -> Result<()> {
+				Ok(())
+			}
+			#[allow(non_snake_case)]
+			fn named_iter(
+				&self,
+				s: State,
+				ctx: Context,
+				tailstrict: bool,
+				handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+			) -> Result<()> {
+				let ($($gen,)*) = self;
+				$(
+					handler(&$gen.0, $gen.1.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
+				)*
+				Ok(())
+			}
+			#[allow(non_snake_case)]
+			fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
+				let ($($gen,)*) = self;
+				$(
+					handler(&$gen.0);
+				)*
+			}
+		}
+	};
+	($count:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {
+		impl_args_like!($count; $($cur)*);
+		impl_args_like!($count + 1usize; $($cur)* $c @ $($rest)*);
+	};
+	($count:expr; $($cur:ident)* @) => {
+		impl_args_like!($count; $($cur)*);
+	}
+}
+impl_args_like! {
+	0usize; A @ B C D E F G H I J K L
+}
+
+impl ArgsLike for () {
+	fn unnamed_len(&self) -> usize {
+		0
+	}
+
+	fn unnamed_iter(
+		&self,
+		_s: State,
+		_ctx: Context,
+		_tailstrict: bool,
+		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+	) -> Result<()> {
+		Ok(())
+	}
+
+	fn named_iter(
+		&self,
+		_s: State,
+		_ctx: Context,
+		_tailstrict: bool,
+		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+	) -> Result<()> {
+		Ok(())
+	}
+
+	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
addedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -0,0 +1,66 @@
+use std::{borrow::Cow, path::Path, rc::Rc};
+
+use gcmodule::Trace;
+
+use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
+use crate::{error::Result, gc::TraceBox, Context, State, Val};
+
+pub type BuiltinParamName = Cow<'static, str>;
+
+#[derive(Clone, Trace)]
+pub struct BuiltinParam {
+	pub name: BuiltinParamName,
+	pub has_default: bool,
+}
+
+/// Do not implement it directly, instead use #[builtin] macro
+pub trait Builtin: Trace {
+	fn name(&self) -> &str;
+	fn params(&self) -> &[BuiltinParam];
+	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
+}
+
+pub trait StaticBuiltin: Builtin + Send + Sync
+where
+	Self: 'static,
+{
+	// In impl, to make it object safe:
+	// const INST: &'static Self;
+}
+
+#[derive(Trace)]
+pub struct NativeCallback {
+	pub(crate) params: Vec<BuiltinParam>,
+	handler: TraceBox<dyn NativeCallbackHandler>,
+}
+impl NativeCallback {
+	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
+	pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
+		Self { params, handler }
+	}
+}
+
+impl Builtin for NativeCallback {
+	fn name(&self) -> &str {
+		// TODO: standard natives gets their names from definition
+		// But builitins should already have them
+		"<native>"
+	}
+
+	fn params(&self) -> &[BuiltinParam] {
+		&self.params
+	}
+
+	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
+		let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
+		let mut out_args = Vec::with_capacity(self.params.len());
+		for p in &self.params {
+			out_args.push(args[&p.name].evaluate(s.clone())?);
+		}
+		self.handler.call(s, loc.0.map(|l| l.0.clone()), &out_args)
+	}
+}
+
+pub trait NativeCallbackHandler: Trace {
+	fn call(&self, s: State, from: Option<Rc<Path>>, args: &[Val]) -> Result<Val>;
+}
addedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -0,0 +1,168 @@
+use std::fmt::Debug;
+
+pub use arglike::{ArgLike, ArgsLike, TlaArg};
+use gcmodule::{Cc, Trace};
+use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::builtin;
+use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc};
+
+use self::{
+	builtin::{Builtin, StaticBuiltin},
+	native::NativeDesc,
+	parse::{parse_default_function_call, parse_function_call},
+};
+use crate::{evaluate, gc::TraceBox, typed::Any, Context, Result, State, Val};
+
+pub mod arglike;
+pub mod builtin;
+pub mod native;
+pub mod parse;
+
+#[derive(Clone, Copy)]
+pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
+impl<'l> CallLocation<'l> {
+	pub const fn new(loc: &'l ExprLocation) -> Self {
+		Self(Some(loc))
+	}
+}
+impl CallLocation<'static> {
+	pub const fn native() -> Self {
+		Self(None)
+	}
+}
+
+/// Function implemented in jsonnet
+#[derive(Debug, PartialEq, Trace)]
+pub struct FuncDesc {
+	/// In expressions like
+	/// ```jsonnet
+	/// local a = function() ...
+	/// local a() ...
+	/// { a: function() ... }
+	/// { a() = ... }
+	/// ```
+	///
+	/// Deducted to `a`, unspecified otherwise
+	pub name: IStr,
+	/// Context, in which this function was evaluated
+	///
+	/// I.e in
+	/// ```jsonnet
+	/// local a = 2;
+	/// function() ...
+	/// ```
+	/// context will contain `a`
+	pub ctx: Context,
+
+	pub params: ParamsDesc,
+	pub body: LocExpr,
+}
+impl FuncDesc {
+	/// Create body context, but fill arguments without defaults with lazy error
+	pub fn default_body_context(&self) -> Context {
+		parse_default_function_call(self.ctx.clone(), &self.params)
+	}
+
+	/// Create context, with which body code will run
+	pub fn call_body_context(
+		&self,
+		s: State,
+		call_ctx: Context,
+		args: &dyn ArgsLike,
+		tailstrict: bool,
+	) -> Result<Context> {
+		parse_function_call(
+			s,
+			call_ctx,
+			self.ctx.clone(),
+			&self.params,
+			args,
+			tailstrict,
+		)
+	}
+}
+
+/// Any possible function value, including plain functions and user-provided builtins
+#[allow(clippy::module_name_repetitions)]
+#[derive(Trace, Clone)]
+pub enum FuncVal {
+	/// std.id
+	Id,
+	/// Plain function implemented in jsonnet
+	Normal(Cc<FuncDesc>),
+	/// Standard library function
+	StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),
+	/// User-provided function
+	Builtin(Cc<TraceBox<dyn Builtin>>),
+}
+
+impl Debug for FuncVal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		match self {
+			Self::Id => f.debug_tuple("Id").finish(),
+			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),
+			Self::StaticBuiltin(arg0) => {
+				f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()
+			}
+			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),
+		}
+	}
+}
+
+impl FuncVal {
+	pub fn into_native<D: NativeDesc>(self) -> D::Value {
+		D::into_native(self)
+	}
+	pub fn params_len(&self) -> usize {
+		match self {
+			Self::Id => 1,
+			Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),
+			Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),
+			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),
+		}
+	}
+	pub fn name(&self) -> IStr {
+		match self {
+			Self::Id => "id".into(),
+			Self::Normal(normal) => normal.name.clone(),
+			Self::StaticBuiltin(builtin) => builtin.name().into(),
+			Self::Builtin(builtin) => builtin.name().into(),
+		}
+	}
+	pub fn evaluate(
+		&self,
+		s: State,
+		call_ctx: Context,
+		loc: CallLocation,
+		args: &dyn ArgsLike,
+		tailstrict: bool,
+	) -> Result<Val> {
+		match self {
+			Self::Id => {
+				#[builtin]
+				fn builtin_id(v: Any) -> Result<Any> {
+					Ok(v)
+				}
+				static ID: &'static builtin_id = &builtin_id {};
+
+				ID.call(s, call_ctx, loc, args)
+			}
+			Self::Normal(func) => {
+				let body_ctx = func.call_body_context(s.clone(), call_ctx, args, tailstrict)?;
+				evaluate(s, body_ctx, &func.body)
+			}
+			Self::StaticBuiltin(b) => b.call(s, call_ctx, loc, args),
+			Self::Builtin(b) => b.call(s, call_ctx, loc, args),
+		}
+	}
+	pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {
+		self.evaluate(s, Context::default(), CallLocation::native(), args, true)
+	}
+
+	pub fn is_identity(&self) -> bool {
+		matches!(self, Self::Id)
+	}
+	pub fn identity() -> Self {
+		Self::Id
+	}
+}
addedcrates/jrsonnet-evaluator/src/function/native.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/native.rs
@@ -0,0 +1,43 @@
+use super::{arglike::ArgLike, CallLocation, FuncVal};
+use crate::{error::Result, typed::Typed, State};
+
+pub trait NativeDesc {
+	type Value;
+	fn into_native(val: FuncVal) -> Self::Value;
+}
+macro_rules! impl_native_desc {
+	($($gen:ident)*) => {
+		impl<$($gen,)* O> NativeDesc for (($($gen,)*), O)
+		where
+			$($gen: ArgLike,)*
+			O: Typed,
+		{
+			type Value = Box<dyn Fn(State, $($gen,)*) -> Result<O>>;
+
+			#[allow(non_snake_case)]
+			fn into_native(val: FuncVal) -> Self::Value {
+				Box::new(move |s: State, $($gen),*| {
+					let val = val.evaluate(
+						s.clone(),
+						s.create_default_context(),
+						CallLocation::native(),
+						&($($gen,)*),
+						true
+					)?;
+					O::from_untyped(val, s.clone())
+				})
+			}
+		}
+	};
+	($($cur:ident)* @ $c:ident $($rest:ident)*) => {
+		impl_native_desc!($($cur)*);
+		impl_native_desc!($($cur)* $c @ $($rest)*);
+	};
+	($($cur:ident)* @) => {
+		impl_native_desc!($($cur)*);
+	}
+}
+
+impl_native_desc! {
+	@ A B C D E F G H I J K L
+}
addedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -0,0 +1,225 @@
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_parser::{LocExpr, ParamsDesc};
+
+use super::{
+	arglike::ArgsLike,
+	builtin::{BuiltinParam, BuiltinParamName},
+};
+use crate::{
+	error::{Error::*, Result},
+	evaluate_named,
+	gc::{GcHashMap, TraceBox},
+	throw,
+	val::LazyValValue,
+	Context, FutureWrapper, LazyVal, State, Val,
+};
+
+#[derive(Trace)]
+struct EvaluateNamedLazyVal {
+	ctx: FutureWrapper<Context>,
+	name: IStr,
+	value: LocExpr,
+}
+
+impl LazyValValue for EvaluateNamedLazyVal {
+	fn get(self: Box<Self>, s: State) -> Result<Val> {
+		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
+	}
+}
+
+/// Creates correct [context](Context) for function body evaluation returning error on invalid call.
+///
+/// ## Parameters
+/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
+/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)
+/// * `params`: function parameters' definition
+/// * `args`: passed function arguments
+/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
+pub fn parse_function_call(
+	s: State,
+	ctx: Context,
+	body_ctx: Context,
+	params: &ParamsDesc,
+	args: &dyn ArgsLike,
+	tailstrict: bool,
+) -> Result<Context> {
+	let mut passed_args = GcHashMap::with_capacity(params.len());
+	if args.unnamed_len() > params.len() {
+		throw!(TooManyArgsFunctionHas(params.len()))
+	}
+
+	let mut filled_args = 0;
+
+	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
+		let name = params[id].0.clone();
+		passed_args.insert(name, arg);
+		filled_args += 1;
+		Ok(())
+	})?;
+
+	args.named_iter(s, ctx, tailstrict, &mut |name, value| {
+		// FIXME: O(n) for arg existence check
+		if !params.iter().any(|p| &p.0 == name) {
+			throw!(UnknownFunctionParameter((name as &str).to_owned()));
+		}
+		if passed_args.insert(name.clone(), value).is_some() {
+			throw!(BindingParameterASecondTime(name.clone()));
+		}
+		filled_args += 1;
+		Ok(())
+	})?;
+
+	if filled_args < params.len() {
+		// Some args are unset, but maybe we have defaults for them
+		// Default values should be created in newly created context
+		let fctx = Context::new_future();
+		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);
+
+		for param in params.iter().filter(|p| p.1.is_some()) {
+			if passed_args.contains_key(&param.0.clone()) {
+				continue;
+			}
+
+			defaults.insert(
+				param.0.clone(),
+				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
+					ctx: fctx.clone(),
+					name: param.0.clone(),
+					value: param.1.clone().expect("default exists"),
+				}))),
+			);
+			filled_args += 1;
+		}
+
+		// Some args still wasn't filled
+		if filled_args != params.len() {
+			for param in params.iter().skip(args.unnamed_len()) {
+				let mut found = false;
+				args.named_names(&mut |name| {
+					if name == &param.0 {
+						found = true;
+					}
+				});
+				if !found {
+					throw!(FunctionParameterNotBoundInCall(param.0.clone()));
+				}
+			}
+			unreachable!();
+		}
+
+		Ok(body_ctx
+			.extend(passed_args, None, None, None)
+			.extend_bound(defaults)
+			.into_future(fctx))
+	} else {
+		let body_ctx = body_ctx.extend(passed_args, None, None, None);
+		Ok(body_ctx)
+	}
+}
+
+/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead
+///
+/// ## Parameters
+/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
+/// * `params`: function parameters' definition
+/// * `args`: passed function arguments
+/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
+pub fn parse_builtin_call(
+	s: State,
+	ctx: Context,
+	params: &[BuiltinParam],
+	args: &dyn ArgsLike,
+	tailstrict: bool,
+) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {
+	let mut passed_args = GcHashMap::with_capacity(params.len());
+	if args.unnamed_len() > params.len() {
+		throw!(TooManyArgsFunctionHas(params.len()))
+	}
+
+	let mut filled_args = 0;
+
+	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
+		let name = params[id].name.clone();
+		passed_args.insert(name, arg);
+		filled_args += 1;
+		Ok(())
+	})?;
+
+	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
+		// FIXME: O(n) for arg existence check
+		let p = params
+			.iter()
+			.find(|p| p.name == name as &str)
+			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
+		if passed_args.insert(p.name.clone(), arg).is_some() {
+			throw!(BindingParameterASecondTime(name.clone()));
+		}
+		filled_args += 1;
+		Ok(())
+	})?;
+
+	if filled_args < params.len() {
+		for param in params.iter().filter(|p| p.has_default) {
+			if passed_args.contains_key(&param.name) {
+				continue;
+			}
+			filled_args += 1;
+		}
+
+		// Some args still wasn't filled
+		if filled_args != params.len() {
+			for param in params.iter().skip(args.unnamed_len()) {
+				let mut found = false;
+				args.named_names(&mut |name| {
+					if name as &str == &param.name as &str {
+						found = true;
+					}
+				});
+				if !found {
+					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));
+				}
+			}
+			unreachable!();
+		}
+	}
+	Ok(passed_args)
+}
+
+/// Creates Context, which has all argument default values applied
+/// and with unbound values causing error to be returned
+pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {
+	#[derive(Trace)]
+	struct DependsOnUnbound(IStr);
+	impl LazyValValue for DependsOnUnbound {
+		fn get(self: Box<Self>, _: State) -> Result<Val> {
+			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
+		}
+	}
+
+	let fctx = Context::new_future();
+
+	let mut bindings = GcHashMap::new();
+
+	for param in params.iter() {
+		if let Some(v) = &param.1 {
+			bindings.insert(
+				param.0.clone(),
+				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
+					ctx: fctx.clone(),
+					name: param.0.clone(),
+					value: v.clone(),
+				}))),
+			);
+		} else {
+			bindings.insert(
+				param.0.clone(),
+				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),
+			);
+		}
+	}
+
+	body_ctx
+		.extend(bindings, None, None, None)
+		.into_future(fctx)
+}
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -22,7 +22,6 @@
 // For jrsonnet-macros
 extern crate self as jrsonnet_evaluator;
 
-mod builtin;
 mod ctx;
 mod dynamic;
 pub mod error;
@@ -32,8 +31,8 @@
 mod import;
 mod integrations;
 mod map;
-pub mod native;
 mod obj;
+mod stdlib;
 pub mod trace;
 pub mod typed;
 pub mod val;
@@ -41,7 +40,7 @@
 use std::{
 	cell::{Ref, RefCell, RefMut},
 	collections::HashMap,
-	fmt::Debug,
+	fmt::{self, Debug},
 	path::{Path, PathBuf},
 	rc::Rc,
 };
@@ -50,7 +49,7 @@
 pub use dynamic::*;
 use error::{Error::*, LocError, Result, StackTraceElement};
 pub use evaluate::*;
-use function::{Builtin, CallLocation, TlaArg};
+use function::{builtin::Builtin, CallLocation, TlaArg};
 use gc::{GcHashMap, TraceBox};
 use gcmodule::{Cc, Trace, Weak};
 pub use import::*;
@@ -77,7 +76,7 @@
 }
 
 impl Debug for LazyBinding {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 		write!(f, "LazyBinding")
 	}
 }
@@ -329,7 +328,7 @@
 		self.add_parsed_file(
 			std_path.clone(),
 			STDLIB_STR.to_owned().into(),
-			builtin::get_parsed_stdlib(),
+			stdlib::get_parsed_stdlib(),
 		)
 		.expect("stdlib is correct");
 		let val = self
deletedcrates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/native.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-#![allow(clippy::type_complexity)]
-
-use std::{path::Path, rc::Rc};
-
-use gcmodule::Trace;
-
-use crate::{
-	error::Result,
-	function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam, CallLocation},
-	gc::TraceBox,
-	Context, State, Val,
-};
-
-#[derive(Trace)]
-pub struct NativeCallback {
-	pub(crate) params: Vec<BuiltinParam>,
-	handler: TraceBox<dyn NativeCallbackHandler>,
-}
-impl NativeCallback {
-	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
-	pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
-		Self { params, handler }
-	}
-}
-
-impl Builtin for NativeCallback {
-	fn name(&self) -> &str {
-		// TODO: standard natives gets their names from definition
-		// But builitins should already have them
-		"<native>"
-	}
-
-	fn params(&self) -> &[BuiltinParam] {
-		&self.params
-	}
-
-	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
-		let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
-		let mut out_args = Vec::with_capacity(self.params.len());
-		for p in &self.params {
-			out_args.push(args[&p.name].evaluate(s.clone())?);
-		}
-		self.handler.call(s, loc.0.map(|l| l.0.clone()), &out_args)
-	}
-}
-
-pub trait NativeCallbackHandler: Trace {
-	fn call(&self, s: State, from: Option<Rc<Path>>, args: &[Val]) -> Result<Val>;
-}
addedcrates/jrsonnet-evaluator/src/stdlib/expr.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/expr.rs
@@ -0,0 +1,28 @@
+use std::path::PathBuf;
+
+use jrsonnet_parser::{LocExpr, ParserSettings};
+
+thread_local! {
+	/// To avoid parsing again when issued from the same thread
+	#[allow(unreachable_code)]
+	static PARSED_STDLIB: LocExpr = {
+		#[cfg(feature = "serialized-stdlib")]
+		{
+			// Should not panic, stdlib.bincode is generated in build.rs
+			return bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))
+				.unwrap();
+		}
+
+		jrsonnet_parser::parse(
+			jrsonnet_stdlib::STDLIB_STR,
+			&ParserSettings {
+				file_name: PathBuf::from("std.jsonnet").into(),
+			},
+		)
+		.unwrap()
+	}
+}
+
+pub fn get_parsed_stdlib() -> LocExpr {
+	PARSED_STDLIB.with(Clone::clone)
+}
addedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -0,0 +1,796 @@
+//! faster std.format impl
+#![allow(clippy::too_many_arguments)]
+
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_types::ValType;
+use thiserror::Error;
+
+use crate::{error::Error::*, throw, typed::Typed, LocError, ObjValue, Result, State, Val};
+
+#[derive(Debug, Clone, Error, Trace)]
+pub enum FormatError {
+	#[error("truncated format code")]
+	TruncatedFormatCode,
+	#[error("unrecognized conversion type: {0}")]
+	UnrecognizedConversionType(char),
+
+	#[error("not enough values")]
+	NotEnoughValues,
+
+	#[error("cannot use * width with object")]
+	CannotUseStarWidthWithObject,
+	#[error("mapping keys required")]
+	MappingKeysRequired,
+	#[error("no such format field: {0}")]
+	NoSuchFormatField(IStr),
+}
+
+impl From<FormatError> for LocError {
+	fn from(e: FormatError) -> Self {
+		Self::new(Format(e))
+	}
+}
+
+use FormatError::*;
+
+type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;
+
+pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	if bytes[0] == b'(' {
+		let mut i = 1;
+		while i < bytes.len() {
+			if bytes[i] == b')' {
+				return Ok((&str[1..i as usize], &str[i as usize + 1..]));
+			}
+			i += 1;
+		}
+		Err(TruncatedFormatCode)
+	} else {
+		Ok(("", str))
+	}
+}
+
+#[cfg(test)]
+pub mod tests_key {
+	use super::*;
+
+	#[test]
+	fn parse_key() {
+		assert_eq!(
+			try_parse_mapping_key("(hello ) world").unwrap(),
+			("hello ", " world")
+		);
+		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));
+		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));
+		assert_eq!(
+			try_parse_mapping_key(" () world").unwrap(),
+			("", " () world")
+		);
+	}
+
+	#[test]
+	#[should_panic]
+	fn parse_key_missing_start() {
+		try_parse_mapping_key("").unwrap();
+	}
+
+	#[test]
+	#[should_panic]
+	fn parse_key_missing_end() {
+		try_parse_mapping_key("(   ").unwrap();
+	}
+}
+
+#[allow(clippy::struct_excessive_bools)]
+#[derive(Default, Debug)]
+pub struct CFlags {
+	pub alt: bool,
+	pub zero: bool,
+	pub left: bool,
+	pub blank: bool,
+	pub sign: bool,
+}
+
+pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	let mut i = 0;
+	let mut out = CFlags::default();
+	loop {
+		if bytes.len() == i {
+			return Err(TruncatedFormatCode);
+		}
+		match bytes[i] {
+			b'#' => out.alt = true,
+			b'0' => out.zero = true,
+			b'-' => out.left = true,
+			b' ' => out.blank = true,
+			b'+' => out.sign = true,
+			_ => break,
+		}
+		i += 1;
+	}
+	Ok((out, &str[i..]))
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Width {
+	Star,
+	Fixed(usize),
+}
+pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	if bytes[0] == b'*' {
+		return Ok((Width::Star, &str[1..]));
+	}
+	let mut out: usize = 0;
+	let mut digits = 0;
+	while let Some(digit) = (bytes[digits] as char).to_digit(10) {
+		out *= 10;
+		out += digit as usize;
+		digits += 1;
+		if digits == bytes.len() {
+			return Err(TruncatedFormatCode);
+		}
+	}
+	Ok((Width::Fixed(out), &str[digits..]))
+}
+
+pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	if bytes[0] == b'.' {
+		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))
+	} else {
+		Ok((None, str))
+	}
+}
+
+// Only skips
+pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let bytes = str.as_bytes();
+	let mut idx = 0;
+	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {
+		idx += 1;
+		if bytes.len() == idx {
+			return Err(TruncatedFormatCode);
+		}
+	}
+	Ok(((), &str[idx..]))
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ConvTypeV {
+	Decimal,
+	Octal,
+	Hexadecimal,
+	Scientific,
+	Float,
+	Shorter,
+	Char,
+	String,
+	Percent,
+}
+pub struct ConvType {
+	v: ConvTypeV,
+	caps: bool,
+}
+
+pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+
+	let code = str.as_bytes()[0];
+	let v: (ConvTypeV, bool) = match code {
+		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),
+		b'o' => (ConvTypeV::Octal, false),
+		b'x' => (ConvTypeV::Hexadecimal, false),
+		b'X' => (ConvTypeV::Hexadecimal, true),
+		b'e' => (ConvTypeV::Scientific, false),
+		b'E' => (ConvTypeV::Scientific, true),
+		b'f' => (ConvTypeV::Float, false),
+		b'F' => (ConvTypeV::Float, true),
+		b'g' => (ConvTypeV::Shorter, false),
+		b'G' => (ConvTypeV::Shorter, true),
+		b'c' => (ConvTypeV::Char, false),
+		b's' => (ConvTypeV::String, false),
+		b'%' => (ConvTypeV::Percent, false),
+		c => return Err(UnrecognizedConversionType(c as char)),
+	};
+
+	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))
+}
+
+#[derive(Debug)]
+pub struct Code<'s> {
+	mkey: &'s str,
+	cflags: CFlags,
+	width: Width,
+	precision: Option<Width>,
+	convtype: ConvTypeV,
+	caps: bool,
+}
+pub fn parse_code(str: &str) -> ParseResult<Code> {
+	if str.is_empty() {
+		return Err(TruncatedFormatCode);
+	}
+	let (mkey, str) = try_parse_mapping_key(str)?;
+	let (cflags, str) = try_parse_cflags(str)?;
+	let (width, str) = try_parse_field_width(str)?;
+	let (precision, str) = try_parse_precision(str)?;
+	let (_, str) = try_parse_length_modifier(str)?;
+	let (convtype, str) = parse_conversion_type(str)?;
+
+	Ok((
+		Code {
+			mkey,
+			cflags,
+			width,
+			precision,
+			convtype: convtype.v,
+			caps: convtype.caps,
+		},
+		str,
+	))
+}
+
+#[derive(Debug)]
+pub enum Element<'s> {
+	String(&'s str),
+	Code(Code<'s>),
+}
+pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {
+	let mut bytes = str.as_bytes();
+	let mut out = vec![];
+	let mut offset = 0;
+
+	loop {
+		while offset != bytes.len() && bytes[offset] != b'%' {
+			offset += 1;
+		}
+		if offset != 0 {
+			out.push(Element::String(&str[0..offset]));
+		}
+		if offset == bytes.len() {
+			return Ok(out);
+		}
+		str = &str[offset + 1..];
+		let code;
+		(code, str) = parse_code(str)?;
+		bytes = str.as_bytes();
+		offset = 0;
+
+		out.push(Element::Code(code));
+	}
+}
+
+const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
+
+#[inline]
+pub fn render_integer(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+	radix: i64,
+	prefix: &str,
+	caps: bool,
+) {
+	// Digit char indexes in reverse order, i.e
+	// for radix = 16 and n = 12f: [15, 2, 1]
+	let digits = if iv == 0 {
+		vec![0u8]
+	} else {
+		let mut v = iv.abs();
+		let mut nums = Vec::with_capacity(1);
+		while v > 0 {
+			nums.push((v % radix) as u8);
+			v /= radix;
+		}
+		nums
+	};
+	let neg = iv < 0;
+	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
+	let zp2 = zp
+		.max(precision)
+		.saturating_sub(prefix.len() + digits.len());
+
+	if neg {
+		out.push('-');
+	} else if sign {
+		out.push('+');
+	} else if blank {
+		out.push(' ');
+	}
+
+	out.reserve(zp2);
+	for _ in 0..zp2 {
+		out.push('0');
+	}
+	out.push_str(prefix);
+
+	for digit in digits.into_iter().rev() {
+		let ch = NUMBERS[digit as usize] as char;
+		out.push(if caps { ch.to_ascii_uppercase() } else { ch });
+	}
+}
+
+pub fn render_decimal(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+) {
+	render_integer(out, iv, padding, precision, blank, sign, 10, "", false);
+}
+pub fn render_octal(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	alt: bool,
+	blank: bool,
+	sign: bool,
+) {
+	render_integer(
+		out,
+		iv,
+		padding,
+		precision,
+		blank,
+		sign,
+		8,
+		if alt && iv != 0 { "0" } else { "" },
+		false,
+	);
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub fn render_hexadecimal(
+	out: &mut String,
+	iv: i64,
+	padding: usize,
+	precision: usize,
+	alt: bool,
+	blank: bool,
+	sign: bool,
+	caps: bool,
+) {
+	render_integer(
+		out,
+		iv,
+		padding,
+		precision,
+		blank,
+		sign,
+		16,
+		match (alt, caps) {
+			(true, true) => "0X",
+			(true, false) => "0x",
+			(false, _) => "",
+		},
+		caps,
+	);
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub fn render_float(
+	out: &mut String,
+	n: f64,
+	mut padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+	ensure_pt: bool,
+	trailing: bool,
+) {
+	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
+	padding = padding.saturating_sub(dot_size + precision);
+	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
+	if precision == 0 {
+		if ensure_pt {
+			out.push('.');
+		}
+		return;
+	}
+	let frac = n
+		.fract()
+		.mul_add(10.0_f64.powf(precision as f64), 0.5)
+		.floor();
+	if trailing || frac > 0.0 {
+		out.push('.');
+		let mut frac_str = String::new();
+		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
+		let mut trim = frac_str.len();
+		if !trailing {
+			for b in frac_str.as_bytes().iter().rev() {
+				if *b == b'0' {
+					trim -= 1;
+				}
+			}
+		}
+		out.push_str(&frac_str[..trim]);
+	} else if ensure_pt {
+		out.push('.');
+	}
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub fn render_float_sci(
+	out: &mut String,
+	n: f64,
+	mut padding: usize,
+	precision: usize,
+	blank: bool,
+	sign: bool,
+	ensure_pt: bool,
+	trailing: bool,
+	caps: bool,
+) {
+	let exponent = n.log10().floor();
+	let mantissa = if exponent as i16 == -324 {
+		n * 10.0 / 10.0_f64.powf(exponent + 1.0)
+	} else {
+		n / 10.0_f64.powf(exponent)
+	};
+	let mut exponent_str = String::new();
+	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
+
+	// +1 for e
+	padding = padding.saturating_sub(exponent_str.len() + 1);
+
+	render_float(
+		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,
+	);
+	out.push(if caps { 'E' } else { 'e' });
+	out.push_str(&exponent_str);
+}
+
+#[allow(clippy::too_many_lines)]
+pub fn format_code(
+	s: State,
+	out: &mut String,
+	value: &Val,
+	code: &Code,
+	width: usize,
+	precision: Option<usize>,
+) -> Result<()> {
+	let clfags = &code.cflags;
+	let (fpprec, iprec) = match precision {
+		Some(v) => (v, v),
+		None => (6, 0),
+	};
+	let padding = if clfags.zero && !clfags.left {
+		width
+	} else {
+		0
+	};
+
+	// TODO: If left padded, can optimize by writing directly to out
+	let mut tmp_out = String::new();
+
+	match code.convtype {
+		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string(s)?),
+		ConvTypeV::Decimal => {
+			let value = f64::from_untyped(value.clone(), s)?;
+			render_decimal(
+				&mut tmp_out,
+				value as i64,
+				padding,
+				iprec,
+				clfags.blank,
+				clfags.sign,
+			);
+		}
+		ConvTypeV::Octal => {
+			let value = f64::from_untyped(value.clone(), s)?;
+			render_octal(
+				&mut tmp_out,
+				value as i64,
+				padding,
+				iprec,
+				clfags.alt,
+				clfags.blank,
+				clfags.sign,
+			);
+		}
+		ConvTypeV::Hexadecimal => {
+			let value = f64::from_untyped(value.clone(), s)?;
+			render_hexadecimal(
+				&mut tmp_out,
+				value as i64,
+				padding,
+				iprec,
+				clfags.alt,
+				clfags.blank,
+				clfags.sign,
+				code.caps,
+			);
+		}
+		ConvTypeV::Scientific => {
+			let value = f64::from_untyped(value.clone(), s)?;
+			render_float_sci(
+				&mut tmp_out,
+				value,
+				padding,
+				fpprec,
+				clfags.blank,
+				clfags.sign,
+				clfags.alt,
+				true,
+				code.caps,
+			);
+		}
+		ConvTypeV::Float => {
+			let value = f64::from_untyped(value.clone(), s)?;
+			render_float(
+				&mut tmp_out,
+				value,
+				padding,
+				fpprec,
+				clfags.blank,
+				clfags.sign,
+				clfags.alt,
+				true,
+			);
+		}
+		ConvTypeV::Shorter => {
+			let value = f64::from_untyped(value.clone(), s)?;
+			let exponent = value.log10().floor();
+			if exponent < -4.0 || exponent >= fpprec as f64 {
+				render_float_sci(
+					&mut tmp_out,
+					value,
+					padding,
+					fpprec - 1,
+					clfags.blank,
+					clfags.sign,
+					clfags.alt,
+					clfags.alt,
+					code.caps,
+				);
+			} else {
+				let digits_before_pt = 1.max(exponent as usize + 1);
+				render_float(
+					&mut tmp_out,
+					value,
+					padding,
+					fpprec - digits_before_pt,
+					clfags.blank,
+					clfags.sign,
+					clfags.alt,
+					clfags.alt,
+				);
+			}
+		}
+		ConvTypeV::Char => match value.clone() {
+			Val::Num(n) => tmp_out
+				.push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),
+			Val::Str(s) => {
+				if s.chars().count() != 1 {
+					throw!(RuntimeError(
+						format!("%c expected 1 char string, got {}", s.chars().count()).into(),
+					));
+				}
+				tmp_out.push_str(&s);
+			}
+			_ => {
+				throw!(TypeMismatch(
+					"%c requires number/string",
+					vec![ValType::Num, ValType::Str],
+					value.value_type(),
+				));
+			}
+		},
+		ConvTypeV::Percent => tmp_out.push('%'),
+	};
+
+	let padding = width.saturating_sub(tmp_out.len());
+
+	if !clfags.left {
+		for _ in 0..padding {
+			out.push(' ');
+		}
+	}
+	out.push_str(&tmp_out);
+	if clfags.left {
+		for _ in 0..padding {
+			out.push(' ');
+		}
+	}
+
+	Ok(())
+}
+
+pub fn format_arr(s: State, str: &str, mut values: &[Val]) -> Result<String> {
+	let codes = parse_codes(str)?;
+	let mut out = String::new();
+
+	for code in codes {
+		match code {
+			Element::String(s) => {
+				out.push_str(s);
+			}
+			Element::Code(c) => {
+				let width = match c.width {
+					Width::Star => {
+						if values.is_empty() {
+							throw!(NotEnoughValues);
+						}
+						let value = &values[0];
+						values = &values[1..];
+						usize::from_untyped(value.clone(), s.clone())?
+					}
+					Width::Fixed(n) => n,
+				};
+				let precision = match c.precision {
+					Some(Width::Star) => {
+						if values.is_empty() {
+							throw!(NotEnoughValues);
+						}
+						let value = &values[0];
+						values = &values[1..];
+						Some(usize::from_untyped(value.clone(), s.clone())?)
+					}
+					Some(Width::Fixed(n)) => Some(n),
+					None => None,
+				};
+
+				// %% should not consume a value
+				let value = if c.convtype == ConvTypeV::Percent {
+					&Val::Null
+				} else {
+					if values.is_empty() {
+						throw!(NotEnoughValues);
+					}
+					let value = &values[0];
+					values = &values[1..];
+					value
+				};
+
+				format_code(s.clone(), &mut out, value, &c, width, precision)?;
+			}
+		}
+	}
+
+	Ok(out)
+}
+
+pub fn format_obj(s: State, str: &str, values: &ObjValue) -> Result<String> {
+	let codes = parse_codes(str)?;
+	let mut out = String::new();
+
+	for code in codes {
+		match code {
+			Element::String(s) => {
+				out.push_str(s);
+			}
+			Element::Code(c) => {
+				// TODO: Operate on ref
+				let f: IStr = c.mkey.into();
+				let width = match c.width {
+					Width::Star => {
+						throw!(CannotUseStarWidthWithObject);
+					}
+					Width::Fixed(n) => n,
+				};
+				let precision = match c.precision {
+					Some(Width::Star) => {
+						throw!(CannotUseStarWidthWithObject);
+					}
+					Some(Width::Fixed(n)) => Some(n),
+					None => None,
+				};
+
+				let value = if c.convtype == ConvTypeV::Percent {
+					Val::Null
+				} else {
+					if f.is_empty() {
+						throw!(MappingKeysRequired);
+					}
+					if let Some(v) = values.get(s.clone(), f.clone())? {
+						v
+					} else {
+						throw!(NoSuchFormatField(f));
+					}
+				};
+
+				format_code(s.clone(), &mut out, &value, &c, width, precision)?;
+			}
+		}
+	}
+
+	Ok(out)
+}
+
+#[cfg(test)]
+pub mod test_format {
+	use super::*;
+
+	#[test]
+	fn parse() {
+		assert_eq!(
+			parse_codes(
+				"How much error budget is left looking at our %.3f%% availability gurantees?"
+			)
+			.unwrap()
+			.len(),
+			4
+		);
+	}
+
+	#[test]
+	fn octals() {
+		let s = State::default();
+		assert_eq!(
+			format_arr(s.clone(), "%#o", &[Val::Num(8.0)]).unwrap(),
+			"010"
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%#4o", &[Val::Num(8.0)]).unwrap(),
+			" 010"
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%4o", &[Val::Num(8.0)]).unwrap(),
+			"  10"
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%04o", &[Val::Num(8.0)]).unwrap(),
+			"0010"
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%+4o", &[Val::Num(8.0)]).unwrap(),
+			" +10"
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%+04o", &[Val::Num(8.0)]).unwrap(),
+			"+010"
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%-4o", &[Val::Num(8.0)]).unwrap(),
+			"10  "
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%+-4o", &[Val::Num(8.0)]).unwrap(),
+			"+10 "
+		);
+		assert_eq!(
+			format_arr(s.clone(), "%+-04o", &[Val::Num(8.0)]).unwrap(),
+			"+10 "
+		);
+	}
+
+	#[test]
+	fn percent_doesnt_consumes_values() {
+		let s = State::default();
+		assert_eq!(
+			format_arr(
+				s,
+				"How much error budget is left looking at our %.3f%% availability gurantees?",
+				&[Val::Num(4.0)]
+			)
+			.unwrap(),
+			"How much error budget is left looking at our 4.000% availability gurantees?"
+		);
+	}
+}
addedcrates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
@@ -0,0 +1,347 @@
+use crate::{
+	error::{Error::*, Result},
+	throw, State, Val,
+};
+
+#[derive(PartialEq, Clone, Copy)]
+pub enum ManifestType {
+	// Applied in manifestification
+	Manifest,
+	/// Used for std.manifestJson
+	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest
+	Std,
+	/// No line breaks, used in `obj+''`
+	ToString,
+	/// Minified json
+	Minify,
+}
+
+pub struct ManifestJsonOptions<'s> {
+	pub padding: &'s str,
+	pub mtype: ManifestType,
+	pub newline: &'s str,
+	pub key_val_sep: &'s str,
+	#[cfg(feature = "exp-preserve-order")]
+	pub preserve_order: bool,
+}
+
+pub fn manifest_json_ex(s: State, val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {
+	let mut out = String::new();
+	manifest_json_ex_buf(s, val, &mut out, &mut String::new(), options)?;
+	Ok(out)
+}
+fn manifest_json_ex_buf(
+	s: State,
+	val: &Val,
+	buf: &mut String,
+	cur_padding: &mut String,
+	options: &ManifestJsonOptions<'_>,
+) -> Result<()> {
+	use std::fmt::Write;
+	let mtype = options.mtype;
+	match val {
+		Val::Bool(v) => {
+			if *v {
+				buf.push_str("true");
+			} else {
+				buf.push_str("false");
+			}
+		}
+		Val::Null => buf.push_str("null"),
+		Val::Str(s) => escape_string_json_buf(s, buf),
+		Val::Num(n) => write!(buf, "{}", n).unwrap(),
+		Val::Arr(items) => {
+			buf.push('[');
+			if !items.is_empty() {
+				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+					buf.push_str(options.newline);
+				}
+
+				let old_len = cur_padding.len();
+				cur_padding.push_str(options.padding);
+				for (i, item) in items.iter(s.clone()).enumerate() {
+					if i != 0 {
+						buf.push(',');
+						if mtype == ManifestType::ToString {
+							buf.push(' ');
+						} else if mtype != ManifestType::Minify {
+							buf.push_str(options.newline);
+						}
+					}
+					buf.push_str(cur_padding);
+					manifest_json_ex_buf(s.clone(), &item?, buf, cur_padding, options)?;
+				}
+				cur_padding.truncate(old_len);
+
+				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+					buf.push_str(options.newline);
+					buf.push_str(cur_padding);
+				}
+			} else if mtype == ManifestType::Std {
+				buf.push_str("\n\n");
+				buf.push_str(cur_padding);
+			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
+				buf.push(' ');
+			}
+			buf.push(']');
+		}
+		Val::Obj(obj) => {
+			obj.run_assertions(s.clone())?;
+			buf.push('{');
+			let fields = obj.fields(
+				#[cfg(feature = "exp-preserve-order")]
+				options.preserve_order,
+			);
+			if !fields.is_empty() {
+				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+					buf.push_str(options.newline);
+				}
+
+				let old_len = cur_padding.len();
+				cur_padding.push_str(options.padding);
+				for (i, field) in fields.into_iter().enumerate() {
+					if i != 0 {
+						buf.push(',');
+						if mtype == ManifestType::ToString {
+							buf.push(' ');
+						} else if mtype != ManifestType::Minify {
+							buf.push_str(options.newline);
+						}
+					}
+					buf.push_str(cur_padding);
+					escape_string_json_buf(&field, buf);
+					buf.push_str(options.key_val_sep);
+					s.push_description(
+						|| format!("field <{}> manifestification", field.clone()),
+						|| {
+							let value = obj.get(s.clone(), field.clone())?.unwrap();
+							manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;
+							Ok(Val::Null)
+						},
+					)?;
+				}
+				cur_padding.truncate(old_len);
+
+				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+					buf.push_str(options.newline);
+					buf.push_str(cur_padding);
+				}
+			} else if mtype == ManifestType::Std {
+				buf.push_str("\n\n");
+				buf.push_str(cur_padding);
+			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
+				buf.push(' ');
+			}
+			buf.push('}');
+		}
+		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
+	};
+	Ok(())
+}
+
+pub fn escape_string_json(s: &str) -> String {
+	let mut buf = String::new();
+	escape_string_json_buf(s, &mut buf);
+	buf
+}
+
+fn escape_string_json_buf(s: &str, buf: &mut String) {
+	use std::fmt::Write;
+	buf.push('"');
+	for c in s.chars() {
+		match c {
+			'"' => buf.push_str("\\\""),
+			'\\' => buf.push_str("\\\\"),
+			'\u{0008}' => buf.push_str("\\b"),
+			'\u{000c}' => buf.push_str("\\f"),
+			'\n' => buf.push_str("\\n"),
+			'\r' => buf.push_str("\\r"),
+			'\t' => buf.push_str("\\t"),
+			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {
+				write!(buf, "\\u{:04x}", c as u32).unwrap();
+			}
+			c => buf.push(c),
+		}
+	}
+	buf.push('"');
+}
+
+pub struct ManifestYamlOptions<'s> {
+	/// Padding before fields, i.e
+	/// ```yaml
+	/// a:
+	///   b:
+	/// ## <- this
+	/// ```
+	pub padding: &'s str,
+	/// Padding before array elements in objects
+	/// ```yaml
+	/// a:
+	///   - 1
+	/// ## <- this
+	/// ```
+	pub arr_element_padding: &'s str,
+	/// Should yaml keys appear unescaped, when possible
+	/// ```yaml
+	/// "safe_key": 1
+	/// # vs
+	/// safe_key: 1
+	/// ```
+	pub quote_keys: bool,
+	/// If true - then order of fields is preserved as written,
+	/// instead of sorting alphabetically
+	#[cfg(feature = "exp-preserve-order")]
+	pub preserve_order: bool,
+}
+
+/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>
+/// With added date check
+fn yaml_needs_quotes(string: &str) -> bool {
+	fn need_quotes_spaces(string: &str) -> bool {
+		string.starts_with(' ') || string.ends_with(' ')
+	}
+
+	string.is_empty()
+		|| need_quotes_spaces(string)
+		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))
+		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))
+		|| [
+			// http://yaml.org/type/bool.html
+			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
+			// them as string, not booleans, although it is violating the YAML 1.1 specification.
+			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.
+			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",
+			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html
+			"null", "Null", "NULL", "~",
+		].contains(&string)
+		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))
+			&& string.chars().filter(|c| *c == '-').count() == 2)
+		|| string.starts_with('.')
+		|| string.starts_with("0x")
+		|| string.parse::<i64>().is_ok()
+		|| string.parse::<f64>().is_ok()
+}
+
+pub fn manifest_yaml_ex(s: State, val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
+	let mut out = String::new();
+	manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?;
+	Ok(out)
+}
+
+#[allow(clippy::too_many_lines)]
+fn manifest_yaml_ex_buf(
+	s: State,
+	val: &Val,
+	buf: &mut String,
+	cur_padding: &mut String,
+	options: &ManifestYamlOptions<'_>,
+) -> Result<()> {
+	use std::fmt::Write;
+	match val {
+		Val::Bool(v) => {
+			if *v {
+				buf.push_str("true");
+			} else {
+				buf.push_str("false");
+			}
+		}
+		Val::Null => buf.push_str("null"),
+		Val::Str(s) => {
+			if s.is_empty() {
+				buf.push_str("\"\"");
+			} else if let Some(s) = s.strip_suffix('\n') {
+				buf.push('|');
+				for line in s.split('\n') {
+					buf.push('\n');
+					buf.push_str(cur_padding);
+					buf.push_str(options.padding);
+					buf.push_str(line);
+				}
+			} else if !options.quote_keys && !yaml_needs_quotes(s) {
+				buf.push_str(s);
+			} else {
+				escape_string_json_buf(s, buf);
+			}
+		}
+		Val::Num(n) => write!(buf, "{}", *n).unwrap(),
+		Val::Arr(a) => {
+			if a.is_empty() {
+				buf.push_str("[]");
+			} else {
+				for (i, item) in a.iter(s.clone()).enumerate() {
+					if i != 0 {
+						buf.push('\n');
+						buf.push_str(cur_padding);
+					}
+					let item = item?;
+					buf.push('-');
+					match &item {
+						Val::Arr(a) if !a.is_empty() => {
+							buf.push('\n');
+							buf.push_str(cur_padding);
+							buf.push_str(options.padding);
+						}
+						_ => buf.push(' '),
+					}
+					let extra_padding = match &item {
+						Val::Arr(a) => !a.is_empty(),
+						Val::Obj(o) => !o.is_empty(),
+						_ => false,
+					};
+					let prev_len = cur_padding.len();
+					if extra_padding {
+						cur_padding.push_str(options.padding);
+					}
+					manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
+					cur_padding.truncate(prev_len);
+				}
+			}
+		}
+		Val::Obj(o) => {
+			if o.is_empty() {
+				buf.push_str("{}");
+			} else {
+				for (i, key) in o
+					.fields(
+						#[cfg(feature = "exp-preserve-order")]
+						options.preserve_order,
+					)
+					.iter()
+					.enumerate()
+				{
+					if i != 0 {
+						buf.push('\n');
+						buf.push_str(cur_padding);
+					}
+					if !options.quote_keys && !yaml_needs_quotes(key) {
+						buf.push_str(key);
+					} else {
+						escape_string_json_buf(key, buf);
+					}
+					buf.push(':');
+					let prev_len = cur_padding.len();
+					let item = o.get(s.clone(), key.clone())?.expect("field exists");
+					match &item {
+						Val::Arr(a) if !a.is_empty() => {
+							buf.push('\n');
+							buf.push_str(cur_padding);
+							buf.push_str(options.arr_element_padding);
+							cur_padding.push_str(options.arr_element_padding);
+						}
+						Val::Obj(o) if !o.is_empty() => {
+							buf.push('\n');
+							buf.push_str(cur_padding);
+							buf.push_str(options.padding);
+							cur_padding.push_str(options.padding);
+						}
+						_ => buf.push(' '),
+					}
+					manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
+					cur_padding.truncate(prev_len);
+				}
+			}
+		}
+		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
+	}
+	Ok(())
+}
addedcrates/jrsonnet-evaluator/src/stdlib/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs
@@ -0,0 +1,743 @@
+// All builtins should return results
+#![allow(clippy::unnecessary_wraps)]
+
+use std::collections::HashMap;
+
+use format::{format_arr, format_obj};
+use gcmodule::Cc;
+use jrsonnet_interner::IStr;
+use serde::Deserialize;
+use serde_yaml::DeserializingQuirks;
+
+use crate::{
+	error::{Error::*, Result},
+	function::{builtin::StaticBuiltin, CallLocation, FuncVal},
+	operator::evaluate_mod_op,
+	stdlib::manifest::{manifest_yaml_ex, ManifestYamlOptions},
+	throw,
+	typed::{Any, BoundedUsize, Bytes, Either2, Either4, PositiveF64, Typed, VecVal, M1},
+	val::{equals, primitive_equals, ArrValue, IndexableVal, Slice},
+	Either, ObjValue, State, Val,
+};
+
+pub mod expr;
+pub use expr::*;
+
+use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
+
+pub mod format;
+pub mod manifest;
+pub mod sort;
+
+pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
+	s.push(
+		CallLocation::native(),
+		|| format!("std.format of {}", str),
+		|| {
+			Ok(match vals {
+				Val::Arr(vals) => format_arr(s.clone(), &str, &vals.evaluated(s.clone())?)?,
+				Val::Obj(obj) => format_obj(s.clone(), &str, &obj)?,
+				o => format_arr(s.clone(), &str, &[o])?,
+			})
+		},
+	)
+}
+
+pub fn std_slice(
+	indexable: IndexableVal,
+	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+) -> Result<Val> {
+	match &indexable {
+		IndexableVal::Str(s) => {
+			let index = index.as_deref().copied().unwrap_or(0);
+			let end = end.as_deref().copied().unwrap_or(usize::MAX);
+			let step = step.as_deref().copied().unwrap_or(1);
+
+			if index >= end {
+				return Ok(Val::Str("".into()));
+			}
+
+			Ok(Val::Str(
+				(s.chars()
+					.skip(index)
+					.take(end - index)
+					.step_by(step)
+					.collect::<String>())
+				.into(),
+			))
+		}
+		IndexableVal::Arr(arr) => {
+			let index = index.as_deref().copied().unwrap_or(0);
+			let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
+			let step = step.as_deref().copied().unwrap_or(1);
+
+			if index >= end {
+				return Ok(Val::Arr(ArrValue::new_eager()));
+			}
+
+			Ok(Val::Arr(ArrValue::Slice(Box::new(Slice {
+				inner: arr.clone(),
+				from: index as u32,
+				to: end as u32,
+				step: step as u32,
+			}))))
+		}
+	}
+}
+
+type BuiltinsType = HashMap<IStr, &'static dyn StaticBuiltin>;
+
+thread_local! {
+	pub static BUILTINS: BuiltinsType = {
+		[
+			("length".into(), builtin_length::INST),
+			("type".into(), builtin_type::INST),
+			("makeArray".into(), builtin_make_array::INST),
+			("codepoint".into(), builtin_codepoint::INST),
+			("objectFieldsEx".into(), builtin_object_fields_ex::INST),
+			("objectHasEx".into(), builtin_object_has_ex::INST),
+			("slice".into(), builtin_slice::INST),
+			("substr".into(), builtin_substr::INST),
+			("primitiveEquals".into(), builtin_primitive_equals::INST),
+			("equals".into(), builtin_equals::INST),
+			("modulo".into(), builtin_modulo::INST),
+			("mod".into(), builtin_mod::INST),
+			("floor".into(), builtin_floor::INST),
+			("ceil".into(), builtin_ceil::INST),
+			("log".into(), builtin_log::INST),
+			("pow".into(), builtin_pow::INST),
+			("sqrt".into(), builtin_sqrt::INST),
+			("sin".into(), builtin_sin::INST),
+			("cos".into(), builtin_cos::INST),
+			("tan".into(), builtin_tan::INST),
+			("asin".into(), builtin_asin::INST),
+			("acos".into(), builtin_acos::INST),
+			("atan".into(), builtin_atan::INST),
+			("exp".into(), builtin_exp::INST),
+			("mantissa".into(), builtin_mantissa::INST),
+			("exponent".into(), builtin_exponent::INST),
+			("extVar".into(), builtin_ext_var::INST),
+			("native".into(), builtin_native::INST),
+			("filter".into(), builtin_filter::INST),
+			("map".into(), builtin_map::INST),
+			("flatMap".into(), builtin_flatmap::INST),
+			("foldl".into(), builtin_foldl::INST),
+			("foldr".into(), builtin_foldr::INST),
+			("sort".into(), builtin_sort::INST),
+			("format".into(), builtin_format::INST),
+			("range".into(), builtin_range::INST),
+			("char".into(), builtin_char::INST),
+			("encodeUTF8".into(), builtin_encode_utf8::INST),
+			("decodeUTF8".into(), builtin_decode_utf8::INST),
+			("md5".into(), builtin_md5::INST),
+			("base64".into(), builtin_base64::INST),
+			("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST),
+			("base64Decode".into(), builtin_base64_decode::INST),
+			("trace".into(), builtin_trace::INST),
+			("join".into(), builtin_join::INST),
+			("escapeStringJson".into(), builtin_escape_string_json::INST),
+			("manifestJsonEx".into(), builtin_manifest_json_ex::INST),
+			("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST),
+			("reverse".into(), builtin_reverse::INST),
+			("strReplace".into(), builtin_str_replace::INST),
+			("splitLimit".into(), builtin_splitlimit::INST),
+			("parseJson".into(), builtin_parse_json::INST),
+			("parseYaml".into(), builtin_parse_yaml::INST),
+			("asciiUpper".into(), builtin_ascii_upper::INST),
+			("asciiLower".into(), builtin_ascii_lower::INST),
+			("member".into(), builtin_member::INST),
+			("count".into(), builtin_count::INST),
+			("any".into(), builtin_any::INST),
+			("all".into(), builtin_all::INST),
+		].iter().cloned().collect()
+	};
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
+	use Either4::*;
+	Ok(match x {
+		A(x) => x.chars().count(),
+		B(x) => x.len(),
+		C(x) => x.len(),
+		D(f) => f.params_len(),
+	})
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_type(x: Any) -> Result<IStr> {
+	Ok(x.0.value_type().name().into())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
+	let mut out = Vec::with_capacity(sz);
+	for i in 0..sz {
+		out.push(func.evaluate_simple(s.clone(), &(i as f64,))?);
+	}
+	Ok(VecVal(Cc::new(out)))
+}
+
+#[jrsonnet_macros::builtin]
+const fn builtin_codepoint(str: char) -> Result<u32> {
+	Ok(str as u32)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_object_fields_ex(
+	obj: ObjValue,
+	inc_hidden: bool,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<VecVal> {
+	#[cfg(feature = "exp-preserve-order")]
+	let preserve_order = preserve_order.unwrap_or(false);
+	let out = obj.fields_ex(
+		inc_hidden,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	);
+	Ok(VecVal(Cc::new(
+		out.into_iter().map(Val::Str).collect::<Vec<_>>(),
+	)))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
+	Ok(obj.has_field_ex(f, inc_hidden))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
+	use serde_json::Value;
+	let value: Value = serde_json::from_str(&s)
+		.map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
+	Ok(Any(Value::into_untyped(value, st)?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
+	use serde_json::Value;
+	let value = serde_yaml::Deserializer::from_str_with_quirks(
+		&s,
+		DeserializingQuirks { old_octals: true },
+	);
+	let mut out = vec![];
+	for item in value {
+		let value = Value::deserialize(item)
+			.map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
+		let val = Value::into_untyped(value, st.clone())?;
+		out.push(val);
+	}
+	Ok(Any(if out.is_empty() {
+		Val::Null
+	} else if out.len() == 1 {
+		out.into_iter().next().unwrap()
+	} else {
+		Val::Arr(out.into())
+	}))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_slice(
+	indexable: IndexableVal,
+	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+) -> Result<Any> {
+	std_slice(indexable, index, end, step).map(Any)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
+	Ok(str.chars().skip(from as usize).take(len as usize).collect())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
+	primitive_equals(&a.0, &b.0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
+	equals(s, &a.0, &b.0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
+	Ok(a % b)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
+	use Either2::*;
+	Ok(Any(evaluate_mod_op(
+		s,
+		&match a {
+			A(v) => Val::Num(v),
+			B(s) => Val::Str(s),
+		},
+		&b.0,
+	)?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_floor(x: f64) -> Result<f64> {
+	Ok(x.floor())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ceil(x: f64) -> Result<f64> {
+	Ok(x.ceil())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_log(n: f64) -> Result<f64> {
+	Ok(n.ln())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_pow(x: f64, n: f64) -> Result<f64> {
+	Ok(x.powf(n))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
+	Ok(x.0.sqrt())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_sin(x: f64) -> Result<f64> {
+	Ok(x.sin())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_cos(x: f64) -> Result<f64> {
+	Ok(x.cos())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_tan(x: f64) -> Result<f64> {
+	Ok(x.tan())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_asin(x: f64) -> Result<f64> {
+	Ok(x.asin())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_acos(x: f64) -> Result<f64> {
+	Ok(x.acos())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_atan(x: f64) -> Result<f64> {
+	Ok(x.atan())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_exp(x: f64) -> Result<f64> {
+	Ok(x.exp())
+}
+
+fn frexp(s: f64) -> (f64, i16) {
+	if 0.0 == s {
+		(s, 0)
+	} else {
+		let lg = s.abs().log2();
+		let x = (lg - lg.floor() - 1.0).exp2();
+		let exp = lg.floor() + 1.0;
+		(s.signum() * x, exp as i16)
+	}
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_mantissa(x: f64) -> Result<f64> {
+	Ok(frexp(x).0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_exponent(x: f64) -> Result<i16> {
+	Ok(frexp(x).1)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ext_var(s: State, x: IStr) -> Result<Any> {
+	Ok(Any(s
+		.settings()
+		.ext_vars
+		.get(&x)
+		.cloned()
+		.ok_or(UndefinedExternalVariable(x))?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_native(s: State, name: IStr) -> Result<Any> {
+	Ok(Any(s
+		.settings()
+		.ext_natives
+		.get(&name)
+		.cloned()
+		.map_or(Val::Null, |v| {
+			Val::Func(FuncVal::Builtin(v.clone()))
+		})))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+	arr.filter(s.clone(), |val| {
+		bool::from_untyped(
+			func.evaluate_simple(s.clone(), &(Any(val.clone()),))?,
+			s.clone(),
+		)
+	})
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+	arr.map(s.clone(), |val| {
+		func.evaluate_simple(s.clone(), &(Any(val),))
+	})
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
+	match arr {
+		IndexableVal::Str(str) => {
+			let mut out = String::new();
+			for c in str.chars() {
+				match func.evaluate_simple(s.clone(), &(c.to_string(),))? {
+					Val::Str(o) => out.push_str(&o),
+					Val::Null => continue,
+					_ => throw!(RuntimeError(
+						"in std.join all items should be strings".into()
+					)),
+				};
+			}
+			Ok(IndexableVal::Str(out.into()))
+		}
+		IndexableVal::Arr(a) => {
+			let mut out = Vec::new();
+			for el in a.iter(s.clone()) {
+				let el = el?;
+				match func.evaluate_simple(s.clone(), &(Any(el),))? {
+					Val::Arr(o) => {
+						for oe in o.iter(s.clone()) {
+							out.push(oe?);
+						}
+					}
+					Val::Null => continue,
+					_ => throw!(RuntimeError(
+						"in std.join all items should be arrays".into()
+					)),
+				};
+			}
+			Ok(IndexableVal::Arr(out.into()))
+		}
+	}
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+	let mut acc = init.0;
+	for i in arr.iter(s.clone()) {
+		acc = func.evaluate_simple(s.clone(), &(Any(acc), Any(i?)))?;
+	}
+	Ok(Any(acc))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+	let mut acc = init.0;
+	for i in arr.iter(s.clone()).rev() {
+		acc = func.evaluate_simple(s.clone(), &(Any(i?), Any(acc)))?;
+	}
+	Ok(Any(acc))
+}
+
+#[jrsonnet_macros::builtin]
+#[allow(non_snake_case)]
+fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+	if arr.len() <= 1 {
+		return Ok(arr);
+	}
+	Ok(ArrValue::Eager(sort::sort(
+		s.clone(),
+		arr.evaluated(s)?,
+		keyF.unwrap_or(FuncVal::identity()),
+	)?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
+	std_format(s, str, vals.0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
+	if to < from {
+		return Ok(ArrValue::new_eager());
+	}
+	Ok(ArrValue::new_range(from, to))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_char(n: u32) -> Result<char> {
+	Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_encode_utf8(str: IStr) -> Result<Bytes> {
+	Ok(Bytes(str.bytes().collect::<Vec<u8>>().into()))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_decode_utf8(arr: Bytes) -> Result<IStr> {
+	Ok(std::str::from_utf8(&arr.0)
+		.map_err(|_| RuntimeError("bad utf8".into()))?
+		.into())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_md5(str: IStr) -> Result<String> {
+	Ok(format!("{:x}", md5::compute(&str.as_bytes())))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
+	eprint!("TRACE:");
+	if let Some(loc) = loc.0 {
+		let locs = s.map_source_locations(&loc.0, &[loc.1]);
+		eprint!(
+			" {}:{}",
+			loc.0.file_name().unwrap().to_str().unwrap(),
+			locs[0].line
+		);
+	}
+	eprintln!(" {}", str);
+	Ok(rest) as Result<Any>
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_base64(input: Either![Bytes, IStr]) -> Result<String> {
+	use Either2::*;
+	Ok(match input {
+		A(a) => base64::encode(a.0),
+		B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
+	})
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_base64_decode_bytes(input: IStr) -> Result<Bytes> {
+	Ok(Bytes(
+		base64::decode(&input.as_bytes())
+			.map_err(|_| RuntimeError("bad base64".into()))?
+			.into(),
+	))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_base64_decode(input: IStr) -> Result<String> {
+	let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
+	Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
+	Ok(match sep {
+		IndexableVal::Arr(joiner_items) => {
+			let mut out = Vec::new();
+
+			let mut first = true;
+			for item in arr.iter(s.clone()) {
+				let item = item?.clone();
+				if let Val::Arr(items) = item {
+					if !first {
+						out.reserve(joiner_items.len());
+						// TODO: extend
+						for item in joiner_items.iter(s.clone()) {
+							out.push(item?);
+						}
+					}
+					first = false;
+					out.reserve(items.len());
+					for item in items.iter(s.clone()) {
+						out.push(item?);
+					}
+				} else if matches!(item, Val::Null) {
+					continue;
+				} else {
+					throw!(RuntimeError(
+						"in std.join all items should be arrays".into()
+					));
+				}
+			}
+
+			IndexableVal::Arr(out.into())
+		}
+		IndexableVal::Str(sep) => {
+			let mut out = String::new();
+
+			let mut first = true;
+			for item in arr.iter(s) {
+				let item = item?.clone();
+				if let Val::Str(item) = item {
+					if !first {
+						out += &sep;
+					}
+					first = false;
+					out += &item;
+				} else if matches!(item, Val::Null) {
+					continue;
+				} else {
+					throw!(RuntimeError(
+						"in std.join all items should be strings".into()
+					));
+				}
+			}
+
+			IndexableVal::Str(out.into())
+		}
+	})
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_escape_string_json(str_: IStr) -> Result<String> {
+	Ok(escape_string_json(&str_))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_manifest_json_ex(
+	s: State,
+	value: Any,
+	indent: IStr,
+	newline: Option<IStr>,
+	key_val_sep: Option<IStr>,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+	let newline = newline.as_deref().unwrap_or("\n");
+	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
+	manifest_json_ex(
+		s,
+		&value.0,
+		&ManifestJsonOptions {
+			padding: &indent,
+			mtype: ManifestType::Std,
+			newline,
+			key_val_sep,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: preserve_order.unwrap_or(false),
+		},
+	)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_manifest_yaml_doc(
+	s: State,
+	value: Any,
+	indent_array_in_object: Option<bool>,
+	quote_keys: Option<bool>,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+	manifest_yaml_ex(
+		s,
+		&value.0,
+		&ManifestYamlOptions {
+			padding: "  ",
+			arr_element_padding: if indent_array_in_object.unwrap_or(false) {
+				"  "
+			} else {
+				""
+			},
+			quote_keys: quote_keys.unwrap_or(true),
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: preserve_order.unwrap_or(false),
+		},
+	)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
+	Ok(value.reversed())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
+	Ok(str.replace(&from as &str, &to as &str))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
+	use Either2::*;
+	Ok(VecVal(Cc::new(match maxsplits {
+		A(n) => str
+			.splitn(n + 1, &c as &str)
+			.map(|s| Val::Str(s.into()))
+			.collect(),
+		B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
+	})))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ascii_upper(str: IStr) -> Result<String> {
+	Ok(str.to_ascii_uppercase())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ascii_lower(str: IStr) -> Result<String> {
+	Ok(str.to_ascii_lowercase())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
+	match arr {
+		IndexableVal::Str(str) => {
+			let x: IStr = IStr::from_untyped(x.0, s)?;
+			Ok(!x.is_empty() && str.contains(&*x))
+		}
+		IndexableVal::Arr(a) => {
+			for item in a.iter(s.clone()) {
+				let item = item?;
+				if equals(s.clone(), &item, &x.0)? {
+					return Ok(true);
+				}
+			}
+			Ok(false)
+		}
+	}
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
+	let mut count = 0;
+	for item in &arr {
+		if equals(s.clone(), &item.0, &v.0)? {
+			count += 1;
+		}
+	}
+	Ok(count)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
+	for v in arr.iter(s.clone()) {
+		let v = bool::from_untyped(v?, s.clone())?;
+		if v {
+			return Ok(true);
+		}
+	}
+	Ok(false)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
+	for v in arr.iter(s.clone()) {
+		let v = bool::from_untyped(v?, s.clone())?;
+		if !v {
+			return Ok(false);
+		}
+	}
+	Ok(true)
+}
addedcrates/jrsonnet-evaluator/src/stdlib/sort.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/sort.rs
@@ -0,0 +1,110 @@
+use gcmodule::{Cc, Trace};
+
+use crate::{
+	error::{Error, LocError, Result},
+	function::FuncVal,
+	throw,
+	typed::Any,
+	State, Val,
+};
+
+#[derive(Debug, Clone, thiserror::Error, Trace)]
+pub enum SortError {
+	#[error("sort key should be string or number")]
+	SortKeyShouldBeStringOrNumber,
+	#[error("sort elements should have equal types")]
+	SortElementsShouldHaveEqualType,
+}
+
+impl From<SortError> for LocError {
+	fn from(s: SortError) -> Self {
+		Self::new(Error::Sort(s))
+	}
+}
+
+#[derive(Copy, Clone)]
+enum SortKeyType {
+	Number,
+	String,
+	Unknown,
+}
+
+#[derive(PartialEq)]
+struct NonNaNf64(f64);
+impl PartialOrd for NonNaNf64 {
+	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+		self.0.partial_cmp(&other.0)
+	}
+}
+impl Eq for NonNaNf64 {}
+impl Ord for NonNaNf64 {
+	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+		self.partial_cmp(other).expect("non nan")
+	}
+}
+
+fn get_sort_type<T>(
+	values: &mut Vec<T>,
+	key_getter: impl Fn(&mut T) -> &mut Val,
+) -> Result<SortKeyType> {
+	let mut sort_type = SortKeyType::Unknown;
+	for i in values.iter_mut() {
+		let i = key_getter(i);
+		match (i, sort_type) {
+			(Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
+			(Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
+			(Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
+			(Val::Str(_) | Val::Num(_), _) => {
+				throw!(SortError::SortElementsShouldHaveEqualType)
+			}
+			_ => throw!(SortError::SortKeyShouldBeStringOrNumber),
+		}
+	}
+	Ok(sort_type)
+}
+
+/// * `key_getter` - None, if identity sort required
+pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: FuncVal) -> Result<Cc<Vec<Val>>> {
+	if values.len() <= 1 {
+		return Ok(values);
+	}
+	if !key_getter.is_identity() {
+		// Slow path, user provided key getter
+		let mut vk = Vec::with_capacity(values.len());
+		for value in values.iter() {
+			vk.push((
+				value.clone(),
+				key_getter.evaluate_simple(s.clone(), &(Any(value.clone()),))?,
+			));
+		}
+		let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
+		match sort_type {
+			SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
+				Val::Num(n) => NonNaNf64(n),
+				_ => unreachable!(),
+			}),
+			SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
+				Val::Str(s) => s.clone(),
+				_ => unreachable!(),
+			}),
+			SortKeyType::Unknown => unreachable!(),
+		};
+		Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
+	} else {
+		// Fast path, identity key getter
+		let mut values = (*values).clone();
+		let sort_type = get_sort_type(&mut values, |k| k)?;
+		match sort_type {
+			SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
+				Val::Num(n) => NonNaNf64(*n),
+				_ => unreachable!(),
+			}),
+			SortKeyType::String => values.sort_unstable_by_key(|v| match v {
+				Val::Str(s) => s.clone(),
+				_ => unreachable!(),
+			}),
+			SortKeyType::Unknown => unreachable!(),
+		};
+		Ok(Cc::new(values))
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -7,9 +7,10 @@
 
 use crate::{
 	error::{Error::*, Result},
+	function::{FuncDesc, FuncVal},
 	throw,
 	typed::CheckType,
-	val::{ArrValue, FuncDesc, FuncVal, IndexableVal},
+	val::{ArrValue, IndexableVal},
 	ObjValue, ObjValueBuilder, State, Val,
 };
 
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,22 +2,17 @@
 
 use gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{LocExpr, ParamsDesc};
 use jrsonnet_types::ValType;
 
 use crate::{
-	builtin::manifest::{
-		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
-	},
 	cc_ptr_eq,
 	error::{Error::*, LocError},
-	evaluate,
-	function::{
-		parse_default_function_call, parse_function_call, ArgsLike, Builtin, CallLocation,
-		StaticBuiltin,
+	function::FuncVal,
+	gc::TraceBox,
+	stdlib::manifest::{
+		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
 	},
-	gc::TraceBox,
-	throw, Context, ObjValue, Result, State,
+	throw, ObjValue, Result, State,
 };
 
 pub trait LazyValValue: Trace {
@@ -80,98 +75,6 @@
 impl PartialEq for LazyVal {
 	fn eq(&self, other: &Self) -> bool {
 		cc_ptr_eq(&self.0, &other.0)
-	}
-}
-
-#[derive(Debug, PartialEq, Trace)]
-pub struct FuncDesc {
-	pub name: IStr,
-	pub ctx: Context,
-	pub params: ParamsDesc,
-	pub body: LocExpr,
-}
-impl FuncDesc {
-	/// Create body context, but fill arguments without defaults with lazy error
-	pub fn default_body_context(&self) -> Context {
-		parse_default_function_call(self.ctx.clone(), &self.params)
-	}
-
-	/// Create context, with which body code will run
-	pub fn call_body_context(
-		&self,
-		s: State,
-		call_ctx: Context,
-		args: &dyn ArgsLike,
-		tailstrict: bool,
-	) -> Result<Context> {
-		parse_function_call(
-			s,
-			call_ctx,
-			self.ctx.clone(),
-			&self.params,
-			args,
-			tailstrict,
-		)
-	}
-}
-
-#[allow(clippy::module_name_repetitions)]
-#[derive(Trace, Clone)]
-pub enum FuncVal {
-	/// Plain function implemented in jsonnet
-	Normal(Cc<FuncDesc>),
-	/// Standard library function
-	StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),
-	/// User-provided function
-	Builtin(Cc<TraceBox<dyn Builtin>>),
-}
-
-impl Debug for FuncVal {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		match self {
-			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),
-			Self::StaticBuiltin(arg0) => {
-				f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()
-			}
-			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),
-		}
-	}
-}
-
-impl FuncVal {
-	pub fn args_len(&self) -> usize {
-		match self {
-			Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),
-			Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),
-			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),
-		}
-	}
-	pub fn name(&self) -> IStr {
-		match self {
-			Self::Normal(normal) => normal.name.clone(),
-			Self::StaticBuiltin(builtin) => builtin.name().into(),
-			Self::Builtin(builtin) => builtin.name().into(),
-		}
-	}
-	pub fn evaluate(
-		&self,
-		s: State,
-		call_ctx: Context,
-		loc: CallLocation,
-		args: &dyn ArgsLike,
-		tailstrict: bool,
-	) -> Result<Val> {
-		match self {
-			Self::Normal(func) => {
-				let body_ctx = func.call_body_context(s.clone(), call_ctx, args, tailstrict)?;
-				evaluate(s, body_ctx, &func.body)
-			}
-			Self::StaticBuiltin(b) => b.call(s, call_ctx, loc, args),
-			Self::Builtin(b) => b.call(s, call_ctx, loc, args),
-		}
-	}
-	pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {
-		self.evaluate(s, Context::default(), CallLocation::native(), args, true)
 	}
 }
 
addedcrates/jrsonnet-evaluator/tests/as_native.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/as_native.rs
@@ -0,0 +1,21 @@
+use std::path::PathBuf;
+
+use jrsonnet_evaluator::{error::Result, State};
+
+mod common;
+
+#[test]
+fn as_native() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	let val = s.evaluate_snippet_raw(PathBuf::new().into(), r#"function(a, b) a + b"#.into())?;
+	let func = val.as_func().expect("this is function");
+
+	let native = func.into_native::<((u32, u32), u32)>();
+
+	ensure_eq!(native(s.clone(), 1, 2)?, 3);
+	ensure_eq!(native(s, 3, 4)?, 7);
+
+	Ok(())
+}
modifiedcrates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/builtin.rs
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -5,10 +5,9 @@
 use gcmodule::Cc;
 use jrsonnet_evaluator::{
 	error::Result,
-	function::{builtin, Builtin, CallLocation},
+	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
 	gc::TraceBox,
 	typed::Typed,
-	val::FuncVal,
 	State, Val,
 };
 
@@ -26,7 +25,7 @@
 			s.clone(),
 			s.create_default_context(),
 			CallLocation::native(),
-			&[],
+			&(),
 		)?,
 		s.clone(),
 	)?;
modifiedcrates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/common.rs
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -1,17 +1,16 @@
 use jrsonnet_evaluator::{
-	error::Result, function::builtin, throw_runtime, val::FuncVal, LazyVal, ObjValueBuilder, State,
-	Val,
+	error::Result,
+	function::{builtin, FuncVal},
+	throw_runtime, LazyVal, ObjValueBuilder, State, Val,
 };
 
 #[macro_export]
 macro_rules! ensure_eq {
 	($a:expr, $b:expr $(,)?) => {{
-		if $a != $b {
-			::jrsonnet_evaluator::throw_runtime!(
-				"assertion failed: a != b\na={:#?}\nb={:#?}",
-				$a,
-				$b,
-			)
+		let a = &$a;
+		let b = &$b;
+		if a != b {
+			::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
 		}
 	}};
 }
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -349,7 +349,7 @@
 		const _: () = {
 			use ::jrsonnet_evaluator::{
 				State, Val,
-				function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
+				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam}, CallLocation, ArgsLike, parse::parse_builtin_call},
 				error::Result, Context, typed::Typed,
 				parser::ExprLocation,
 			};
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -298,6 +298,8 @@
 	Function(ParamsDesc, LocExpr),
 	/// std.thisFile
 	IntrinsicThisFile,
+	/// std.id,
+	IntrinsicId,
 	/// std.primitiveEquals
 	Intrinsic(IStr),
 	/// if true == false then 1 else 2
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-parser/src/lib.rs
1#![allow(clippy::redundant_closure_call)]23use std::{4	path::{Path, PathBuf},5	rc::Rc,6};78use peg::parser;9mod expr;10pub use expr::*;11pub use jrsonnet_interner::IStr;12pub use peg;13mod unescape;1415pub struct ParserSettings {16	pub file_name: Rc<Path>,17}1819macro_rules! expr_bin {20	($a:ident $op:ident $b:ident) => {21		Expr::BinaryOp($a, $op, $b)22	};23}24macro_rules! expr_un {25	($op:ident $a:ident) => {26		Expr::UnaryOp($op, $a)27	};28}2930parser! {31	grammar jsonnet_parser() for str {32		use peg::ParseLiteral;3334		rule eof() = quiet!{![_]} / expected!("<eof>")35		rule eol() = "\n" / eof()3637		/// Standard C-like comments38		rule comment()39			= "//" (!eol()[_])* eol()40			/ "/*" ("\\*/" / "\\\\" / (!("*/")[_]))* "*/"41			/ "#" (!eol()[_])* eol()4243		rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")44		rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")4546		/// For comma-delimited elements47		rule comma() = quiet!{_ "," _} / expected!("<comma>")48		rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}49		rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}50		rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']51		/// Sequence of digits52		rule uint_str() -> &'input str = a:$(digit()+) { a }53		/// Number in scientific notation format54		rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.parse().map_err(|_| "<number>") }} / expected!("<number>")5556		/// Reserved word followed by any non-alphanumberic57		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()58		rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")5960		rule keyword(id: &'static str) -> ()61			= ##parse_string_literal(id) end_of_ident()6263		pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) }64		pub rule params(s: &ParserSettings) -> expr::ParamsDesc65			= params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) }66			/ { expr::ParamsDesc(Rc::new(Vec::new())) }6768		pub rule arg(s: &ParserSettings) -> (Option<IStr>, LocExpr)69			= quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} }70			/ expected!("<argument>")7172		pub rule args(s: &ParserSettings) -> expr::ArgsDesc73			= args:arg(s)**comma() comma()? {?74				let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();75				let mut unnamed = Vec::with_capacity(unnamed_count);76				let mut named = Vec::with_capacity(args.len() - unnamed_count);77				let mut named_started = false;78				for (name, value) in args {79					if let Some(name) = name {80						named_started = true;81						named.push((name, value));82					} else {83						if named_started {84							return Err("<named argument>")85						}86						unnamed.push(value);87					}88				}89				Ok(expr::ArgsDesc::new(unnamed, named))90			}9192		pub rule bind(s: &ParserSettings) -> expr::BindSpec93			= name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}94			/ name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}}95		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt96			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }9798		pub rule whole_line() -> &'input str99			= str:$((!['\n'][_])* "\n") {str}100		pub rule string_block() -> String101			= "|||" (!['\n']single_whitespace())* "\n"102			  empty_lines:$(['\n']*)103			  prefix:[' ' | '\t']+ first_line:whole_line()104			  lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*105			  [' ' | '\t']*<, {prefix.len() - 1}> "|||"106			  {let mut l = empty_lines.to_owned(); l.push_str(first_line); l.extend(lines); l}107108		rule hex_char()109			= quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")110111		rule string_char(c: rule<()>)112			= (!['\\']!c()[_])+113			/ "\\\\"114			/ "\\u" hex_char() hex_char() hex_char() hex_char()115			/ "\\x" hex_char() hex_char()116			/ ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))117		pub rule string() -> String118			= ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}119			/ ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}120			/ quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}121			/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}122			/ string_block() } / expected!("<string>")123124		pub rule field_name(s: &ParserSettings) -> expr::FieldName125			= name:$(id()) {expr::FieldName::Fixed(name.into())}126			/ name:string() {expr::FieldName::Fixed(name.into())}127			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}128		pub rule visibility() -> expr::Visibility129			= ":::" {expr::Visibility::Unhide}130			/ "::" {expr::Visibility::Hidden}131			/ ":" {expr::Visibility::Normal}132		pub rule field(s: &ParserSettings) -> expr::FieldMember133			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{134				name,135				plus: plus.is_some(),136				params: None,137				visibility,138				value,139			}}140			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{141				name,142				plus: false,143				params: Some(params),144				visibility,145				value,146			}}147		pub rule obj_local(s: &ParserSettings) -> BindSpec148			= keyword("local") _ bind:bind(s) {bind}149		pub rule member(s: &ParserSettings) -> expr::Member150			= bind:obj_local(s) {expr::Member::BindStmt(bind)}151			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}152			/ field:field(s) {expr::Member::Field(field)}153		pub rule objinside(s: &ParserSettings) -> expr::ObjBody154			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {155				let mut compspecs = vec![CompSpec::ForSpec(forspec)];156				compspecs.extend(others.unwrap_or_default());157				expr::ObjBody::ObjComp(expr::ObjComp{158					pre_locals,159					key,160					plus: plus.is_some(),161					value,162					post_locals,163					compspecs,164				})165			}166			/ members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}167		pub rule ifspec(s: &ParserSettings) -> IfSpecData168			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}169		pub rule forspec(s: &ParserSettings) -> ForSpecData170			= keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)}171		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>172			= s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}173		pub rule local_expr(s: &ParserSettings) -> Expr174			= keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }175		pub rule string_expr(s: &ParserSettings) -> Expr176			= s:string() {Expr::Str(s.into())}177		pub rule obj_expr(s: &ParserSettings) -> Expr178			= "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}179		pub rule array_expr(s: &ParserSettings) -> Expr180			= "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}181		pub rule array_comp_expr(s: &ParserSettings) -> Expr182			= "[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {183				let mut specs = vec![CompSpec::ForSpec(forspec)];184				specs.extend(others.unwrap_or_default());185				Expr::ArrComp(expr, specs)186			}187		pub rule number_expr(s: &ParserSettings) -> Expr188			= n:number() { expr::Expr::Num(n) }189		pub rule var_expr(s: &ParserSettings) -> Expr190			= n:$(id()) { expr::Expr::Var(n.into()) }191		pub rule id_loc(s: &ParserSettings) -> LocExpr192			= a:position!() n:$(id()) b:position!() { LocExpr(Rc::new(expr::Expr::Str(n.into())), ExprLocation(s.file_name.clone(), a,b)) }193		pub rule if_then_else_expr(s: &ParserSettings) -> Expr194			= cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{195				cond,196				cond_then,197				cond_else,198			}}199200		pub rule literal(s: &ParserSettings) -> Expr201			= v:(202				keyword("null") {LiteralType::Null}203				/ keyword("true") {LiteralType::True}204				/ keyword("false") {LiteralType::False}205				/ keyword("self") {LiteralType::This}206				/ keyword("$") {LiteralType::Dollar}207				/ keyword("super") {LiteralType::Super}208			) {Expr::Literal(v)}209210		pub rule expr_basic(s: &ParserSettings) -> Expr211			= literal(s)212213			/ quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}214			/ quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}215216			/ string_expr(s) / number_expr(s)217			/ array_expr(s)218			/ obj_expr(s)219			/ array_expr(s)220			/ array_comp_expr(s)221222			/ keyword("importstr") _ path:string() {Expr::ImportStr(PathBuf::from(path))}223			/ keyword("importbin") _ path:string() {Expr::ImportBin(PathBuf::from(path))}224			/ keyword("import") _ path:string() {Expr::Import(PathBuf::from(path))}225226			/ var_expr(s)227			/ local_expr(s)228			/ if_then_else_expr(s)229230			/ keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}231			/ assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }232233			/ keyword("error") _ expr:expr(s) { Expr::ErrorStmt(expr) }234235		rule slice_part(s: &ParserSettings) -> Option<LocExpr>236			= _ e:(e:expr(s) _{e})? {e}237		pub rule slice_desc(s: &ParserSettings) -> SliceDesc238			= start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {239				let (end, step) = if let Some((end, step)) = pair {240					(end, step)241				}else{242					(None, None)243				};244245				SliceDesc { start, end, step }246			}247248		rule binop(x: rule<()>) -> ()249			= quiet!{ x() } / expected!("<binary op>")250		rule unaryop(x: rule<()>) -> ()251			= quiet!{ x() } / expected!("<unary op>")252253254		use BinaryOpType::*;255		use UnaryOpType::*;256		rule expr(s: &ParserSettings) -> LocExpr257			= precedence! {258				start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.file_name.clone(), start, end)) }259				--260				a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}261				--262				a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}263				--264				a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}265				--266				a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}267				--268				a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}269				--270				a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}271				a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}272				--273				a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}274				a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}275				a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}276				a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}277				a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}278				--279				a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}280				a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}281				--282				a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}283				a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}284				--285				a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}286				a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}287				a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}288				--289						unaryop(<"-">) _ b:@ {expr_un!(Minus b)}290						unaryop(<"!">) _ b:@ {expr_un!(Not b)}291						unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}292				--293				a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}294				a:(@) _ "." _ a:position!() e:id_loc(s) b:position!() {Expr::Index(a, e)}295				a:(@) _ "[" _ e:expr(s) _ "]" {Expr::Index(a, e)}296				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}297				a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}298				--299				e:expr_basic(s) {e}300				"(" _ e:expr(s) _ ")" {Expr::Parened(e)}301			}302303		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}304	}305}306307pub type ParseError = peg::error::ParseError<peg::str::LineCol>;308pub fn parse(str: &str, settings: &ParserSettings) -> Result<LocExpr, ParseError> {309	jsonnet_parser::jsonnet(str, settings)310}311/// Used for importstr values312pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> LocExpr {313	let len = str.len();314	LocExpr(315		Rc::new(Expr::Str(str)),316		ExprLocation(settings.file_name.clone(), 0, len),317	)318}319320#[cfg(test)]321pub mod tests {322	use std::path::PathBuf;323324	use BinaryOpType::*;325326	use super::{expr::*, parse};327	use crate::ParserSettings;328329	macro_rules! parse {330		($s:expr) => {331			parse(332				$s,333				&ParserSettings {334					file_name: PathBuf::from("test.jsonnet").into(),335				},336			)337			.unwrap()338		};339	}340341	macro_rules! el {342		($expr:expr, $from:expr, $to:expr$(,)?) => {343			LocExpr(344				std::rc::Rc::new($expr),345				ExprLocation(PathBuf::from("test.jsonnet").into(), $from, $to),346			)347		};348	}349350	#[test]351	fn multiline_string() {352		assert_eq!(353			parse!("|||\n    Hello world!\n     a\n|||"),354			el!(Expr::Str("Hello world!\n a\n".into()), 0, 31),355		);356		assert_eq!(357			parse!("|||\n  Hello world!\n   a\n|||"),358			el!(Expr::Str("Hello world!\n a\n".into()), 0, 27),359		);360		assert_eq!(361			parse!("|||\n\t\tHello world!\n\t\t\ta\n|||"),362			el!(Expr::Str("Hello world!\n\ta\n".into()), 0, 27),363		);364		assert_eq!(365			parse!("|||\n   Hello world!\n    a\n |||"),366			el!(Expr::Str("Hello world!\n a\n".into()), 0, 30),367		);368	}369370	#[test]371	fn slice() {372		parse!("a[1:]");373		parse!("a[1::]");374		parse!("a[:1:]");375		parse!("a[::1]");376		parse!("str[:len - 1]");377	}378379	#[test]380	fn string_escaping() {381		assert_eq!(382			parse!(r#""Hello, \"world\"!""#),383			el!(Expr::Str(r#"Hello, "world"!"#.into()), 0, 19),384		);385		assert_eq!(386			parse!(r#"'Hello \'world\'!'"#),387			el!(Expr::Str("Hello 'world'!".into()), 0, 18),388		);389		assert_eq!(parse!(r#"'\\\\'"#), el!(Expr::Str("\\\\".into()), 0, 6));390	}391392	#[test]393	fn string_unescaping() {394		assert_eq!(395			parse!(r#""Hello\nWorld""#),396			el!(Expr::Str("Hello\nWorld".into()), 0, 14),397		);398	}399400	#[test]401	fn string_verbantim() {402		assert_eq!(403			parse!(r#"@"Hello\n""World""""#),404			el!(Expr::Str("Hello\\n\"World\"".into()), 0, 19),405		);406	}407408	#[test]409	fn imports() {410		assert_eq!(411			parse!("import \"hello\""),412			el!(Expr::Import(PathBuf::from("hello")), 0, 14),413		);414		assert_eq!(415			parse!("importstr \"garnish.txt\""),416			el!(Expr::ImportStr(PathBuf::from("garnish.txt")), 0, 23)417		);418	}419420	#[test]421	fn empty_object() {422		assert_eq!(423			parse!("{}"),424			el!(Expr::Obj(ObjBody::MemberList(vec![])), 0, 2)425		);426	}427428	#[test]429	fn basic_math() {430		assert_eq!(431			parse!("2+2*2"),432			el!(433				Expr::BinaryOp(434					el!(Expr::Num(2.0), 0, 1),435					Add,436					el!(437						Expr::BinaryOp(el!(Expr::Num(2.0), 2, 3), Mul, el!(Expr::Num(2.0), 4, 5)),438						2,439						5440					)441				),442				0,443				5444			)445		);446	}447448	#[test]449	fn basic_math_with_indents() {450		assert_eq!(451			parse!("2	+ 	  2	  *	2   	"),452			el!(453				Expr::BinaryOp(454					el!(Expr::Num(2.0), 0, 1),455					Add,456					el!(457						Expr::BinaryOp(el!(Expr::Num(2.0), 7, 8), Mul, el!(Expr::Num(2.0), 13, 14),),458						7,459						14460					),461				),462				0,463				14464			)465		);466	}467468	#[test]469	fn basic_math_parened() {470		assert_eq!(471			parse!("2+(2+2*2)"),472			el!(473				Expr::BinaryOp(474					el!(Expr::Num(2.0), 0, 1),475					Add,476					el!(477						Expr::Parened(el!(478							Expr::BinaryOp(479								el!(Expr::Num(2.0), 3, 4),480								Add,481								el!(482									Expr::BinaryOp(483										el!(Expr::Num(2.0), 5, 6),484										Mul,485										el!(Expr::Num(2.0), 7, 8),486									),487									5,488									8489								),490							),491							3,492							8493						)),494						2,495						9496					),497				),498				0,499				9500			)501		);502	}503504	/// Comments should not affect parsing505	#[test]506	fn comments() {507		assert_eq!(508			parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),509			el!(510				Expr::BinaryOp(511					el!(Expr::Num(2.0), 0, 1),512					Add,513					el!(514						Expr::BinaryOp(515							el!(Expr::Num(3.0), 22, 23),516							Mul,517							el!(Expr::Num(4.0), 40, 41)518						),519						22,520						41521					)522				),523				0,524				41525			)526		);527	}528529	/// Comments should be able to be escaped530	#[test]531	fn comment_escaping() {532		assert_eq!(533			parse!("2/*\\*/+*/ - 22"),534			el!(535				Expr::BinaryOp(el!(Expr::Num(2.0), 0, 1), Sub, el!(Expr::Num(22.0), 12, 14)),536				0,537				14538			)539		);540	}541542	#[test]543	fn suffix() {544		// assert_eq!(parse!("std.test"), el!(Expr::Num(2.2)));545		// assert_eq!(parse!("std(2)"), el!(Expr::Num(2.2)));546		// assert_eq!(parse!("std.test(2)"), el!(Expr::Num(2.2)));547		// assert_eq!(parse!("a[b]"), el!(Expr::Num(2.2)))548	}549550	#[test]551	fn array_comp() {552		use Expr::*;553		/*554		`ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Var("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`,555		`ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Str("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`556				*/557		assert_eq!(558			parse!("[std.deepJoin(x) for x in arr]"),559			el!(560				ArrComp(561					el!(562						Apply(563							el!(564								Index(565									el!(Var("std".into()), 1, 4),566									el!(Str("deepJoin".into()), 5, 13)567								),568								1,569								13570							),571							ArgsDesc::new(vec![el!(Var("x".into()), 14, 15)], vec![]),572							false,573						),574						1,575						16576					),577					vec![CompSpec::ForSpec(ForSpecData(578						"x".into(),579						el!(Var("arr".into()), 26, 29)580					))]581				),582				0,583				30584			),585		)586	}587588	#[test]589	fn reserved() {590		use Expr::*;591		assert_eq!(parse!("null"), el!(Literal(LiteralType::Null), 0, 4));592		assert_eq!(parse!("nulla"), el!(Var("nulla".into()), 0, 5));593	}594595	#[test]596	fn multiple_args_buf() {597		parse!("a(b, null_fields)");598	}599600	#[test]601	fn infix_precedence() {602		use Expr::*;603		assert_eq!(604			parse!("!a && !b"),605			el!(606				BinaryOp(607					el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),608					And,609					el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 7, 8)), 6, 8)610				),611				0,612				8613			)614		);615	}616617	#[test]618	fn infix_precedence_division() {619		use Expr::*;620		assert_eq!(621			parse!("!a / !b"),622			el!(623				BinaryOp(624					el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),625					Div,626					el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 6, 7)), 5, 7)627				),628				0,629				7630			)631		);632	}633634	#[test]635	fn double_negation() {636		use Expr::*;637		assert_eq!(638			parse!("!!a"),639			el!(640				UnaryOp(641					UnaryOpType::Not,642					el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 2, 3)), 1, 3)643				),644				0,645				3646			)647		)648	}649650	#[test]651	fn array_test_error() {652		parse!("[a for a in b if c for e in f]");653		//                    ^^^^ failed code654	}655656	#[test]657	fn missing_newline_between_comment_and_eof() {658		parse!(659			"{a:1}660661			//+213"662		);663	}664665	#[test]666	fn default_param_before_nondefault() {667		parse!("local x(foo = 'foo', bar) = null; null");668	}669670	#[test]671	fn can_parse_stdlib() {672		parse!(jrsonnet_stdlib::STDLIB_STR);673	}674675	#[test]676	fn add_location_info_to_all_sub_expressions() {677		use Expr::*;678679		let file_name: std::rc::Rc<std::path::Path> = PathBuf::from("test.jsonnet").into();680		let expr = parse(681			"{} { local x = 1, x: x } + {}",682			&ParserSettings {683				file_name: file_name.clone(),684			},685		)686		.unwrap();687		assert_eq!(688			expr,689			el!(690				BinaryOp(691					el!(692						ObjExtend(693							el!(Obj(ObjBody::MemberList(vec![])), 0, 2),694							ObjBody::MemberList(vec![695								Member::BindStmt(BindSpec {696									name: "x".into(),697									params: None,698									value: el!(Num(1.0), 15, 16)699								}),700								Member::Field(FieldMember {701									name: FieldName::Fixed("x".into()),702									plus: false,703									params: None,704									visibility: Visibility::Normal,705									value: el!(Var("x".into()), 21, 22),706								})707							])708						),709						0,710						24711					),712					BinaryOpType::Add,713					el!(Obj(ObjBody::MemberList(vec![])), 27, 29),714				),715				0,716				29717			),718		);719	}720	// From source code721	/*722	#[bench]723	fn bench_parse_peg(b: &mut Bencher) {724		b.iter(|| parse!(jrsonnet_stdlib::STDLIB_STR))725	}726	*/727}
after · crates/jrsonnet-parser/src/lib.rs
1#![allow(clippy::redundant_closure_call)]23use std::{4	path::{Path, PathBuf},5	rc::Rc,6};78use peg::parser;9mod expr;10pub use expr::*;11pub use jrsonnet_interner::IStr;12pub use peg;13mod unescape;1415pub struct ParserSettings {16	pub file_name: Rc<Path>,17}1819macro_rules! expr_bin {20	($a:ident $op:ident $b:ident) => {21		Expr::BinaryOp($a, $op, $b)22	};23}24macro_rules! expr_un {25	($op:ident $a:ident) => {26		Expr::UnaryOp($op, $a)27	};28}2930parser! {31	grammar jsonnet_parser() for str {32		use peg::ParseLiteral;3334		rule eof() = quiet!{![_]} / expected!("<eof>")35		rule eol() = "\n" / eof()3637		/// Standard C-like comments38		rule comment()39			= "//" (!eol()[_])* eol()40			/ "/*" ("\\*/" / "\\\\" / (!("*/")[_]))* "*/"41			/ "#" (!eol()[_])* eol()4243		rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")44		rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")4546		/// For comma-delimited elements47		rule comma() = quiet!{_ "," _} / expected!("<comma>")48		rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}49		rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}50		rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']51		/// Sequence of digits52		rule uint_str() -> &'input str = a:$(digit()+) { a }53		/// Number in scientific notation format54		rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.parse().map_err(|_| "<number>") }} / expected!("<number>")5556		/// Reserved word followed by any non-alphanumberic57		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()58		rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")5960		rule keyword(id: &'static str) -> ()61			= ##parse_string_literal(id) end_of_ident()6263		pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) }64		pub rule params(s: &ParserSettings) -> expr::ParamsDesc65			= params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) }66			/ { expr::ParamsDesc(Rc::new(Vec::new())) }6768		pub rule arg(s: &ParserSettings) -> (Option<IStr>, LocExpr)69			= quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} }70			/ expected!("<argument>")7172		pub rule args(s: &ParserSettings) -> expr::ArgsDesc73			= args:arg(s)**comma() comma()? {?74				let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();75				let mut unnamed = Vec::with_capacity(unnamed_count);76				let mut named = Vec::with_capacity(args.len() - unnamed_count);77				let mut named_started = false;78				for (name, value) in args {79					if let Some(name) = name {80						named_started = true;81						named.push((name, value));82					} else {83						if named_started {84							return Err("<named argument>")85						}86						unnamed.push(value);87					}88				}89				Ok(expr::ArgsDesc::new(unnamed, named))90			}9192		pub rule bind(s: &ParserSettings) -> expr::BindSpec93			= name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}94			/ name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}}95		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt96			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }9798		pub rule whole_line() -> &'input str99			= str:$((!['\n'][_])* "\n") {str}100		pub rule string_block() -> String101			= "|||" (!['\n']single_whitespace())* "\n"102			  empty_lines:$(['\n']*)103			  prefix:[' ' | '\t']+ first_line:whole_line()104			  lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*105			  [' ' | '\t']*<, {prefix.len() - 1}> "|||"106			  {let mut l = empty_lines.to_owned(); l.push_str(first_line); l.extend(lines); l}107108		rule hex_char()109			= quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")110111		rule string_char(c: rule<()>)112			= (!['\\']!c()[_])+113			/ "\\\\"114			/ "\\u" hex_char() hex_char() hex_char() hex_char()115			/ "\\x" hex_char() hex_char()116			/ ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))117		pub rule string() -> String118			= ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}119			/ ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}120			/ quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}121			/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}122			/ string_block() } / expected!("<string>")123124		pub rule field_name(s: &ParserSettings) -> expr::FieldName125			= name:$(id()) {expr::FieldName::Fixed(name.into())}126			/ name:string() {expr::FieldName::Fixed(name.into())}127			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}128		pub rule visibility() -> expr::Visibility129			= ":::" {expr::Visibility::Unhide}130			/ "::" {expr::Visibility::Hidden}131			/ ":" {expr::Visibility::Normal}132		pub rule field(s: &ParserSettings) -> expr::FieldMember133			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{134				name,135				plus: plus.is_some(),136				params: None,137				visibility,138				value,139			}}140			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{141				name,142				plus: false,143				params: Some(params),144				visibility,145				value,146			}}147		pub rule obj_local(s: &ParserSettings) -> BindSpec148			= keyword("local") _ bind:bind(s) {bind}149		pub rule member(s: &ParserSettings) -> expr::Member150			= bind:obj_local(s) {expr::Member::BindStmt(bind)}151			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}152			/ field:field(s) {expr::Member::Field(field)}153		pub rule objinside(s: &ParserSettings) -> expr::ObjBody154			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {155				let mut compspecs = vec![CompSpec::ForSpec(forspec)];156				compspecs.extend(others.unwrap_or_default());157				expr::ObjBody::ObjComp(expr::ObjComp{158					pre_locals,159					key,160					plus: plus.is_some(),161					value,162					post_locals,163					compspecs,164				})165			}166			/ members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}167		pub rule ifspec(s: &ParserSettings) -> IfSpecData168			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}169		pub rule forspec(s: &ParserSettings) -> ForSpecData170			= keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)}171		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>172			= s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}173		pub rule local_expr(s: &ParserSettings) -> Expr174			= keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }175		pub rule string_expr(s: &ParserSettings) -> Expr176			= s:string() {Expr::Str(s.into())}177		pub rule obj_expr(s: &ParserSettings) -> Expr178			= "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}179		pub rule array_expr(s: &ParserSettings) -> Expr180			= "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}181		pub rule array_comp_expr(s: &ParserSettings) -> Expr182			= "[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {183				let mut specs = vec![CompSpec::ForSpec(forspec)];184				specs.extend(others.unwrap_or_default());185				Expr::ArrComp(expr, specs)186			}187		pub rule number_expr(s: &ParserSettings) -> Expr188			= n:number() { expr::Expr::Num(n) }189		pub rule var_expr(s: &ParserSettings) -> Expr190			= n:$(id()) { expr::Expr::Var(n.into()) }191		pub rule id_loc(s: &ParserSettings) -> LocExpr192			= a:position!() n:$(id()) b:position!() { LocExpr(Rc::new(expr::Expr::Str(n.into())), ExprLocation(s.file_name.clone(), a,b)) }193		pub rule if_then_else_expr(s: &ParserSettings) -> Expr194			= cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{195				cond,196				cond_then,197				cond_else,198			}}199200		pub rule literal(s: &ParserSettings) -> Expr201			= v:(202				keyword("null") {LiteralType::Null}203				/ keyword("true") {LiteralType::True}204				/ keyword("false") {LiteralType::False}205				/ keyword("self") {LiteralType::This}206				/ keyword("$") {LiteralType::Dollar}207				/ keyword("super") {LiteralType::Super}208			) {Expr::Literal(v)}209210		pub rule expr_basic(s: &ParserSettings) -> Expr211			= literal(s)212213			/ quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}214			/ quiet!{"$intrinsicId" {Expr::IntrinsicId}}215			/ quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}216217			/ string_expr(s) / number_expr(s)218			/ array_expr(s)219			/ obj_expr(s)220			/ array_expr(s)221			/ array_comp_expr(s)222223			/ keyword("importstr") _ path:string() {Expr::ImportStr(PathBuf::from(path))}224			/ keyword("importbin") _ path:string() {Expr::ImportBin(PathBuf::from(path))}225			/ keyword("import") _ path:string() {Expr::Import(PathBuf::from(path))}226227			/ var_expr(s)228			/ local_expr(s)229			/ if_then_else_expr(s)230231			/ keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}232			/ assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }233234			/ keyword("error") _ expr:expr(s) { Expr::ErrorStmt(expr) }235236		rule slice_part(s: &ParserSettings) -> Option<LocExpr>237			= _ e:(e:expr(s) _{e})? {e}238		pub rule slice_desc(s: &ParserSettings) -> SliceDesc239			= start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {240				let (end, step) = if let Some((end, step)) = pair {241					(end, step)242				}else{243					(None, None)244				};245246				SliceDesc { start, end, step }247			}248249		rule binop(x: rule<()>) -> ()250			= quiet!{ x() } / expected!("<binary op>")251		rule unaryop(x: rule<()>) -> ()252			= quiet!{ x() } / expected!("<unary op>")253254255		use BinaryOpType::*;256		use UnaryOpType::*;257		rule expr(s: &ParserSettings) -> LocExpr258			= precedence! {259				start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.file_name.clone(), start, end)) }260				--261				a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}262				--263				a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}264				--265				a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}266				--267				a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}268				--269				a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}270				--271				a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}272				a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}273				--274				a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}275				a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}276				a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}277				a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}278				a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}279				--280				a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}281				a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}282				--283				a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}284				a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}285				--286				a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}287				a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}288				a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}289				--290						unaryop(<"-">) _ b:@ {expr_un!(Minus b)}291						unaryop(<"!">) _ b:@ {expr_un!(Not b)}292						unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}293				--294				a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}295				a:(@) _ "." _ a:position!() e:id_loc(s) b:position!() {Expr::Index(a, e)}296				a:(@) _ "[" _ e:expr(s) _ "]" {Expr::Index(a, e)}297				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}298				a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}299				--300				e:expr_basic(s) {e}301				"(" _ e:expr(s) _ ")" {Expr::Parened(e)}302			}303304		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}305	}306}307308pub type ParseError = peg::error::ParseError<peg::str::LineCol>;309pub fn parse(str: &str, settings: &ParserSettings) -> Result<LocExpr, ParseError> {310	jsonnet_parser::jsonnet(str, settings)311}312/// Used for importstr values313pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> LocExpr {314	let len = str.len();315	LocExpr(316		Rc::new(Expr::Str(str)),317		ExprLocation(settings.file_name.clone(), 0, len),318	)319}320321#[cfg(test)]322pub mod tests {323	use std::path::PathBuf;324325	use BinaryOpType::*;326327	use super::{expr::*, parse};328	use crate::ParserSettings;329330	macro_rules! parse {331		($s:expr) => {332			parse(333				$s,334				&ParserSettings {335					file_name: PathBuf::from("test.jsonnet").into(),336				},337			)338			.unwrap()339		};340	}341342	macro_rules! el {343		($expr:expr, $from:expr, $to:expr$(,)?) => {344			LocExpr(345				std::rc::Rc::new($expr),346				ExprLocation(PathBuf::from("test.jsonnet").into(), $from, $to),347			)348		};349	}350351	#[test]352	fn multiline_string() {353		assert_eq!(354			parse!("|||\n    Hello world!\n     a\n|||"),355			el!(Expr::Str("Hello world!\n a\n".into()), 0, 31),356		);357		assert_eq!(358			parse!("|||\n  Hello world!\n   a\n|||"),359			el!(Expr::Str("Hello world!\n a\n".into()), 0, 27),360		);361		assert_eq!(362			parse!("|||\n\t\tHello world!\n\t\t\ta\n|||"),363			el!(Expr::Str("Hello world!\n\ta\n".into()), 0, 27),364		);365		assert_eq!(366			parse!("|||\n   Hello world!\n    a\n |||"),367			el!(Expr::Str("Hello world!\n a\n".into()), 0, 30),368		);369	}370371	#[test]372	fn slice() {373		parse!("a[1:]");374		parse!("a[1::]");375		parse!("a[:1:]");376		parse!("a[::1]");377		parse!("str[:len - 1]");378	}379380	#[test]381	fn string_escaping() {382		assert_eq!(383			parse!(r#""Hello, \"world\"!""#),384			el!(Expr::Str(r#"Hello, "world"!"#.into()), 0, 19),385		);386		assert_eq!(387			parse!(r#"'Hello \'world\'!'"#),388			el!(Expr::Str("Hello 'world'!".into()), 0, 18),389		);390		assert_eq!(parse!(r#"'\\\\'"#), el!(Expr::Str("\\\\".into()), 0, 6));391	}392393	#[test]394	fn string_unescaping() {395		assert_eq!(396			parse!(r#""Hello\nWorld""#),397			el!(Expr::Str("Hello\nWorld".into()), 0, 14),398		);399	}400401	#[test]402	fn string_verbantim() {403		assert_eq!(404			parse!(r#"@"Hello\n""World""""#),405			el!(Expr::Str("Hello\\n\"World\"".into()), 0, 19),406		);407	}408409	#[test]410	fn imports() {411		assert_eq!(412			parse!("import \"hello\""),413			el!(Expr::Import(PathBuf::from("hello")), 0, 14),414		);415		assert_eq!(416			parse!("importstr \"garnish.txt\""),417			el!(Expr::ImportStr(PathBuf::from("garnish.txt")), 0, 23)418		);419	}420421	#[test]422	fn empty_object() {423		assert_eq!(424			parse!("{}"),425			el!(Expr::Obj(ObjBody::MemberList(vec![])), 0, 2)426		);427	}428429	#[test]430	fn basic_math() {431		assert_eq!(432			parse!("2+2*2"),433			el!(434				Expr::BinaryOp(435					el!(Expr::Num(2.0), 0, 1),436					Add,437					el!(438						Expr::BinaryOp(el!(Expr::Num(2.0), 2, 3), Mul, el!(Expr::Num(2.0), 4, 5)),439						2,440						5441					)442				),443				0,444				5445			)446		);447	}448449	#[test]450	fn basic_math_with_indents() {451		assert_eq!(452			parse!("2	+ 	  2	  *	2   	"),453			el!(454				Expr::BinaryOp(455					el!(Expr::Num(2.0), 0, 1),456					Add,457					el!(458						Expr::BinaryOp(el!(Expr::Num(2.0), 7, 8), Mul, el!(Expr::Num(2.0), 13, 14),),459						7,460						14461					),462				),463				0,464				14465			)466		);467	}468469	#[test]470	fn basic_math_parened() {471		assert_eq!(472			parse!("2+(2+2*2)"),473			el!(474				Expr::BinaryOp(475					el!(Expr::Num(2.0), 0, 1),476					Add,477					el!(478						Expr::Parened(el!(479							Expr::BinaryOp(480								el!(Expr::Num(2.0), 3, 4),481								Add,482								el!(483									Expr::BinaryOp(484										el!(Expr::Num(2.0), 5, 6),485										Mul,486										el!(Expr::Num(2.0), 7, 8),487									),488									5,489									8490								),491							),492							3,493							8494						)),495						2,496						9497					),498				),499				0,500				9501			)502		);503	}504505	/// Comments should not affect parsing506	#[test]507	fn comments() {508		assert_eq!(509			parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),510			el!(511				Expr::BinaryOp(512					el!(Expr::Num(2.0), 0, 1),513					Add,514					el!(515						Expr::BinaryOp(516							el!(Expr::Num(3.0), 22, 23),517							Mul,518							el!(Expr::Num(4.0), 40, 41)519						),520						22,521						41522					)523				),524				0,525				41526			)527		);528	}529530	/// Comments should be able to be escaped531	#[test]532	fn comment_escaping() {533		assert_eq!(534			parse!("2/*\\*/+*/ - 22"),535			el!(536				Expr::BinaryOp(el!(Expr::Num(2.0), 0, 1), Sub, el!(Expr::Num(22.0), 12, 14)),537				0,538				14539			)540		);541	}542543	#[test]544	fn suffix() {545		// assert_eq!(parse!("std.test"), el!(Expr::Num(2.2)));546		// assert_eq!(parse!("std(2)"), el!(Expr::Num(2.2)));547		// assert_eq!(parse!("std.test(2)"), el!(Expr::Num(2.2)));548		// assert_eq!(parse!("a[b]"), el!(Expr::Num(2.2)))549	}550551	#[test]552	fn array_comp() {553		use Expr::*;554		/*555		`ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Var("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`,556		`ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Str("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`557				*/558		assert_eq!(559			parse!("[std.deepJoin(x) for x in arr]"),560			el!(561				ArrComp(562					el!(563						Apply(564							el!(565								Index(566									el!(Var("std".into()), 1, 4),567									el!(Str("deepJoin".into()), 5, 13)568								),569								1,570								13571							),572							ArgsDesc::new(vec![el!(Var("x".into()), 14, 15)], vec![]),573							false,574						),575						1,576						16577					),578					vec![CompSpec::ForSpec(ForSpecData(579						"x".into(),580						el!(Var("arr".into()), 26, 29)581					))]582				),583				0,584				30585			),586		)587	}588589	#[test]590	fn reserved() {591		use Expr::*;592		assert_eq!(parse!("null"), el!(Literal(LiteralType::Null), 0, 4));593		assert_eq!(parse!("nulla"), el!(Var("nulla".into()), 0, 5));594	}595596	#[test]597	fn multiple_args_buf() {598		parse!("a(b, null_fields)");599	}600601	#[test]602	fn infix_precedence() {603		use Expr::*;604		assert_eq!(605			parse!("!a && !b"),606			el!(607				BinaryOp(608					el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),609					And,610					el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 7, 8)), 6, 8)611				),612				0,613				8614			)615		);616	}617618	#[test]619	fn infix_precedence_division() {620		use Expr::*;621		assert_eq!(622			parse!("!a / !b"),623			el!(624				BinaryOp(625					el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),626					Div,627					el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 6, 7)), 5, 7)628				),629				0,630				7631			)632		);633	}634635	#[test]636	fn double_negation() {637		use Expr::*;638		assert_eq!(639			parse!("!!a"),640			el!(641				UnaryOp(642					UnaryOpType::Not,643					el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 2, 3)), 1, 3)644				),645				0,646				3647			)648		)649	}650651	#[test]652	fn array_test_error() {653		parse!("[a for a in b if c for e in f]");654		//                    ^^^^ failed code655	}656657	#[test]658	fn missing_newline_between_comment_and_eof() {659		parse!(660			"{a:1}661662			//+213"663		);664	}665666	#[test]667	fn default_param_before_nondefault() {668		parse!("local x(foo = 'foo', bar) = null; null");669	}670671	#[test]672	fn can_parse_stdlib() {673		parse!(jrsonnet_stdlib::STDLIB_STR);674	}675676	#[test]677	fn add_location_info_to_all_sub_expressions() {678		use Expr::*;679680		let file_name: std::rc::Rc<std::path::Path> = PathBuf::from("test.jsonnet").into();681		let expr = parse(682			"{} { local x = 1, x: x } + {}",683			&ParserSettings {684				file_name: file_name.clone(),685			},686		)687		.unwrap();688		assert_eq!(689			expr,690			el!(691				BinaryOp(692					el!(693						ObjExtend(694							el!(Obj(ObjBody::MemberList(vec![])), 0, 2),695							ObjBody::MemberList(vec![696								Member::BindStmt(BindSpec {697									name: "x".into(),698									params: None,699									value: el!(Num(1.0), 15, 16)700								}),701								Member::Field(FieldMember {702									name: FieldName::Fixed("x".into()),703									plus: false,704									params: None,705									visibility: Visibility::Normal,706									value: el!(Var("x".into()), 21, 22),707								})708							])709						),710						0,711						24712					),713					BinaryOpType::Add,714					el!(Obj(ObjBody::MemberList(vec![])), 27, 29),715				),716				0,717				29718			),719		);720	}721	// From source code722	/*723	#[bench]724	fn bench_parse_peg(b: &mut Bencher) {725		b.iter(|| parse!(jrsonnet_stdlib::STDLIB_STR))726	}727	*/728}
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -4,6 +4,7 @@
 
   # Magic legacy field
   thisFile:: $intrinsicThisFile,
+  id:: $intrinsicId,
 
   # Those functions aren't normally located in stdlib
   length:: $intrinsic(length),
@@ -24,7 +25,6 @@
   decodeUTF8:: $intrinsic(decodeUTF8),
   md5:: $intrinsic(md5),
   trace:: $intrinsic(trace),
-  id:: $intrinsic(id),
   parseJson:: $intrinsic(parseJson),
   parseYaml:: $intrinsic(parseYaml),