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}15impl<T> ManifestFormat for Box<T>16where17 T: ManifestFormat + ?Sized,18{19 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {20 let inner = &**self;21 inner.manifest_buf(val, buf)22 }23}24impl<T> ManifestFormat for &'_ T25where26 T: ManifestFormat + ?Sized,27{28 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {29 let inner = &**self;30 inner.manifest_buf(val, buf)31 }32}3334pub struct BlackBoxFormat;35impl ManifestFormat for BlackBoxFormat {36 #[allow(clippy::only_used_in_recursion)]37 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38 match val {39 Val::Bool(v) => {40 black_box(v);41 }42 val @ Val::Null => {43 black_box(val);44 }45 Val::Str(str_value) => {46 black_box(format!("{str_value}"));47 }48 Val::Num(num_value) => {49 black_box(num_value);50 }51 Val::Arr(arr_value) => {52 for ele in arr_value.iter() {53 let ele = ele?;54 self.manifest_buf(ele, buf)?;55 }56 }57 Val::Obj(obj_value) => {58 for (name, value) in obj_value.iter(59 #[cfg(feature = "exp-preserve-order")]60 true,61 ) {62 black_box(name);63 let value = value?;64 self.manifest_buf(value, buf)?;65 }66 }67 Val::Func(func_val) => {68 black_box(func_val);69 bail!("tried to manifest function")70 }71 #[cfg(feature = "exp-bigint")]72 Val::BigInt(n) => {73 black_box(n);74 }75 }76 Ok(())77 }78}7980#[derive(PartialEq, Eq, Clone, Copy)]81enum JsonFormatting {82 83 Manifest,84 85 86 Std,87 88 ToString,89 90 Minify,91}9293pub struct JsonFormat<'s> {94 padding: Cow<'s, str>,95 mtype: JsonFormatting,96 newline: &'s str,97 key_val_sep: &'s str,98 #[cfg(feature = "exp-preserve-order")]99 preserve_order: bool,100 #[cfg(feature = "exp-bigint")]101 preserve_bigints: bool,102}103104impl<'s> JsonFormat<'s> {105 106 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {107 Self {108 padding: Cow::Borrowed(""),109 mtype: JsonFormatting::Minify,110 newline: "\n",111 key_val_sep: ":",112 #[cfg(feature = "exp-preserve-order")]113 preserve_order,114 #[cfg(feature = "exp-bigint")]115 preserve_bigints: false,116 }117 }118 119 120 const fn std_to_string_helper() -> Self {121 Self {122 padding: Cow::Borrowed(""),123 mtype: JsonFormatting::ToString,124 newline: "\n",125 key_val_sep: ": ",126 #[cfg(feature = "exp-preserve-order")]127 preserve_order: false,128 #[cfg(feature = "exp-bigint")]129 preserve_bigints: false,130 }131 }132 pub fn std_to_json(133 padding: String,134 newline: &'s str,135 key_val_sep: &'s str,136 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,137 ) -> Self {138 Self {139 padding: Cow::Owned(padding),140 mtype: JsonFormatting::Std,141 newline,142 key_val_sep,143 #[cfg(feature = "exp-preserve-order")]144 preserve_order,145 #[cfg(feature = "exp-bigint")]146 preserve_bigints: false,147 }148 }149 150 pub fn cli(151 padding: usize,152 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,153 ) -> Self {154 if padding == 0 {155 return Self::minify(156 #[cfg(feature = "exp-preserve-order")]157 preserve_order,158 );159 }160 Self {161 padding: Cow::Owned(" ".repeat(padding)),162 mtype: JsonFormatting::Manifest,163 newline: "\n",164 key_val_sep: ": ",165 #[cfg(feature = "exp-preserve-order")]166 preserve_order,167 #[cfg(feature = "exp-bigint")]168 preserve_bigints: false,169 }170 }171 172 pub fn debug() -> Self {173 Self {174 padding: Cow::Borrowed(" "),175 mtype: JsonFormatting::Manifest,176 newline: "\n",177 key_val_sep: ": ",178 #[cfg(feature = "exp-preserve-order")]179 preserve_order: true,180 #[cfg(feature = "exp-bigint")]181 preserve_bigints: true,182 }183 }184}185impl Default for JsonFormat<'static> {186 fn default() -> Self {187 Self {188 padding: Cow::Borrowed(" "),189 mtype: JsonFormatting::Manifest,190 newline: "\n",191 key_val_sep: ": ",192 #[cfg(feature = "exp-preserve-order")]193 preserve_order: false,194 #[cfg(feature = "exp-bigint")]195 preserve_bigints: false,196 }197 }198}199200pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {201 let mut out = String::new();202 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;203 Ok(out)204}205206#[allow(clippy::too_many_lines)]207fn manifest_json_ex_buf(208 val: &Val,209 buf: &mut String,210 cur_padding: &mut String,211 options: &JsonFormat<'_>,212) -> Result<()> {213 use JsonFormatting::*;214215 let mtype = options.mtype;216 match val {217 Val::Bool(v) => {218 if *v {219 buf.push_str("true");220 } else {221 buf.push_str("false");222 }223 }224 Val::Null => buf.push_str("null"),225 Val::Str(s) => {226 buf.reserve(2 + s.len());227 buf.push('"');228 s.chunks(&mut |c| {229 escape_string_json_buf_raw(c, buf);230 });231 buf.push('"');232 }233 Val::Num(n) => write!(buf, "{n}").unwrap(),234 #[cfg(feature = "exp-bigint")]235 Val::BigInt(n) => {236 if options.preserve_bigints {237 write!(buf, "{n}").unwrap();238 } else {239 write!(buf, "{:?}", n.to_string()).unwrap();240 }241 }242 Val::Arr(items) => ensure_sufficient_stack(|| {243 buf.push('[');244245 let old_len = cur_padding.len();246 cur_padding.push_str(&options.padding);247248 let mut had_items = false;249 for (i, item) in items.iter().enumerate() {250 had_items = true;251 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;252253 if i != 0 {254 buf.push(',');255 }256 match mtype {257 Manifest | Std => {258 buf.push_str(options.newline);259 buf.push_str(cur_padding);260 }261 ToString if i != 0 => buf.push(' '),262 Minify | ToString => {}263 }264265 in_description_frame(266 || format!("elem <{i}> manifestification"),267 || manifest_json_ex_buf(&item, buf, cur_padding, options),268 )?;269 }270271 cur_padding.truncate(old_len);272273 match mtype {274 Manifest | ToString if !had_items => {275 276 buf.push(' ');277 }278 Manifest => {279 buf.push_str(options.newline);280 buf.push_str(cur_padding);281 }282 Std => {283 if !had_items {284 285 buf.push_str(options.newline);286 }287 buf.push_str(options.newline);288 buf.push_str(cur_padding);289 }290 Minify | ToString => {}291 }292293 buf.push(']');294 Ok::<_, Error>(())295 })?,296 Val::Obj(obj) => ensure_sufficient_stack(|| {297 obj.run_assertions()?;298 buf.push('{');299300 let old_len = cur_padding.len();301 cur_padding.push_str(&options.padding);302303 let mut had_fields = false;304 for (i, (key, value)) in obj305 .iter(306 #[cfg(feature = "exp-preserve-order")]307 options.preserve_order,308 )309 .enumerate()310 {311 had_fields = true;312 let value = value.with_description(|| format!("field <{key}> evaluation"))?;313314 if i != 0 {315 buf.push(',');316 }317 match mtype {318 Manifest | Std => {319 buf.push_str(options.newline);320 buf.push_str(cur_padding);321 }322 ToString if i != 0 => buf.push(' '),323 Minify | ToString => {}324 }325326 escape_string_json_buf(&key, buf);327 buf.push_str(options.key_val_sep);328 in_description_frame(329 || format!("field <{key}> manifestification"),330 || manifest_json_ex_buf(&value, buf, cur_padding, options),331 )?;332 }333334 cur_padding.truncate(old_len);335336 match mtype {337 Manifest | ToString if !had_fields => {338 339 buf.push(' ');340 }341 Manifest => {342 buf.push_str(options.newline);343 buf.push_str(cur_padding);344 }345 Std => {346 if !had_fields {347 348 buf.push_str(options.newline);349 }350 buf.push_str(options.newline);351 buf.push_str(cur_padding);352 }353 Minify | ToString => {}354 }355356 buf.push('}');357 Ok::<_, Error>(())358 })?,359 Val::Func(_) => bail!("tried to manifest function"),360 }361 Ok(())362}363364impl ManifestFormat for JsonFormat<'_> {365 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {366 manifest_json_ex_buf(&val, buf, &mut String::new(), self)367 }368}369370371372pub struct ToStringFormat;373impl ManifestFormat for ToStringFormat {374 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {375 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();376 if let Some(str) = val.as_str() {377 out.push_str(&str);378 return Ok(());379 }380 #[cfg(feature = "exp-bigint")]381 if let Some(int) = val.as_bigint() {382 out.push_str(&int.to_str_radix(10));383 return Ok(());384 }385 JSON_TO_STRING.manifest_buf(val, out)386 }387}388pub struct StringFormat;389impl ManifestFormat for StringFormat {390 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {391 let Val::Str(s) = val else {392 bail!(393 "output should be string for string manifest format, got {}",394 val.value_type()395 )396 };397 write!(out, "{s}").unwrap();398 Ok(())399 }400}401402pub struct YamlStreamFormat<I> {403 inner: I,404 c_document_end: bool,405 end_newline: bool,406}407impl<I> YamlStreamFormat<I> {408 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {409 Self {410 inner,411 c_document_end,412 413 end_newline: true,414 }415 }416 pub fn cli(inner: I) -> Self {417 Self {418 inner,419 c_document_end: true,420 end_newline: false,421 }422 }423}424impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {425 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {426 let Val::Arr(arr) = val else {427 bail!(428 "output should be array for yaml stream format, got {}",429 val.value_type()430 )431 };432 for (i, v) in arr.iter().enumerate() {433 if i != 0 {434 out.push('\n');435 }436 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;437 out.push_str("---\n");438 in_description_frame(439 || format!("elem <{i}> manifestification"),440 || self.inner.manifest_buf(v, out),441 )?;442 }443 if self.c_document_end {444 out.push('\n');445 out.push_str("...");446 }447 if self.end_newline {448 out.push('\n');449 }450 Ok(())451 }452}453454pub fn escape_string_json(s: &str) -> String {455 let mut buf = String::new();456 escape_string_json_buf(s, &mut buf);457 buf458}459460461462const BB: u8 = b'b'; 463const TT: u8 = b't'; 464const NN: u8 = b'n'; 465const FF: u8 = b'f'; 466const RR: u8 = b'r'; 467const QU: u8 = b'"'; 468const BS: u8 = b'\\'; 469const UU: u8 = b'u'; 470const __: u8 = 0;471472473474static ESCAPE: [u8; 256] = [475 476 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 477 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 478 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 479 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 480 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 481 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 482 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 483 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 484 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 485 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 486 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 487 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 488 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 489 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 490 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 491 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 492];493494pub fn escape_string_json_buf(value: &str, buf: &mut String) {495 buf.reserve_exact(value.len() + 2);496 buf.push('"');497 escape_string_json_buf_raw(value, buf);498 buf.push('"');499}500501fn escape_string_json_buf_raw(value: &str, buf: &mut String) {502 503 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };504 let bytes = value.as_bytes();505506 let mut start = 0;507508 for (i, &byte) in bytes.iter().enumerate() {509 let escape = ESCAPE[byte as usize];510 if escape == __ {511 continue;512 }513514 if start < i {515 buf.extend_from_slice(&bytes[start..i]);516 }517 start = i + 1;518519 match escape {520 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {521 buf.extend_from_slice(&[b'\\', escape]);522 }523 self::UU => {524 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";525 let bytes = &[526 b'\\',527 b'u',528 b'0',529 b'0',530 HEX_DIGITS[(byte >> 4) as usize],531 HEX_DIGITS[(byte & 0xF) as usize],532 ];533 buf.extend_from_slice(bytes);534 }535 _ => unreachable!(),536 }537 }538539 if start == bytes.len() {540 return;541 }542543 buf.extend_from_slice(&bytes[start..]);544}