From 1b608b16ee760a953a21905aca226b200e967969 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 4 May 2024 23:32:06 +0200 Subject: [PATCH] feat: add description stacktrace frames for all formats --- crates/jrsonnet-evaluator/src/manifest.rs | 166 +++++++++++------- .../src/typed/conversions.rs | 9 +- crates/jrsonnet-stdlib/src/manifest/toml.rs | 53 ++++-- crates/jrsonnet-stdlib/src/manifest/xml.rs | 35 ++-- crates/jrsonnet-stdlib/src/manifest/yaml.rs | 133 +++++++------- 5 files changed, 233 insertions(+), 163 deletions(-) diff --git a/crates/jrsonnet-evaluator/src/manifest.rs b/crates/jrsonnet-evaluator/src/manifest.rs index ac282828..6cb66379 100644 --- a/crates/jrsonnet-evaluator/src/manifest.rs +++ b/crates/jrsonnet-evaluator/src/manifest.rs @@ -183,6 +183,8 @@ fn manifest_json_ex_buf( cur_padding: &mut String, options: &JsonFormat<'_>, ) -> Result<()> { + use JsonFormatting::*; + let mtype = options.mtype; match val { Val::Bool(v) => { @@ -218,89 +220,118 @@ fn manifest_json_ex_buf( } Val::Arr(items) => { buf.push('['); - if !items.is_empty() { - if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify { - buf.push_str(options.newline); - } - let old_len = cur_padding.len(); - cur_padding.push_str(&options.padding); - for (i, item) in items.iter().enumerate() { - if i != 0 { - buf.push(','); - if mtype == JsonFormatting::ToString { - buf.push(' '); - } else if mtype != JsonFormatting::Minify { - buf.push_str(options.newline); - } + let old_len = cur_padding.len(); + cur_padding.push_str(&options.padding); + + let mut had_items = false; + for (i, item) in items.iter().enumerate() { + had_items = true; + let item = item.with_description(|| format!("elem <{i}> evaluation"))?; + + if i != 0 { + buf.push(','); + } + match mtype { + Manifest | Std => { + buf.push_str(options.newline); + buf.push_str(cur_padding); } + ToString => buf.push(' '), + Minify => {} + }; + + buf.push_str(cur_padding); + State::push_description( + || format!("elem <{i}> manifestification"), + || manifest_json_ex_buf(&item, buf, cur_padding, options), + )?; + } + + cur_padding.truncate(old_len); + + match mtype { + Manifest | ToString if !had_items => { + // Empty array as "[ ]" + buf.push(' '); + } + Manifest => { + buf.push_str(options.newline); buf.push_str(cur_padding); - manifest_json_ex_buf(&item?, buf, cur_padding, options) - .with_description(|| format!("elem <{i}> manifestification"))?; } - cur_padding.truncate(old_len); - - if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify { + Std => { + if !had_items { + // Stdlib formats empty array as "[\n\n]" + buf.push_str(options.newline); + } buf.push_str(options.newline); buf.push_str(cur_padding); } - } else if mtype == JsonFormatting::Std { - buf.push_str(options.newline); - buf.push_str(options.newline); - buf.push_str(cur_padding); - } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest { - buf.push(' '); + Minify | ToString => {} } + buf.push(']'); } Val::Obj(obj) => { obj.run_assertions()?; buf.push('{'); - let fields = obj.fields( - #[cfg(feature = "exp-preserve-order")] - options.preserve_order, - ); - if !fields.is_empty() { - if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify { - buf.push_str(options.newline); - } - let old_len = cur_padding.len(); - cur_padding.push_str(&options.padding); - for (i, field) in fields.into_iter().enumerate() { - if i != 0 { - buf.push(','); - if mtype == JsonFormatting::ToString { - buf.push(' '); - } else if mtype != JsonFormatting::Minify { - buf.push_str(options.newline); - } + let old_len = cur_padding.len(); + cur_padding.push_str(&options.padding); + + let mut had_fields = false; + for (i, (key, value)) in obj + .iter( + #[cfg(feature = "exp-preserve-order")] + options.preserve_order, + ) + .enumerate() + { + had_fields = true; + let value = value.with_description(|| format!("field <{key}> evaluation"))?; + + if i != 0 { + buf.push(','); + } + match mtype { + Manifest | Std => { + buf.push_str(options.newline); + buf.push_str(cur_padding); } - buf.push_str(cur_padding); - escape_string_json_buf(&field, buf); - buf.push_str(options.key_val_sep); - State::push_description( - || format!("field <{}> manifestification", field.clone()), - || { - let value = obj.get(field.clone())?.unwrap(); - manifest_json_ex_buf(&value, buf, cur_padding, options)?; - Ok(()) - }, - )?; + ToString => buf.push(' '), + Minify => {} } - cur_padding.truncate(old_len); - if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify { + escape_string_json_buf(&key, buf); + buf.push_str(options.key_val_sep); + State::push_description( + || format!("field <{key}> manifestification"), + || manifest_json_ex_buf(&value, buf, cur_padding, options), + )?; + } + + cur_padding.truncate(old_len); + + match mtype { + Manifest | ToString if !had_fields => { + // Empty object as "{ }" + buf.push(' '); + } + Manifest => { buf.push_str(options.newline); buf.push_str(cur_padding); } - } else if mtype == JsonFormatting::Std { - buf.push_str(options.newline); - buf.push_str(options.newline); - buf.push_str(cur_padding); - } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest { - buf.push(' '); + Std => { + if !had_fields { + // Stdlib formats empty object as "{\n\n}" + buf.push_str(options.newline); + } + buf.push_str(options.newline); + buf.push_str(cur_padding); + } + Minify | ToString => {} } + buf.push('}'); } Val::Func(_) => bail!("tried to manifest function"), @@ -350,7 +381,7 @@ impl YamlStreamFormat { Self { inner, c_document_end, - // Stdlib format always inserts newline at the end + // Stdlib format always inserts useless newline at the end end_newline: true, } } @@ -371,10 +402,13 @@ impl ManifestFormat for YamlStreamFormat { ) }; if !arr.is_empty() { - for v in arr.iter() { - let v = v?; + for (i, v) in arr.iter().enumerate() { + let v = v.with_description(|| format!("elem <{i}> evaluation"))?; out.push_str("---\n"); - self.inner.manifest_buf(v, out)?; + State::push_description( + || format!("elem <{i}> manifestification"), + || self.inner.manifest_buf(v, out), + )?; out.push('\n'); } } diff --git a/crates/jrsonnet-evaluator/src/typed/conversions.rs b/crates/jrsonnet-evaluator/src/typed/conversions.rs index 4c77bcab..75f65341 100644 --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -11,7 +11,7 @@ use crate::{ function::{native::NativeDesc, FuncDesc, FuncVal}, typed::CheckType, val::{IndexableVal, StrValue, ThunkMapper}, - ObjValue, ObjValueBuilder, Result, Thunk, Val, + ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, }; #[derive(Trace)] @@ -359,7 +359,12 @@ where unreachable!("typecheck should fail") }; a.iter() - .map(|r| r.and_then(T::from_untyped)) + .enumerate() + .map(|(i, r)| { + r.and_then(|t| { + T::from_untyped(t).with_description(|| format!("parsing elem <{i}>")) + }) + }) .collect::>() } } diff --git a/crates/jrsonnet-stdlib/src/manifest/toml.rs b/crates/jrsonnet-stdlib/src/manifest/toml.rs index f878fd5c..398f33fb 100644 --- a/crates/jrsonnet-stdlib/src/manifest/toml.rs +++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs @@ -4,7 +4,7 @@ use jrsonnet_evaluator::{ bail, manifest::{escape_string_json_buf, ManifestFormat}, val::ArrValue, - IStr, ObjValue, Result, Val, + IStr, ObjValue, Result, ResultExt, Val, State, }; pub struct TomlFormat<'s> { @@ -106,16 +106,15 @@ fn manifest_value( #[cfg(feature = "exp-bigint")] Val::BigInt(n) => write!(buf, "{n}").unwrap(), Val::Arr(a) => { - if a.is_empty() { - buf.push_str("[]"); - return Ok(()); - } + buf.push('['); + + let mut had_items = false; for (i, e) in a.iter().enumerate() { - let e = e?; + had_items = true; + let e = e.with_description(|| format!("elem <{i}> evaluation"))?; + if i != 0 { buf.push(','); - } else { - buf.push('['); } if inline { buf.push(' '); @@ -124,9 +123,15 @@ fn manifest_value( buf.push_str(cur_padding); buf.push_str(&options.padding); } - manifest_value(&e, true, buf, "", options)?; + + State::push_description( + || format!("elem <{i}> manifestification"), + || manifest_value(&e, true, buf, "", options), + )?; } - if inline { + + if !had_items { + } else if inline { buf.push(' '); } else { buf.push('\n'); @@ -135,10 +140,10 @@ fn manifest_value( buf.push(']'); } Val::Obj(o) => { - if o.is_empty() { - buf.push_str("{}"); - } - buf.push_str("{ "); + o.run_assertions()?; + buf.push('{'); + + let mut had_fields = false; for (i, (k, v)) in o .iter( #[cfg(feature = "exp-preserve-order")] @@ -146,15 +151,27 @@ fn manifest_value( ) .enumerate() { - let v = v?; + had_fields = true; + let v = v.with_description(|| format!("field <{k}> evaluation"))?; + if i != 0 { - buf.push_str(", "); + buf.push(','); } + buf.push(' '); + escape_key_toml_buf(&k, buf); buf.push_str(" = "); - manifest_value(&v, true, buf, "", options)?; + State::push_description( + || format!("field <{k}> manifestification"), + || manifest_value(&v, true, buf, "", options), + )?; } - buf.push_str(" }"); + + if had_fields { + buf.push(' '); + } + + buf.push('}'); } Val::Null => { bail!("tried to manifest null") diff --git a/crates/jrsonnet-stdlib/src/manifest/xml.rs b/crates/jrsonnet-stdlib/src/manifest/xml.rs index c1a39594..29f3fc99 100644 --- a/crates/jrsonnet-stdlib/src/manifest/xml.rs +++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs @@ -1,9 +1,9 @@ use jrsonnet_evaluator::{ bail, manifest::{ManifestFormat, ToStringFormat}, - typed::{ComplexValType, Either4, Typed, ValType}, + typed::{ComplexValType, Either2, Either4, Typed, ValType}, val::{ArrValue, IndexableVal}, - Either, ObjValue, Result, ResultExt, Val, + Either, ObjValue, Result, ResultExt, Val, State, }; pub struct XmlJsonmlFormat { @@ -38,20 +38,22 @@ impl Typed for JSONMLValue { } fn from_untyped(untyped: Val) -> Result { - let Val::Arr(arr) = untyped else { - if let Val::Str(s) = untyped { - return Ok(Self::String(s.to_string())); - }; - bail!("expected JSONML value (an array or string)"); + let val = ::from_untyped(untyped) + .with_description(|| format!("parsing JSONML value (an array or string)"))?; + let arr = match val { + Either2::A(a) => a, + Either2::B(s) => return Ok(Self::String(s)), }; if arr.len() < 1 { - bail!("JSONML value should have tag"); + bail!("JSONML value should have tag (array length should be >=1)"); }; let tag = String::from_untyped( arr.get(0) .with_description(|| "getting JSONML tag")? .expect("length checked"), - )?; + ) + .with_description(|| format!("parsing JSONML tag"))?; + let (has_attrs, attrs) = if arr.len() >= 2 { let maybe_attrs = arr .get(1) @@ -68,11 +70,16 @@ impl Typed for JSONMLValue { Ok(Self::Tag { tag, attrs, - children: Typed::from_untyped(Val::Arr(arr.slice( - Some(if has_attrs { 2 } else { 1 }), - None, - None, - )))?, + children: State::push_description( + || format!("parsing children"), + || { + Typed::from_untyped(Val::Arr(arr.slice( + Some(if has_attrs { 2 } else { 1 }), + None, + None, + ))) + }, + )?, }) } } diff --git a/crates/jrsonnet-stdlib/src/manifest/yaml.rs b/crates/jrsonnet-stdlib/src/manifest/yaml.rs index 20ee5731..7a9cd52b 100644 --- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs +++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::Write}; use jrsonnet_evaluator::{ bail, manifest::{escape_string_json_buf, ManifestFormat}, - Result, Val, + Result, ResultExt, State, Val, }; pub struct YamlFormat<'s> { @@ -152,80 +152,87 @@ fn manifest_yaml_ex_buf( #[cfg(feature = "exp-bigint")] Val::BigInt(n) => write!(buf, "{}", *n).unwrap(), Val::Arr(a) => { - if a.is_empty() { - buf.push_str("[]"); - } else { - for (i, item) in a.iter().enumerate() { - if i != 0 { + let mut had_items = false; + for (i, item) in a.iter().enumerate() { + had_items = true; + let item = item.with_description(|| format!("elem <{i}> evaluation"))?; + if i != 0 { + buf.push('\n'); + buf.push_str(cur_padding); + } + buf.push('-'); + match &item { + Val::Arr(a) if !a.is_empty() => { buf.push('\n'); buf.push_str(cur_padding); + buf.push_str(&options.padding); } - let item = item?; - buf.push('-'); - match &item { - Val::Arr(a) if !a.is_empty() => { - buf.push('\n'); - buf.push_str(cur_padding); - buf.push_str(&options.padding); - } - _ => buf.push(' '), - } - let extra_padding = match &item { - Val::Arr(a) => !a.is_empty(), - Val::Obj(o) => !o.is_empty(), - _ => false, - }; - let prev_len = cur_padding.len(); - if extra_padding { - cur_padding.push_str(&options.padding); - } - manifest_yaml_ex_buf(&item, buf, cur_padding, options)?; - cur_padding.truncate(prev_len); + _ => buf.push(' '), + } + let extra_padding = match &item { + Val::Arr(a) => !a.is_empty(), + Val::Obj(o) => !o.is_empty(), + _ => false, + }; + let prev_len = cur_padding.len(); + if extra_padding { + cur_padding.push_str(&options.padding); } + State::push_description( + || format!("elem <{i}> manifestification"), + || manifest_yaml_ex_buf(&item, buf, cur_padding, options), + )?; + cur_padding.truncate(prev_len); + } + if !had_items { + buf.push_str("[]"); } } Val::Obj(o) => { - if o.is_empty() { - buf.push_str("{}"); - } else { - for (i, key) in o - .fields( - #[cfg(feature = "exp-preserve-order")] - options.preserve_order, - ) - .iter() - .enumerate() - { - if i != 0 { + let mut had_fields = false; + for (i, (key, value)) in o + .iter( + #[cfg(feature = "exp-preserve-order")] + options.preserve_order, + ) + .enumerate() + { + had_fields = true; + let value = value.with_description(|| format!("field <{key}> evaluation"))?; + if i != 0 { + buf.push('\n'); + buf.push_str(cur_padding); + } + if !options.quote_keys && !yaml_needs_quotes(&key) { + buf.push_str(&key); + } else { + escape_string_json_buf(&key, buf); + } + buf.push(':'); + let prev_len = cur_padding.len(); + match &value { + Val::Arr(a) if !a.is_empty() => { buf.push('\n'); buf.push_str(cur_padding); + buf.push_str(&options.arr_element_padding); + cur_padding.push_str(&options.arr_element_padding); } - if !options.quote_keys && !yaml_needs_quotes(key) { - buf.push_str(key); - } else { - escape_string_json_buf(key, buf); - } - buf.push(':'); - let prev_len = cur_padding.len(); - let item = o.get(key.clone())?.expect("field exists"); - match &item { - Val::Arr(a) if !a.is_empty() => { - buf.push('\n'); - buf.push_str(cur_padding); - buf.push_str(&options.arr_element_padding); - cur_padding.push_str(&options.arr_element_padding); - } - Val::Obj(o) if !o.is_empty() => { - buf.push('\n'); - buf.push_str(cur_padding); - buf.push_str(&options.padding); - cur_padding.push_str(&options.padding); - } - _ => buf.push(' '), + Val::Obj(o) if !o.is_empty() => { + buf.push('\n'); + buf.push_str(cur_padding); + buf.push_str(&options.padding); + cur_padding.push_str(&options.padding); } - manifest_yaml_ex_buf(&item, buf, cur_padding, options)?; - cur_padding.truncate(prev_len); + _ => buf.push(' '), } + State::push_description( + || format!("field <{key}> manifestification"), + || manifest_yaml_ex_buf(&value, buf, cur_padding, options), + )?; + cur_padding.truncate(prev_len); + } + if !had_fields { + buf.push_str("{}"); } } Val::Func(_) => bail!("tried to manifest function"),