git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

source

crates/jrsonnet-evaluator/src/manifest.rs13.8 KiBsourcehistory
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}