1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{4 Error, Result, ResultExt, Val, bail, evaluate::ensure_sufficient_stack, in_description_frame,5};67pub trait ManifestFormat {8 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;9 fn manifest(&self, val: Val) -> Result<String> {10 let mut out = String::new();11 self.manifest_buf(val, &mut out)?;12 Ok(out)13 }14 15 16 17 18 fn file_trailing_newline(&self) -> bool {19 true20 }21}22impl<T> ManifestFormat for Box<T>23where24 T: ManifestFormat + ?Sized,25{26 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {27 let inner = &**self;28 inner.manifest_buf(val, buf)29 }30 fn file_trailing_newline(&self) -> bool {31 let inner = &**self;32 inner.file_trailing_newline()33 }34}35impl<T> ManifestFormat for &'_ T36where37 T: ManifestFormat + ?Sized,38{39 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {40 let inner = &**self;41 inner.manifest_buf(val, buf)42 }43 fn file_trailing_newline(&self) -> bool {44 let inner = &**self;45 inner.file_trailing_newline()46 }47}4849pub struct BlackBoxFormat;50impl ManifestFormat for BlackBoxFormat {51 #[allow(clippy::only_used_in_recursion)]52 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {53 match val {54 Val::Bool(v) => {55 black_box(v);56 }57 val @ Val::Null => {58 black_box(val);59 }60 Val::Str(str_value) => {61 black_box(format!("{str_value}"));62 }63 Val::Num(num_value) => {64 black_box(num_value);65 }66 Val::Arr(arr_value) => {67 for ele in arr_value.iter() {68 let ele = ele?;69 self.manifest_buf(ele, buf)?;70 }71 }72 Val::Obj(obj_value) => {73 for (name, value) in obj_value.iter(74 #[cfg(feature = "exp-preserve-order")]75 true,76 ) {77 black_box(name);78 let value = value?;79 self.manifest_buf(value, buf)?;80 }81 }82 Val::Func(func_val) => {83 black_box(func_val);84 bail!("tried to manifest function")85 }86 #[cfg(feature = "exp-bigint")]87 Val::BigInt(n) => {88 black_box(n);89 }90 }91 Ok(())92 }93}9495#[derive(PartialEq, Eq, Clone, Copy)]96enum JsonFormatting {97 98 Manifest,99 100 101 Std,102 103 ToString,104 105 Minify,106}107108pub struct JsonFormat<'s> {109 padding: Cow<'s, str>,110 mtype: JsonFormatting,111 newline: &'s str,112 key_val_sep: &'s str,113 #[cfg(feature = "exp-preserve-order")]114 preserve_order: bool,115 #[cfg(feature = "exp-bigint")]116 preserve_bigints: bool,117}118119impl<'s> JsonFormat<'s> {120 121 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {122 Self {123 padding: Cow::Borrowed(""),124 mtype: JsonFormatting::Minify,125 newline: "\n",126 key_val_sep: ":",127 #[cfg(feature = "exp-preserve-order")]128 preserve_order,129 #[cfg(feature = "exp-bigint")]130 preserve_bigints: false,131 }132 }133 134 135 const fn std_to_string_helper() -> Self {136 Self {137 padding: Cow::Borrowed(""),138 mtype: JsonFormatting::ToString,139 newline: "\n",140 key_val_sep: ": ",141 #[cfg(feature = "exp-preserve-order")]142 preserve_order: false,143 #[cfg(feature = "exp-bigint")]144 preserve_bigints: false,145 }146 }147 pub fn std_to_json(148 padding: String,149 newline: &'s str,150 key_val_sep: &'s str,151 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,152 ) -> Self {153 Self {154 padding: Cow::Owned(padding),155 mtype: JsonFormatting::Std,156 newline,157 key_val_sep,158 #[cfg(feature = "exp-preserve-order")]159 preserve_order,160 #[cfg(feature = "exp-bigint")]161 preserve_bigints: false,162 }163 }164 165 pub fn cli(166 padding: usize,167 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,168 ) -> Self {169 if padding == 0 {170 return Self::minify(171 #[cfg(feature = "exp-preserve-order")]172 preserve_order,173 );174 }175 Self {176 padding: Cow::Owned(" ".repeat(padding)),177 mtype: JsonFormatting::Manifest,178 newline: "\n",179 key_val_sep: ": ",180 #[cfg(feature = "exp-preserve-order")]181 preserve_order,182 #[cfg(feature = "exp-bigint")]183 preserve_bigints: false,184 }185 }186 187 pub fn debug() -> Self {188 Self {189 padding: Cow::Borrowed(" "),190 mtype: JsonFormatting::Manifest,191 newline: "\n",192 key_val_sep: ": ",193 #[cfg(feature = "exp-preserve-order")]194 preserve_order: true,195 #[cfg(feature = "exp-bigint")]196 preserve_bigints: true,197 }198 }199}200impl Default for JsonFormat<'static> {201 fn default() -> Self {202 Self {203 padding: Cow::Borrowed(" "),204 mtype: JsonFormatting::Manifest,205 newline: "\n",206 key_val_sep: ": ",207 #[cfg(feature = "exp-preserve-order")]208 preserve_order: false,209 #[cfg(feature = "exp-bigint")]210 preserve_bigints: false,211 }212 }213}214215pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {216 let mut out = String::new();217 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;218 Ok(out)219}220221#[allow(clippy::too_many_lines)]222fn manifest_json_ex_buf(223 val: &Val,224 buf: &mut String,225 cur_padding: &mut String,226 options: &JsonFormat<'_>,227) -> Result<()> {228 use JsonFormatting::*;229230 let mtype = options.mtype;231 match val {232 Val::Bool(v) => {233 if *v {234 buf.push_str("true");235 } else {236 buf.push_str("false");237 }238 }239 Val::Null => buf.push_str("null"),240 Val::Str(s) => {241 buf.reserve(2 + s.len());242 buf.push('"');243 s.chunks(&mut |c| {244 escape_string_json_buf_raw(c, buf);245 });246 buf.push('"');247 }248 Val::Num(n) => write!(buf, "{n}").unwrap(),249 #[cfg(feature = "exp-bigint")]250 Val::BigInt(n) => {251 if options.preserve_bigints {252 write!(buf, "{n}").unwrap();253 } else {254 write!(buf, "{:?}", n.to_string()).unwrap();255 }256 }257 Val::Arr(items) => ensure_sufficient_stack(|| {258 buf.push('[');259260 let old_len = cur_padding.len();261 cur_padding.push_str(&options.padding);262263 let mut had_items = false;264 for (i, item) in items.iter().enumerate() {265 had_items = true;266 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;267268 if i != 0 {269 buf.push(',');270 }271 match mtype {272 Manifest | Std => {273 buf.push_str(options.newline);274 buf.push_str(cur_padding);275 }276 ToString if i != 0 => buf.push(' '),277 Minify | ToString => {}278 }279280 in_description_frame(281 || format!("elem <{i}> manifestification"),282 || manifest_json_ex_buf(&item, buf, cur_padding, options),283 )?;284 }285286 cur_padding.truncate(old_len);287288 match mtype {289 Manifest | ToString if !had_items => {290 291 buf.push(' ');292 }293 Manifest => {294 buf.push_str(options.newline);295 buf.push_str(cur_padding);296 }297 Std => {298 if !had_items {299 300 buf.push_str(options.newline);301 }302 buf.push_str(options.newline);303 buf.push_str(cur_padding);304 }305 Minify | ToString => {}306 }307308 buf.push(']');309 Ok::<_, Error>(())310 })?,311 Val::Obj(obj) => ensure_sufficient_stack(|| {312 obj.run_assertions()?;313 buf.push('{');314315 let old_len = cur_padding.len();316 cur_padding.push_str(&options.padding);317318 let mut had_fields = false;319 for (i, (key, value)) in obj320 .iter(321 #[cfg(feature = "exp-preserve-order")]322 options.preserve_order,323 )324 .enumerate()325 {326 had_fields = true;327 let value = value.with_description(|| format!("field <{key}> evaluation"))?;328329 if i != 0 {330 buf.push(',');331 }332 match mtype {333 Manifest | Std => {334 buf.push_str(options.newline);335 buf.push_str(cur_padding);336 }337 ToString if i != 0 => buf.push(' '),338 Minify | ToString => {}339 }340341 escape_string_json_buf(&key, buf);342 buf.push_str(options.key_val_sep);343 in_description_frame(344 || format!("field <{key}> manifestification"),345 || manifest_json_ex_buf(&value, buf, cur_padding, options),346 )?;347 }348349 cur_padding.truncate(old_len);350351 match mtype {352 Manifest | ToString if !had_fields => {353 354 buf.push(' ');355 }356 Manifest => {357 buf.push_str(options.newline);358 buf.push_str(cur_padding);359 }360 Std => {361 if !had_fields {362 363 buf.push_str(options.newline);364 }365 buf.push_str(options.newline);366 buf.push_str(cur_padding);367 }368 Minify | ToString => {}369 }370371 buf.push('}');372 Ok::<_, Error>(())373 })?,374 Val::Func(_) => bail!("tried to manifest function"),375 }376 Ok(())377}378379impl ManifestFormat for JsonFormat<'_> {380 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {381 manifest_json_ex_buf(&val, buf, &mut String::new(), self)382 }383}384385386387pub struct ToStringFormat;388impl ManifestFormat for ToStringFormat {389 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {390 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();391 if let Some(str) = val.as_str() {392 out.push_str(&str);393 return Ok(());394 }395 #[cfg(feature = "exp-bigint")]396 if let Some(int) = val.as_bigint() {397 out.push_str(&int.to_str_radix(10));398 return Ok(());399 }400 JSON_TO_STRING.manifest_buf(val, out)401 }402 fn file_trailing_newline(&self) -> bool {403 false404 }405}406pub struct StringFormat;407impl ManifestFormat for StringFormat {408 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {409 let Val::Str(s) = val else {410 bail!(411 "output should be string for string manifest format, got {}",412 val.value_type()413 )414 };415 write!(out, "{s}").unwrap();416 Ok(())417 }418 fn file_trailing_newline(&self) -> bool {419 false420 }421}422423pub struct YamlStreamFormat<I> {424 inner: I,425 c_document_end: bool,426 end_newline: bool,427}428impl<I> YamlStreamFormat<I> {429 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {430 Self {431 inner,432 c_document_end,433 434 end_newline: true,435 }436 }437 pub fn cli(inner: I) -> Self {438 Self {439 inner,440 c_document_end: true,441 end_newline: false,442 }443 }444}445impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {446 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {447 let Val::Arr(arr) = val else {448 bail!(449 "output should be array for yaml stream format, got {}",450 val.value_type()451 )452 };453 for (i, v) in arr.iter().enumerate() {454 if i != 0 {455 out.push('\n');456 }457 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;458 out.push_str("---\n");459 in_description_frame(460 || format!("elem <{i}> manifestification"),461 || self.inner.manifest_buf(v, out),462 )?;463 }464 if self.c_document_end {465 out.push('\n');466 out.push_str("...");467 }468 if self.end_newline {469 out.push('\n');470 }471 Ok(())472 }473}474475pub fn escape_string_json(s: &str) -> String {476 let mut buf = String::new();477 escape_string_json_buf(s, &mut buf);478 buf479}480481482483const BB: u8 = b'b'; 484const TT: u8 = b't'; 485const NN: u8 = b'n'; 486const FF: u8 = b'f'; 487const RR: u8 = b'r'; 488const QU: u8 = b'"'; 489const BS: u8 = b'\\'; 490const UU: u8 = b'u'; 491const __: u8 = 0;492493494495static ESCAPE: [u8; 256] = [496 497 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 498 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 499 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 500 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 502 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 503 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 504 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 505 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 506 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 507 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 508 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 509 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 510 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 511 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 512 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 513];514515pub fn escape_string_json_buf(value: &str, buf: &mut String) {516 buf.reserve_exact(value.len() + 2);517 buf.push('"');518 escape_string_json_buf_raw(value, buf);519 buf.push('"');520}521522fn escape_string_json_buf_raw(value: &str, buf: &mut String) {523 524 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };525 let bytes = value.as_bytes();526527 let mut start = 0;528529 for (i, &byte) in bytes.iter().enumerate() {530 let escape = ESCAPE[byte as usize];531 if escape == __ {532 continue;533 }534535 if start < i {536 buf.extend_from_slice(&bytes[start..i]);537 }538 start = i + 1;539540 match escape {541 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {542 buf.extend_from_slice(&[b'\\', escape]);543 }544 self::UU => {545 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";546 let bytes = &[547 b'\\',548 b'u',549 b'0',550 b'0',551 HEX_DIGITS[(byte >> 4) as usize],552 HEX_DIGITS[(byte & 0xF) as usize],553 ];554 buf.extend_from_slice(bytes);555 }556 _ => unreachable!(),557 }558 }559560 if start == bytes.len() {561 return;562 }563564 buf.extend_from_slice(&bytes[start..]);565}