difftreelog
refactor do not expose direct operator evaluator access
in: master
7 files changed
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 marker::PhantomData,6 mem::replace,7 num::NonZeroU32,8 rc::Rc,9};1011use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};12use jrsonnet_interner::IStr;13pub use jrsonnet_macros::Thunk;14use jrsonnet_types::ValType;15use rustc_hash::FxHashMap;1617pub use crate::arr::{ArrValue, ArrayLike};18use crate::{19 NumValue, ObjValue, Result, SupThis, Unbound, WeakSupThis, bail,20 error::{Error, ErrorKind::*},21 function::FuncVal,22 gc::WithCapacityExt as _,23 manifest::{ManifestFormat, ToStringFormat},24 typed::BoundedUsize,25};2627pub trait ThunkValue: Trace {28 type Output;29 fn get(&self) -> Result<Self::Output>;30}3132#[derive(Trace)]33enum MemoizedClusureThunkInner<D: Trace, T: Trace> {34 Computed(T),35 Errored(Error),36 Waiting {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<T>,42 },43 Pending,44}45#[derive(Trace)]46pub struct MemoizedClosureThunk<D: Trace, T: Trace>(RefCell<MemoizedClusureThunkInner<D, T>>);47impl<D: Trace, T: Trace> MemoizedClosureThunk<D, T> {48 pub fn new(env: D, closure: fn(D) -> Result<T>) -> Self {49 Self(RefCell::new(MemoizedClusureThunkInner::Waiting {50 env,51 closure,52 }))53 }54}5556impl<D: Trace, T: Trace + Clone> ThunkValue for MemoizedClosureThunk<D, T> {57 type Output = T;5859 fn get(&self) -> Result<Self::Output> {60 match &*self.0.borrow() {61 MemoizedClusureThunkInner::Computed(v) => return Ok(v.clone()),62 MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),63 MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),64 MemoizedClusureThunkInner::Waiting { .. } => (),65 }66 let MemoizedClusureThunkInner::Waiting { env, closure } = replace(67 &mut *self.0.borrow_mut(),68 MemoizedClusureThunkInner::Pending,69 ) else {70 unreachable!();71 };72 let new_value = match closure(env) {73 Ok(v) => v,74 Err(e) => {75 *self.0.borrow_mut() = MemoizedClusureThunkInner::Errored(e.clone());76 return Err(e);77 }78 };79 *self.0.borrow_mut() = MemoizedClusureThunkInner::Computed(new_value.clone());80 Ok(new_value)81 }82}8384cc_dyn!(85 /// Lazily evaluated value86 #[derive(Clone)] Thunk<V: Trace>,87 ThunkValue<Output = V>,88 pub fn new() {...}89);9091impl<T: Trace> Thunk<T> {92 pub fn evaluated(val: T) -> Self93 where94 T: Clone,95 {96 #[derive(Trace)]97 struct EvaluatedThunk<T: Trace>(T);98 impl<T> ThunkValue for EvaluatedThunk<T>99 where100 T: Clone + Trace,101 {102 type Output = T;103104 fn get(&self) -> Result<Self::Output> {105 Ok(self.0.clone())106 }107 }108 Self::new(EvaluatedThunk(val))109 }110 pub fn errored(e: Error) -> Self {111 #[derive(Trace)]112 struct ErroredThunk<T: Trace>(Error, PhantomData<T>);113 impl<T> ThunkValue for ErroredThunk<T>114 where115 T: Trace,116 {117 type Output = T;118119 fn get(&self) -> Result<Self::Output> {120 Err(self.0.clone())121 }122 }123 Self::new(ErroredThunk(e, PhantomData))124 }125 pub fn result(res: Result<T, Error>) -> Self126 where127 T: Clone,128 {129 match res {130 Ok(o) => Self::evaluated(o),131 Err(e) => Self::errored(e),132 }133 }134}135136impl<T> Thunk<T>137where138 T: Trace,139{140 pub fn force(&self) -> Result<()> {141 self.evaluate()?;142 Ok(())143 }144145 /// Evaluate thunk, or return cached value146 ///147 /// # Errors148 ///149 /// - Lazy value evaluation returned error150 /// - This method was called during inner value evaluation151 pub fn evaluate(&self) -> Result<T> {152 self.0.get()153 }154}155156pub trait ThunkMapper<Input>: Trace {157 type Output;158 fn map(self, from: Input) -> Result<Self::Output>;159}160impl<Input> Thunk<Input>161where162 Input: Trace,163{164 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>165 where166 M: ThunkMapper<Input>,167 M::Output: Trace + Clone,168 {169 let inner = self;170 Thunk!(move || {171 let value = inner.evaluate()?;172 let mapped = mapper.map(value)?;173 Ok(mapped)174 })175 }176}177178impl<T: Trace + Clone> From<Result<T>> for Thunk<T> {179 fn from(value: Result<T>) -> Self {180 match value {181 Ok(o) => Self::evaluated(o),182 Err(e) => Self::errored(e),183 }184 }185}186impl<T, V: Trace> From<T> for Thunk<V>187where188 T: ThunkValue<Output = V>,189{190 fn from(value: T) -> Self {191 Self::new(value)192 }193}194195impl<T: Trace + Default + Clone> Default for Thunk<T> {196 fn default() -> Self {197 Self::evaluated(T::default())198 }199}200201#[derive(Trace, Clone)]202pub struct CachedUnbound<I, T>203where204 I: Unbound<Bound = T>,205 T: Trace,206{207 cache: Cc<RefCell<FxHashMap<WeakSupThis, T>>>,208 value: I,209}210impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {211 pub fn new(value: I) -> Self {212 Self {213 cache: Cc::new(RefCell::new(FxHashMap::new())),214 value,215 }216 }217}218impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {219 type Bound = T;220 fn bind(&self, sup_this: SupThis) -> Result<T> {221 let cache_key = sup_this.clone().downgrade();222 {223 if let Some(t) = self.cache.borrow().get(&cache_key) {224 return Ok(t.clone());225 }226 }227 let bound = self.value.bind(sup_this)?;228229 {230 let mut cache = self.cache.borrow_mut();231 cache.insert(cache_key, bound.clone());232 }233234 Ok(bound)235 }236}237238impl<T: Debug + Trace> Debug for Thunk<T> {239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {240 write!(f, "Lazy")241 }242}243impl<T: Trace> PartialEq for Thunk<T> {244 fn eq(&self, other: &Self) -> bool {245 Cc::ptr_eq(&self.0, &other.0)246 }247}248249/// Represents a Jsonnet value, which can be sliced or indexed (string or array).250#[allow(clippy::module_name_repetitions)]251pub enum IndexableVal {252 /// String.253 Str(IStr),254 /// Array.255 Arr(ArrValue),256}257impl IndexableVal {258 pub fn is_empty(&self) -> bool {259 match self {260 Self::Str(s) => s.is_empty(),261 Self::Arr(s) => s.is_empty(),262 }263 }264265 pub fn to_array(self) -> ArrValue {266 match self {267 Self::Str(s) => s.chars().collect(),268 Self::Arr(arr) => arr,269 }270 }271 /// Slice the value.272 ///273 /// # Implementation274 ///275 /// For strings, will create a copy of specified interval.276 ///277 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.278 pub fn slice(279 self,280 index: Option<i32>,281 end: Option<i32>,282 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,283 ) -> Result<Self> {284 match &self {285 Self::Str(s) => {286 let mut computed_len = None;287 let mut get_len = || {288 computed_len.unwrap_or_else(|| {289 let len = s.chars().count();290 let _ = computed_len.insert(len);291 len292 })293 };294 let mut get_idx = |pos: Option<i32>, default| {295 match pos {296 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]297 Some(v) if v < 0 => get_len().saturating_sub((-v as isize) as usize),298 // No need to clamp, as iterator interface is used299 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]300 Some(v) => v as usize,301 None => default,302 }303 };304305 let index = get_idx(index, 0);306 let end = get_idx(end, usize::MAX);307 let step = step.as_deref().copied().unwrap_or(1);308309 if index >= end {310 return Ok(Self::Str("".into()));311 }312313 Ok(Self::Str(314 (s.chars()315 .skip(index)316 .take(end - index)317 .step_by(step)318 .collect::<String>())319 .into(),320 ))321 }322 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(323 index,324 end,325 #[expect(326 clippy::cast_possible_truncation,327 reason = "overflow will result with skip too large which would be equivalent"328 )]329 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),330 ))),331 }332 }333}334335#[derive(Debug, Clone, Acyclic)]336pub enum StrValue {337 Flat(IStr),338 Tree(Rc<(StrValue, StrValue, usize)>),339}340impl StrValue {341 pub fn concat(a: Self, b: Self) -> Self {342 // TODO: benchmark for an optimal value, currently just a arbitrary choice343 const STRING_EXTEND_THRESHOLD: usize = 100;344345 if a.is_empty() {346 b347 } else if b.is_empty() {348 a349 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {350 Self::Flat(format!("{a}{b}").into())351 } else {352 let len = a.len() + b.len();353 Self::Tree(Rc::new((a, b, len)))354 }355 }356 pub fn chunks(&self, c: &mut impl FnMut(&IStr)) {357 fn write_buf(s: &StrValue, c: &mut impl FnMut(&IStr)) {358 match s {359 StrValue::Flat(f) => c(f),360 StrValue::Tree(t) => {361 write_buf(&t.0, c);362 write_buf(&t.1, c);363 }364 }365 }366 write_buf(self, c);367 }368 pub fn into_flat(&self) -> IStr {369 fn write_buf(s: &StrValue, out: &mut String) {370 match s {371 StrValue::Flat(f) => out.push_str(f),372 StrValue::Tree(t) => {373 write_buf(&t.0, out);374 write_buf(&t.1, out);375 }376 }377 }378 match self {379 Self::Flat(f) => f.clone(),380 Self::Tree(_) => {381 let mut buf = String::with_capacity(self.len());382 write_buf(self, &mut buf);383 buf.into()384 }385 }386 }387 pub fn len(&self) -> usize {388 match self {389 Self::Flat(v) => v.len(),390 Self::Tree(t) => t.2,391 }392 }393 pub fn is_empty(&self) -> bool {394 match self {395 Self::Flat(v) => v.is_empty(),396 // Can't create non-flat empty string397 Self::Tree(_) => false,398 }399 }400}401impl<T> From<T> for StrValue402where403 IStr: From<T>,404{405 fn from(value: T) -> Self {406 Self::Flat(IStr::from(value))407 }408}409impl Display for StrValue {410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {411 match self {412 Self::Flat(v) => write!(f, "{v}"),413 Self::Tree(t) => {414 write!(f, "{}", t.0)?;415 write!(f, "{}", t.1)416 }417 }418 }419}420impl PartialEq for StrValue {421 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.422 #[allow(clippy::unconditional_recursion)]423 fn eq(&self, other: &Self) -> bool {424 let a = self.clone().into_flat();425 let b = other.clone().into_flat();426 a == b427 }428}429impl Eq for StrValue {}430impl PartialOrd for StrValue {431 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {432 Some(self.cmp(other))433 }434}435impl Ord for StrValue {436 fn cmp(&self, other: &Self) -> Ordering {437 let a = self.clone().into_flat();438 let b = other.clone().into_flat();439 a.cmp(&b)440 }441}442443/// Represents any valid Jsonnet value.444#[derive(Debug, Clone, Trace, Default)]445pub enum Val {446 /// Represents a Jsonnet boolean.447 Bool(bool),448 /// Represents a Jsonnet null value.449 #[default]450 Null,451 /// Represents a Jsonnet string.452 Str(StrValue),453 /// Represents a Jsonnet number.454 /// Should be finite, and not NaN455 /// This restriction isn't enforced by enum, as enum field can't be marked as private456 Num(NumValue),457 /// Experimental bigint458 #[cfg(feature = "exp-bigint")]459 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),460 /// Represents a Jsonnet array.461 Arr(ArrValue),462 /// Represents a Jsonnet object.463 Obj(ObjValue),464 /// Represents a Jsonnet function.465 Func(FuncVal),466}467468#[cfg(target_pointer_width = "64")]469static_assertions::assert_eq_size!(Val, [u8; 24]);470471impl From<IndexableVal> for Val {472 fn from(v: IndexableVal) -> Self {473 match v {474 IndexableVal::Str(s) => Self::string(s),475 IndexableVal::Arr(a) => Self::Arr(a),476 }477 }478}479480impl Val {481 pub const fn as_bool(&self) -> Option<bool> {482 match self {483 Self::Bool(v) => Some(*v),484 _ => None,485 }486 }487 pub const fn as_null(&self) -> Option<()> {488 match self {489 Self::Null => Some(()),490 _ => None,491 }492 }493 pub fn as_str(&self) -> Option<IStr> {494 match self {495 Self::Str(s) => Some(s.clone().into_flat()),496 _ => None,497 }498 }499 pub const fn as_num(&self) -> Option<f64> {500 match self {501 Self::Num(n) => Some(n.get()),502 _ => None,503 }504 }505 #[cfg(feature = "exp-bigint")]506 pub fn as_bigint(&self) -> Option<num_bigint::BigInt> {507 match self {508 Self::BigInt(n) => Some(*n.clone()),509 _ => None,510 }511 }512 pub fn as_arr(&self) -> Option<ArrValue> {513 match self {514 Self::Arr(a) => Some(a.clone()),515 _ => None,516 }517 }518 pub fn as_obj(&self) -> Option<ObjValue> {519 match self {520 Self::Obj(o) => Some(o.clone()),521 _ => None,522 }523 }524 pub fn as_func(&self) -> Option<FuncVal> {525 match self {526 Self::Func(f) => Some(f.clone()),527 _ => None,528 }529 }530531 pub const fn value_type(&self) -> ValType {532 match self {533 Self::Str(..) => ValType::Str,534 Self::Num(..) => ValType::Num,535 #[cfg(feature = "exp-bigint")]536 Self::BigInt(..) => ValType::BigInt,537 Self::Arr(..) => ValType::Arr,538 Self::Obj(..) => ValType::Obj,539 Self::Bool(_) => ValType::Bool,540 Self::Null => ValType::Null,541 Self::Func(..) => ValType::Func,542 }543 }544545 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {546 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {547 manifest.manifest(val.clone())548 }549 manifest_dyn(self, &format)550 }551552 pub fn to_string(&self) -> Result<IStr> {553 Ok(match self {554 Self::Bool(true) => "true".into(),555 Self::Bool(false) => "false".into(),556 Self::Null => "null".into(),557 Self::Str(s) => s.clone().into_flat(),558 _ => self.manifest(ToStringFormat).map(IStr::from)?,559 })560 }561562 pub fn into_indexable(self) -> Result<IndexableVal> {563 Ok(match self {564 Self::Str(s) => IndexableVal::Str(s.into_flat()),565 Self::Arr(arr) => IndexableVal::Arr(arr),566 _ => bail!(ValueIsNotIndexable(self.value_type())),567 })568 }569570 pub fn function(function: impl Into<FuncVal>) -> Self {571 Self::Func(function.into())572 }573 pub fn string(string: impl Into<StrValue>) -> Self {574 Self::Str(string.into())575 }576 pub fn num(num: impl Into<NumValue>) -> Self {577 Self::Num(num.into())578 }579 pub fn try_num<V, E>(num: V) -> Result<Self, E>580 where581 NumValue: TryFrom<V, Error = E>,582 {583 Ok(Self::Num(num.try_into()?))584 }585 pub fn arr(a: impl ArrayLike) -> Self {586 Self::Arr(ArrValue::new(a))587 }588}589590impl From<IStr> for Val {591 fn from(value: IStr) -> Self {592 Self::string(value)593 }594}595impl From<String> for Val {596 fn from(value: String) -> Self {597 Self::string(value)598 }599}600impl From<&str> for Val {601 fn from(value: &str) -> Self {602 Self::string(value)603 }604}605impl From<ObjValue> for Val {606 fn from(value: ObjValue) -> Self {607 Self::Obj(value)608 }609}610611const fn is_function_like(val: &Val) -> bool {612 matches!(val, Val::Func(_))613}614615/// Native implementation of `std.primitiveEquals`616pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {617 Ok(match (val_a, val_b) {618 (Val::Bool(a), Val::Bool(b)) => a == b,619 (Val::Null, Val::Null) => true,620 (Val::Str(a), Val::Str(b)) => a == b,621 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,622 #[cfg(feature = "exp-bigint")]623 (Val::BigInt(a), Val::BigInt(b)) => a == b,624 (Val::Arr(_), Val::Arr(_)) => {625 bail!("primitiveEquals operates on primitive types, got array")626 }627 (Val::Obj(_), Val::Obj(_)) => {628 bail!("primitiveEquals operates on primitive types, got object")629 }630 (a, b) if is_function_like(a) && is_function_like(b) => {631 bail!("cannot test equality of functions")632 }633 (_, _) => false,634 })635}636637/// Native implementation of `std.equals`638pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {639 if val_a.value_type() != val_b.value_type() {640 return Ok(false);641 }642 match (val_a, val_b) {643 (Val::Arr(a), Val::Arr(b)) => {644 if ArrValue::ptr_eq(a, b) {645 return Ok(true);646 }647 if a.len() != b.len() {648 return Ok(false);649 }650 for (a, b) in a.iter().zip(b.iter()) {651 if !equals(&a?, &b?)? {652 return Ok(false);653 }654 }655 Ok(true)656 }657 (Val::Obj(a), Val::Obj(b)) => {658 if ObjValue::ptr_eq(a, b) {659 return Ok(true);660 }661 let fields = a.fields(662 #[cfg(feature = "exp-preserve-order")]663 false,664 );665 if fields666 != b.fields(667 #[cfg(feature = "exp-preserve-order")]668 false,669 ) {670 return Ok(false);671 }672 for field in fields {673 if !equals(674 &a.get(field.clone())?.expect("field exists"),675 &b.get(field)?.expect("field exists"),676 )? {677 return Ok(false);678 }679 }680 Ok(true)681 }682 (a, b) => Ok(primitive_equals(a, b)?),683 }684}1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 marker::PhantomData,6 mem::replace,7 num::NonZeroU32,8 rc::Rc,9};1011use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};12use jrsonnet_interner::IStr;13use jrsonnet_ir::BinaryOpType;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use rustc_hash::FxHashMap;1718pub use crate::arr::{ArrValue, ArrayLike};19use crate::{20 NumValue, ObjValue, Result, SupThis, Unbound, WeakSupThis, bail, error::{Error, ErrorKind::*}, evaluate::operator::{evaluate_compare_op, evaluate_mod_op}, function::FuncVal, gc::WithCapacityExt as _, manifest::{ManifestFormat, ToStringFormat}, typed::BoundedUsize21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(&self) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum MemoizedClusureThunkInner<D: Trace, T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting {33 env: D,34 // Carries no data, as it is not a real closure, all the35 // captured environment is stored in `env` field.36 #[trace(skip)]37 closure: fn(D) -> Result<T>,38 },39 Pending,40}41#[derive(Trace)]42pub struct MemoizedClosureThunk<D: Trace, T: Trace>(RefCell<MemoizedClusureThunkInner<D, T>>);43impl<D: Trace, T: Trace> MemoizedClosureThunk<D, T> {44 pub fn new(env: D, closure: fn(D) -> Result<T>) -> Self {45 Self(RefCell::new(MemoizedClusureThunkInner::Waiting {46 env,47 closure,48 }))49 }50}5152impl<D: Trace, T: Trace + Clone> ThunkValue for MemoizedClosureThunk<D, T> {53 type Output = T;5455 fn get(&self) -> Result<Self::Output> {56 match &*self.0.borrow() {57 MemoizedClusureThunkInner::Computed(v) => return Ok(v.clone()),58 MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),59 MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),60 MemoizedClusureThunkInner::Waiting { .. } => (),61 }62 let MemoizedClusureThunkInner::Waiting { env, closure } = replace(63 &mut *self.0.borrow_mut(),64 MemoizedClusureThunkInner::Pending,65 ) else {66 unreachable!();67 };68 let new_value = match closure(env) {69 Ok(v) => v,70 Err(e) => {71 *self.0.borrow_mut() = MemoizedClusureThunkInner::Errored(e.clone());72 return Err(e);73 }74 };75 *self.0.borrow_mut() = MemoizedClusureThunkInner::Computed(new_value.clone());76 Ok(new_value)77 }78}7980cc_dyn!(81 /// Lazily evaluated value82 #[derive(Clone)] Thunk<V: Trace>,83 ThunkValue<Output = V>,84 pub fn new() {...}85);8687impl<T: Trace> Thunk<T> {88 pub fn evaluated(val: T) -> Self89 where90 T: Clone,91 {92 #[derive(Trace)]93 struct EvaluatedThunk<T: Trace>(T);94 impl<T> ThunkValue for EvaluatedThunk<T>95 where96 T: Clone + Trace,97 {98 type Output = T;99100 fn get(&self) -> Result<Self::Output> {101 Ok(self.0.clone())102 }103 }104 Self::new(EvaluatedThunk(val))105 }106 pub fn errored(e: Error) -> Self {107 #[derive(Trace)]108 struct ErroredThunk<T: Trace>(Error, PhantomData<T>);109 impl<T> ThunkValue for ErroredThunk<T>110 where111 T: Trace,112 {113 type Output = T;114115 fn get(&self) -> Result<Self::Output> {116 Err(self.0.clone())117 }118 }119 Self::new(ErroredThunk(e, PhantomData))120 }121 pub fn result(res: Result<T, Error>) -> Self122 where123 T: Clone,124 {125 match res {126 Ok(o) => Self::evaluated(o),127 Err(e) => Self::errored(e),128 }129 }130}131132impl<T> Thunk<T>133where134 T: Trace,135{136 pub fn force(&self) -> Result<()> {137 self.evaluate()?;138 Ok(())139 }140141 /// Evaluate thunk, or return cached value142 ///143 /// # Errors144 ///145 /// - Lazy value evaluation returned error146 /// - This method was called during inner value evaluation147 pub fn evaluate(&self) -> Result<T> {148 self.0.get()149 }150}151152pub trait ThunkMapper<Input>: Trace {153 type Output;154 fn map(self, from: Input) -> Result<Self::Output>;155}156impl<Input> Thunk<Input>157where158 Input: Trace,159{160 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>161 where162 M: ThunkMapper<Input>,163 M::Output: Trace + Clone,164 {165 let inner = self;166 Thunk!(move || {167 let value = inner.evaluate()?;168 let mapped = mapper.map(value)?;169 Ok(mapped)170 })171 }172}173174impl<T: Trace + Clone> From<Result<T>> for Thunk<T> {175 fn from(value: Result<T>) -> Self {176 match value {177 Ok(o) => Self::evaluated(o),178 Err(e) => Self::errored(e),179 }180 }181}182impl<T, V: Trace> From<T> for Thunk<V>183where184 T: ThunkValue<Output = V>,185{186 fn from(value: T) -> Self {187 Self::new(value)188 }189}190191impl<T: Trace + Default + Clone> Default for Thunk<T> {192 fn default() -> Self {193 Self::evaluated(T::default())194 }195}196197#[derive(Trace, Clone)]198pub struct CachedUnbound<I, T>199where200 I: Unbound<Bound = T>,201 T: Trace,202{203 cache: Cc<RefCell<FxHashMap<WeakSupThis, T>>>,204 value: I,205}206impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {207 pub fn new(value: I) -> Self {208 Self {209 cache: Cc::new(RefCell::new(FxHashMap::new())),210 value,211 }212 }213}214impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {215 type Bound = T;216 fn bind(&self, sup_this: SupThis) -> Result<T> {217 let cache_key = sup_this.clone().downgrade();218 {219 if let Some(t) = self.cache.borrow().get(&cache_key) {220 return Ok(t.clone());221 }222 }223 let bound = self.value.bind(sup_this)?;224225 {226 let mut cache = self.cache.borrow_mut();227 cache.insert(cache_key, bound.clone());228 }229230 Ok(bound)231 }232}233234impl<T: Debug + Trace> Debug for Thunk<T> {235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {236 write!(f, "Lazy")237 }238}239impl<T: Trace> PartialEq for Thunk<T> {240 fn eq(&self, other: &Self) -> bool {241 Cc::ptr_eq(&self.0, &other.0)242 }243}244245/// Represents a Jsonnet value, which can be sliced or indexed (string or array).246#[allow(clippy::module_name_repetitions)]247pub enum IndexableVal {248 /// String.249 Str(IStr),250 /// Array.251 Arr(ArrValue),252}253impl IndexableVal {254 pub fn is_empty(&self) -> bool {255 match self {256 Self::Str(s) => s.is_empty(),257 Self::Arr(s) => s.is_empty(),258 }259 }260261 pub fn to_array(self) -> ArrValue {262 match self {263 Self::Str(s) => s.chars().collect(),264 Self::Arr(arr) => arr,265 }266 }267 /// Slice the value.268 ///269 /// # Implementation270 ///271 /// For strings, will create a copy of specified interval.272 ///273 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.274 pub fn slice(275 self,276 index: Option<i32>,277 end: Option<i32>,278 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,279 ) -> Result<Self> {280 match &self {281 Self::Str(s) => {282 let mut computed_len = None;283 let mut get_len = || {284 computed_len.unwrap_or_else(|| {285 let len = s.chars().count();286 let _ = computed_len.insert(len);287 len288 })289 };290 let mut get_idx = |pos: Option<i32>, default| {291 match pos {292 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]293 Some(v) if v < 0 => get_len().saturating_sub((-v as isize) as usize),294 // No need to clamp, as iterator interface is used295 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]296 Some(v) => v as usize,297 None => default,298 }299 };300301 let index = get_idx(index, 0);302 let end = get_idx(end, usize::MAX);303 let step = step.as_deref().copied().unwrap_or(1);304305 if index >= end {306 return Ok(Self::Str("".into()));307 }308309 Ok(Self::Str(310 (s.chars()311 .skip(index)312 .take(end - index)313 .step_by(step)314 .collect::<String>())315 .into(),316 ))317 }318 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(319 index,320 end,321 #[expect(322 clippy::cast_possible_truncation,323 reason = "overflow will result with skip too large which would be equivalent"324 )]325 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),326 ))),327 }328 }329}330331#[derive(Debug, Clone, Acyclic)]332pub enum StrValue {333 Flat(IStr),334 Tree(Rc<(StrValue, StrValue, usize)>),335}336impl StrValue {337 pub fn concat(a: Self, b: Self) -> Self {338 // TODO: benchmark for an optimal value, currently just a arbitrary choice339 const STRING_EXTEND_THRESHOLD: usize = 100;340341 if a.is_empty() {342 b343 } else if b.is_empty() {344 a345 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {346 Self::Flat(format!("{a}{b}").into())347 } else {348 let len = a.len() + b.len();349 Self::Tree(Rc::new((a, b, len)))350 }351 }352 pub fn chunks(&self, c: &mut impl FnMut(&IStr)) {353 fn write_buf(s: &StrValue, c: &mut impl FnMut(&IStr)) {354 match s {355 StrValue::Flat(f) => c(f),356 StrValue::Tree(t) => {357 write_buf(&t.0, c);358 write_buf(&t.1, c);359 }360 }361 }362 write_buf(self, c);363 }364 pub fn into_flat(&self) -> IStr {365 fn write_buf(s: &StrValue, out: &mut String) {366 match s {367 StrValue::Flat(f) => out.push_str(f),368 StrValue::Tree(t) => {369 write_buf(&t.0, out);370 write_buf(&t.1, out);371 }372 }373 }374 match self {375 Self::Flat(f) => f.clone(),376 Self::Tree(_) => {377 let mut buf = String::with_capacity(self.len());378 write_buf(self, &mut buf);379 buf.into()380 }381 }382 }383 pub fn len(&self) -> usize {384 match self {385 Self::Flat(v) => v.len(),386 Self::Tree(t) => t.2,387 }388 }389 pub fn is_empty(&self) -> bool {390 match self {391 Self::Flat(v) => v.is_empty(),392 // Can't create non-flat empty string393 Self::Tree(_) => false,394 }395 }396}397impl<T> From<T> for StrValue398where399 IStr: From<T>,400{401 fn from(value: T) -> Self {402 Self::Flat(IStr::from(value))403 }404}405impl Display for StrValue {406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {407 match self {408 Self::Flat(v) => write!(f, "{v}"),409 Self::Tree(t) => {410 write!(f, "{}", t.0)?;411 write!(f, "{}", t.1)412 }413 }414 }415}416impl PartialEq for StrValue {417 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.418 #[allow(clippy::unconditional_recursion)]419 fn eq(&self, other: &Self) -> bool {420 let a = self.clone().into_flat();421 let b = other.clone().into_flat();422 a == b423 }424}425impl Eq for StrValue {}426impl PartialOrd for StrValue {427 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {428 Some(self.cmp(other))429 }430}431impl Ord for StrValue {432 fn cmp(&self, other: &Self) -> Ordering {433 let a = self.clone().into_flat();434 let b = other.clone().into_flat();435 a.cmp(&b)436 }437}438439/// Represents any valid Jsonnet value.440#[derive(Debug, Clone, Trace, Default)]441pub enum Val {442 /// Represents a Jsonnet boolean.443 Bool(bool),444 /// Represents a Jsonnet null value.445 #[default]446 Null,447 /// Represents a Jsonnet string.448 Str(StrValue),449 /// Represents a Jsonnet number.450 /// Should be finite, and not NaN451 /// This restriction isn't enforced by enum, as enum field can't be marked as private452 Num(NumValue),453 /// Experimental bigint454 #[cfg(feature = "exp-bigint")]455 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),456 /// Represents a Jsonnet array.457 Arr(ArrValue),458 /// Represents a Jsonnet object.459 Obj(ObjValue),460 /// Represents a Jsonnet function.461 Func(FuncVal),462}463464#[cfg(target_pointer_width = "64")]465static_assertions::assert_eq_size!(Val, [u8; 24]);466467impl From<IndexableVal> for Val {468 fn from(v: IndexableVal) -> Self {469 match v {470 IndexableVal::Str(s) => Self::string(s),471 IndexableVal::Arr(a) => Self::Arr(a),472 }473 }474}475476impl Val {477 pub const fn as_bool(&self) -> Option<bool> {478 match self {479 Self::Bool(v) => Some(*v),480 _ => None,481 }482 }483 pub const fn as_null(&self) -> Option<()> {484 match self {485 Self::Null => Some(()),486 _ => None,487 }488 }489 pub fn as_str(&self) -> Option<IStr> {490 match self {491 Self::Str(s) => Some(s.clone().into_flat()),492 _ => None,493 }494 }495 pub const fn as_num(&self) -> Option<f64> {496 match self {497 Self::Num(n) => Some(n.get()),498 _ => None,499 }500 }501 #[cfg(feature = "exp-bigint")]502 pub fn as_bigint(&self) -> Option<num_bigint::BigInt> {503 match self {504 Self::BigInt(n) => Some(*n.clone()),505 _ => None,506 }507 }508 pub fn as_arr(&self) -> Option<ArrValue> {509 match self {510 Self::Arr(a) => Some(a.clone()),511 _ => None,512 }513 }514 pub fn as_obj(&self) -> Option<ObjValue> {515 match self {516 Self::Obj(o) => Some(o.clone()),517 _ => None,518 }519 }520 pub fn as_func(&self) -> Option<FuncVal> {521 match self {522 Self::Func(f) => Some(f.clone()),523 _ => None,524 }525 }526527 pub const fn value_type(&self) -> ValType {528 match self {529 Self::Str(..) => ValType::Str,530 Self::Num(..) => ValType::Num,531 #[cfg(feature = "exp-bigint")]532 Self::BigInt(..) => ValType::BigInt,533 Self::Arr(..) => ValType::Arr,534 Self::Obj(..) => ValType::Obj,535 Self::Bool(_) => ValType::Bool,536 Self::Null => ValType::Null,537 Self::Func(..) => ValType::Func,538 }539 }540541 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {542 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {543 manifest.manifest(val.clone())544 }545 manifest_dyn(self, &format)546 }547548 pub fn to_string(&self) -> Result<IStr> {549 Ok(match self {550 Self::Bool(true) => "true".into(),551 Self::Bool(false) => "false".into(),552 Self::Null => "null".into(),553 Self::Str(s) => s.clone().into_flat(),554 _ => self.manifest(ToStringFormat).map(IStr::from)?,555 })556 }557558 pub fn into_indexable(self) -> Result<IndexableVal> {559 Ok(match self {560 Self::Str(s) => IndexableVal::Str(s.into_flat()),561 Self::Arr(arr) => IndexableVal::Arr(arr),562 _ => bail!(ValueIsNotIndexable(self.value_type())),563 })564 }565566 pub fn function(function: impl Into<FuncVal>) -> Self {567 Self::Func(function.into())568 }569 pub fn string(string: impl Into<StrValue>) -> Self {570 Self::Str(string.into())571 }572 pub fn num(num: impl Into<NumValue>) -> Self {573 Self::Num(num.into())574 }575 pub fn try_num<V, E>(num: V) -> Result<Self, E>576 where577 NumValue: TryFrom<V, Error = E>,578 {579 Ok(Self::Num(num.try_into()?))580 }581 pub fn arr(a: impl ArrayLike) -> Self {582 Self::Arr(ArrValue::new(a))583 }584585 pub fn try_cmp(a: &Val, b: &Val) -> Result<Ordering> {586 evaluate_compare_op(a, b, BinaryOpType::Lt)587 }588 pub fn try_mod(a: &Val, b: &Val) -> Result<Val> {589 evaluate_mod_op(a, b)590 }591}592593impl From<IStr> for Val {594 fn from(value: IStr) -> Self {595 Self::string(value)596 }597}598impl From<String> for Val {599 fn from(value: String) -> Self {600 Self::string(value)601 }602}603impl From<&str> for Val {604 fn from(value: &str) -> Self {605 Self::string(value)606 }607}608impl From<ObjValue> for Val {609 fn from(value: ObjValue) -> Self {610 Self::Obj(value)611 }612}613614const fn is_function_like(val: &Val) -> bool {615 matches!(val, Val::Func(_))616}617618/// Native implementation of `std.primitiveEquals`619pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {620 Ok(match (val_a, val_b) {621 (Val::Bool(a), Val::Bool(b)) => a == b,622 (Val::Null, Val::Null) => true,623 (Val::Str(a), Val::Str(b)) => a == b,624 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,625 #[cfg(feature = "exp-bigint")]626 (Val::BigInt(a), Val::BigInt(b)) => a == b,627 (Val::Arr(_), Val::Arr(_)) => {628 bail!("primitiveEquals operates on primitive types, got array")629 }630 (Val::Obj(_), Val::Obj(_)) => {631 bail!("primitiveEquals operates on primitive types, got object")632 }633 (a, b) if is_function_like(a) && is_function_like(b) => {634 bail!("cannot test equality of functions")635 }636 (_, _) => false,637 })638}639640/// Native implementation of `std.equals`641pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {642 if val_a.value_type() != val_b.value_type() {643 return Ok(false);644 }645 match (val_a, val_b) {646 (Val::Arr(a), Val::Arr(b)) => {647 if ArrValue::ptr_eq(a, b) {648 return Ok(true);649 }650 if a.len() != b.len() {651 return Ok(false);652 }653 for (a, b) in a.iter().zip(b.iter()) {654 if !equals(&a?, &b?)? {655 return Ok(false);656 }657 }658 Ok(true)659 }660 (Val::Obj(a), Val::Obj(b)) => {661 if ObjValue::ptr_eq(a, b) {662 return Ok(true);663 }664 let fields = a.fields(665 #[cfg(feature = "exp-preserve-order")]666 false,667 );668 if fields669 != b.fields(670 #[cfg(feature = "exp-preserve-order")]671 false,672 ) {673 return Ok(false);674 }675 for field in fields {676 if !equals(677 &a.get(field.clone())?.expect("field exists"),678 &b.get(field)?.expect("field exists"),679 )? {680 return Ok(false);681 }682 }683 Ok(true)684 }685 (a, b) => Ok(primitive_equals(a, b)?),686 }687}crates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -16,17 +16,13 @@
# Bigint type
exp-bigint = ["dep:num-bigint", "jrsonnet-evaluator/exp-bigint"]
-exp-null-coaelse = [
- "jrsonnet-ir/exp-null-coaelse",
- "jrsonnet-evaluator/exp-null-coaelse",
-]
+exp-null-coaelse = ["jrsonnet-evaluator/exp-null-coaelse"]
# std.regexMatch and other helpers
exp-regex = ["dep:regex", "dep:lru", "dep:rustc-hash"]
[dependencies]
jrsonnet-evaluator.workspace = true
jrsonnet-macros.workspace = true
-jrsonnet-ir.workspace = true
jrsonnet-gcmodule.workspace = true
# Used for std.parseJson/std.parseYaml
crates/jrsonnet-stdlib/src/compat.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/compat.rs
+++ b/crates/jrsonnet-stdlib/src/compat.rs
@@ -1,19 +1,15 @@
use std::cmp::Ordering;
-use jrsonnet_evaluator::{
- Result, Val, function::builtin, operator::evaluate_compare_op, val::ArrValue,
-};
+use jrsonnet_evaluator::{function::builtin, val::ArrValue, Result, Val};
#[builtin]
#[allow(non_snake_case)]
pub fn builtin___compare(v1: Val, v2: Val) -> Result<i32> {
- Ok(
- match evaluate_compare_op(&v1, &v2, jrsonnet_ir::BinaryOpType::Lt)? {
- Ordering::Less => -1,
- Ordering::Equal => 0,
- Ordering::Greater => 1,
- },
- )
+ Ok(match Val::try_cmp(&v1, &v2)? {
+ Ordering::Less => -1,
+ Ordering::Equal => 0,
+ Ordering::Greater => 1,
+ })
}
#[builtin]
@@ -27,11 +23,7 @@
#[builtin]
#[allow(non_snake_case)]
pub fn $name(arr1: ArrValue, arr2: ArrValue) -> Result<bool> {
- let ordering = evaluate_compare_op(
- &Val::Arr(arr1),
- &Val::Arr(arr2),
- jrsonnet_ir::BinaryOpType::Lt,
- )?;
+ let ordering = Val::try_cmp(&Val::Arr(arr1), &Val::Arr(arr2))?;
Ok($operator.contains(&ordering))
}
};
crates/jrsonnet-stdlib/src/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/operator.rs
+++ b/crates/jrsonnet-stdlib/src/operator.rs
@@ -2,18 +2,17 @@
//! However, in our case we instead implement them in native, and implement native functions on top of core for backwards compatibility
use jrsonnet_evaluator::{
- IStr, NumValue, Result, Val,
function::builtin,
- operator::evaluate_mod_op,
stdlib::std_format,
typed::{Either, Either2},
val::{equals, primitive_equals},
+ IStr, NumValue, Result, Val,
};
#[builtin]
pub fn builtin_mod(a: Either![NumValue, IStr], b: Val) -> Result<Val> {
use Either2::*;
- evaluate_mod_op(
+ Val::try_mod(
&match a {
A(v) => Val::Num(v),
B(s) => Val::string(s),
crates/jrsonnet-stdlib/src/sets.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/sets.rs
+++ b/crates/jrsonnet-stdlib/src/sets.rs
@@ -1,9 +1,6 @@
use std::cmp::Ordering;
-use jrsonnet_evaluator::{
- Result, Thunk, Val, function::builtin, operator::evaluate_compare_op, val::ArrValue,
-};
-use jrsonnet_ir::BinaryOpType;
+use jrsonnet_evaluator::{function::builtin, val::ArrValue, Result, Thunk, Val};
use crate::keyf::KeyF;
@@ -16,9 +13,9 @@
let x = keyF.eval(x)?;
while low < high {
- let middle = usize::midpoint(high, low);
+ let middle = u32::midpoint(high, low);
let comp = keyF.eval(arr.get_lazy(middle).expect("in bounds"))?;
- match evaluate_compare_op(&comp, &x, BinaryOpType::Lt)? {
+ match Val::try_cmp(&comp, &x)? {
Ordering::Less => low = middle + 1,
Ordering::Equal => return Ok(true),
Ordering::Greater => high = middle,
@@ -46,7 +43,7 @@
let mut out = Vec::new();
while let (Some(ac), Some(bc)) = (&ak, &bk) {
- match evaluate_compare_op(ac, bc, BinaryOpType::Lt)? {
+ match Val::try_cmp(ac, bc)? {
Ordering::Less => {
av = a.next();
ak = av.clone().map(keyF).transpose()?;
@@ -86,7 +83,7 @@
let mut out = Vec::new();
while let (Some(ac), Some(bc)) = (&ak, &bk) {
- match evaluate_compare_op(ac, bc, BinaryOpType::Lt)? {
+ match Val::try_cmp(ac, bc)? {
Ordering::Less => {
// In a, but not in b
out.push(av.clone().expect("ak != None"));
@@ -133,7 +130,7 @@
let mut out = Vec::new();
while let (Some(ac), Some(bc)) = (&ak, &bk) {
- match evaluate_compare_op(ac, bc, BinaryOpType::Lt)? {
+ match Val::try_cmp(ac, bc)? {
Ordering::Less => {
out.push(av.clone().expect("ak != None"));
av = a.next();
crates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -3,12 +3,11 @@
use std::cmp::Ordering;
use jrsonnet_evaluator::{
- Result, Thunk, Val, bail,
+ bail,
function::builtin,
- operator::evaluate_compare_op,
- val::{ArrValue, equals},
+ val::{equals, ArrValue},
+ Result, Thunk, Val,
};
-use jrsonnet_ir::BinaryOpType;
use crate::{eval_on_empty, keyf::KeyF};
@@ -53,7 +52,7 @@
let mut err = None;
// evaluate_compare_op will never return equal on types, which are different from
// jsonnet perspective
- values.sort_unstable_by(|a, b| match evaluate_compare_op(a, b, BinaryOpType::Lt) {
+ values.sort_unstable_by(|a, b| match Val::try_cmp(a, b) {
Ok(ord) => ord,
Err(e) if err.is_none() => {
let _ = err.insert(e);
@@ -71,7 +70,7 @@
fn sort_keyf(values: ArrValue, keyf: KeyF) -> Result<Vec<Thunk<Val>>> {
// Slow path, user provided key getter
- let mut vk = Vec::with_capacity(values.len());
+ let mut vk = Vec::with_capacity(values.len() as usize);
for value in values.iter_lazy() {
vk.push((value.clone(), keyf.eval(value)?));
}
@@ -89,16 +88,14 @@
let mut err = None;
// evaluate_compare_op will never return equal on types, which are different from
// jsonnet perspective
- vk.sort_by(
- |(_a, ak), (_b, bk)| match evaluate_compare_op(ak, bk, BinaryOpType::Lt) {
- Ok(ord) => ord,
- Err(e) if err.is_none() => {
- let _ = err.insert(e);
- Ordering::Equal
- }
- Err(_) => Ordering::Equal,
- },
- );
+ vk.sort_by(|(_a, ak), (_b, bk)| match Val::try_cmp(ak, bk) {
+ Ok(ord) => ord,
+ Err(e) if err.is_none() => {
+ let _ = err.insert(e);
+ Ordering::Equal
+ }
+ Err(_) => Ordering::Equal,
+ });
if let Some(err) = err {
return Err(err);
}
@@ -195,7 +192,7 @@
for item in iter {
let cur = item?;
let cur_key = keyf.eval(Thunk::evaluated(cur.clone()))?;
- if evaluate_compare_op(&cur_key, &min_key, BinaryOpType::Lt)? == ordering {
+ if Val::try_cmp(&cur_key, &min_key)? == ordering {
min = cur;
min_key = cur_key;
}
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -26,7 +26,7 @@
#[builtin]
pub fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
if from.is_empty() {
- bail!("'from' string must not be zero length");
+ bail!("`from` string must not be zero length");
}
Ok(str.replace(&from as &str, &to as &str))
}