1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 mem::replace,5 rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 throw,19 typed::BoundedUsize,20 ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting(TraceBox<dyn ThunkValue<Output = T>>),33 Pending,34}3536#[allow(clippy::module_name_repetitions)]37#[derive(Clone, Trace)]38pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3940impl<T: Trace> Thunk<T> {41 pub fn evaluated(val: T) -> Self {42 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))43 }44 pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {45 Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))46 }47 pub fn errored(e: Error) -> Self {48 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))49 }50}5152impl<T> Thunk<T>53where54 T: Clone + Trace,55{56 pub fn force(&self) -> Result<()> {57 self.evaluate()?;58 Ok(())59 }60 pub fn evaluate(&self) -> Result<T> {61 match &*self.0.borrow() {62 ThunkInner::Computed(v) => return Ok(v.clone()),63 ThunkInner::Errored(e) => return Err(e.clone()),64 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),65 ThunkInner::Waiting(..) => (),66 };67 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {68 unreachable!();69 };70 let new_value = match value.0.get() {71 Ok(v) => v,72 Err(e) => {73 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());74 return Err(e);75 }76 };77 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());78 Ok(new_value)79 }80}8182type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);8384#[derive(Trace, Clone)]85pub struct CachedUnbound<I, T>86where87 I: Unbound<Bound = T>,88 T: Trace,89{90 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,91 value: I,92}93impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {94 pub fn new(value: I) -> Self {95 Self {96 cache: Cc::new(RefCell::new(GcHashMap::new())),97 value,98 }99 }100}101impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {102 type Bound = T;103 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {104 let cache_key = (105 sup.as_ref().map(|s| s.clone().downgrade()),106 this.as_ref().map(|t| t.clone().downgrade()),107 );108 {109 if let Some(t) = self.cache.borrow().get(&cache_key) {110 return Ok(t.clone());111 }112 }113 let bound = self.value.bind(sup, this)?;114115 {116 let mut cache = self.cache.borrow_mut();117 cache.insert(cache_key, bound.clone());118 }119120 Ok(bound)121 }122}123124impl<T: Debug + Trace> Debug for Thunk<T> {125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {126 write!(f, "Lazy")127 }128}129impl<T: Trace> PartialEq for Thunk<T> {130 fn eq(&self, other: &Self) -> bool {131 Cc::ptr_eq(&self.0, &other.0)132 }133}134135136#[allow(clippy::module_name_repetitions)]137pub enum IndexableVal {138 139 Str(IStr),140 141 Arr(ArrValue),142}143impl IndexableVal {144 145 146 147 148 149 150 151 pub fn slice(152 self,153 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,154 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,155 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,156 ) -> Result<Self> {157 match &self {158 IndexableVal::Str(s) => {159 let index = index.as_deref().copied().unwrap_or(0);160 let end = end.as_deref().copied().unwrap_or(usize::MAX);161 let step = step.as_deref().copied().unwrap_or(1);162163 if index >= end {164 return Ok(Self::Str("".into()));165 }166167 Ok(Self::Str(168 (s.chars()169 .skip(index)170 .take(end - index)171 .step_by(step)172 .collect::<String>())173 .into(),174 ))175 }176 IndexableVal::Arr(arr) => {177 let index = index.as_deref().copied().unwrap_or(0);178 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());179 let step = step.as_deref().copied().unwrap_or(1);180181 if index >= end {182 return Ok(Self::Arr(ArrValue::empty()));183 }184185 Ok(Self::Arr(186 arr.clone()187 .slice(Some(index), Some(end), Some(step))188 .expect("arguments checked"),189 ))190 }191 }192 }193}194195#[derive(Debug, Clone, Trace)]196pub enum StrValue {197 Flat(IStr),198 Tree(Rc<(StrValue, StrValue, usize)>),199}200impl StrValue {201 pub fn concat(a: StrValue, b: StrValue) -> Self {202 203 const STRING_EXTEND_THRESHOLD: usize = 100;204205 if a.is_empty() {206 b207 } else if b.is_empty() {208 a209 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {210 Self::Flat(format!("{a}{b}").into())211 } else {212 let len = a.len() + b.len();213 Self::Tree(Rc::new((a, b, len)))214 }215 }216 pub fn into_flat(self) -> IStr {217 #[cold]218 fn write_buf(s: &StrValue, out: &mut String) {219 match s {220 StrValue::Flat(f) => out.push_str(f),221 StrValue::Tree(t) => {222 write_buf(&t.0, out);223 write_buf(&t.1, out);224 }225 }226 }227 match self {228 StrValue::Flat(f) => f,229 StrValue::Tree(_) => {230 let mut buf = String::with_capacity(self.len());231 write_buf(&self, &mut buf);232 buf.into()233 }234 }235 }236 pub fn len(&self) -> usize {237 match self {238 StrValue::Flat(v) => v.len(),239 StrValue::Tree(t) => t.2,240 }241 }242 pub fn is_empty(&self) -> bool {243 match self {244 Self::Flat(v) => v.is_empty(),245 246 Self::Tree(_) => false,247 }248 }249}250impl Display for StrValue {251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {252 match self {253 StrValue::Flat(v) => write!(f, "{v}"),254 StrValue::Tree(t) => {255 write!(f, "{}", t.0)?;256 write!(f, "{}", t.1)257 }258 }259 }260}261impl PartialEq for StrValue {262 fn eq(&self, other: &Self) -> bool {263 let a = self.clone().into_flat();264 let b = other.clone().into_flat();265 a == b266 }267}268impl Eq for StrValue {}269impl PartialOrd for StrValue {270 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {271 let a = self.clone().into_flat();272 let b = other.clone().into_flat();273 Some(a.cmp(&b))274 }275}276impl Ord for StrValue {277 fn cmp(&self, other: &Self) -> std::cmp::Ordering {278 self.partial_cmp(other)279 .expect("partial_cmp always returns Some")280 }281}282283284#[derive(Debug, Clone, Trace)]285pub enum Val {286 287 Bool(bool),288 289 Null,290 291 Str(StrValue),292 293 294 295 Num(f64),296 297 Arr(ArrValue),298 299 Obj(ObjValue),300 301 Func(FuncVal),302}303304static_assertions::assert_eq_size!(Val, [u8; 24]);305306impl From<IndexableVal> for Val {307 fn from(v: IndexableVal) -> Self {308 match v {309 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),310 IndexableVal::Arr(a) => Self::Arr(a),311 }312 }313}314315impl Val {316 pub const fn as_bool(&self) -> Option<bool> {317 match self {318 Self::Bool(v) => Some(*v),319 _ => None,320 }321 }322 pub const fn as_null(&self) -> Option<()> {323 match self {324 Self::Null => Some(()),325 _ => None,326 }327 }328 pub fn as_str(&self) -> Option<IStr> {329 match self {330 Self::Str(s) => Some(s.clone().into_flat()),331 _ => None,332 }333 }334 pub const fn as_num(&self) -> Option<f64> {335 match self {336 Self::Num(n) => Some(*n),337 _ => None,338 }339 }340 pub fn as_arr(&self) -> Option<ArrValue> {341 match self {342 Self::Arr(a) => Some(a.clone()),343 _ => None,344 }345 }346 pub fn as_obj(&self) -> Option<ObjValue> {347 match self {348 Self::Obj(o) => Some(o.clone()),349 _ => None,350 }351 }352 pub fn as_func(&self) -> Option<FuncVal> {353 match self {354 Self::Func(f) => Some(f.clone()),355 _ => None,356 }357 }358359 360 361 pub fn new_checked_num(num: f64) -> Result<Self> {362 if num.is_finite() {363 Ok(Self::Num(num))364 } else {365 throw!("overflow")366 }367 }368369 pub const fn value_type(&self) -> ValType {370 match self {371 Self::Str(..) => ValType::Str,372 Self::Num(..) => ValType::Num,373 Self::Arr(..) => ValType::Arr,374 Self::Obj(..) => ValType::Obj,375 Self::Bool(_) => ValType::Bool,376 Self::Null => ValType::Null,377 Self::Func(..) => ValType::Func,378 }379 }380381 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {382 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {383 manifest.manifest(val.clone())384 }385 manifest_dyn(self, &format)386 }387388 pub fn to_string(&self) -> Result<IStr> {389 Ok(match self {390 Self::Bool(true) => "true".into(),391 Self::Bool(false) => "false".into(),392 Self::Null => "null".into(),393 Self::Str(s) => s.clone().into_flat(),394 _ => self.manifest(ToStringFormat).map(IStr::from)?,395 })396 }397398 pub fn into_indexable(self) -> Result<IndexableVal> {399 Ok(match self {400 Val::Str(s) => IndexableVal::Str(s.into_flat()),401 Val::Arr(arr) => IndexableVal::Arr(arr),402 _ => throw!(ValueIsNotIndexable(self.value_type())),403 })404 }405}406407const fn is_function_like(val: &Val) -> bool {408 matches!(val, Val::Func(_))409}410411412pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {413 Ok(match (val_a, val_b) {414 (Val::Bool(a), Val::Bool(b)) => a == b,415 (Val::Null, Val::Null) => true,416 (Val::Str(a), Val::Str(b)) => a == b,417 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,418 (Val::Arr(_), Val::Arr(_)) => {419 throw!("primitiveEquals operates on primitive types, got array")420 }421 (Val::Obj(_), Val::Obj(_)) => {422 throw!("primitiveEquals operates on primitive types, got object")423 }424 (a, b) if is_function_like(a) && is_function_like(b) => {425 throw!("cannot test equality of functions")426 }427 (_, _) => false,428 })429}430431432pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {433 if val_a.value_type() != val_b.value_type() {434 return Ok(false);435 }436 match (val_a, val_b) {437 (Val::Arr(a), Val::Arr(b)) => {438 if ArrValue::ptr_eq(a, b) {439 return Ok(true);440 }441 if a.len() != b.len() {442 return Ok(false);443 }444 for (a, b) in a.iter().zip(b.iter()) {445 if !equals(&a?, &b?)? {446 return Ok(false);447 }448 }449 Ok(true)450 }451 (Val::Obj(a), Val::Obj(b)) => {452 if ObjValue::ptr_eq(a, b) {453 return Ok(true);454 }455 let fields = a.fields(456 #[cfg(feature = "exp-preserve-order")]457 false,458 );459 if fields460 != b.fields(461 #[cfg(feature = "exp-preserve-order")]462 false,463 ) {464 return Ok(false);465 }466 for field in fields {467 if !equals(468 &a.get(field.clone())?.expect("field exists"),469 &b.get(field)?.expect("field exists"),470 )? {471 return Ok(false);472 }473 }474 Ok(true)475 }476 (a, b) => Ok(primitive_equals(a, b)?),477 }478}