git.delta.rocks / jrsonnet / refs/commits / 259b3abbb098

difftreelog

fix ensure stack for deep manifest recursion

qooxlqlmYaroslav Bolyukin2026-04-25parent: #f3c8ed7.patch.diff
in: master

1 file changed

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					#[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	buf.push('"');514	escape_string_json_buf_raw(value, buf);515	buf.push('"');516}517518fn escape_string_json_buf_raw(value: &str, buf: &mut String) {519	// Safety: we only write correct utf-8 in this function520	let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };521	let bytes = value.as_bytes();522523	let mut start = 0;524525	for (i, &byte) in bytes.iter().enumerate() {526		let escape = ESCAPE[byte as usize];527		if escape == __ {528			continue;529		}530531		if start < i {532			buf.extend_from_slice(&bytes[start..i]);533		}534		start = i + 1;535536		match escape {537			self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {538				buf.extend_from_slice(&[b'\\', escape]);539			}540			self::UU => {541				static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";542				let bytes = &[543					b'\\',544					b'u',545					b'0',546					b'0',547					HEX_DIGITS[(byte >> 4) as usize],548					HEX_DIGITS[(byte & 0xF) as usize],549				];550				buf.extend_from_slice(bytes);551			}552			_ => unreachable!(),553		}554	}555556	if start == bytes.len() {557		return;558	}559560	buf.extend_from_slice(&bytes[start..]);561}
after · crates/jrsonnet-evaluator/src/manifest.rs
1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{4	bail, evaluate::ensure_sufficient_stack, in_description_frame, Error,5	Result, ResultExt, Val,6};78pub trait ManifestFormat {9	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;10	fn manifest(&self, val: Val) -> Result<String> {11		let mut out = String::new();12		self.manifest_buf(val, &mut out)?;13		Ok(out)14	}15	/// When outputing to file, is it safe to append a trailing newline (I.e newline won't change16	/// the meaning).17	///18	/// Default implementation returns `true`19	fn file_trailing_newline(&self) -> bool {20		true21	}22}23impl<T> ManifestFormat for Box<T>24where25	T: ManifestFormat + ?Sized,26{27	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {28		let inner = &**self;29		inner.manifest_buf(val, buf)30	}31	fn file_trailing_newline(&self) -> bool {32		let inner = &**self;33		inner.file_trailing_newline()34	}35}36impl<T> ManifestFormat for &'_ T37where38	T: ManifestFormat + ?Sized,39{40	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {41		let inner = &**self;42		inner.manifest_buf(val, buf)43	}44	fn file_trailing_newline(&self) -> bool {45		let inner = &**self;46		inner.file_trailing_newline()47	}48}4950pub struct BlackBoxFormat;51impl ManifestFormat for BlackBoxFormat {52	#[allow(clippy::only_used_in_recursion)]53	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {54		match val {55			Val::Bool(v) => {56				black_box(v);57			}58			val @ Val::Null => {59				black_box(val);60			}61			Val::Str(str_value) => {62				black_box(format!("{str_value}"));63			}64			Val::Num(num_value) => {65				black_box(num_value);66			}67			Val::Arr(arr_value) => {68				for ele in arr_value.iter() {69					let ele = ele?;70					self.manifest_buf(ele, buf)?;71				}72			}73			Val::Obj(obj_value) => {74				for (name, value) in obj_value.iter(75					#[cfg(feature = "exp-preserve-order")]76					true,77				) {78					black_box(name);79					let value = value?;80					self.manifest_buf(value, buf)?;81				}82			}83			Val::Func(func_val) => {84				black_box(func_val);85				bail!("tried to manifest function")86			}87			#[cfg(feature = "exp-bigint")]88			Val::BigInt(n) => {89				black_box(n);90			}91		}92		Ok(())93	}94}9596#[derive(PartialEq, Eq, Clone, Copy)]97enum JsonFormatting {98	// Applied in manifestification99	Manifest,100	/// Used for std.manifestJson101	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest102	Std,103	/// No line breaks, used in `obj+''`104	ToString,105	/// Minified json106	Minify,107}108109pub struct JsonFormat<'s> {110	padding: Cow<'s, str>,111	mtype: JsonFormatting,112	newline: &'s str,113	key_val_sep: &'s str,114	#[cfg(feature = "exp-preserve-order")]115	preserve_order: bool,116	#[cfg(feature = "exp-bigint")]117	preserve_bigints: bool,118}119120impl<'s> JsonFormat<'s> {121	// Minifying format122	pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {123		Self {124			padding: Cow::Borrowed(""),125			mtype: JsonFormatting::Minify,126			newline: "\n",127			key_val_sep: ":",128			#[cfg(feature = "exp-preserve-order")]129			preserve_order,130			#[cfg(feature = "exp-bigint")]131			preserve_bigints: false,132		}133	}134	/// Same format as std.toString, except does not keeps top-level string as-is135	/// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead136	const fn std_to_string_helper() -> Self {137		Self {138			padding: Cow::Borrowed(""),139			mtype: JsonFormatting::ToString,140			newline: "\n",141			key_val_sep: ": ",142			#[cfg(feature = "exp-preserve-order")]143			preserve_order: false,144			#[cfg(feature = "exp-bigint")]145			preserve_bigints: false,146		}147	}148	pub fn std_to_json(149		padding: String,150		newline: &'s str,151		key_val_sep: &'s str,152		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,153	) -> Self {154		Self {155			padding: Cow::Owned(padding),156			mtype: JsonFormatting::Std,157			newline,158			key_val_sep,159			#[cfg(feature = "exp-preserve-order")]160			preserve_order,161			#[cfg(feature = "exp-bigint")]162			preserve_bigints: false,163		}164	}165	// Same format as CLI manifestification166	pub fn cli(167		padding: usize,168		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,169	) -> Self {170		if padding == 0 {171			return Self::minify(172				#[cfg(feature = "exp-preserve-order")]173				preserve_order,174			);175		}176		Self {177			padding: Cow::Owned(" ".repeat(padding)),178			mtype: JsonFormatting::Manifest,179			newline: "\n",180			key_val_sep: ": ",181			#[cfg(feature = "exp-preserve-order")]182			preserve_order,183			#[cfg(feature = "exp-bigint")]184			preserve_bigints: false,185		}186	}187	// Same format as CLI manifestification188	pub fn debug() -> Self {189		Self {190			padding: Cow::Borrowed("   "),191			mtype: JsonFormatting::Manifest,192			newline: "\n",193			key_val_sep: ": ",194			#[cfg(feature = "exp-preserve-order")]195			preserve_order: true,196			#[cfg(feature = "exp-bigint")]197			preserve_bigints: true,198		}199	}200}201impl Default for JsonFormat<'static> {202	fn default() -> Self {203		Self {204			padding: Cow::Borrowed("    "),205			mtype: JsonFormatting::Manifest,206			newline: "\n",207			key_val_sep: ": ",208			#[cfg(feature = "exp-preserve-order")]209			preserve_order: false,210			#[cfg(feature = "exp-bigint")]211			preserve_bigints: false,212		}213	}214}215216pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {217	let mut out = String::new();218	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;219	Ok(out)220}221222#[allow(clippy::too_many_lines)]223fn manifest_json_ex_buf(224	val: &Val,225	buf: &mut String,226	cur_padding: &mut String,227	options: &JsonFormat<'_>,228) -> Result<()> {229	use JsonFormatting::*;230231	let mtype = options.mtype;232	match val {233		Val::Bool(v) => {234			if *v {235				buf.push_str("true");236			} else {237				buf.push_str("false");238			}239		}240		Val::Null => buf.push_str("null"),241		Val::Str(s) => {242			buf.reserve(2 + s.len());243			buf.push('"');244			s.chunks(&mut |c| {245				escape_string_json_buf_raw(c, buf);246			});247			buf.push('"');248		}249		Val::Num(n) => write!(buf, "{n}").unwrap(),250		#[cfg(feature = "exp-bigint")]251		Val::BigInt(n) => {252			if options.preserve_bigints {253				write!(buf, "{n}").unwrap();254			} else {255				write!(buf, "{:?}", n.to_string()).unwrap();256			}257		}258		Val::Arr(items) => ensure_sufficient_stack(|| {259			buf.push('[');260261			let old_len = cur_padding.len();262			cur_padding.push_str(&options.padding);263264			let mut had_items = false;265			for (i, item) in items.iter().enumerate() {266				had_items = true;267				let item = item.with_description(|| format!("elem <{i}> evaluation"))?;268269				if i != 0 {270					buf.push(',');271				}272				match mtype {273					Manifest | Std => {274						buf.push_str(options.newline);275						buf.push_str(cur_padding);276					}277					ToString if i != 0 => buf.push(' '),278					Minify | ToString => {}279				}280281				in_description_frame(282					|| format!("elem <{i}> manifestification"),283					|| manifest_json_ex_buf(&item, buf, cur_padding, options),284				)?;285			}286287			cur_padding.truncate(old_len);288289			match mtype {290				Manifest | ToString if !had_items => {291					// Empty array as "[ ]"292					buf.push(' ');293				}294				Manifest => {295					buf.push_str(options.newline);296					buf.push_str(cur_padding);297				}298				Std => {299					if !had_items {300						// Stdlib formats empty array as "[\n\n]"301						buf.push_str(options.newline);302					}303					buf.push_str(options.newline);304					buf.push_str(cur_padding);305				}306				Minify | ToString => {}307			}308309			buf.push(']');310			Ok::<_, Error>(())311		})?,312		Val::Obj(obj) => ensure_sufficient_stack(|| {313			obj.run_assertions()?;314			buf.push('{');315316			let old_len = cur_padding.len();317			cur_padding.push_str(&options.padding);318319			let mut had_fields = false;320			for (i, (key, value)) in obj321				.iter(322					#[cfg(feature = "exp-preserve-order")]323					options.preserve_order,324				)325				.enumerate()326			{327				had_fields = true;328				let value = value.with_description(|| format!("field <{key}> evaluation"))?;329330				if i != 0 {331					buf.push(',');332				}333				match mtype {334					Manifest | Std => {335						buf.push_str(options.newline);336						buf.push_str(cur_padding);337					}338					ToString if i != 0 => buf.push(' '),339					Minify | ToString => {}340				}341342				escape_string_json_buf(&key, buf);343				buf.push_str(options.key_val_sep);344				in_description_frame(345					|| format!("field <{key}> manifestification"),346					|| manifest_json_ex_buf(&value, buf, cur_padding, options),347				)?;348			}349350			cur_padding.truncate(old_len);351352			match mtype {353				Manifest | ToString if !had_fields => {354					// Empty object as "{ }"355					buf.push(' ');356				}357				Manifest => {358					buf.push_str(options.newline);359					buf.push_str(cur_padding);360				}361				Std => {362					if !had_fields {363						// Stdlib formats empty object as "{\n\n}"364						buf.push_str(options.newline);365					}366					buf.push_str(options.newline);367					buf.push_str(cur_padding);368				}369				Minify | ToString => {}370			}371372			buf.push('}');373			Ok::<_, Error>(())374		})?,375		Val::Func(_) => bail!("tried to manifest function"),376	}377	Ok(())378}379380impl ManifestFormat for JsonFormat<'_> {381	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {382		manifest_json_ex_buf(&val, buf, &mut String::new(), self)383	}384}385386/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,387/// without quoting.388pub struct ToStringFormat;389impl ManifestFormat for ToStringFormat {390	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {391		const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();392		if let Some(str) = val.as_str() {393			out.push_str(&str);394			return Ok(());395		}396		#[cfg(feature = "exp-bigint")]397		if let Some(int) = val.as_bigint() {398			out.push_str(&int.to_str_radix(10));399			return Ok(());400		}401		JSON_TO_STRING.manifest_buf(val, out)402	}403	fn file_trailing_newline(&self) -> bool {404		false405	}406}407pub struct StringFormat;408impl ManifestFormat for StringFormat {409	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {410		let Val::Str(s) = val else {411			bail!(412				"output should be string for string manifest format, got {}",413				val.value_type()414			)415		};416		write!(out, "{s}").unwrap();417		Ok(())418	}419	fn file_trailing_newline(&self) -> bool {420		false421	}422}423424pub struct YamlStreamFormat<I> {425	inner: I,426	c_document_end: bool,427	end_newline: bool,428}429impl<I> YamlStreamFormat<I> {430	pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {431		Self {432			inner,433			c_document_end,434			// Stdlib format always inserts useless newline at the end435			end_newline: true,436		}437	}438	pub fn cli(inner: I) -> Self {439		Self {440			inner,441			c_document_end: true,442			end_newline: false,443		}444	}445}446impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {447	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {448		let Val::Arr(arr) = val else {449			bail!(450				"output should be array for yaml stream format, got {}",451				val.value_type()452			)453		};454		for (i, v) in arr.iter().enumerate() {455			if i != 0 {456				out.push('\n');457			}458			let v = v.with_description(|| format!("elem <{i}> evaluation"))?;459			out.push_str("---\n");460			in_description_frame(461				|| format!("elem <{i}> manifestification"),462				|| self.inner.manifest_buf(v, out),463			)?;464		}465		if self.c_document_end {466			out.push('\n');467			out.push_str("...");468		}469		if self.end_newline {470			out.push('\n');471		}472		Ok(())473	}474}475476pub fn escape_string_json(s: &str) -> String {477	let mut buf = String::new();478	escape_string_json_buf(s, &mut buf);479	buf480}481482// Json string encoding was borrowed from https://github.com/serde-rs/json483484const BB: u8 = b'b'; // \x08485const TT: u8 = b't'; // \x09486const NN: u8 = b'n'; // \x0A487const FF: u8 = b'f'; // \x0C488const RR: u8 = b'r'; // \x0D489const QU: u8 = b'"'; // \x22490const BS: u8 = b'\\'; // \x5C491const UU: u8 = b'u'; // \x00...\x1F except the ones above492const __: u8 = 0;493494// Lookup table of escape sequences. A value of b'x' at index i means that byte495// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.496static ESCAPE: [u8; 256] = [497	//   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F498	UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0499	UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1500	__, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2501	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3502	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4503	__, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5504	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6505	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7506	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8507	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9508	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A509	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B510	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C511	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D512	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E513	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F514];515516pub fn escape_string_json_buf(value: &str, buf: &mut String) {517	buf.reserve_exact(value.len() + 2);518	buf.push('"');519	escape_string_json_buf_raw(value, buf);520	buf.push('"');521}522523fn escape_string_json_buf_raw(value: &str, buf: &mut String) {524	// Safety: we only write correct utf-8 in this function525	let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };526	let bytes = value.as_bytes();527528	let mut start = 0;529530	for (i, &byte) in bytes.iter().enumerate() {531		let escape = ESCAPE[byte as usize];532		if escape == __ {533			continue;534		}535536		if start < i {537			buf.extend_from_slice(&bytes[start..i]);538		}539		start = i + 1;540541		match escape {542			self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {543				buf.extend_from_slice(&[b'\\', escape]);544			}545			self::UU => {546				static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";547				let bytes = &[548					b'\\',549					b'u',550					b'0',551					b'0',552					HEX_DIGITS[(byte >> 4) as usize],553					HEX_DIGITS[(byte & 0xF) as usize],554				];555				buf.extend_from_slice(bytes);556			}557			_ => unreachable!(),558		}559	}560561	if start == bytes.len() {562		return;563	}564565	buf.extend_from_slice(&bytes[start..]);566}