difftreelog
feat exp-bigint
in: master
16 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -298,6 +298,7 @@
"jrsonnet-macros",
"jrsonnet-parser",
"jrsonnet-types",
+ "num-bigint",
"pathdiff",
"rustc-hash",
"serde",
@@ -370,6 +371,7 @@
"jrsonnet-macros",
"jrsonnet-parser",
"md5",
+ "num-bigint",
"serde",
"serde_json",
"serde_yaml_with_quirks",
@@ -449,6 +451,37 @@
]
[[package]]
+name = "num-bigint"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-package.version = "0.5.0"
+package.version = "0.5.0-pre7"
members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
default-members = ["cmds/jrsonnet"]
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -19,6 +19,8 @@
exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
# Iteration over objects yields [key, value] elements
exp-object-iteration = ["jrsonnet-evaluator/exp-object-iteration"]
+# Bigint type
+exp-bigint = ["jrsonnet-evaluator/exp-bigint", "jrsonnet-cli/exp-bigint"]
# std.thisFile support
legacy-this-file = ["jrsonnet-cli/legacy-this-file"]
crates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -11,6 +11,10 @@
"jrsonnet-evaluator/exp-preserve-order",
"jrsonnet-stdlib/exp-preserve-order",
]
+exp-bigint = [
+ "jrsonnet-evaluator/exp-bigint",
+ "jrsonnet-stdlib/exp-bigint",
+]
legacy-this-file = ["jrsonnet-stdlib/legacy-this-file"]
[dependencies]
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -24,6 +24,8 @@
exp-destruct = ["jrsonnet-parser/exp-destruct"]
# Iteration over objects yields [key, value] elements
exp-object-iteration = []
+# Bigint type
+exp-bigint = ["num-bigint"]
# Improves performance, and implements some useful things using nightly-only features
nightly = ["hashbrown/nightly"]
@@ -54,3 +56,5 @@
annotate-snippets = { version = "0.9.1", features = ["color"], optional = true }
# Async imports
async-trait = { version = "0.1.60", optional = true }
+# Bigint
+num-bigint = { version = "0.4.3", features = ["serde"], optional = true }
crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -47,6 +47,8 @@
(Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),
(Num(v1), Num(v2)) => Val::new_checked_num(v1 + v2)?,
+ #[cfg(feature = "exp-bigint")]
+ (BigInt(a), BigInt(b)) => BigInt(Box::new((&**a).clone() + (&**b).clone())),
_ => throw!(BinaryOperatorDoesNotOperateOnValues(
BinaryOpType::Add,
a.value_type(),
@@ -95,6 +97,8 @@
Ok(match (a, b) {
(Str(a), Str(b)) => a.cmp(b),
(Num(a), Num(b)) => a.partial_cmp(b).expect("jsonnet numbers are non NaN"),
+ #[cfg(feature = "exp-bigint")]
+ (BigInt(a), BigInt(b)) => a.cmp(b),
(Arr(a), Arr(b)) => {
if let (Some(ai), Some(bi)) = (a.iter_cheap(), b.iter_cheap()) {
for (a, b) in ai.zip(bi) {
@@ -174,6 +178,12 @@
Num(f64::from((*v1 as i32) >> (*v2 as i32)))
}
+ // Bigint X Bigint
+ #[cfg(feature = "exp-bigint")]
+ (BigInt(a), Mul, BigInt(b)) => BigInt(Box::new((&**a).clone() * (&**b).clone())),
+ #[cfg(feature = "exp-bigint")]
+ (BigInt(a), Sub, BigInt(b)) => BigInt(Box::new((&**a).clone() - (&**b).clone())),
+
_ => throw!(BinaryOperatorDoesNotOperateOnValues(
op,
a.value_type(),
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -162,6 +162,8 @@
Val::Null => serializer.serialize_none(),
Val::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
Val::Num(n) => serializer.serialize_f64(*n),
+ #[cfg(feature = "exp-bigint")]
+ Val::BigInt(b) => b.serialize(serializer),
Val::Arr(arr) => {
let mut seq = serializer.serialize_seq(Some(arr.len()))?;
for (i, element) in arr.iter().enumerate() {
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -149,6 +149,8 @@
Val::Null => buf.push_str("null"),
Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),
Val::Num(n) => write!(buf, "{n}").unwrap(),
+ #[cfg(feature = "exp-bigint")]
+ Val::BigInt(n) => write!(buf, "{n}").unwrap(),
Val::Arr(items) => {
buf.push('[');
if !items.is_empty() {
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 mem::replace,5 rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 tb, throw,19 typed::BoundedUsize,20 ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting(TraceBox<dyn ThunkValue<Output = T>>),33 Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42 pub fn evaluated(val: T) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44 }45 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47 }48 pub fn errored(e: Error) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50 }51}5253impl<T> Thunk<T>54where55 T: Clone + Trace,56{57 pub fn force(&self) -> Result<()> {58 self.evaluate()?;59 Ok(())60 }6162 /// Evaluate thunk, or return cached value63 ///64 /// # Errors65 ///66 /// - Lazy value evaluation returned error67 /// - This method was called during inner value evaluation68 pub fn evaluate(&self) -> Result<T> {69 match &*self.0.borrow() {70 ThunkInner::Computed(v) => return Ok(v.clone()),71 ThunkInner::Errored(e) => return Err(e.clone()),72 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73 ThunkInner::Waiting(..) => (),74 };75 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {76 unreachable!();77 };78 let new_value = match value.0.get() {79 Ok(v) => v,80 Err(e) => {81 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());82 return Err(e);83 }84 };85 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());86 Ok(new_value)87 }88}8990type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);9192#[derive(Trace, Clone)]93pub struct CachedUnbound<I, T>94where95 I: Unbound<Bound = T>,96 T: Trace,97{98 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,99 value: I,100}101impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {102 pub fn new(value: I) -> Self {103 Self {104 cache: Cc::new(RefCell::new(GcHashMap::new())),105 value,106 }107 }108}109impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {110 type Bound = T;111 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {112 let cache_key = (113 sup.as_ref().map(|s| s.clone().downgrade()),114 this.as_ref().map(|t| t.clone().downgrade()),115 );116 {117 if let Some(t) = self.cache.borrow().get(&cache_key) {118 return Ok(t.clone());119 }120 }121 let bound = self.value.bind(sup, this)?;122123 {124 let mut cache = self.cache.borrow_mut();125 cache.insert(cache_key, bound.clone());126 }127128 Ok(bound)129 }130}131132impl<T: Debug + Trace> Debug for Thunk<T> {133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134 write!(f, "Lazy")135 }136}137impl<T: Trace> PartialEq for Thunk<T> {138 fn eq(&self, other: &Self) -> bool {139 Cc::ptr_eq(&self.0, &other.0)140 }141}142143/// Represents a Jsonnet value, which can be sliced or indexed (string or array).144#[allow(clippy::module_name_repetitions)]145pub enum IndexableVal {146 /// String.147 Str(IStr),148 /// Array.149 Arr(ArrValue),150}151impl IndexableVal {152 /// Slice the value.153 ///154 /// # Implementation155 ///156 /// For strings, will create a copy of specified interval.157 ///158 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.159 pub fn slice(160 self,161 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,162 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,163 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,164 ) -> Result<Self> {165 match &self {166 IndexableVal::Str(s) => {167 let index = index.as_deref().copied().unwrap_or(0);168 let end = end.as_deref().copied().unwrap_or(usize::MAX);169 let step = step.as_deref().copied().unwrap_or(1);170171 if index >= end {172 return Ok(Self::Str("".into()));173 }174175 Ok(Self::Str(176 (s.chars()177 .skip(index)178 .take(end - index)179 .step_by(step)180 .collect::<String>())181 .into(),182 ))183 }184 IndexableVal::Arr(arr) => {185 let index = index.as_deref().copied().unwrap_or(0);186 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());187 let step = step.as_deref().copied().unwrap_or(1);188189 if index >= end {190 return Ok(Self::Arr(ArrValue::empty()));191 }192193 Ok(Self::Arr(194 arr.clone()195 .slice(Some(index), Some(end), Some(step))196 .expect("arguments checked"),197 ))198 }199 }200 }201}202203#[derive(Debug, Clone, Trace)]204pub enum StrValue {205 Flat(IStr),206 Tree(Rc<(StrValue, StrValue, usize)>),207}208impl StrValue {209 pub fn concat(a: StrValue, b: StrValue) -> Self {210 // TODO: benchmark for an optimal value, currently just a arbitrary choice211 const STRING_EXTEND_THRESHOLD: usize = 100;212213 if a.is_empty() {214 b215 } else if b.is_empty() {216 a217 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {218 Self::Flat(format!("{a}{b}").into())219 } else {220 let len = a.len() + b.len();221 Self::Tree(Rc::new((a, b, len)))222 }223 }224 pub fn into_flat(self) -> IStr {225 #[cold]226 fn write_buf(s: &StrValue, out: &mut String) {227 match s {228 StrValue::Flat(f) => out.push_str(f),229 StrValue::Tree(t) => {230 write_buf(&t.0, out);231 write_buf(&t.1, out);232 }233 }234 }235 match self {236 StrValue::Flat(f) => f,237 StrValue::Tree(_) => {238 let mut buf = String::with_capacity(self.len());239 write_buf(&self, &mut buf);240 buf.into()241 }242 }243 }244 pub fn len(&self) -> usize {245 match self {246 StrValue::Flat(v) => v.len(),247 StrValue::Tree(t) => t.2,248 }249 }250 pub fn is_empty(&self) -> bool {251 match self {252 Self::Flat(v) => v.is_empty(),253 // Can't create non-flat empty string254 Self::Tree(_) => false,255 }256 }257}258impl From<&str> for StrValue {259 fn from(value: &str) -> Self {260 Self::Flat(value.into())261 }262}263impl From<String> for StrValue {264 fn from(value: String) -> Self {265 Self::Flat(value.into())266 }267}268impl Display for StrValue {269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {270 match self {271 StrValue::Flat(v) => write!(f, "{v}"),272 StrValue::Tree(t) => {273 write!(f, "{}", t.0)?;274 write!(f, "{}", t.1)275 }276 }277 }278}279impl PartialEq for StrValue {280 fn eq(&self, other: &Self) -> bool {281 let a = self.clone().into_flat();282 let b = other.clone().into_flat();283 a == b284 }285}286impl Eq for StrValue {}287impl PartialOrd for StrValue {288 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {289 let a = self.clone().into_flat();290 let b = other.clone().into_flat();291 Some(a.cmp(&b))292 }293}294impl Ord for StrValue {295 fn cmp(&self, other: &Self) -> std::cmp::Ordering {296 self.partial_cmp(other)297 .expect("partial_cmp always returns Some")298 }299}300301/// Represents any valid Jsonnet value.302#[derive(Debug, Clone, Trace)]303pub enum Val {304 /// Represents a Jsonnet boolean.305 Bool(bool),306 /// Represents a Jsonnet null value.307 Null,308 /// Represents a Jsonnet string.309 Str(StrValue),310 /// Represents a Jsonnet number.311 /// Should be finite, and not NaN312 /// This restriction isn't enforced by enum, as enum field can't be marked as private313 Num(f64),314 /// Represents a Jsonnet array.315 Arr(ArrValue),316 /// Represents a Jsonnet object.317 Obj(ObjValue),318 /// Represents a Jsonnet function.319 Func(FuncVal),320}321322#[cfg(target_pointer_width = "64")]323static_assertions::assert_eq_size!(Val, [u8; 24]);324325impl From<IndexableVal> for Val {326 fn from(v: IndexableVal) -> Self {327 match v {328 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),329 IndexableVal::Arr(a) => Self::Arr(a),330 }331 }332}333334impl Val {335 pub const fn as_bool(&self) -> Option<bool> {336 match self {337 Self::Bool(v) => Some(*v),338 _ => None,339 }340 }341 pub const fn as_null(&self) -> Option<()> {342 match self {343 Self::Null => Some(()),344 _ => None,345 }346 }347 pub fn as_str(&self) -> Option<IStr> {348 match self {349 Self::Str(s) => Some(s.clone().into_flat()),350 _ => None,351 }352 }353 pub const fn as_num(&self) -> Option<f64> {354 match self {355 Self::Num(n) => Some(*n),356 _ => None,357 }358 }359 pub fn as_arr(&self) -> Option<ArrValue> {360 match self {361 Self::Arr(a) => Some(a.clone()),362 _ => None,363 }364 }365 pub fn as_obj(&self) -> Option<ObjValue> {366 match self {367 Self::Obj(o) => Some(o.clone()),368 _ => None,369 }370 }371 pub fn as_func(&self) -> Option<FuncVal> {372 match self {373 Self::Func(f) => Some(f.clone()),374 _ => None,375 }376 }377378 /// Creates `Val::Num` after checking for numeric overflow.379 /// As numbers are `f64`, we can just check for their finity.380 pub fn new_checked_num(num: f64) -> Result<Self> {381 if num.is_finite() {382 Ok(Self::Num(num))383 } else {384 throw!("overflow")385 }386 }387388 pub const fn value_type(&self) -> ValType {389 match self {390 Self::Str(..) => ValType::Str,391 Self::Num(..) => ValType::Num,392 Self::Arr(..) => ValType::Arr,393 Self::Obj(..) => ValType::Obj,394 Self::Bool(_) => ValType::Bool,395 Self::Null => ValType::Null,396 Self::Func(..) => ValType::Func,397 }398 }399400 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {401 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {402 manifest.manifest(val.clone())403 }404 manifest_dyn(self, &format)405 }406407 pub fn to_string(&self) -> Result<IStr> {408 Ok(match self {409 Self::Bool(true) => "true".into(),410 Self::Bool(false) => "false".into(),411 Self::Null => "null".into(),412 Self::Str(s) => s.clone().into_flat(),413 _ => self.manifest(ToStringFormat).map(IStr::from)?,414 })415 }416417 pub fn into_indexable(self) -> Result<IndexableVal> {418 Ok(match self {419 Val::Str(s) => IndexableVal::Str(s.into_flat()),420 Val::Arr(arr) => IndexableVal::Arr(arr),421 _ => throw!(ValueIsNotIndexable(self.value_type())),422 })423 }424}425426const fn is_function_like(val: &Val) -> bool {427 matches!(val, Val::Func(_))428}429430/// Native implementation of `std.primitiveEquals`431pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {432 Ok(match (val_a, val_b) {433 (Val::Bool(a), Val::Bool(b)) => a == b,434 (Val::Null, Val::Null) => true,435 (Val::Str(a), Val::Str(b)) => a == b,436 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,437 (Val::Arr(_), Val::Arr(_)) => {438 throw!("primitiveEquals operates on primitive types, got array")439 }440 (Val::Obj(_), Val::Obj(_)) => {441 throw!("primitiveEquals operates on primitive types, got object")442 }443 (a, b) if is_function_like(a) && is_function_like(b) => {444 throw!("cannot test equality of functions")445 }446 (_, _) => false,447 })448}449450/// Native implementation of `std.equals`451pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {452 if val_a.value_type() != val_b.value_type() {453 return Ok(false);454 }455 match (val_a, val_b) {456 (Val::Arr(a), Val::Arr(b)) => {457 if ArrValue::ptr_eq(a, b) {458 return Ok(true);459 }460 if a.len() != b.len() {461 return Ok(false);462 }463 for (a, b) in a.iter().zip(b.iter()) {464 if !equals(&a?, &b?)? {465 return Ok(false);466 }467 }468 Ok(true)469 }470 (Val::Obj(a), Val::Obj(b)) => {471 if ObjValue::ptr_eq(a, b) {472 return Ok(true);473 }474 let fields = a.fields(475 #[cfg(feature = "exp-preserve-order")]476 false,477 );478 if fields479 != b.fields(480 #[cfg(feature = "exp-preserve-order")]481 false,482 ) {483 return Ok(false);484 }485 for field in fields {486 if !equals(487 &a.get(field.clone())?.expect("field exists"),488 &b.get(field)?.expect("field exists"),489 )? {490 return Ok(false);491 }492 }493 Ok(true)494 }495 (a, b) => Ok(primitive_equals(a, b)?),496 }497}1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 mem::replace,5 rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 tb, throw,19 typed::BoundedUsize,20 ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting(TraceBox<dyn ThunkValue<Output = T>>),33 Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42 pub fn evaluated(val: T) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44 }45 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47 }48 pub fn errored(e: Error) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50 }51}5253impl<T> Thunk<T>54where55 T: Clone + Trace,56{57 pub fn force(&self) -> Result<()> {58 self.evaluate()?;59 Ok(())60 }6162 /// Evaluate thunk, or return cached value63 ///64 /// # Errors65 ///66 /// - Lazy value evaluation returned error67 /// - This method was called during inner value evaluation68 pub fn evaluate(&self) -> Result<T> {69 match &*self.0.borrow() {70 ThunkInner::Computed(v) => return Ok(v.clone()),71 ThunkInner::Errored(e) => return Err(e.clone()),72 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73 ThunkInner::Waiting(..) => (),74 };75 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {76 unreachable!();77 };78 let new_value = match value.0.get() {79 Ok(v) => v,80 Err(e) => {81 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());82 return Err(e);83 }84 };85 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());86 Ok(new_value)87 }88}8990type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);9192#[derive(Trace, Clone)]93pub struct CachedUnbound<I, T>94where95 I: Unbound<Bound = T>,96 T: Trace,97{98 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,99 value: I,100}101impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {102 pub fn new(value: I) -> Self {103 Self {104 cache: Cc::new(RefCell::new(GcHashMap::new())),105 value,106 }107 }108}109impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {110 type Bound = T;111 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {112 let cache_key = (113 sup.as_ref().map(|s| s.clone().downgrade()),114 this.as_ref().map(|t| t.clone().downgrade()),115 );116 {117 if let Some(t) = self.cache.borrow().get(&cache_key) {118 return Ok(t.clone());119 }120 }121 let bound = self.value.bind(sup, this)?;122123 {124 let mut cache = self.cache.borrow_mut();125 cache.insert(cache_key, bound.clone());126 }127128 Ok(bound)129 }130}131132impl<T: Debug + Trace> Debug for Thunk<T> {133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134 write!(f, "Lazy")135 }136}137impl<T: Trace> PartialEq for Thunk<T> {138 fn eq(&self, other: &Self) -> bool {139 Cc::ptr_eq(&self.0, &other.0)140 }141}142143/// Represents a Jsonnet value, which can be sliced or indexed (string or array).144#[allow(clippy::module_name_repetitions)]145pub enum IndexableVal {146 /// String.147 Str(IStr),148 /// Array.149 Arr(ArrValue),150}151impl IndexableVal {152 /// Slice the value.153 ///154 /// # Implementation155 ///156 /// For strings, will create a copy of specified interval.157 ///158 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.159 pub fn slice(160 self,161 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,162 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,163 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,164 ) -> Result<Self> {165 match &self {166 IndexableVal::Str(s) => {167 let index = index.as_deref().copied().unwrap_or(0);168 let end = end.as_deref().copied().unwrap_or(usize::MAX);169 let step = step.as_deref().copied().unwrap_or(1);170171 if index >= end {172 return Ok(Self::Str("".into()));173 }174175 Ok(Self::Str(176 (s.chars()177 .skip(index)178 .take(end - index)179 .step_by(step)180 .collect::<String>())181 .into(),182 ))183 }184 IndexableVal::Arr(arr) => {185 let index = index.as_deref().copied().unwrap_or(0);186 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());187 let step = step.as_deref().copied().unwrap_or(1);188189 if index >= end {190 return Ok(Self::Arr(ArrValue::empty()));191 }192193 Ok(Self::Arr(194 arr.clone()195 .slice(Some(index), Some(end), Some(step))196 .expect("arguments checked"),197 ))198 }199 }200 }201}202203#[derive(Debug, Clone, Trace)]204pub enum StrValue {205 Flat(IStr),206 Tree(Rc<(StrValue, StrValue, usize)>),207}208impl StrValue {209 pub fn concat(a: StrValue, b: StrValue) -> Self {210 // TODO: benchmark for an optimal value, currently just a arbitrary choice211 const STRING_EXTEND_THRESHOLD: usize = 100;212213 if a.is_empty() {214 b215 } else if b.is_empty() {216 a217 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {218 Self::Flat(format!("{a}{b}").into())219 } else {220 let len = a.len() + b.len();221 Self::Tree(Rc::new((a, b, len)))222 }223 }224 pub fn into_flat(self) -> IStr {225 #[cold]226 fn write_buf(s: &StrValue, out: &mut String) {227 match s {228 StrValue::Flat(f) => out.push_str(f),229 StrValue::Tree(t) => {230 write_buf(&t.0, out);231 write_buf(&t.1, out);232 }233 }234 }235 match self {236 StrValue::Flat(f) => f,237 StrValue::Tree(_) => {238 let mut buf = String::with_capacity(self.len());239 write_buf(&self, &mut buf);240 buf.into()241 }242 }243 }244 pub fn len(&self) -> usize {245 match self {246 StrValue::Flat(v) => v.len(),247 StrValue::Tree(t) => t.2,248 }249 }250 pub fn is_empty(&self) -> bool {251 match self {252 Self::Flat(v) => v.is_empty(),253 // Can't create non-flat empty string254 Self::Tree(_) => false,255 }256 }257}258impl From<&str> for StrValue {259 fn from(value: &str) -> Self {260 Self::Flat(value.into())261 }262}263impl From<String> for StrValue {264 fn from(value: String) -> Self {265 Self::Flat(value.into())266 }267}268impl Display for StrValue {269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {270 match self {271 StrValue::Flat(v) => write!(f, "{v}"),272 StrValue::Tree(t) => {273 write!(f, "{}", t.0)?;274 write!(f, "{}", t.1)275 }276 }277 }278}279impl PartialEq for StrValue {280 fn eq(&self, other: &Self) -> bool {281 let a = self.clone().into_flat();282 let b = other.clone().into_flat();283 a == b284 }285}286impl Eq for StrValue {}287impl PartialOrd for StrValue {288 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {289 let a = self.clone().into_flat();290 let b = other.clone().into_flat();291 Some(a.cmp(&b))292 }293}294impl Ord for StrValue {295 fn cmp(&self, other: &Self) -> std::cmp::Ordering {296 self.partial_cmp(other)297 .expect("partial_cmp always returns Some")298 }299}300301/// Represents any valid Jsonnet value.302#[derive(Debug, Clone, Trace)]303pub enum Val {304 /// Represents a Jsonnet boolean.305 Bool(bool),306 /// Represents a Jsonnet null value.307 Null,308 /// Represents a Jsonnet string.309 Str(StrValue),310 /// Represents a Jsonnet number.311 /// Should be finite, and not NaN312 /// This restriction isn't enforced by enum, as enum field can't be marked as private313 Num(f64),314 /// Experimental bigint315 #[cfg(feature = "exp-bigint")]316 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),317 /// Represents a Jsonnet array.318 Arr(ArrValue),319 /// Represents a Jsonnet object.320 Obj(ObjValue),321 /// Represents a Jsonnet function.322 Func(FuncVal),323}324325#[cfg(target_pointer_width = "64")]326static_assertions::assert_eq_size!(Val, [u8; 24]);327328impl From<IndexableVal> for Val {329 fn from(v: IndexableVal) -> Self {330 match v {331 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),332 IndexableVal::Arr(a) => Self::Arr(a),333 }334 }335}336337impl Val {338 pub const fn as_bool(&self) -> Option<bool> {339 match self {340 Self::Bool(v) => Some(*v),341 _ => None,342 }343 }344 pub const fn as_null(&self) -> Option<()> {345 match self {346 Self::Null => Some(()),347 _ => None,348 }349 }350 pub fn as_str(&self) -> Option<IStr> {351 match self {352 Self::Str(s) => Some(s.clone().into_flat()),353 _ => None,354 }355 }356 pub const fn as_num(&self) -> Option<f64> {357 match self {358 Self::Num(n) => Some(*n),359 _ => None,360 }361 }362 pub fn as_arr(&self) -> Option<ArrValue> {363 match self {364 Self::Arr(a) => Some(a.clone()),365 _ => None,366 }367 }368 pub fn as_obj(&self) -> Option<ObjValue> {369 match self {370 Self::Obj(o) => Some(o.clone()),371 _ => None,372 }373 }374 pub fn as_func(&self) -> Option<FuncVal> {375 match self {376 Self::Func(f) => Some(f.clone()),377 _ => None,378 }379 }380381 /// Creates `Val::Num` after checking for numeric overflow.382 /// As numbers are `f64`, we can just check for their finity.383 pub fn new_checked_num(num: f64) -> Result<Self> {384 if num.is_finite() {385 Ok(Self::Num(num))386 } else {387 throw!("overflow")388 }389 }390391 pub const fn value_type(&self) -> ValType {392 match self {393 Self::Str(..) => ValType::Str,394 Self::Num(..) => ValType::Num,395 #[cfg(feature = "exp-bigint")]396 Self::BigInt(..) => ValType::BigInt,397 Self::Arr(..) => ValType::Arr,398 Self::Obj(..) => ValType::Obj,399 Self::Bool(_) => ValType::Bool,400 Self::Null => ValType::Null,401 Self::Func(..) => ValType::Func,402 }403 }404405 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {406 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {407 manifest.manifest(val.clone())408 }409 manifest_dyn(self, &format)410 }411412 pub fn to_string(&self) -> Result<IStr> {413 Ok(match self {414 Self::Bool(true) => "true".into(),415 Self::Bool(false) => "false".into(),416 Self::Null => "null".into(),417 Self::Str(s) => s.clone().into_flat(),418 _ => self.manifest(ToStringFormat).map(IStr::from)?,419 })420 }421422 pub fn into_indexable(self) -> Result<IndexableVal> {423 Ok(match self {424 Val::Str(s) => IndexableVal::Str(s.into_flat()),425 Val::Arr(arr) => IndexableVal::Arr(arr),426 _ => throw!(ValueIsNotIndexable(self.value_type())),427 })428 }429}430431const fn is_function_like(val: &Val) -> bool {432 matches!(val, Val::Func(_))433}434435/// Native implementation of `std.primitiveEquals`436pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {437 Ok(match (val_a, val_b) {438 (Val::Bool(a), Val::Bool(b)) => a == b,439 (Val::Null, Val::Null) => true,440 (Val::Str(a), Val::Str(b)) => a == b,441 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,442 (Val::Arr(_), Val::Arr(_)) => {443 throw!("primitiveEquals operates on primitive types, got array")444 }445 (Val::Obj(_), Val::Obj(_)) => {446 throw!("primitiveEquals operates on primitive types, got object")447 }448 (a, b) if is_function_like(a) && is_function_like(b) => {449 throw!("cannot test equality of functions")450 }451 (_, _) => false,452 })453}454455/// Native implementation of `std.equals`456pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {457 if val_a.value_type() != val_b.value_type() {458 return Ok(false);459 }460 match (val_a, val_b) {461 (Val::Arr(a), Val::Arr(b)) => {462 if ArrValue::ptr_eq(a, b) {463 return Ok(true);464 }465 if a.len() != b.len() {466 return Ok(false);467 }468 for (a, b) in a.iter().zip(b.iter()) {469 if !equals(&a?, &b?)? {470 return Ok(false);471 }472 }473 Ok(true)474 }475 (Val::Obj(a), Val::Obj(b)) => {476 if ObjValue::ptr_eq(a, b) {477 return Ok(true);478 }479 let fields = a.fields(480 #[cfg(feature = "exp-preserve-order")]481 false,482 );483 if fields484 != b.fields(485 #[cfg(feature = "exp-preserve-order")]486 false,487 ) {488 return Ok(false);489 }490 for field in fields {491 if !equals(492 &a.get(field.clone())?.expect("field exists"),493 &b.get(field)?.expect("field exists"),494 )? {495 return Ok(false);496 }497 }498 Ok(true)499 }500 (a, b) => Ok(primitive_equals(a, b)?),501 }502}crates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -17,6 +17,8 @@
exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
# Add nonstandard `std.sha256` function
exp-more-hashes = ["dep:sha2"]
+# Bigint type
+exp-bigint = ["num-bigint", "jrsonnet-evaluator/exp-bigint"]
[dependencies]
jrsonnet-evaluator.workspace = true
@@ -39,6 +41,7 @@
serde_yaml_with_quirks = "0.8.24"
sha2 = { version = "0.10.6", optional = true }
+num-bigint = { version = "0.4.3", optional = true }
[build-dependencies]
jrsonnet-parser.workspace = true
crates/jrsonnet-stdlib/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/expr.rs
+++ b/crates/jrsonnet-stdlib/src/expr.rs
@@ -83,7 +83,7 @@
pub(super) use std::{option::Option, rc::Rc, vec};
pub(super) use jrsonnet_parser::*;
- };
+ }
include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))
}
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -143,6 +143,8 @@
("asciiLower", builtin_ascii_lower::INST),
("findSubstr", builtin_find_substr::INST),
("parseInt", builtin_parse_int::INST),
+ #[cfg(feature = "exp-bigint")]
+ ("bigint", builtin_bigint::INST),
("parseOctal", builtin_parse_octal::INST),
("parseHex", builtin_parse_hex::INST),
// Misc
crates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/toml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -103,6 +103,8 @@
escape_string_json_buf(&s.clone().into_flat(), buf);
}
Val::Num(n) => write!(buf, "{n}").unwrap(),
+ #[cfg(feature = "exp-bigint")]
+ Val::BigInt(n) => write!(buf, "{n}").unwrap(),
Val::Arr(a) => {
if a.is_empty() {
buf.push_str("[]");
crates/jrsonnet-stdlib/src/manifest/yaml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs
@@ -140,6 +140,8 @@
}
}
Val::Num(n) => write!(buf, "{}", *n).unwrap(),
+ #[cfg(feature = "exp-bigint")]
+ Val::BigInt(n) => write!(buf, "{}", *n).unwrap(),
Val::Arr(a) => {
if a.is_empty() {
buf.push_str("[]");
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -151,6 +151,20 @@
})
}
+#[cfg(feature = "exp-bigint")]
+#[builtin]
+pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {
+ use Either2::*;
+ Ok(match v {
+ A(a) => Val::BigInt(Box::new((a as i64).into())),
+ B(b) => Val::BigInt(Box::new(
+ b.as_str()
+ .parse()
+ .map_err(|e| RuntimeError(format!("bad bigint: {e}").into()))?,
+ )),
+ })
+}
+
#[cfg(test)]
mod tests {
use super::*;
crates/jrsonnet-types/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -88,6 +88,8 @@
Null,
Str,
Num,
+ #[cfg(feature = "exp-bigint")]
+ BigInt,
Arr,
Obj,
Func,
@@ -101,6 +103,8 @@
Null => "null",
Str => "string",
Num => "number",
+ #[cfg(feature = "exp-bigint")]
+ BigInt => "bigint",
Arr => "array",
Obj => "object",
Func => "function",