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

difftreelog

fix BlackBox formatter support for bigint

ptyvotvzYaroslav Bolyukin2026-04-05parent: #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}