git.delta.rocks / jrsonnet / refs/commits / 449686f01d55

difftreelog

fix BlackBox formatter support for bigint

ptyvotvzYaroslav Bolyukin2026-04-25parent: #1d941f8.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -31,5 +31,3 @@
 }
 
 pub fn assert_trace<T: Trace>(_v: &T) {}
-
-pub type ImHashMap<K, V> = im_rc::HashMap<K, V, FxBuildHasher>;
modifiedcrates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/manifest.rs
1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{Result, ResultExt, Val, bail, in_description_frame};45pub trait ManifestFormat {6	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7	fn manifest(&self, val: Val) -> Result<String> {8		let mut out = String::new();9		self.manifest_buf(val, &mut out)?;10		Ok(out)11	}12	/// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13	/// the meaning).14	///15	/// Default implementation returns `true`16	fn file_trailing_newline(&self) -> bool {17		true18	}19}20impl<T> ManifestFormat for Box<T>21where22	T: ManifestFormat + ?Sized,23{24	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25		let inner = &**self;26		inner.manifest_buf(val, buf)27	}28	fn file_trailing_newline(&self) -> bool {29		let inner = &**self;30		inner.file_trailing_newline()31	}32}33impl<T> ManifestFormat for &'_ T34where35	T: ManifestFormat + ?Sized,36{37	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38		let inner = &**self;39		inner.manifest_buf(val, buf)40	}41	fn file_trailing_newline(&self) -> bool {42		let inner = &**self;43		inner.file_trailing_newline()44	}45}4647pub struct BlackBoxFormat;48impl ManifestFormat for BlackBoxFormat {49	#[allow(clippy::only_used_in_recursion)]50	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {51		match val {52			Val::Bool(v) => {53				black_box(v);54			}55			val @ Val::Null => {56				black_box(val);57			}58			Val::Str(str_value) => {59				black_box(format!("{str_value}"));60			}61			Val::Num(num_value) => {62				black_box(num_value);63			}64			Val::Arr(arr_value) => {65				for ele in arr_value.iter() {66					let ele = ele?;67					self.manifest_buf(ele, buf)?;68				}69			}70			Val::Obj(obj_value) => {71				for (name, value) in obj_value.iter() {72					black_box(name);73					let value = value?;74					self.manifest_buf(value, buf)?;75				}76			}77			Val::Func(func_val) => {78				black_box(func_val);79				bail!("tried to manifest function")80			}81		}82		Ok(())83	}84}8586#[derive(PartialEq, Eq, Clone, Copy)]87enum JsonFormatting {88	// Applied in manifestification89	Manifest,90	/// Used for std.manifestJson91	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest92	Std,93	/// No line breaks, used in `obj+''`94	ToString,95	/// Minified json96	Minify,97}9899pub struct JsonFormat<'s> {100	padding: Cow<'s, str>,101	mtype: JsonFormatting,102	newline: &'s str,103	key_val_sep: &'s str,104	#[cfg(feature = "exp-preserve-order")]105	preserve_order: bool,106	#[cfg(feature = "exp-bigint")]107	preserve_bigints: bool,108}109110impl<'s> JsonFormat<'s> {111	// Minifying format112	pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {113		Self {114			padding: Cow::Borrowed(""),115			mtype: JsonFormatting::Minify,116			newline: "\n",117			key_val_sep: ":",118			#[cfg(feature = "exp-preserve-order")]119			preserve_order,120			#[cfg(feature = "exp-bigint")]121			preserve_bigints: false,122		}123	}124	/// Same format as std.toString, except does not keeps top-level string as-is125	/// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead126	const fn std_to_string_helper() -> Self {127		Self {128			padding: Cow::Borrowed(""),129			mtype: JsonFormatting::ToString,130			newline: "\n",131			key_val_sep: ": ",132			#[cfg(feature = "exp-preserve-order")]133			preserve_order: false,134			#[cfg(feature = "exp-bigint")]135			preserve_bigints: false,136		}137	}138	pub fn std_to_json(139		padding: String,140		newline: &'s str,141		key_val_sep: &'s str,142		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,143	) -> Self {144		Self {145			padding: Cow::Owned(padding),146			mtype: JsonFormatting::Std,147			newline,148			key_val_sep,149			#[cfg(feature = "exp-preserve-order")]150			preserve_order,151			#[cfg(feature = "exp-bigint")]152			preserve_bigints: false,153		}154	}155	// Same format as CLI manifestification156	pub fn cli(157		padding: usize,158		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,159	) -> Self {160		if padding == 0 {161			return Self::minify(162				#[cfg(feature = "exp-preserve-order")]163				preserve_order,164			);165		}166		Self {167			padding: Cow::Owned(" ".repeat(padding)),168			mtype: JsonFormatting::Manifest,169			newline: "\n",170			key_val_sep: ": ",171			#[cfg(feature = "exp-preserve-order")]172			preserve_order,173			#[cfg(feature = "exp-bigint")]174			preserve_bigints: false,175		}176	}177	// Same format as CLI manifestification178	pub fn debug() -> Self {179		Self {180			padding: Cow::Borrowed("   "),181			mtype: JsonFormatting::Manifest,182			newline: "\n",183			key_val_sep: ": ",184			#[cfg(feature = "exp-preserve-order")]185			preserve_order: true,186			#[cfg(feature = "exp-bigint")]187			preserve_bigints: true,188		}189	}190}191impl Default for JsonFormat<'static> {192	fn default() -> Self {193		Self {194			padding: Cow::Borrowed("    "),195			mtype: JsonFormatting::Manifest,196			newline: "\n",197			key_val_sep: ": ",198			#[cfg(feature = "exp-preserve-order")]199			preserve_order: false,200			#[cfg(feature = "exp-bigint")]201			preserve_bigints: false,202		}203	}204}205206pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {207	let mut out = String::new();208	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;209	Ok(out)210}211212#[allow(clippy::too_many_lines)]213fn manifest_json_ex_buf(214	val: &Val,215	buf: &mut String,216	cur_padding: &mut String,217	options: &JsonFormat<'_>,218) -> Result<()> {219	use JsonFormatting::*;220221	let mtype = options.mtype;222	match val {223		Val::Bool(v) => {224			if *v {225				buf.push_str("true");226			} else {227				buf.push_str("false");228			}229		}230		Val::Null => buf.push_str("null"),231		Val::Str(s) => {232			buf.reserve(2 + s.len());233			buf.push('"');234			s.chunks(&mut |c| {235				escape_string_json_buf_raw(c, buf);236			});237			buf.push('"');238		}239		Val::Num(n) => write!(buf, "{n}").unwrap(),240		#[cfg(feature = "exp-bigint")]241		Val::BigInt(n) => {242			if options.preserve_bigints {243				write!(buf, "{n}").unwrap();244			} else {245				write!(buf, "{:?}", n.to_string()).unwrap();246			}247		}248		Val::Arr(items) => {249			buf.push('[');250251			let old_len = cur_padding.len();252			cur_padding.push_str(&options.padding);253254			let mut had_items = false;255			for (i, item) in items.iter().enumerate() {256				had_items = true;257				let item = item.with_description(|| format!("elem <{i}> evaluation"))?;258259				if i != 0 {260					buf.push(',');261				}262				match mtype {263					Manifest | Std => {264						buf.push_str(options.newline);265						buf.push_str(cur_padding);266					}267					ToString if i != 0 => buf.push(' '),268					Minify | ToString => {}269				}270271				in_description_frame(272					|| format!("elem <{i}> manifestification"),273					|| manifest_json_ex_buf(&item, buf, cur_padding, options),274				)?;275			}276277			cur_padding.truncate(old_len);278279			match mtype {280				Manifest | ToString if !had_items => {281					// Empty array as "[ ]"282					buf.push(' ');283				}284				Manifest => {285					buf.push_str(options.newline);286					buf.push_str(cur_padding);287				}288				Std => {289					if !had_items {290						// Stdlib formats empty array as "[\n\n]"291						buf.push_str(options.newline);292					}293					buf.push_str(options.newline);294					buf.push_str(cur_padding);295				}296				Minify | ToString => {}297			}298299			buf.push(']');300		}301		Val::Obj(obj) => {302			obj.run_assertions()?;303			buf.push('{');304305			let old_len = cur_padding.len();306			cur_padding.push_str(&options.padding);307308			let mut had_fields = false;309			for (i, (key, value)) in obj310				.iter(311					#[cfg(feature = "exp-preserve-order")]312					options.preserve_order,313				)314				.enumerate()315			{316				had_fields = true;317				let value = value.with_description(|| format!("field <{key}> evaluation"))?;318319				if i != 0 {320					buf.push(',');321				}322				match mtype {323					Manifest | Std => {324						buf.push_str(options.newline);325						buf.push_str(cur_padding);326					}327					ToString if i != 0 => buf.push(' '),328					Minify | ToString => {}329				}330331				escape_string_json_buf(&key, buf);332				buf.push_str(options.key_val_sep);333				in_description_frame(334					|| format!("field <{key}> manifestification"),335					|| manifest_json_ex_buf(&value, buf, cur_padding, options),336				)?;337			}338339			cur_padding.truncate(old_len);340341			match mtype {342				Manifest | ToString if !had_fields => {343					// Empty object as "{ }"344					buf.push(' ');345				}346				Manifest => {347					buf.push_str(options.newline);348					buf.push_str(cur_padding);349				}350				Std => {351					if !had_fields {352						// Stdlib formats empty object as "{\n\n}"353						buf.push_str(options.newline);354					}355					buf.push_str(options.newline);356					buf.push_str(cur_padding);357				}358				Minify | ToString => {}359			}360361			buf.push('}');362		}363		Val::Func(_) => bail!("tried to manifest function"),364	}365	Ok(())366}367368impl ManifestFormat for JsonFormat<'_> {369	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {370		manifest_json_ex_buf(&val, buf, &mut String::new(), self)371	}372}373374/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,375/// without quoting.376pub struct ToStringFormat;377impl ManifestFormat for ToStringFormat {378	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {379		const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();380		if let Some(str) = val.as_str() {381			out.push_str(&str);382			return Ok(());383		}384		#[cfg(feature = "exp-bigint")]385		if let Some(int) = val.as_bigint() {386			out.push_str(&int.to_str_radix(10));387			return Ok(());388		}389		JSON_TO_STRING.manifest_buf(val, out)390	}391	fn file_trailing_newline(&self) -> bool {392		false393	}394}395pub struct StringFormat;396impl ManifestFormat for StringFormat {397	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {398		let Val::Str(s) = val else {399			bail!(400				"output should be string for string manifest format, got {}",401				val.value_type()402			)403		};404		write!(out, "{s}").unwrap();405		Ok(())406	}407	fn file_trailing_newline(&self) -> bool {408		false409	}410}411412pub struct YamlStreamFormat<I> {413	inner: I,414	c_document_end: bool,415	end_newline: bool,416}417impl<I> YamlStreamFormat<I> {418	pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {419		Self {420			inner,421			c_document_end,422			// Stdlib format always inserts useless newline at the end423			end_newline: true,424		}425	}426	pub fn cli(inner: I) -> Self {427		Self {428			inner,429			c_document_end: true,430			end_newline: false,431		}432	}433}434impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {435	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {436		let Val::Arr(arr) = val else {437			bail!(438				"output should be array for yaml stream format, got {}",439				val.value_type()440			)441		};442		for (i, v) in arr.iter().enumerate() {443			if i != 0 {444				out.push('\n');445			}446			let v = v.with_description(|| format!("elem <{i}> evaluation"))?;447			out.push_str("---\n");448			in_description_frame(449				|| format!("elem <{i}> manifestification"),450				|| self.inner.manifest_buf(v, out),451			)?;452		}453		if self.c_document_end {454			out.push('\n');455			out.push_str("...");456		}457		if self.end_newline {458			out.push('\n');459		}460		Ok(())461	}462}463464pub fn escape_string_json(s: &str) -> String {465	let mut buf = String::new();466	escape_string_json_buf(s, &mut buf);467	buf468}469470// Json string encoding was borrowed from https://github.com/serde-rs/json471472const BB: u8 = b'b'; // \x08473const TT: u8 = b't'; // \x09474const NN: u8 = b'n'; // \x0A475const FF: u8 = b'f'; // \x0C476const RR: u8 = b'r'; // \x0D477const QU: u8 = b'"'; // \x22478const BS: u8 = b'\\'; // \x5C479const UU: u8 = b'u'; // \x00...\x1F except the ones above480const __: u8 = 0;481482// Lookup table of escape sequences. A value of b'x' at index i means that byte483// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.484static ESCAPE: [u8; 256] = [485	//   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F486	UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0487	UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1488	__, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2489	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3490	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4491	__, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5492	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6493	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7494	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8495	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9496	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A497	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B498	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C499	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D500	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E501	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F502];503504pub fn escape_string_json_buf(value: &str, buf: &mut String) {505	buf.reserve_exact(value.len() + 2);506	escape_string_json_buf_raw(value, buf);507}508509fn escape_string_json_buf_raw(value: &str, buf: &mut String) {510	// Safety: we only write correct utf-8 in this function511	let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };512	let bytes = value.as_bytes();513514	let mut start = 0;515516	for (i, &byte) in bytes.iter().enumerate() {517		let escape = ESCAPE[byte as usize];518		if escape == __ {519			continue;520		}521522		if start < i {523			buf.extend_from_slice(&bytes[start..i]);524		}525		start = i + 1;526527		match escape {528			self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {529				buf.extend_from_slice(&[b'\\', escape]);530			}531			self::UU => {532				static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";533				let bytes = &[534					b'\\',535					b'u',536					b'0',537					b'0',538					HEX_DIGITS[(byte >> 4) as usize],539					HEX_DIGITS[(byte & 0xF) as usize],540				];541				buf.extend_from_slice(bytes);542			}543			_ => unreachable!(),544		}545	}546547	if start == bytes.len() {548		return;549	}550551	buf.extend_from_slice(&bytes[start..]);552}
after · crates/jrsonnet-evaluator/src/manifest.rs
1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{Result, ResultExt, Val, bail, in_description_frame};45pub trait ManifestFormat {6	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7	fn manifest(&self, val: Val) -> Result<String> {8		let mut out = String::new();9		self.manifest_buf(val, &mut out)?;10		Ok(out)11	}12	/// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13	/// the meaning).14	///15	/// Default implementation returns `true`16	fn file_trailing_newline(&self) -> bool {17		true18	}19}20impl<T> ManifestFormat for Box<T>21where22	T: ManifestFormat + ?Sized,23{24	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25		let inner = &**self;26		inner.manifest_buf(val, buf)27	}28	fn file_trailing_newline(&self) -> bool {29		let inner = &**self;30		inner.file_trailing_newline()31	}32}33impl<T> ManifestFormat for &'_ T34where35	T: ManifestFormat + ?Sized,36{37	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38		let inner = &**self;39		inner.manifest_buf(val, buf)40	}41	fn file_trailing_newline(&self) -> bool {42		let inner = &**self;43		inner.file_trailing_newline()44	}45}4647pub struct BlackBoxFormat;48impl ManifestFormat for BlackBoxFormat {49	#[allow(clippy::only_used_in_recursion)]50	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {51		match val {52			Val::Bool(v) => {53				black_box(v);54			}55			val @ Val::Null => {56				black_box(val);57			}58			Val::Str(str_value) => {59				black_box(format!("{str_value}"));60			}61			Val::Num(num_value) => {62				black_box(num_value);63			}64			Val::Arr(arr_value) => {65				for ele in arr_value.iter() {66					let ele = ele?;67					self.manifest_buf(ele, buf)?;68				}69			}70			Val::Obj(obj_value) => {71				for (name, value) in obj_value.iter(72					#[cfg(feature = "exp-preserve-order")]73					true,74				) {75					black_box(name);76					let value = value?;77					self.manifest_buf(value, buf)?;78				}79			}80			Val::Func(func_val) => {81				black_box(func_val);82				bail!("tried to manifest function")83			}84			#[cfg(feature = "exp-bigint")]85			Val::BigInt(n) => {86				black_box(n);87			}88		}89		Ok(())90	}91}9293#[derive(PartialEq, Eq, Clone, Copy)]94enum JsonFormatting {95	// Applied in manifestification96	Manifest,97	/// Used for std.manifestJson98	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest99	Std,100	/// No line breaks, used in `obj+''`101	ToString,102	/// Minified json103	Minify,104}105106pub struct JsonFormat<'s> {107	padding: Cow<'s, str>,108	mtype: JsonFormatting,109	newline: &'s str,110	key_val_sep: &'s str,111	#[cfg(feature = "exp-preserve-order")]112	preserve_order: bool,113	#[cfg(feature = "exp-bigint")]114	preserve_bigints: bool,115}116117impl<'s> JsonFormat<'s> {118	// Minifying format119	pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {120		Self {121			padding: Cow::Borrowed(""),122			mtype: JsonFormatting::Minify,123			newline: "\n",124			key_val_sep: ":",125			#[cfg(feature = "exp-preserve-order")]126			preserve_order,127			#[cfg(feature = "exp-bigint")]128			preserve_bigints: false,129		}130	}131	/// Same format as std.toString, except does not keeps top-level string as-is132	/// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead133	const fn std_to_string_helper() -> Self {134		Self {135			padding: Cow::Borrowed(""),136			mtype: JsonFormatting::ToString,137			newline: "\n",138			key_val_sep: ": ",139			#[cfg(feature = "exp-preserve-order")]140			preserve_order: false,141			#[cfg(feature = "exp-bigint")]142			preserve_bigints: false,143		}144	}145	pub fn std_to_json(146		padding: String,147		newline: &'s str,148		key_val_sep: &'s str,149		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,150	) -> Self {151		Self {152			padding: Cow::Owned(padding),153			mtype: JsonFormatting::Std,154			newline,155			key_val_sep,156			#[cfg(feature = "exp-preserve-order")]157			preserve_order,158			#[cfg(feature = "exp-bigint")]159			preserve_bigints: false,160		}161	}162	// Same format as CLI manifestification163	pub fn cli(164		padding: usize,165		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,166	) -> Self {167		if padding == 0 {168			return Self::minify(169				#[cfg(feature = "exp-preserve-order")]170				preserve_order,171			);172		}173		Self {174			padding: Cow::Owned(" ".repeat(padding)),175			mtype: JsonFormatting::Manifest,176			newline: "\n",177			key_val_sep: ": ",178			#[cfg(feature = "exp-preserve-order")]179			preserve_order,180			#[cfg(feature = "exp-bigint")]181			preserve_bigints: false,182		}183	}184	// Same format as CLI manifestification185	pub fn debug() -> Self {186		Self {187			padding: Cow::Borrowed("   "),188			mtype: JsonFormatting::Manifest,189			newline: "\n",190			key_val_sep: ": ",191			#[cfg(feature = "exp-preserve-order")]192			preserve_order: true,193			#[cfg(feature = "exp-bigint")]194			preserve_bigints: true,195		}196	}197}198impl Default for JsonFormat<'static> {199	fn default() -> Self {200		Self {201			padding: Cow::Borrowed("    "),202			mtype: JsonFormatting::Manifest,203			newline: "\n",204			key_val_sep: ": ",205			#[cfg(feature = "exp-preserve-order")]206			preserve_order: false,207			#[cfg(feature = "exp-bigint")]208			preserve_bigints: false,209		}210	}211}212213pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {214	let mut out = String::new();215	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;216	Ok(out)217}218219#[allow(clippy::too_many_lines)]220fn manifest_json_ex_buf(221	val: &Val,222	buf: &mut String,223	cur_padding: &mut String,224	options: &JsonFormat<'_>,225) -> Result<()> {226	use JsonFormatting::*;227228	let mtype = options.mtype;229	match val {230		Val::Bool(v) => {231			if *v {232				buf.push_str("true");233			} else {234				buf.push_str("false");235			}236		}237		Val::Null => buf.push_str("null"),238		Val::Str(s) => {239			buf.reserve(2 + s.len());240			buf.push('"');241			s.chunks(&mut |c| {242				escape_string_json_buf_raw(c, buf);243			});244			buf.push('"');245		}246		Val::Num(n) => write!(buf, "{n}").unwrap(),247		#[cfg(feature = "exp-bigint")]248		Val::BigInt(n) => {249			if options.preserve_bigints {250				write!(buf, "{n}").unwrap();251			} else {252				write!(buf, "{:?}", n.to_string()).unwrap();253			}254		}255		Val::Arr(items) => {256			buf.push('[');257258			let old_len = cur_padding.len();259			cur_padding.push_str(&options.padding);260261			let mut had_items = false;262			for (i, item) in items.iter().enumerate() {263				had_items = true;264				let item = item.with_description(|| format!("elem <{i}> evaluation"))?;265266				if i != 0 {267					buf.push(',');268				}269				match mtype {270					Manifest | Std => {271						buf.push_str(options.newline);272						buf.push_str(cur_padding);273					}274					ToString if i != 0 => buf.push(' '),275					Minify | ToString => {}276				}277278				in_description_frame(279					|| format!("elem <{i}> manifestification"),280					|| manifest_json_ex_buf(&item, buf, cur_padding, options),281				)?;282			}283284			cur_padding.truncate(old_len);285286			match mtype {287				Manifest | ToString if !had_items => {288					// Empty array as "[ ]"289					buf.push(' ');290				}291				Manifest => {292					buf.push_str(options.newline);293					buf.push_str(cur_padding);294				}295				Std => {296					if !had_items {297						// Stdlib formats empty array as "[\n\n]"298						buf.push_str(options.newline);299					}300					buf.push_str(options.newline);301					buf.push_str(cur_padding);302				}303				Minify | ToString => {}304			}305306			buf.push(']');307		}308		Val::Obj(obj) => {309			obj.run_assertions()?;310			buf.push('{');311312			let old_len = cur_padding.len();313			cur_padding.push_str(&options.padding);314315			let mut had_fields = false;316			for (i, (key, value)) in obj317				.iter(318					#[cfg(feature = "exp-preserve-order")]319					options.preserve_order,320				)321				.enumerate()322			{323				had_fields = true;324				let value = value.with_description(|| format!("field <{key}> evaluation"))?;325326				if i != 0 {327					buf.push(',');328				}329				match mtype {330					Manifest | Std => {331						buf.push_str(options.newline);332						buf.push_str(cur_padding);333					}334					ToString if i != 0 => buf.push(' '),335					Minify | ToString => {}336				}337338				escape_string_json_buf(&key, buf);339				buf.push_str(options.key_val_sep);340				in_description_frame(341					|| format!("field <{key}> manifestification"),342					|| manifest_json_ex_buf(&value, buf, cur_padding, options),343				)?;344			}345346			cur_padding.truncate(old_len);347348			match mtype {349				Manifest | ToString if !had_fields => {350					// Empty object as "{ }"351					buf.push(' ');352				}353				Manifest => {354					buf.push_str(options.newline);355					buf.push_str(cur_padding);356				}357				Std => {358					if !had_fields {359						// Stdlib formats empty object as "{\n\n}"360						buf.push_str(options.newline);361					}362					buf.push_str(options.newline);363					buf.push_str(cur_padding);364				}365				Minify | ToString => {}366			}367368			buf.push('}');369		}370		Val::Func(_) => bail!("tried to manifest function"),371	}372	Ok(())373}374375impl ManifestFormat for JsonFormat<'_> {376	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {377		manifest_json_ex_buf(&val, buf, &mut String::new(), self)378	}379}380381/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,382/// without quoting.383pub struct ToStringFormat;384impl ManifestFormat for ToStringFormat {385	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {386		const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();387		if let Some(str) = val.as_str() {388			out.push_str(&str);389			return Ok(());390		}391		#[cfg(feature = "exp-bigint")]392		if let Some(int) = val.as_bigint() {393			out.push_str(&int.to_str_radix(10));394			return Ok(());395		}396		JSON_TO_STRING.manifest_buf(val, out)397	}398	fn file_trailing_newline(&self) -> bool {399		false400	}401}402pub struct StringFormat;403impl ManifestFormat for StringFormat {404	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {405		let Val::Str(s) = val else {406			bail!(407				"output should be string for string manifest format, got {}",408				val.value_type()409			)410		};411		write!(out, "{s}").unwrap();412		Ok(())413	}414	fn file_trailing_newline(&self) -> bool {415		false416	}417}418419pub struct YamlStreamFormat<I> {420	inner: I,421	c_document_end: bool,422	end_newline: bool,423}424impl<I> YamlStreamFormat<I> {425	pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {426		Self {427			inner,428			c_document_end,429			// Stdlib format always inserts useless newline at the end430			end_newline: true,431		}432	}433	pub fn cli(inner: I) -> Self {434		Self {435			inner,436			c_document_end: true,437			end_newline: false,438		}439	}440}441impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {442	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {443		let Val::Arr(arr) = val else {444			bail!(445				"output should be array for yaml stream format, got {}",446				val.value_type()447			)448		};449		for (i, v) in arr.iter().enumerate() {450			if i != 0 {451				out.push('\n');452			}453			let v = v.with_description(|| format!("elem <{i}> evaluation"))?;454			out.push_str("---\n");455			in_description_frame(456				|| format!("elem <{i}> manifestification"),457				|| self.inner.manifest_buf(v, out),458			)?;459		}460		if self.c_document_end {461			out.push('\n');462			out.push_str("...");463		}464		if self.end_newline {465			out.push('\n');466		}467		Ok(())468	}469}470471pub fn escape_string_json(s: &str) -> String {472	let mut buf = String::new();473	escape_string_json_buf(s, &mut buf);474	buf475}476477// Json string encoding was borrowed from https://github.com/serde-rs/json478479const BB: u8 = b'b'; // \x08480const TT: u8 = b't'; // \x09481const NN: u8 = b'n'; // \x0A482const FF: u8 = b'f'; // \x0C483const RR: u8 = b'r'; // \x0D484const QU: u8 = b'"'; // \x22485const BS: u8 = b'\\'; // \x5C486const UU: u8 = b'u'; // \x00...\x1F except the ones above487const __: u8 = 0;488489// Lookup table of escape sequences. A value of b'x' at index i means that byte490// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.491static ESCAPE: [u8; 256] = [492	//   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F493	UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0494	UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1495	__, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2496	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3497	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4498	__, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5499	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6500	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7501	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8502	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9503	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A504	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B505	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C506	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D507	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E508	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F509];510511pub fn escape_string_json_buf(value: &str, buf: &mut String) {512	buf.reserve_exact(value.len() + 2);513	escape_string_json_buf_raw(value, buf);514}515516fn escape_string_json_buf_raw(value: &str, buf: &mut String) {517	// Safety: we only write correct utf-8 in this function518	let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };519	let bytes = value.as_bytes();520521	let mut start = 0;522523	for (i, &byte) in bytes.iter().enumerate() {524		let escape = ESCAPE[byte as usize];525		if escape == __ {526			continue;527		}528529		if start < i {530			buf.extend_from_slice(&bytes[start..i]);531		}532		start = i + 1;533534		match escape {535			self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {536				buf.extend_from_slice(&[b'\\', escape]);537			}538			self::UU => {539				static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";540				let bytes = &[541					b'\\',542					b'u',543					b'0',544					b'0',545					HEX_DIGITS[(byte >> 4) as usize],546					HEX_DIGITS[(byte & 0xF) as usize],547				];548				buf.extend_from_slice(bytes);549			}550			_ => unreachable!(),551		}552	}553554	if start == bytes.len() {555		return;556	}557558	buf.extend_from_slice(&bytes[start..]);559}