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 black_box(name);73 let value = value?;74 self.manifest_buf(value, buf)?;75 }76 }77 Val::Func(func_val) => {78 black_box(func_val);79 bail!("tried to manifest function")80 }81 }82 Ok(())83 }84}8586#[derive(PartialEq, Eq, Clone, Copy)]87enum JsonFormatting {88 // Applied in manifestification89 Manifest,90 /// Used for std.manifestJson91 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest92 Std,93 /// No line breaks, used in `obj+''`94 ToString,95 /// Minified json96 Minify,97}9899pub struct JsonFormat<'s> {100 padding: Cow<'s, str>,101 mtype: JsonFormatting,102 newline: &'s str,103 key_val_sep: &'s str,104 #[cfg(feature = "exp-preserve-order")]105 preserve_order: bool,106 #[cfg(feature = "exp-bigint")]107 preserve_bigints: bool,108}109110impl<'s> JsonFormat<'s> {111 // Minifying format112 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {113 Self {114 padding: Cow::Borrowed(""),115 mtype: JsonFormatting::Minify,116 newline: "\n",117 key_val_sep: ":",118 #[cfg(feature = "exp-preserve-order")]119 preserve_order,120 #[cfg(feature = "exp-bigint")]121 preserve_bigints: false,122 }123 }124 /// Same format as std.toString, except does not keeps top-level string as-is125 /// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead126 const fn std_to_string_helper() -> Self {127 Self {128 padding: Cow::Borrowed(""),129 mtype: JsonFormatting::ToString,130 newline: "\n",131 key_val_sep: ": ",132 #[cfg(feature = "exp-preserve-order")]133 preserve_order: false,134 #[cfg(feature = "exp-bigint")]135 preserve_bigints: false,136 }137 }138 pub fn std_to_json(139 padding: String,140 newline: &'s str,141 key_val_sep: &'s str,142 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,143 ) -> Self {144 Self {145 padding: Cow::Owned(padding),146 mtype: JsonFormatting::Std,147 newline,148 key_val_sep,149 #[cfg(feature = "exp-preserve-order")]150 preserve_order,151 #[cfg(feature = "exp-bigint")]152 preserve_bigints: false,153 }154 }155 // Same format as CLI manifestification156 pub fn cli(157 padding: usize,158 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,159 ) -> Self {160 if padding == 0 {161 return Self::minify(162 #[cfg(feature = "exp-preserve-order")]163 preserve_order,164 );165 }166 Self {167 padding: Cow::Owned(" ".repeat(padding)),168 mtype: JsonFormatting::Manifest,169 newline: "\n",170 key_val_sep: ": ",171 #[cfg(feature = "exp-preserve-order")]172 preserve_order,173 #[cfg(feature = "exp-bigint")]174 preserve_bigints: false,175 }176 }177 // Same format as CLI manifestification178 pub fn debug() -> Self {179 Self {180 padding: Cow::Borrowed(" "),181 mtype: JsonFormatting::Manifest,182 newline: "\n",183 key_val_sep: ": ",184 #[cfg(feature = "exp-preserve-order")]185 preserve_order: true,186 #[cfg(feature = "exp-bigint")]187 preserve_bigints: true,188 }189 }190}191impl Default for JsonFormat<'static> {192 fn default() -> Self {193 Self {194 padding: Cow::Borrowed(" "),195 mtype: JsonFormatting::Manifest,196 newline: "\n",197 key_val_sep: ": ",198 #[cfg(feature = "exp-preserve-order")]199 preserve_order: false,200 #[cfg(feature = "exp-bigint")]201 preserve_bigints: false,202 }203 }204}205206pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {207 let mut out = String::new();208 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;209 Ok(out)210}211212#[allow(clippy::too_many_lines)]213fn manifest_json_ex_buf(214 val: &Val,215 buf: &mut String,216 cur_padding: &mut String,217 options: &JsonFormat<'_>,218) -> Result<()> {219 use JsonFormatting::*;220221 let mtype = options.mtype;222 match val {223 Val::Bool(v) => {224 if *v {225 buf.push_str("true");226 } else {227 buf.push_str("false");228 }229 }230 Val::Null => buf.push_str("null"),231 Val::Str(s) => {232 buf.reserve(2 + s.len());233 buf.push('"');234 s.chunks(&mut |c| {235 escape_string_json_buf_raw(c, buf);236 });237 buf.push('"');238 }239 Val::Num(n) => write!(buf, "{n}").unwrap(),240 #[cfg(feature = "exp-bigint")]241 Val::BigInt(n) => {242 if options.preserve_bigints {243 write!(buf, "{n}").unwrap();244 } else {245 write!(buf, "{:?}", n.to_string()).unwrap();246 }247 }248 Val::Arr(items) => {249 buf.push('[');250251 let old_len = cur_padding.len();252 cur_padding.push_str(&options.padding);253254 let mut had_items = false;255 for (i, item) in items.iter().enumerate() {256 had_items = true;257 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;258259 if i != 0 {260 buf.push(',');261 }262 match mtype {263 Manifest | Std => {264 buf.push_str(options.newline);265 buf.push_str(cur_padding);266 }267 ToString if i != 0 => buf.push(' '),268 Minify | ToString => {}269 }270271 in_description_frame(272 || format!("elem <{i}> manifestification"),273 || manifest_json_ex_buf(&item, buf, cur_padding, options),274 )?;275 }276277 cur_padding.truncate(old_len);278279 match mtype {280 Manifest | ToString if !had_items => {281 // Empty array as "[ ]"282 buf.push(' ');283 }284 Manifest => {285 buf.push_str(options.newline);286 buf.push_str(cur_padding);287 }288 Std => {289 if !had_items {290 // Stdlib formats empty array as "[\n\n]"291 buf.push_str(options.newline);292 }293 buf.push_str(options.newline);294 buf.push_str(cur_padding);295 }296 Minify | ToString => {}297 }298299 buf.push(']');300 }301 Val::Obj(obj) => {302 obj.run_assertions()?;303 buf.push('{');304305 let old_len = cur_padding.len();306 cur_padding.push_str(&options.padding);307308 let mut had_fields = false;309 for (i, (key, value)) in obj310 .iter(311 #[cfg(feature = "exp-preserve-order")]312 options.preserve_order,313 )314 .enumerate()315 {316 had_fields = true;317 let value = value.with_description(|| format!("field <{key}> evaluation"))?;318319 if i != 0 {320 buf.push(',');321 }322 match mtype {323 Manifest | Std => {324 buf.push_str(options.newline);325 buf.push_str(cur_padding);326 }327 ToString if i != 0 => buf.push(' '),328 Minify | ToString => {}329 }330331 escape_string_json_buf(&key, buf);332 buf.push_str(options.key_val_sep);333 in_description_frame(334 || format!("field <{key}> manifestification"),335 || manifest_json_ex_buf(&value, buf, cur_padding, options),336 )?;337 }338339 cur_padding.truncate(old_len);340341 match mtype {342 Manifest | ToString if !had_fields => {343 // Empty object as "{ }"344 buf.push(' ');345 }346 Manifest => {347 buf.push_str(options.newline);348 buf.push_str(cur_padding);349 }350 Std => {351 if !had_fields {352 // Stdlib formats empty object as "{\n\n}"353 buf.push_str(options.newline);354 }355 buf.push_str(options.newline);356 buf.push_str(cur_padding);357 }358 Minify | ToString => {}359 }360361 buf.push('}');362 }363 Val::Func(_) => bail!("tried to manifest function"),364 }365 Ok(())366}367368impl ManifestFormat for JsonFormat<'_> {369 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {370 manifest_json_ex_buf(&val, buf, &mut String::new(), self)371 }372}373374/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,375/// without quoting.376pub struct ToStringFormat;377impl ManifestFormat for ToStringFormat {378 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {379 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();380 if let Some(str) = val.as_str() {381 out.push_str(&str);382 return Ok(());383 }384 #[cfg(feature = "exp-bigint")]385 if let Some(int) = val.as_bigint() {386 out.push_str(&int.to_str_radix(10));387 return Ok(());388 }389 JSON_TO_STRING.manifest_buf(val, out)390 }391 fn file_trailing_newline(&self) -> bool {392 false393 }394}395pub struct StringFormat;396impl ManifestFormat for StringFormat {397 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {398 let Val::Str(s) = val else {399 bail!(400 "output should be string for string manifest format, got {}",401 val.value_type()402 )403 };404 write!(out, "{s}").unwrap();405 Ok(())406 }407 fn file_trailing_newline(&self) -> bool {408 false409 }410}411412pub struct YamlStreamFormat<I> {413 inner: I,414 c_document_end: bool,415 end_newline: bool,416}417impl<I> YamlStreamFormat<I> {418 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {419 Self {420 inner,421 c_document_end,422 // Stdlib format always inserts useless newline at the end423 end_newline: true,424 }425 }426 pub fn cli(inner: I) -> Self {427 Self {428 inner,429 c_document_end: true,430 end_newline: false,431 }432 }433}434impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {435 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {436 let Val::Arr(arr) = val else {437 bail!(438 "output should be array for yaml stream format, got {}",439 val.value_type()440 )441 };442 for (i, v) in arr.iter().enumerate() {443 if i != 0 {444 out.push('\n');445 }446 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;447 out.push_str("---\n");448 in_description_frame(449 || format!("elem <{i}> manifestification"),450 || self.inner.manifest_buf(v, out),451 )?;452 }453 if self.c_document_end {454 out.push('\n');455 out.push_str("...");456 }457 if self.end_newline {458 out.push('\n');459 }460 Ok(())461 }462}463464pub fn escape_string_json(s: &str) -> String {465 let mut buf = String::new();466 escape_string_json_buf(s, &mut buf);467 buf468}469470// Json string encoding was borrowed from https://github.com/serde-rs/json471472const BB: u8 = b'b'; // \x08473const TT: u8 = b't'; // \x09474const NN: u8 = b'n'; // \x0A475const FF: u8 = b'f'; // \x0C476const RR: u8 = b'r'; // \x0D477const QU: u8 = b'"'; // \x22478const BS: u8 = b'\\'; // \x5C479const UU: u8 = b'u'; // \x00...\x1F except the ones above480const __: u8 = 0;481482// Lookup table of escape sequences. A value of b'x' at index i means that byte483// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.484static ESCAPE: [u8; 256] = [485 // 1 2 3 4 5 6 7 8 9 A B C D E F486 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0487 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1488 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2489 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3490 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4491 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5492 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6493 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7494 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8495 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9496 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A497 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B498 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C499 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D500 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F502];503504pub fn escape_string_json_buf(value: &str, buf: &mut String) {505 buf.reserve_exact(value.len() + 2);506 escape_string_json_buf_raw(value, buf);507}508509fn escape_string_json_buf_raw(value: &str, buf: &mut String) {510 // Safety: we only write correct utf-8 in this function511 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };512 let bytes = value.as_bytes();513514 let mut start = 0;515516 for (i, &byte) in bytes.iter().enumerate() {517 let escape = ESCAPE[byte as usize];518 if escape == __ {519 continue;520 }521522 if start < i {523 buf.extend_from_slice(&bytes[start..i]);524 }525 start = i + 1;526527 match escape {528 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {529 buf.extend_from_slice(&[b'\\', escape]);530 }531 self::UU => {532 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";533 let bytes = &[534 b'\\',535 b'u',536 b'0',537 b'0',538 HEX_DIGITS[(byte >> 4) as usize],539 HEX_DIGITS[(byte & 0xF) as usize],540 ];541 buf.extend_from_slice(bytes);542 }543 _ => unreachable!(),544 }545 }546547 if start == bytes.len() {548 return;549 }550551 buf.extend_from_slice(&bytes[start..]);552}