difftreelog
refactor drop derivative dependency
in: master
5 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -194,7 +194,7 @@
"heck",
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
]
[[package]]
@@ -244,17 +244,6 @@
dependencies = [
"generic-array",
"typenum",
-]
-
-[[package]]
-name = "derivative"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
]
[[package]]
@@ -483,7 +472,6 @@
dependencies = [
"annotate-snippets",
"anyhow",
- "derivative",
"hashbrown 0.14.5",
"hi-doc",
"jrsonnet-gcmodule",
@@ -533,7 +521,7 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
]
[[package]]
@@ -551,7 +539,7 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
"syn-dissect-closure",
]
@@ -694,7 +682,7 @@
"proc-macro2",
"quote",
"regex-syntax",
- "syn 2.0.76",
+ "syn",
]
[[package]]
@@ -1019,7 +1007,7 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
]
[[package]]
@@ -1123,17 +1111,6 @@
[[package]]
name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
version = "2.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
@@ -1151,7 +1128,7 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
]
[[package]]
@@ -1202,7 +1179,7 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
]
[[package]]
@@ -1428,5 +1405,5 @@
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.76",
+ "syn",
]
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -71,7 +71,6 @@
static_assertions = "1.1"
rustc-hash = "1.1"
num-bigint = "0.4.5"
-derivative = "2.2.0"
strsim = "0.11.0"
proc-macro2 = "1.0"
quote = "1.0"
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -59,5 +59,4 @@
hi-doc = { workspace = true, optional = true }
# Bigint
num-bigint = { workspace = true, features = ["serde"], optional = true }
-derivative.workspace = true
stacker = "0.1.15"
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,8 +1,7 @@
use std::mem::replace;
-use jrsonnet_gcmodule::Trace;
use jrsonnet_interner::IStr;
-use jrsonnet_parser::{LocExpr, ParamsDesc};
+use jrsonnet_parser::ParamsDesc;
use super::{arglike::ArgsLike, builtin::BuiltinParam};
use crate::{
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 mem::replace,6 num::NonZeroU32,7 ops::Deref,8 rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use thiserror::Error;1718pub use crate::arr::{ArrValue, ArrayLike};19use crate::{20 bail,21 error::{Error, ErrorKind::*},22 function::FuncVal,23 gc::{GcHashMap, TraceBox},24 manifest::{ManifestFormat, ToStringFormat},25 tb,26 typed::BoundedUsize,27 ObjValue, Result, Unbound, WeakObjValue,28};2930pub trait ThunkValue: Trace {31 type Output;32 fn get(self: Box<Self>) -> Result<Self::Output>;33}3435#[derive(Trace)]36pub struct ThunkValueClosure<D: Trace, O: 'static> {37 env: D,38 // Carries no data, as it is not a real closure, all the39 // captured environment is stored in `env` field.40 #[trace(skip)]41 closure: fn(D) -> Result<O>,42}43impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {44 pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {45 Self { env, closure }46 }47}48impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {49 type Output = O;5051 fn get(self: Box<Self>) -> Result<Self::Output> {52 (self.closure)(self.env)53 }54}5556#[derive(Trace)]57enum ThunkInner<T: Trace> {58 Computed(T),59 Errored(Error),60 Waiting(TraceBox<dyn ThunkValue<Output = T>>),61 Pending,62}6364/// Lazily evaluated value65#[allow(clippy::module_name_repetitions)]66#[derive(Clone, Trace)]67pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);6869impl<T: Trace> Thunk<T> {70 pub fn evaluated(val: T) -> Self {71 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))72 }73 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {74 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))75 }76 pub fn errored(e: Error) -> Self {77 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))78 }79 pub fn result(res: Result<T, Error>) -> Self {80 match res {81 Ok(o) => Self::evaluated(o),82 Err(e) => Self::errored(e),83 }84 }85}8687impl<T> Thunk<T>88where89 T: Clone + Trace,90{91 pub fn force(&self) -> Result<()> {92 self.evaluate()?;93 Ok(())94 }9596 /// Evaluate thunk, or return cached value97 ///98 /// # Errors99 ///100 /// - Lazy value evaluation returned error101 /// - This method was called during inner value evaluation102 pub fn evaluate(&self) -> Result<T> {103 match &*self.0.borrow() {104 ThunkInner::Computed(v) => return Ok(v.clone()),105 ThunkInner::Errored(e) => return Err(e.clone()),106 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),107 ThunkInner::Waiting(..) => (),108 };109 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)110 else {111 unreachable!();112 };113 let new_value = match value.0.get() {114 Ok(v) => v,115 Err(e) => {116 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());117 return Err(e);118 }119 };120 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());121 Ok(new_value)122 }123}124125pub trait ThunkMapper<Input>: Trace {126 type Output;127 fn map(self, from: Input) -> Result<Self::Output>;128}129impl<Input> Thunk<Input>130where131 Input: Trace + Clone,132{133 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>134 where135 M: ThunkMapper<Input>,136 M::Output: Trace,137 {138 let inner = self;139 Thunk!(move || {140 let value = inner.evaluate()?;141 let mapped = mapper.map(value)?;142 Ok(mapped)143 })144 }145}146147impl<T: Trace> From<Result<T>> for Thunk<T> {148 fn from(value: Result<T>) -> Self {149 match value {150 Ok(o) => Self::evaluated(o),151 Err(e) => Self::errored(e),152 }153 }154}155impl<T, V: Trace> From<T> for Thunk<V>156where157 T: ThunkValue<Output = V>,158{159 fn from(value: T) -> Self {160 Self::new(value)161 }162}163164impl<T: Trace + Default> Default for Thunk<T> {165 fn default() -> Self {166 Self::evaluated(T::default())167 }168}169170type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);171172#[derive(Trace, Clone)]173pub struct CachedUnbound<I, T>174where175 I: Unbound<Bound = T>,176 T: Trace,177{178 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,179 value: I,180}181impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {182 pub fn new(value: I) -> Self {183 Self {184 cache: Cc::new(RefCell::new(GcHashMap::new())),185 value,186 }187 }188}189impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {190 type Bound = T;191 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {192 let cache_key = (193 sup.as_ref().map(|s| s.clone().downgrade()),194 this.as_ref().map(|t| t.clone().downgrade()),195 );196 {197 if let Some(t) = self.cache.borrow().get(&cache_key) {198 return Ok(t.clone());199 }200 }201 let bound = self.value.bind(sup, this)?;202203 {204 let mut cache = self.cache.borrow_mut();205 cache.insert(cache_key, bound.clone());206 }207208 Ok(bound)209 }210}211212impl<T: Debug + Trace> Debug for Thunk<T> {213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {214 write!(f, "Lazy")215 }216}217impl<T: Trace> PartialEq for Thunk<T> {218 fn eq(&self, other: &Self) -> bool {219 Cc::ptr_eq(&self.0, &other.0)220 }221}222223/// Represents a Jsonnet value, which can be sliced or indexed (string or array).224#[allow(clippy::module_name_repetitions)]225pub enum IndexableVal {226 /// String.227 Str(IStr),228 /// Array.229 Arr(ArrValue),230}231impl IndexableVal {232 pub fn is_empty(&self) -> bool {233 match self {234 Self::Str(s) => s.is_empty(),235 Self::Arr(s) => s.is_empty(),236 }237 }238239 pub fn to_array(self) -> ArrValue {240 match self {241 Self::Str(s) => ArrValue::chars(s.chars()),242 Self::Arr(arr) => arr,243 }244 }245 /// Slice the value.246 ///247 /// # Implementation248 ///249 /// For strings, will create a copy of specified interval.250 ///251 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.252 pub fn slice(253 self,254 index: Option<i32>,255 end: Option<i32>,256 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,257 ) -> Result<Self> {258 match &self {259 Self::Str(s) => {260 let mut computed_len = None;261 let mut get_len = || {262 computed_len.map_or_else(263 || {264 let len = s.chars().count();265 let _ = computed_len.insert(len);266 len267 },268 |len| len,269 )270 };271 let mut get_idx = |pos: Option<i32>, default| {272 match pos {273 Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),274 // No need to clamp, as iterator interface is used275 Some(v) => v as usize,276 None => default,277 }278 };279280 let index = get_idx(index, 0);281 let end = get_idx(end, usize::MAX);282 let step = step.as_deref().copied().unwrap_or(1);283284 if index >= end {285 return Ok(Self::Str("".into()));286 }287288 Ok(Self::Str(289 (s.chars()290 .skip(index)291 .take(end - index)292 .step_by(step)293 .collect::<String>())294 .into(),295 ))296 }297 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(298 index,299 end,300 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),301 ))),302 }303 }304}305306#[derive(Debug, Clone, Trace)]307pub enum StrValue {308 Flat(IStr),309 Tree(Rc<(StrValue, StrValue, usize)>),310}311impl StrValue {312 pub fn concat(a: Self, b: Self) -> Self {313 // TODO: benchmark for an optimal value, currently just a arbitrary choice314 const STRING_EXTEND_THRESHOLD: usize = 100;315316 if a.is_empty() {317 b318 } else if b.is_empty() {319 a320 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {321 Self::Flat(format!("{a}{b}").into())322 } else {323 let len = a.len() + b.len();324 Self::Tree(Rc::new((a, b, len)))325 }326 }327 pub fn into_flat(self) -> IStr {328 #[cold]329 fn write_buf(s: &StrValue, out: &mut String) {330 match s {331 StrValue::Flat(f) => out.push_str(f),332 StrValue::Tree(t) => {333 write_buf(&t.0, out);334 write_buf(&t.1, out);335 }336 }337 }338 match self {339 Self::Flat(f) => f,340 Self::Tree(_) => {341 let mut buf = String::with_capacity(self.len());342 write_buf(&self, &mut buf);343 buf.into()344 }345 }346 }347 pub fn len(&self) -> usize {348 match self {349 Self::Flat(v) => v.len(),350 Self::Tree(t) => t.2,351 }352 }353 pub fn is_empty(&self) -> bool {354 match self {355 Self::Flat(v) => v.is_empty(),356 // Can't create non-flat empty string357 Self::Tree(_) => false,358 }359 }360}361impl<T> From<T> for StrValue362where363 IStr: From<T>,364{365 fn from(value: T) -> Self {366 Self::Flat(IStr::from(value))367 }368}369impl Display for StrValue {370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {371 match self {372 Self::Flat(v) => write!(f, "{v}"),373 Self::Tree(t) => {374 write!(f, "{}", t.0)?;375 write!(f, "{}", t.1)376 }377 }378 }379}380impl PartialEq for StrValue {381 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.382 #[allow(clippy::unconditional_recursion)]383 fn eq(&self, other: &Self) -> bool {384 let a = self.clone().into_flat();385 let b = other.clone().into_flat();386 a == b387 }388}389impl Eq for StrValue {}390impl PartialOrd for StrValue {391 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {392 Some(self.cmp(other))393 }394}395impl Ord for StrValue {396 fn cmp(&self, other: &Self) -> Ordering {397 let a = self.clone().into_flat();398 let b = other.clone().into_flat();399 a.cmp(&b)400 }401}402403/// Represents jsonnet number404/// Jsonnet numbers are finite f64, with NaNs disallowed405#[derive(Trace, Clone, Copy, Derivative)]406#[derivative(Debug = "transparent")]407#[repr(transparent)]408pub struct NumValue(f64);409impl NumValue {410 /// Creates a [`NumValue`], if value is finite and not NaN411 pub fn new(v: f64) -> Option<Self> {412 if !v.is_finite() {413 return None;414 }415 Some(Self(v))416 }417 #[inline]418 pub const fn get(&self) -> f64 {419 self.0420 }421}422impl PartialEq for NumValue {423 fn eq(&self, other: &Self) -> bool {424 self.0 == other.0425 }426}427impl Eq for NumValue {}428impl Ord for NumValue {429 #[inline]430 fn cmp(&self, other: &Self) -> Ordering {431 // Can't use `total_cmp`: its behavior for `-0` and `0`432 // is not following wanted.433 unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }434 }435}436impl PartialOrd for NumValue {437 #[inline]438 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {439 Some(self.cmp(other))440 }441}442impl Display for NumValue {443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {444 Display::fmt(&self.0, f)445 }446}447impl Deref for NumValue {448 type Target = f64;449450 #[inline]451 fn deref(&self) -> &Self::Target {452 &self.0453 }454}455macro_rules! impl_num {456 ($($ty:ty),+) => {$(457 impl From<$ty> for NumValue {458 #[inline]459 fn from(value: $ty) -> Self {460 Self(value.into())461 }462 }463 )+};464}465impl_num!(i8, u8, i16, u16, i32, u32);466467#[derive(Clone, Copy, Debug, Error, Trace)]468pub enum ConvertNumValueError {469 #[error("overflow")]470 Overflow,471 #[error("underflow")]472 Underflow,473 #[error("non-finite")]474 NonFinite,475}476impl From<ConvertNumValueError> for Error {477 fn from(e: ConvertNumValueError) -> Self {478 Self::new(e.into())479 }480}481482macro_rules! impl_try_num {483 ($($ty:ty),+) => {$(484 impl TryFrom<$ty> for NumValue {485 type Error = ConvertNumValueError;486 #[inline]487 fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {488 use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};489 let value = value as f64;490 if value < MIN_SAFE_INTEGER {491 return Err(ConvertNumValueError::Underflow)492 } else if value > MAX_SAFE_INTEGER {493 return Err(ConvertNumValueError::Overflow)494 }495 // Number is finite.496 Ok(Self(value))497 }498 }499 )+};500}501impl_try_num!(usize, isize, i64, u64);502503impl TryFrom<f64> for NumValue {504 type Error = ConvertNumValueError;505506 #[inline]507 fn try_from(value: f64) -> Result<Self, Self::Error> {508 Self::new(value).ok_or(ConvertNumValueError::NonFinite)509 }510}511impl TryFrom<f32> for NumValue {512 type Error = ConvertNumValueError;513514 #[inline]515 fn try_from(value: f32) -> Result<Self, Self::Error> {516 Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)517 }518}519520/// Represents any valid Jsonnet value.521#[derive(Debug, Clone, Trace, Default)]522pub enum Val {523 /// Represents a Jsonnet boolean.524 Bool(bool),525 /// Represents a Jsonnet null value.526 #[default]527 Null,528 /// Represents a Jsonnet string.529 Str(StrValue),530 /// Represents a Jsonnet number.531 /// Should be finite, and not NaN532 /// This restriction isn't enforced by enum, as enum field can't be marked as private533 Num(NumValue),534 /// Experimental bigint535 #[cfg(feature = "exp-bigint")]536 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),537 /// Represents a Jsonnet array.538 Arr(ArrValue),539 /// Represents a Jsonnet object.540 Obj(ObjValue),541 /// Represents a Jsonnet function.542 Func(FuncVal),543}544545#[cfg(target_pointer_width = "64")]546static_assertions::assert_eq_size!(Val, [u8; 24]);547548impl From<IndexableVal> for Val {549 fn from(v: IndexableVal) -> Self {550 match v {551 IndexableVal::Str(s) => Self::string(s),552 IndexableVal::Arr(a) => Self::Arr(a),553 }554 }555}556557impl Val {558 pub const fn as_bool(&self) -> Option<bool> {559 match self {560 Self::Bool(v) => Some(*v),561 _ => None,562 }563 }564 pub const fn as_null(&self) -> Option<()> {565 match self {566 Self::Null => Some(()),567 _ => None,568 }569 }570 pub fn as_str(&self) -> Option<IStr> {571 match self {572 Self::Str(s) => Some(s.clone().into_flat()),573 _ => None,574 }575 }576 pub const fn as_num(&self) -> Option<f64> {577 match self {578 Self::Num(n) => Some(n.get()),579 _ => None,580 }581 }582 pub fn as_arr(&self) -> Option<ArrValue> {583 match self {584 Self::Arr(a) => Some(a.clone()),585 _ => None,586 }587 }588 pub fn as_obj(&self) -> Option<ObjValue> {589 match self {590 Self::Obj(o) => Some(o.clone()),591 _ => None,592 }593 }594 pub fn as_func(&self) -> Option<FuncVal> {595 match self {596 Self::Func(f) => Some(f.clone()),597 _ => None,598 }599 }600601 pub const fn value_type(&self) -> ValType {602 match self {603 Self::Str(..) => ValType::Str,604 Self::Num(..) => ValType::Num,605 #[cfg(feature = "exp-bigint")]606 Self::BigInt(..) => ValType::BigInt,607 Self::Arr(..) => ValType::Arr,608 Self::Obj(..) => ValType::Obj,609 Self::Bool(_) => ValType::Bool,610 Self::Null => ValType::Null,611 Self::Func(..) => ValType::Func,612 }613 }614615 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {616 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {617 manifest.manifest(val.clone())618 }619 manifest_dyn(self, &format)620 }621622 pub fn to_string(&self) -> Result<IStr> {623 Ok(match self {624 Self::Bool(true) => "true".into(),625 Self::Bool(false) => "false".into(),626 Self::Null => "null".into(),627 Self::Str(s) => s.clone().into_flat(),628 _ => self.manifest(ToStringFormat).map(IStr::from)?,629 })630 }631632 pub fn into_indexable(self) -> Result<IndexableVal> {633 Ok(match self {634 Self::Str(s) => IndexableVal::Str(s.into_flat()),635 Self::Arr(arr) => IndexableVal::Arr(arr),636 _ => bail!(ValueIsNotIndexable(self.value_type())),637 })638 }639640 pub fn function(function: impl Into<FuncVal>) -> Self {641 Self::Func(function.into())642 }643 pub fn string(string: impl Into<StrValue>) -> Self {644 Self::Str(string.into())645 }646 pub fn num(num: impl Into<NumValue>) -> Self {647 Self::Num(num.into())648 }649 pub fn try_num<V, E>(num: V) -> Result<Self, E>650 where651 NumValue: TryFrom<V, Error = E>,652 {653 Ok(Self::Num(num.try_into()?))654 }655}656657impl From<IStr> for Val {658 fn from(value: IStr) -> Self {659 Self::string(value)660 }661}662impl From<String> for Val {663 fn from(value: String) -> Self {664 Self::string(value)665 }666}667impl From<&str> for Val {668 fn from(value: &str) -> Self {669 Self::string(value)670 }671}672impl From<ObjValue> for Val {673 fn from(value: ObjValue) -> Self {674 Self::Obj(value)675 }676}677678const fn is_function_like(val: &Val) -> bool {679 matches!(val, Val::Func(_))680}681682/// Native implementation of `std.primitiveEquals`683pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {684 Ok(match (val_a, val_b) {685 (Val::Bool(a), Val::Bool(b)) => a == b,686 (Val::Null, Val::Null) => true,687 (Val::Str(a), Val::Str(b)) => a == b,688 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,689 #[cfg(feature = "exp-bigint")]690 (Val::BigInt(a), Val::BigInt(b)) => a == b,691 (Val::Arr(_), Val::Arr(_)) => {692 bail!("primitiveEquals operates on primitive types, got array")693 }694 (Val::Obj(_), Val::Obj(_)) => {695 bail!("primitiveEquals operates on primitive types, got object")696 }697 (a, b) if is_function_like(a) && is_function_like(b) => {698 bail!("cannot test equality of functions")699 }700 (_, _) => false,701 })702}703704/// Native implementation of `std.equals`705pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {706 if val_a.value_type() != val_b.value_type() {707 return Ok(false);708 }709 match (val_a, val_b) {710 (Val::Arr(a), Val::Arr(b)) => {711 if ArrValue::ptr_eq(a, b) {712 return Ok(true);713 }714 if a.len() != b.len() {715 return Ok(false);716 }717 for (a, b) in a.iter().zip(b.iter()) {718 if !equals(&a?, &b?)? {719 return Ok(false);720 }721 }722 Ok(true)723 }724 (Val::Obj(a), Val::Obj(b)) => {725 if ObjValue::ptr_eq(a, b) {726 return Ok(true);727 }728 let fields = a.fields(729 #[cfg(feature = "exp-preserve-order")]730 false,731 );732 if fields733 != b.fields(734 #[cfg(feature = "exp-preserve-order")]735 false,736 ) {737 return Ok(false);738 }739 for field in fields {740 if !equals(741 &a.get(field.clone())?.expect("field exists"),742 &b.get(field)?.expect("field exists"),743 )? {744 return Ok(false);745 }746 }747 Ok(true)748 }749 (a, b) => Ok(primitive_equals(a, b)?),750 }751}1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 mem::replace,6 num::NonZeroU32,7 ops::Deref,8 rc::Rc,9};1011use jrsonnet_gcmodule::{Cc, Trace};12use jrsonnet_interner::IStr;13pub use jrsonnet_macros::Thunk;14use jrsonnet_types::ValType;15use thiserror::Error;1617pub use crate::arr::{ArrValue, ArrayLike};18use crate::{19 bail,20 error::{Error, ErrorKind::*},21 function::FuncVal,22 gc::{GcHashMap, TraceBox},23 manifest::{ManifestFormat, ToStringFormat},24 tb,25 typed::BoundedUsize,26 ObjValue, Result, Unbound, WeakObjValue,27};2829pub trait ThunkValue: Trace {30 type Output;31 fn get(self: Box<Self>) -> Result<Self::Output>;32}3334#[derive(Trace)]35pub struct ThunkValueClosure<D: Trace, O: 'static> {36 env: D,37 // Carries no data, as it is not a real closure, all the38 // captured environment is stored in `env` field.39 #[trace(skip)]40 closure: fn(D) -> Result<O>,41}42impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {43 pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {44 Self { env, closure }45 }46}47impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {48 type Output = O;4950 fn get(self: Box<Self>) -> Result<Self::Output> {51 (self.closure)(self.env)52 }53}5455#[derive(Trace)]56enum ThunkInner<T: Trace> {57 Computed(T),58 Errored(Error),59 Waiting(TraceBox<dyn ThunkValue<Output = T>>),60 Pending,61}6263/// Lazily evaluated value64#[allow(clippy::module_name_repetitions)]65#[derive(Clone, Trace)]66pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);6768impl<T: Trace> Thunk<T> {69 pub fn evaluated(val: T) -> Self {70 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))71 }72 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {73 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))74 }75 pub fn errored(e: Error) -> Self {76 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))77 }78 pub fn result(res: Result<T, Error>) -> Self {79 match res {80 Ok(o) => Self::evaluated(o),81 Err(e) => Self::errored(e),82 }83 }84}8586impl<T> Thunk<T>87where88 T: Clone + Trace,89{90 pub fn force(&self) -> Result<()> {91 self.evaluate()?;92 Ok(())93 }9495 /// Evaluate thunk, or return cached value96 ///97 /// # Errors98 ///99 /// - Lazy value evaluation returned error100 /// - This method was called during inner value evaluation101 pub fn evaluate(&self) -> Result<T> {102 match &*self.0.borrow() {103 ThunkInner::Computed(v) => return Ok(v.clone()),104 ThunkInner::Errored(e) => return Err(e.clone()),105 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),106 ThunkInner::Waiting(..) => (),107 };108 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)109 else {110 unreachable!();111 };112 let new_value = match value.0.get() {113 Ok(v) => v,114 Err(e) => {115 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());116 return Err(e);117 }118 };119 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());120 Ok(new_value)121 }122}123124pub trait ThunkMapper<Input>: Trace {125 type Output;126 fn map(self, from: Input) -> Result<Self::Output>;127}128impl<Input> Thunk<Input>129where130 Input: Trace + Clone,131{132 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>133 where134 M: ThunkMapper<Input>,135 M::Output: Trace,136 {137 let inner = self;138 Thunk!(move || {139 let value = inner.evaluate()?;140 let mapped = mapper.map(value)?;141 Ok(mapped)142 })143 }144}145146impl<T: Trace> From<Result<T>> for Thunk<T> {147 fn from(value: Result<T>) -> Self {148 match value {149 Ok(o) => Self::evaluated(o),150 Err(e) => Self::errored(e),151 }152 }153}154impl<T, V: Trace> From<T> for Thunk<V>155where156 T: ThunkValue<Output = V>,157{158 fn from(value: T) -> Self {159 Self::new(value)160 }161}162163impl<T: Trace + Default> Default for Thunk<T> {164 fn default() -> Self {165 Self::evaluated(T::default())166 }167}168169type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);170171#[derive(Trace, Clone)]172pub struct CachedUnbound<I, T>173where174 I: Unbound<Bound = T>,175 T: Trace,176{177 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,178 value: I,179}180impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {181 pub fn new(value: I) -> Self {182 Self {183 cache: Cc::new(RefCell::new(GcHashMap::new())),184 value,185 }186 }187}188impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {189 type Bound = T;190 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {191 let cache_key = (192 sup.as_ref().map(|s| s.clone().downgrade()),193 this.as_ref().map(|t| t.clone().downgrade()),194 );195 {196 if let Some(t) = self.cache.borrow().get(&cache_key) {197 return Ok(t.clone());198 }199 }200 let bound = self.value.bind(sup, this)?;201202 {203 let mut cache = self.cache.borrow_mut();204 cache.insert(cache_key, bound.clone());205 }206207 Ok(bound)208 }209}210211impl<T: Debug + Trace> Debug for Thunk<T> {212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {213 write!(f, "Lazy")214 }215}216impl<T: Trace> PartialEq for Thunk<T> {217 fn eq(&self, other: &Self) -> bool {218 Cc::ptr_eq(&self.0, &other.0)219 }220}221222/// Represents a Jsonnet value, which can be sliced or indexed (string or array).223#[allow(clippy::module_name_repetitions)]224pub enum IndexableVal {225 /// String.226 Str(IStr),227 /// Array.228 Arr(ArrValue),229}230impl IndexableVal {231 pub fn is_empty(&self) -> bool {232 match self {233 Self::Str(s) => s.is_empty(),234 Self::Arr(s) => s.is_empty(),235 }236 }237238 pub fn to_array(self) -> ArrValue {239 match self {240 Self::Str(s) => ArrValue::chars(s.chars()),241 Self::Arr(arr) => arr,242 }243 }244 /// Slice the value.245 ///246 /// # Implementation247 ///248 /// For strings, will create a copy of specified interval.249 ///250 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.251 pub fn slice(252 self,253 index: Option<i32>,254 end: Option<i32>,255 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,256 ) -> Result<Self> {257 match &self {258 Self::Str(s) => {259 let mut computed_len = None;260 let mut get_len = || {261 computed_len.map_or_else(262 || {263 let len = s.chars().count();264 let _ = computed_len.insert(len);265 len266 },267 |len| len,268 )269 };270 let mut get_idx = |pos: Option<i32>, default| {271 match pos {272 Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),273 // No need to clamp, as iterator interface is used274 Some(v) => v as usize,275 None => default,276 }277 };278279 let index = get_idx(index, 0);280 let end = get_idx(end, usize::MAX);281 let step = step.as_deref().copied().unwrap_or(1);282283 if index >= end {284 return Ok(Self::Str("".into()));285 }286287 Ok(Self::Str(288 (s.chars()289 .skip(index)290 .take(end - index)291 .step_by(step)292 .collect::<String>())293 .into(),294 ))295 }296 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(297 index,298 end,299 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),300 ))),301 }302 }303}304305#[derive(Debug, Clone, Trace)]306pub enum StrValue {307 Flat(IStr),308 Tree(Rc<(StrValue, StrValue, usize)>),309}310impl StrValue {311 pub fn concat(a: Self, b: Self) -> Self {312 // TODO: benchmark for an optimal value, currently just a arbitrary choice313 const STRING_EXTEND_THRESHOLD: usize = 100;314315 if a.is_empty() {316 b317 } else if b.is_empty() {318 a319 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {320 Self::Flat(format!("{a}{b}").into())321 } else {322 let len = a.len() + b.len();323 Self::Tree(Rc::new((a, b, len)))324 }325 }326 pub fn into_flat(self) -> IStr {327 #[cold]328 fn write_buf(s: &StrValue, out: &mut String) {329 match s {330 StrValue::Flat(f) => out.push_str(f),331 StrValue::Tree(t) => {332 write_buf(&t.0, out);333 write_buf(&t.1, out);334 }335 }336 }337 match self {338 Self::Flat(f) => f,339 Self::Tree(_) => {340 let mut buf = String::with_capacity(self.len());341 write_buf(&self, &mut buf);342 buf.into()343 }344 }345 }346 pub fn len(&self) -> usize {347 match self {348 Self::Flat(v) => v.len(),349 Self::Tree(t) => t.2,350 }351 }352 pub fn is_empty(&self) -> bool {353 match self {354 Self::Flat(v) => v.is_empty(),355 // Can't create non-flat empty string356 Self::Tree(_) => false,357 }358 }359}360impl<T> From<T> for StrValue361where362 IStr: From<T>,363{364 fn from(value: T) -> Self {365 Self::Flat(IStr::from(value))366 }367}368impl Display for StrValue {369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {370 match self {371 Self::Flat(v) => write!(f, "{v}"),372 Self::Tree(t) => {373 write!(f, "{}", t.0)?;374 write!(f, "{}", t.1)375 }376 }377 }378}379impl PartialEq for StrValue {380 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.381 #[allow(clippy::unconditional_recursion)]382 fn eq(&self, other: &Self) -> bool {383 let a = self.clone().into_flat();384 let b = other.clone().into_flat();385 a == b386 }387}388impl Eq for StrValue {}389impl PartialOrd for StrValue {390 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {391 Some(self.cmp(other))392 }393}394impl Ord for StrValue {395 fn cmp(&self, other: &Self) -> Ordering {396 let a = self.clone().into_flat();397 let b = other.clone().into_flat();398 a.cmp(&b)399 }400}401402/// Represents jsonnet number403/// Jsonnet numbers are finite f64, with NaNs disallowed404#[derive(Trace, Clone, Copy)]405#[repr(transparent)]406pub struct NumValue(f64);407impl NumValue {408 /// Creates a [`NumValue`], if value is finite and not NaN409 pub fn new(v: f64) -> Option<Self> {410 if !v.is_finite() {411 return None;412 }413 Some(Self(v))414 }415 #[inline]416 pub const fn get(&self) -> f64 {417 self.0418 }419}420impl PartialEq for NumValue {421 fn eq(&self, other: &Self) -> bool {422 self.0 == other.0423 }424}425impl Eq for NumValue {}426impl Ord for NumValue {427 #[inline]428 fn cmp(&self, other: &Self) -> Ordering {429 // Can't use `total_cmp`: its behavior for `-0` and `0`430 // is not following wanted.431 unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }432 }433}434impl PartialOrd for NumValue {435 #[inline]436 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {437 Some(self.cmp(other))438 }439}440impl Debug for NumValue {441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {442 Debug::fmt(&self.0, f)443 }444}445impl Display for NumValue {446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {447 Display::fmt(&self.0, f)448 }449}450impl Deref for NumValue {451 type Target = f64;452453 #[inline]454 fn deref(&self) -> &Self::Target {455 &self.0456 }457}458macro_rules! impl_num {459 ($($ty:ty),+) => {$(460 impl From<$ty> for NumValue {461 #[inline]462 fn from(value: $ty) -> Self {463 Self(value.into())464 }465 }466 )+};467}468impl_num!(i8, u8, i16, u16, i32, u32);469470#[derive(Clone, Copy, Debug, Error, Trace)]471pub enum ConvertNumValueError {472 #[error("overflow")]473 Overflow,474 #[error("underflow")]475 Underflow,476 #[error("non-finite")]477 NonFinite,478}479impl From<ConvertNumValueError> for Error {480 fn from(e: ConvertNumValueError) -> Self {481 Self::new(e.into())482 }483}484485macro_rules! impl_try_num {486 ($($ty:ty),+) => {$(487 impl TryFrom<$ty> for NumValue {488 type Error = ConvertNumValueError;489 #[inline]490 fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {491 use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};492 let value = value as f64;493 if value < MIN_SAFE_INTEGER {494 return Err(ConvertNumValueError::Underflow)495 } else if value > MAX_SAFE_INTEGER {496 return Err(ConvertNumValueError::Overflow)497 }498 // Number is finite.499 Ok(Self(value))500 }501 }502 )+};503}504impl_try_num!(usize, isize, i64, u64);505506impl TryFrom<f64> for NumValue {507 type Error = ConvertNumValueError;508509 #[inline]510 fn try_from(value: f64) -> Result<Self, Self::Error> {511 Self::new(value).ok_or(ConvertNumValueError::NonFinite)512 }513}514impl TryFrom<f32> for NumValue {515 type Error = ConvertNumValueError;516517 #[inline]518 fn try_from(value: f32) -> Result<Self, Self::Error> {519 Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)520 }521}522523/// Represents any valid Jsonnet value.524#[derive(Debug, Clone, Trace, Default)]525pub enum Val {526 /// Represents a Jsonnet boolean.527 Bool(bool),528 /// Represents a Jsonnet null value.529 #[default]530 Null,531 /// Represents a Jsonnet string.532 Str(StrValue),533 /// Represents a Jsonnet number.534 /// Should be finite, and not NaN535 /// This restriction isn't enforced by enum, as enum field can't be marked as private536 Num(NumValue),537 /// Experimental bigint538 #[cfg(feature = "exp-bigint")]539 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),540 /// Represents a Jsonnet array.541 Arr(ArrValue),542 /// Represents a Jsonnet object.543 Obj(ObjValue),544 /// Represents a Jsonnet function.545 Func(FuncVal),546}547548#[cfg(target_pointer_width = "64")]549static_assertions::assert_eq_size!(Val, [u8; 24]);550551impl From<IndexableVal> for Val {552 fn from(v: IndexableVal) -> Self {553 match v {554 IndexableVal::Str(s) => Self::string(s),555 IndexableVal::Arr(a) => Self::Arr(a),556 }557 }558}559560impl Val {561 pub const fn as_bool(&self) -> Option<bool> {562 match self {563 Self::Bool(v) => Some(*v),564 _ => None,565 }566 }567 pub const fn as_null(&self) -> Option<()> {568 match self {569 Self::Null => Some(()),570 _ => None,571 }572 }573 pub fn as_str(&self) -> Option<IStr> {574 match self {575 Self::Str(s) => Some(s.clone().into_flat()),576 _ => None,577 }578 }579 pub const fn as_num(&self) -> Option<f64> {580 match self {581 Self::Num(n) => Some(n.get()),582 _ => None,583 }584 }585 pub fn as_arr(&self) -> Option<ArrValue> {586 match self {587 Self::Arr(a) => Some(a.clone()),588 _ => None,589 }590 }591 pub fn as_obj(&self) -> Option<ObjValue> {592 match self {593 Self::Obj(o) => Some(o.clone()),594 _ => None,595 }596 }597 pub fn as_func(&self) -> Option<FuncVal> {598 match self {599 Self::Func(f) => Some(f.clone()),600 _ => None,601 }602 }603604 pub const fn value_type(&self) -> ValType {605 match self {606 Self::Str(..) => ValType::Str,607 Self::Num(..) => ValType::Num,608 #[cfg(feature = "exp-bigint")]609 Self::BigInt(..) => ValType::BigInt,610 Self::Arr(..) => ValType::Arr,611 Self::Obj(..) => ValType::Obj,612 Self::Bool(_) => ValType::Bool,613 Self::Null => ValType::Null,614 Self::Func(..) => ValType::Func,615 }616 }617618 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {619 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {620 manifest.manifest(val.clone())621 }622 manifest_dyn(self, &format)623 }624625 pub fn to_string(&self) -> Result<IStr> {626 Ok(match self {627 Self::Bool(true) => "true".into(),628 Self::Bool(false) => "false".into(),629 Self::Null => "null".into(),630 Self::Str(s) => s.clone().into_flat(),631 _ => self.manifest(ToStringFormat).map(IStr::from)?,632 })633 }634635 pub fn into_indexable(self) -> Result<IndexableVal> {636 Ok(match self {637 Self::Str(s) => IndexableVal::Str(s.into_flat()),638 Self::Arr(arr) => IndexableVal::Arr(arr),639 _ => bail!(ValueIsNotIndexable(self.value_type())),640 })641 }642643 pub fn function(function: impl Into<FuncVal>) -> Self {644 Self::Func(function.into())645 }646 pub fn string(string: impl Into<StrValue>) -> Self {647 Self::Str(string.into())648 }649 pub fn num(num: impl Into<NumValue>) -> Self {650 Self::Num(num.into())651 }652 pub fn try_num<V, E>(num: V) -> Result<Self, E>653 where654 NumValue: TryFrom<V, Error = E>,655 {656 Ok(Self::Num(num.try_into()?))657 }658}659660impl From<IStr> for Val {661 fn from(value: IStr) -> Self {662 Self::string(value)663 }664}665impl From<String> for Val {666 fn from(value: String) -> Self {667 Self::string(value)668 }669}670impl From<&str> for Val {671 fn from(value: &str) -> Self {672 Self::string(value)673 }674}675impl From<ObjValue> for Val {676 fn from(value: ObjValue) -> Self {677 Self::Obj(value)678 }679}680681const fn is_function_like(val: &Val) -> bool {682 matches!(val, Val::Func(_))683}684685/// Native implementation of `std.primitiveEquals`686pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {687 Ok(match (val_a, val_b) {688 (Val::Bool(a), Val::Bool(b)) => a == b,689 (Val::Null, Val::Null) => true,690 (Val::Str(a), Val::Str(b)) => a == b,691 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,692 #[cfg(feature = "exp-bigint")]693 (Val::BigInt(a), Val::BigInt(b)) => a == b,694 (Val::Arr(_), Val::Arr(_)) => {695 bail!("primitiveEquals operates on primitive types, got array")696 }697 (Val::Obj(_), Val::Obj(_)) => {698 bail!("primitiveEquals operates on primitive types, got object")699 }700 (a, b) if is_function_like(a) && is_function_like(b) => {701 bail!("cannot test equality of functions")702 }703 (_, _) => false,704 })705}706707/// Native implementation of `std.equals`708pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {709 if val_a.value_type() != val_b.value_type() {710 return Ok(false);711 }712 match (val_a, val_b) {713 (Val::Arr(a), Val::Arr(b)) => {714 if ArrValue::ptr_eq(a, b) {715 return Ok(true);716 }717 if a.len() != b.len() {718 return Ok(false);719 }720 for (a, b) in a.iter().zip(b.iter()) {721 if !equals(&a?, &b?)? {722 return Ok(false);723 }724 }725 Ok(true)726 }727 (Val::Obj(a), Val::Obj(b)) => {728 if ObjValue::ptr_eq(a, b) {729 return Ok(true);730 }731 let fields = a.fields(732 #[cfg(feature = "exp-preserve-order")]733 false,734 );735 if fields736 != b.fields(737 #[cfg(feature = "exp-preserve-order")]738 false,739 ) {740 return Ok(false);741 }742 for field in fields {743 if !equals(744 &a.get(field.clone())?.expect("field exists"),745 &b.get(field)?.expect("field exists"),746 )? {747 return Ok(false);748 }749 }750 Ok(true)751 }752 (a, b) => Ok(primitive_equals(a, b)?),753 }754}