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 #[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}