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 35 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 82 #[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 142 143 144 145 146 147 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}244245246#[allow(clippy::module_name_repetitions)]247pub enum IndexableVal {248 249 Str(IStr),250 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 268 269 270 271 272 273 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 295 #[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 339 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 393 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 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}438439440#[derive(Debug, Clone, Trace, Default)]441pub enum Val {442 443 Bool(bool),444 445 #[default]446 Null,447 448 Str(StrValue),449 450 451 452 Num(NumValue),453 454 #[cfg(feature = "exp-bigint")]455 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),456 457 Arr(ArrValue),458 459 Obj(ObjValue),460 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}617618619pub 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}639640641pub 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}