difftreelog
fix BlackBox formatter support for bigint
in: master
2 files changed
crates/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>;
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth1use 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}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}