difftreelog
feat sync jsonnet stdlib changes
in: master
8 files changed
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -413,18 +413,19 @@
val.value_type()
)
};
- if !arr.is_empty() {
- for (i, v) in arr.iter().enumerate() {
- let v = v.with_description(|| format!("elem <{i}> evaluation"))?;
- out.push_str("---\n");
- in_description_frame(
- || format!("elem <{i}> manifestification"),
- || self.inner.manifest_buf(v, out),
- )?;
+ for (i, v) in arr.iter().enumerate() {
+ if i != 0 {
out.push('\n');
}
+ let v = v.with_description(|| format!("elem <{i}> evaluation"))?;
+ out.push_str("---\n");
+ in_description_frame(
+ || format!("elem <{i}> manifestification"),
+ || self.inner.manifest_buf(v, out),
+ )?;
}
if self.c_document_end {
+ out.push('\n');
out.push_str("...");
}
if self.end_newline {
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -237,7 +237,11 @@
Expr::ArrComp(expr, specs)
}
pub rule number_expr(s: &ParserSettings) -> Expr
- = n:number() { expr::Expr::Num(n) }
+ = n:number() {? if n.is_finite() {
+ Ok(expr::Expr::Num(n))
+ } else {
+ Err("!!!numbers are finite")
+ }}
pub rule var_expr(s: &ParserSettings) -> Expr
= n:id() { expr::Expr::Var(n) }
pub rule id_loc(s: &ParserSettings) -> LocExpr
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -3,6 +3,7 @@
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
+ f64,
rc::Rc,
};
@@ -14,6 +15,7 @@
error::{ErrorKind::*, Result},
function::{CallLocation, FuncVal, TlaArg},
trace::PathResolver,
+ val::NumValue,
ContextBuilder, IStr, ObjValue, ObjValueBuilder, Thunk, Val,
};
use jrsonnet_gcmodule::{Acyclic, Cc, Trace};
@@ -63,6 +65,7 @@
("isObject", builtin_is_object::INST),
("isArray", builtin_is_array::INST),
("isFunction", builtin_is_function::INST),
+ ("isNull", builtin_is_null::INST),
// Arrays
("makeArray", builtin_make_array::INST),
("repeat", builtin_repeat::INST),
@@ -104,6 +107,8 @@
("floor", builtin_floor::INST),
("ceil", builtin_ceil::INST),
("log", builtin_log::INST),
+ ("log2", builtin_log2::INST),
+ ("log10", builtin_log10::INST),
("pow", builtin_pow::INST),
("sqrt", builtin_sqrt::INST),
("sin", builtin_sin::INST),
@@ -121,6 +126,9 @@
("isOdd", builtin_is_odd::INST),
("isInteger", builtin_is_integer::INST),
("isDecimal", builtin_is_decimal::INST),
+ ("deg2rad", builtin_deg2rad::INST),
+ ("rad2deg", builtin_rad2deg::INST),
+ ("hypot", builtin_hypot::INST),
// Operator
("mod", builtin_mod::INST),
("primitiveEquals", builtin_primitive_equals::INST),
@@ -201,6 +209,7 @@
("lstripChars", builtin_lstrip_chars::INST),
("rstripChars", builtin_rstrip_chars::INST),
("stripChars", builtin_strip_chars::INST),
+ ("trim", builtin_trim::INST),
// Misc
("length", builtin_length::INST),
("get", builtin_get::INST),
@@ -248,6 +257,10 @@
builder.method("trace", builtin_trace { settings });
builder.method("id", FuncVal::Id);
+ builder.field("pi").hide().value(Val::Num(
+ NumValue::new(f64::consts::PI).expect("pi is finite"),
+ ));
+
#[cfg(feature = "exp-regex")]
{
// Regex
crates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/math.rs
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -1,3 +1,5 @@
+use std::f64;
+
use jrsonnet_evaluator::{function::builtin, typed::PositiveF64};
#[builtin]
@@ -56,6 +58,16 @@
}
#[builtin]
+pub fn builtin_log2(x: f64) -> f64 {
+ x.log2()
+}
+
+#[builtin]
+pub fn builtin_log10(x: f64) -> f64 {
+ x.log10()
+}
+
+#[builtin]
pub fn builtin_pow(x: f64, n: f64) -> f64 {
x.powf(n)
}
@@ -153,3 +165,18 @@
pub fn builtin_is_decimal(x: f64) -> bool {
builtin_round(x) != x
}
+
+#[builtin]
+pub fn builtin_deg2rad(x: f64) -> f64 {
+ x * f64::consts::PI / 180.0
+}
+
+#[builtin]
+pub fn builtin_rad2deg(x: f64) -> f64 {
+ x * 180.0 / f64::consts::PI
+}
+
+#[builtin]
+pub fn builtin_hypot(x: f64, y: f64) -> f64 {
+ x.hypot(y)
+}
crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth1use std::{cell::RefCell, collections::BTreeSet};23use jrsonnet_evaluator::{4 bail,5 error::{ErrorKind::*, Result},6 function::{builtin, ArgLike, CallLocation, FuncVal},7 manifest::JsonFormat,8 typed::{Either2, Either4},9 val::{equals, ArrValue},10 Context, Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,11};12use jrsonnet_gcmodule::Cc;1314use crate::{extvar_source, Settings};1516#[builtin]17pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {18 use Either4::*;19 match x {20 A(x) => x.chars().count(),21 B(x) => x.len(),22 C(x) => x.len(),23 D(f) => f.params_len(),24 }25}2627#[builtin]28pub fn builtin_get(29 o: ObjValue,30 f: IStr,31 default: Option<Thunk<Val>>,32 #[default(true)] inc_hidden: bool,33) -> Result<Val> {34 let do_default = move || {35 let Some(default) = default else {36 return Ok(Val::Null);37 };38 default.evaluate()39 };40 // Happy path for invisible fields41 if !inc_hidden && !o.has_field_ex(f.clone(), false) {42 return do_default();43 }44 let Some(v) = o.get(f)? else {45 return do_default();46 };47 Ok(v)48}4950#[builtin(fields(51 settings: Cc<RefCell<Settings>>,52))]53pub fn builtin_ext_var(this: &builtin_ext_var, ctx: Context, x: IStr) -> Result<Val> {54 let ctx = ctx.state().create_default_context(extvar_source(&x, ""));55 this.settings56 .borrow()57 .ext_vars58 .get(&x)59 .cloned()60 .ok_or_else(|| UndefinedExternalVariable(x))?61 .evaluate_arg(ctx, true)?62 .evaluate()63}6465#[builtin(fields(66 settings: Cc<RefCell<Settings>>,67))]68pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {69 this.settings70 .borrow()71 .ext_natives72 .get(&x)73 .cloned()74 .map_or(Val::Null, Val::Func)75}7677#[builtin(fields(78 settings: Cc<RefCell<Settings>>,79))]80pub fn builtin_trace(81 this: &builtin_trace,82 loc: CallLocation,83 str: Val,84 rest: Option<Thunk<Val>>,85) -> Result<Val> {86 this.settings.borrow().trace_printer.print_trace(87 loc,88 match &str {89 Val::Str(s) => s.clone().into_flat(),90 Val::Func(f) => format!("{f:?}").into(),91 v => v.manifest(JsonFormat::debug())?.into(),92 },93 );94 rest.map_or_else(|| Ok(str), |rest| rest.evaluate())95}9697#[allow(clippy::comparison_chain)]98#[builtin]99pub fn builtin_starts_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {100 Ok(match (a, b) {101 (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),102 (Either2::B(a), Either2::B(b)) => {103 if b.len() > a.len() {104 return Ok(false);105 } else if b.len() == a.len() {106 return equals(&Val::Arr(a), &Val::Arr(b));107 }108 for (a, b) in a.iter().take(b.len()).zip(b.iter()) {109 let a = a?;110 let b = b?;111 if !equals(&a, &b)? {112 return Ok(false);113 }114 }115 true116 }117 _ => bail!("both arguments should be of the same type"),118 })119}120121#[allow(clippy::comparison_chain)]122#[builtin]123pub fn builtin_ends_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {124 Ok(match (a, b) {125 (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),126 (Either2::B(a), Either2::B(b)) => {127 if b.len() > a.len() {128 return Ok(false);129 } else if b.len() == a.len() {130 return equals(&Val::Arr(a), &Val::Arr(b));131 }132 let a_len = a.len();133 for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {134 let a = a?;135 let b = b?;136 if !equals(&a, &b)? {137 return Ok(false);138 }139 }140 true141 }142 _ => bail!("both arguments should be of the same type"),143 })144}145146#[builtin]147pub fn builtin_assert_equal(a: Val, b: Val) -> Result<bool> {148 if equals(&a, &b)? {149 return Ok(true);150 }151 // TODO: Use debug output format152 let format = JsonFormat::std_to_json(153 " ".to_owned(),154 "\n",155 ": ",156 #[cfg(feature = "exp-preserve-order")]157 true,158 );159 let a = a.manifest(&format).description("<a> manifestification")?;160 let b = b.manifest(&format).description("<b> manifestification")?;161 bail!("assertion failed: A != B\nA: {a}\nB: {b}")162}163164#[builtin]165pub fn builtin_merge_patch(target: Val, patch: Val) -> Result<Val> {166 let Some(patch) = patch.as_obj() else {167 return Ok(patch);168 };169 let Some(target) = target.as_obj() else {170 return Ok(Val::Obj(patch));171 };172 let target_fields = target173 .fields(174 // FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here?175 // But IndexSet won't allow fast ordered union...176 // // Makes sense to preserve source ordering where possible.177 // // May affect evaluation order, but it is not specified by jsonnet spec.178 // #[cfg(feature = "exp-preserve-order")]179 // true,180 #[cfg(feature = "exp-preserve-order")]181 false,182 )183 .into_iter()184 .collect::<BTreeSet<IStr>>();185 let patch_fields = patch186 .fields(187 // No need to look at the patch field order, I think?188 // New fields (that will be appended at the end) will be alphabeticaly-ordered,189 // but it is fine for jsonpatch, I don't think people write jsonpatch in jsonnet,190 // when they can use mixins.191 #[cfg(feature = "exp-preserve-order")]192 false,193 )194 .into_iter()195 .collect::<BTreeSet<IStr>>();196197 let mut out = ObjValueBuilder::new();198 for field in target_fields.union(&patch_fields) {199 let Some(field_patch) = patch.get(field.clone())? else {200 out.field(field.clone()).value(target.get(field.clone())?.expect("we're iterating over fields union, if field is missing in patch - it exists in target"));201 continue;202 };203 if matches!(field_patch, Val::Null) {204 continue;205 }206 let Some(field_target) = target.get(field.clone())? else {207 out.field(field.clone()).value(field_patch);208 continue;209 };210 out.field(field.clone())211 .value(builtin_merge_patch(field_target, field_patch)?);212 }213 Ok(out.build().into())214}1use std::{cell::RefCell, collections::BTreeSet};23use jrsonnet_evaluator::{4 bail,5 error::{ErrorKind::*, Result},6 function::{builtin, ArgLike, CallLocation, FuncVal},7 manifest::JsonFormat,8 typed::{Either2, Either4},9 val::{equals, ArrValue},10 Context, Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,11};12use jrsonnet_gcmodule::Cc;1314use crate::{extvar_source, Settings};1516#[builtin]17pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {18 use Either4::*;19 match x {20 A(x) => x.chars().count(),21 B(x) => x.len(),22 C(x) => x.len(),23 D(f) => f.params_len(),24 }25}2627#[builtin]28pub fn builtin_get(29 o: ObjValue,30 f: IStr,31 default: Option<Thunk<Val>>,32 #[default(true)] inc_hidden: bool,33) -> Result<Val> {34 let do_default = move || {35 let Some(default) = default else {36 return Ok(Val::Null);37 };38 default.evaluate()39 };40 // Happy path for invisible fields41 if !inc_hidden && !o.has_field_ex(f.clone(), false) {42 return do_default();43 }44 let Some(v) = o.get(f)? else {45 return do_default();46 };47 Ok(v)48}4950#[builtin(fields(51 settings: Cc<RefCell<Settings>>,52))]53pub fn builtin_ext_var(this: &builtin_ext_var, ctx: Context, x: IStr) -> Result<Val> {54 let ctx = ctx.state().create_default_context(extvar_source(&x, ""));55 this.settings56 .borrow()57 .ext_vars58 .get(&x)59 .cloned()60 .ok_or_else(|| UndefinedExternalVariable(x))?61 .evaluate_arg(ctx, true)?62 .evaluate()63}6465#[builtin(fields(66 settings: Cc<RefCell<Settings>>,67))]68pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {69 this.settings70 .borrow()71 .ext_natives72 .get(&x)73 .cloned()74 .map_or(Val::Null, Val::Func)75}7677#[builtin(fields(78 settings: Cc<RefCell<Settings>>,79))]80pub fn builtin_trace(81 this: &builtin_trace,82 loc: CallLocation,83 str: Val,84 rest: Option<Thunk<Val>>,85) -> Result<Val> {86 this.settings.borrow().trace_printer.print_trace(87 loc,88 match &str {89 Val::Str(s) => s.clone().into_flat(),90 Val::Func(f) => format!("{f:?}").into(),91 v => v.manifest(JsonFormat::debug())?.into(),92 },93 );94 rest.map_or_else(|| Ok(str), |rest| rest.evaluate())95}9697#[allow(clippy::comparison_chain)]98#[builtin]99pub fn builtin_starts_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {100 Ok(match (a, b) {101 (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),102 (Either2::B(a), Either2::B(b)) => {103 if b.len() > a.len() {104 return Ok(false);105 } else if b.len() == a.len() {106 return equals(&Val::Arr(a), &Val::Arr(b));107 }108 for (a, b) in a.iter().take(b.len()).zip(b.iter()) {109 let a = a?;110 let b = b?;111 if !equals(&a, &b)? {112 return Ok(false);113 }114 }115 true116 }117 _ => bail!("both arguments should be of the same type"),118 })119}120121#[allow(clippy::comparison_chain)]122#[builtin]123pub fn builtin_ends_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {124 Ok(match (a, b) {125 (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),126 (Either2::B(a), Either2::B(b)) => {127 if b.len() > a.len() {128 return Ok(false);129 } else if b.len() == a.len() {130 return equals(&Val::Arr(a), &Val::Arr(b));131 }132 let a_len = a.len();133 for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {134 let a = a?;135 let b = b?;136 if !equals(&a, &b)? {137 return Ok(false);138 }139 }140 true141 }142 _ => bail!("both arguments should be of the same type"),143 })144}145146#[builtin]147pub fn builtin_assert_equal(a: Val, b: Val) -> Result<bool> {148 if equals(&a, &b)? {149 return Ok(true);150 }151 // TODO: Use debug output format152 let format = JsonFormat::std_to_json(153 " ".to_owned(),154 "\n",155 ": ",156 #[cfg(feature = "exp-preserve-order")]157 true,158 );159 let a = if let Some(a) = a.as_str() {160 format!("<A>\n{a}\n</A>")161 } else {162 a.manifest(&format).description("<a> manifestification")?163 };164 let b = if let Some(b) = b.as_str() {165 format!("<B>\n{b}\n</B>")166 } else {167 b.manifest(&format).description("<b> manifestification")?168 };169 bail!("assertion failed: A != B\nA: {a}\nB: {b}")170}171172#[builtin]173pub fn builtin_merge_patch(target: Val, patch: Val) -> Result<Val> {174 let Some(patch) = patch.as_obj() else {175 return Ok(patch);176 };177 let target = target.as_obj().unwrap_or_else(|| ObjValue::new_empty());178 let target_fields = target179 .fields(180 // FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here?181 // But IndexSet won't allow fast ordered union...182 // // Makes sense to preserve source ordering where possible.183 // // May affect evaluation order, but it is not specified by jsonnet spec.184 // #[cfg(feature = "exp-preserve-order")]185 // true,186 #[cfg(feature = "exp-preserve-order")]187 false,188 )189 .into_iter()190 .collect::<BTreeSet<IStr>>();191 let patch_fields = patch192 .fields(193 // No need to look at the patch field order, I think?194 // New fields (that will be appended at the end) will be alphabeticaly-ordered,195 // but it is fine for jsonpatch, I don't think people write jsonpatch in jsonnet,196 // when they can use mixins.197 #[cfg(feature = "exp-preserve-order")]198 false,199 )200 .into_iter()201 .collect::<BTreeSet<IStr>>();202203 let mut out = ObjValueBuilder::new();204 for field in target_fields.union(&patch_fields) {205 let Some(field_patch) = patch.get(field.clone())? else {206 out.field(field.clone()).value(target.get(field.clone())?.expect("we're iterating over fields union, if field is missing in patch - it exists in target"));207 continue;208 };209 if matches!(field_patch, Val::Null) {210 continue;211 }212 let field_target = target.get(field.clone())?.unwrap_or(Val::Null);213 out.field(field.clone())214 .value(builtin_merge_patch(field_target, field_patch)?);215 }216 Ok(out.build().into())217}crates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -1,7 +1,8 @@
use jrsonnet_evaluator::{
function::builtin,
+ rustc_hash::FxHashSet,
val::{ArrValue, Val},
- IStr, ObjValue, ObjValueBuilder,
+ IStr, MaybeUnbound, ObjValue, ObjValueBuilder, Thunk,
};
#[builtin]
@@ -166,14 +167,31 @@
preserve_order: bool,
) -> ObjValue {
let mut new_obj = ObjValueBuilder::with_capacity(obj.len() - 1);
- for (k, v) in obj.iter(
+ let all_fields = obj.fields_ex(
+ true,
#[cfg(feature = "exp-preserve-order")]
preserve_order,
- ) {
- if k == key {
+ );
+ let visible_fields = obj
+ .fields_ex(
+ false,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ )
+ .into_iter()
+ .collect::<FxHashSet<_>>();
+
+ for field in &all_fields {
+ if *field == key {
continue;
}
- new_obj.field(k).value(v.unwrap());
+ let mut b = new_obj.field(field.clone());
+ if !visible_fields.contains(&field) {
+ b = b.hide();
+ }
+ let _ = b.binding(MaybeUnbound::Bound(Thunk::result(
+ obj.get(field.clone()).transpose().expect("field exists"),
+ )));
}
new_obj.build()
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -254,6 +254,19 @@
Ok(str.as_str().trim_matches(pattern).into())
}
+#[builtin]
+pub fn builtin_trim(str: IStr) -> String {
+ let filter =
+ |v: char| {
+ v == ' '
+ || v == '\t' || v == '\n'
+ || v == '\u{000c}'
+ || v == '\r' || v == '\u{0085}'
+ || v == '\u{00a0}'
+ };
+ str.as_str().trim_matches(filter).to_string()
+}
+
fn new_trim_pattern(chars: IndexableVal) -> Result<impl Fn(char) -> bool> {
let chars: BTreeSet<char> = match chars {
IndexableVal::Str(chars) => chars.chars().collect(),
crates/jrsonnet-stdlib/src/types.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/types.rs
+++ b/crates/jrsonnet-stdlib/src/types.rs
@@ -29,3 +29,7 @@
pub fn builtin_is_function(v: Val) -> bool {
matches!(v, Val::Func(_))
}
+#[builtin]
+pub fn builtin_is_null(v: Val) -> bool {
+ matches!(v, Val::Null)
+}