--- 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 { --- 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 --- 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 --- 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) +} --- a/crates/jrsonnet-stdlib/src/misc.rs +++ b/crates/jrsonnet-stdlib/src/misc.rs @@ -156,8 +156,16 @@ #[cfg(feature = "exp-preserve-order")] true, ); - let a = a.manifest(&format).description(" manifestification")?; - let b = b.manifest(&format).description(" manifestification")?; + let a = if let Some(a) = a.as_str() { + format!("\n{a}\n") + } else { + a.manifest(&format).description(" manifestification")? + }; + let b = if let Some(b) = b.as_str() { + format!("\n{b}\n") + } else { + b.manifest(&format).description(" manifestification")? + }; bail!("assertion failed: A != B\nA: {a}\nB: {b}") } @@ -166,9 +174,7 @@ let Some(patch) = patch.as_obj() else { return Ok(patch); }; - let Some(target) = target.as_obj() else { - return Ok(Val::Obj(patch)); - }; + let target = target.as_obj().unwrap_or_else(|| ObjValue::new_empty()); let target_fields = target .fields( // FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here? @@ -203,10 +209,7 @@ if matches!(field_patch, Val::Null) { continue; } - let Some(field_target) = target.get(field.clone())? else { - out.field(field.clone()).value(field_patch); - continue; - }; + let field_target = target.get(field.clone())?.unwrap_or(Val::Null); out.field(field.clone()) .value(builtin_merge_patch(field_target, field_patch)?); } --- 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::>(); + + 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() --- 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 bool> { let chars: BTreeSet = match chars { IndexableVal::Str(chars) => chars.chars().collect(), --- 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) +}