difftreelog
fix ensure stack for deep manifest recursion
in: master
1 file changed
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 #[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}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}