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;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 39 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 86 #[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 146 147 148 149 150 151 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}248249250#[allow(clippy::module_name_repetitions)]251pub enum IndexableVal {252 253 Str(IStr),254 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 272 273 274 275 276 277 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 299 #[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 343 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 397 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 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}442443444#[derive(Debug, Clone, Trace, Default)]445pub enum Val {446 447 Bool(bool),448 449 #[default]450 Null,451 452 Str(StrValue),453 454 455 456 Num(NumValue),457 458 #[cfg(feature = "exp-bigint")]459 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),460 461 Arr(ArrValue),462 463 Obj(ObjValue),464 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}614615616pub 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}636637638pub 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}