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 16 17 18 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 99 Manifest,100 101 102 Std,103 104 ToString,105 106 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 122 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 135 136 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 166 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 188 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 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 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 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 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}385386387388pub 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 435 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}481482483484const BB: u8 = b'b'; 485const TT: u8 = b't'; 486const NN: u8 = b'n'; 487const FF: u8 = b'f'; 488const RR: u8 = b'r'; 489const QU: u8 = b'"'; 490const BS: u8 = b'\\'; 491const UU: u8 = b'u'; 492const __: u8 = 0;493494495496static ESCAPE: [u8; 256] = [497 498 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 499 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 500 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 502 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 503 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 504 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 505 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 506 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 507 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 508 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 509 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 510 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 511 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 512 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 513 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 514];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 525 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}