1use std::{cell::RefCell, fmt::Debug, mem::replace};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_types::ValType;67pub use crate::arr::ArrValue;8use crate::{9 error::{Error, ErrorKind::*},10 function::FuncVal,11 gc::{GcHashMap, TraceBox},12 manifest::{ManifestFormat, ToStringFormat},13 throw,14 typed::BoundedUsize,15 ObjValue, Result, Unbound, WeakObjValue,16};1718pub trait ThunkValue: Trace {19 type Output;20 fn get(self: Box<Self>) -> Result<Self::Output>;21}2223#[derive(Trace)]24enum ThunkInner<T: Trace> {25 Computed(T),26 Errored(Error),27 Waiting(TraceBox<dyn ThunkValue<Output = T>>),28 Pending,29}3031#[allow(clippy::module_name_repetitions)]32#[derive(Clone, Trace)]33pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3435impl<T: Trace> Thunk<T> {36 pub fn evaluated(val: T) -> Self {37 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))38 }39 pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {40 Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))41 }42 pub fn errored(e: Error) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))44 }45}4647impl<T> Thunk<T>48where49 T: Clone + Trace,50{51 pub fn force(&self) -> Result<()> {52 self.evaluate()?;53 Ok(())54 }55 pub fn evaluate(&self) -> Result<T> {56 match &*self.0.borrow() {57 ThunkInner::Computed(v) => return Ok(v.clone()),58 ThunkInner::Errored(e) => return Err(e.clone()),59 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),60 ThunkInner::Waiting(..) => (),61 };62 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {63 unreachable!();64 };65 let new_value = match value.0.get() {66 Ok(v) => v,67 Err(e) => {68 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());69 return Err(e);70 }71 };72 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());73 Ok(new_value)74 }75}7677type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);7879#[derive(Trace, Clone)]80pub struct CachedUnbound<I, T>81where82 I: Unbound<Bound = T>,83 T: Trace,84{85 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,86 value: I,87}88impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {89 pub fn new(value: I) -> Self {90 Self {91 cache: Cc::new(RefCell::new(GcHashMap::new())),92 value,93 }94 }95}96impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {97 type Bound = T;98 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {99 let cache_key = (100 sup.as_ref().map(|s| s.clone().downgrade()),101 this.as_ref().map(|t| t.clone().downgrade()),102 );103 {104 if let Some(t) = self.cache.borrow().get(&cache_key) {105 return Ok(t.clone());106 }107 }108 let bound = self.value.bind(sup, this)?;109110 {111 let mut cache = self.cache.borrow_mut();112 cache.insert(cache_key, bound.clone());113 }114115 Ok(bound)116 }117}118119impl<T: Debug + Trace> Debug for Thunk<T> {120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {121 write!(f, "Lazy")122 }123}124impl<T: Trace> PartialEq for Thunk<T> {125 fn eq(&self, other: &Self) -> bool {126 Cc::ptr_eq(&self.0, &other.0)127 }128}129130131#[allow(clippy::module_name_repetitions)]132pub enum IndexableVal {133 134 Str(IStr),135 136 Arr(ArrValue),137}138impl IndexableVal {139 140 141 142 143 144 145 146 pub fn slice(147 self,148 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,149 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,150 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,151 ) -> Result<Self> {152 match &self {153 IndexableVal::Str(s) => {154 let index = index.as_deref().copied().unwrap_or(0);155 let end = end.as_deref().copied().unwrap_or(usize::MAX);156 let step = step.as_deref().copied().unwrap_or(1);157158 if index >= end {159 return Ok(Self::Str("".into()));160 }161162 Ok(Self::Str(163 (s.chars()164 .skip(index)165 .take(end - index)166 .step_by(step)167 .collect::<String>())168 .into(),169 ))170 }171 IndexableVal::Arr(arr) => {172 let index = index.as_deref().copied().unwrap_or(0);173 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());174 let step = step.as_deref().copied().unwrap_or(1);175176 if index >= end {177 return Ok(Self::Arr(ArrValue::empty()));178 }179180 Ok(Self::Arr(181 arr.clone()182 .slice(Some(index), Some(end), Some(step))183 .expect("arguments checked"),184 ))185 }186 }187 }188}189190191#[derive(Debug, Clone, Trace)]192pub enum Val {193 194 Bool(bool),195 196 Null,197 198 Str(IStr),199 200 201 202 Num(f64),203 204 Arr(ArrValue),205 206 Obj(ObjValue),207 208 Func(FuncVal),209}210211impl From<IndexableVal> for Val {212 fn from(v: IndexableVal) -> Self {213 match v {214 IndexableVal::Str(s) => Self::Str(s),215 IndexableVal::Arr(a) => Self::Arr(a),216 }217 }218}219220impl Val {221 pub const fn as_bool(&self) -> Option<bool> {222 match self {223 Self::Bool(v) => Some(*v),224 _ => None,225 }226 }227 pub const fn as_null(&self) -> Option<()> {228 match self {229 Self::Null => Some(()),230 _ => None,231 }232 }233 pub fn as_str(&self) -> Option<IStr> {234 match self {235 Self::Str(s) => Some(s.clone()),236 _ => None,237 }238 }239 pub const fn as_num(&self) -> Option<f64> {240 match self {241 Self::Num(n) => Some(*n),242 _ => None,243 }244 }245 pub fn as_arr(&self) -> Option<ArrValue> {246 match self {247 Self::Arr(a) => Some(a.clone()),248 _ => None,249 }250 }251 pub fn as_obj(&self) -> Option<ObjValue> {252 match self {253 Self::Obj(o) => Some(o.clone()),254 _ => None,255 }256 }257 pub fn as_func(&self) -> Option<FuncVal> {258 match self {259 Self::Func(f) => Some(f.clone()),260 _ => None,261 }262 }263264 265 266 pub fn new_checked_num(num: f64) -> Result<Self> {267 if num.is_finite() {268 Ok(Self::Num(num))269 } else {270 throw!("overflow")271 }272 }273274 pub const fn value_type(&self) -> ValType {275 match self {276 Self::Str(..) => ValType::Str,277 Self::Num(..) => ValType::Num,278 Self::Arr(..) => ValType::Arr,279 Self::Obj(..) => ValType::Obj,280 Self::Bool(_) => ValType::Bool,281 Self::Null => ValType::Null,282 Self::Func(..) => ValType::Func,283 }284 }285286 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {287 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {288 manifest.manifest(val.clone())289 }290 manifest_dyn(self, &format)291 }292293 pub fn to_string(&self) -> Result<IStr> {294 Ok(match self {295 Self::Bool(true) => "true".into(),296 Self::Bool(false) => "false".into(),297 Self::Null => "null".into(),298 Self::Str(s) => s.clone(),299 _ => self.manifest(ToStringFormat).map(IStr::from)?,300 })301 }302303 pub fn into_indexable(self) -> Result<IndexableVal> {304 Ok(match self {305 Val::Str(s) => IndexableVal::Str(s),306 Val::Arr(arr) => IndexableVal::Arr(arr),307 _ => throw!(ValueIsNotIndexable(self.value_type())),308 })309 }310}311312const fn is_function_like(val: &Val) -> bool {313 matches!(val, Val::Func(_))314}315316317pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {318 Ok(match (val_a, val_b) {319 (Val::Bool(a), Val::Bool(b)) => a == b,320 (Val::Null, Val::Null) => true,321 (Val::Str(a), Val::Str(b)) => a == b,322 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,323 (Val::Arr(_), Val::Arr(_)) => {324 throw!("primitiveEquals operates on primitive types, got array")325 }326 (Val::Obj(_), Val::Obj(_)) => {327 throw!("primitiveEquals operates on primitive types, got object")328 }329 (a, b) if is_function_like(a) && is_function_like(b) => {330 throw!("cannot test equality of functions")331 }332 (_, _) => false,333 })334}335336337pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {338 if val_a.value_type() != val_b.value_type() {339 return Ok(false);340 }341 match (val_a, val_b) {342 (Val::Arr(a), Val::Arr(b)) => {343 if ArrValue::ptr_eq(a, b) {344 return Ok(true);345 }346 if a.len() != b.len() {347 return Ok(false);348 }349 for (a, b) in a.iter().zip(b.iter()) {350 if !equals(&a?, &b?)? {351 return Ok(false);352 }353 }354 Ok(true)355 }356 (Val::Obj(a), Val::Obj(b)) => {357 if ObjValue::ptr_eq(a, b) {358 return Ok(true);359 }360 let fields = a.fields(361 #[cfg(feature = "exp-preserve-order")]362 false,363 );364 if fields365 != b.fields(366 #[cfg(feature = "exp-preserve-order")]367 false,368 ) {369 return Ok(false);370 }371 for field in fields {372 if !equals(373 &a.get(field.clone())?.expect("field exists"),374 &b.get(field)?.expect("field exists"),375 )? {376 return Ok(false);377 }378 }379 Ok(true)380 }381 (a, b) => Ok(primitive_equals(a, b)?),382 }383}