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
298 Function(ParamsDesc, LocExpr),298 Function(ParamsDesc, LocExpr),
299 /// std.thisFile299 /// std.thisFile
300 IntrinsicThisFile,300 IntrinsicThisFile,
301 /// std.id,
302 IntrinsicId,
301 /// std.primitiveEquals303 /// std.primitiveEquals
302 Intrinsic(IStr),304 Intrinsic(IStr),
303 /// if true == false then 1 else 2305 /// if true == false then 1 else 2
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -211,6 +211,7 @@
 			= literal(s)
 
 			/ quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}
+			/ quiet!{"$intrinsicId" {Expr::IntrinsicId}}
 			/ quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}
 
 			/ string_expr(s) / number_expr(s)
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),