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 tb, 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: impl ThunkValue<Output = T> + 'static) -> Self {45 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(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}303304#[cfg(target_pointer_width = "64")]305static_assertions::assert_eq_size!(Val, [u8; 24]);306307impl From<IndexableVal> for Val {308 fn from(v: IndexableVal) -> Self {309 match v {310 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),311 IndexableVal::Arr(a) => Self::Arr(a),312 }313 }314}315316impl Val {317 pub const fn as_bool(&self) -> Option<bool> {318 match self {319 Self::Bool(v) => Some(*v),320 _ => None,321 }322 }323 pub const fn as_null(&self) -> Option<()> {324 match self {325 Self::Null => Some(()),326 _ => None,327 }328 }329 pub fn as_str(&self) -> Option<IStr> {330 match self {331 Self::Str(s) => Some(s.clone().into_flat()),332 _ => None,333 }334 }335 pub const fn as_num(&self) -> Option<f64> {336 match self {337 Self::Num(n) => Some(*n),338 _ => None,339 }340 }341 pub fn as_arr(&self) -> Option<ArrValue> {342 match self {343 Self::Arr(a) => Some(a.clone()),344 _ => None,345 }346 }347 pub fn as_obj(&self) -> Option<ObjValue> {348 match self {349 Self::Obj(o) => Some(o.clone()),350 _ => None,351 }352 }353 pub fn as_func(&self) -> Option<FuncVal> {354 match self {355 Self::Func(f) => Some(f.clone()),356 _ => None,357 }358 }359360 361 362 pub fn new_checked_num(num: f64) -> Result<Self> {363 if num.is_finite() {364 Ok(Self::Num(num))365 } else {366 throw!("overflow")367 }368 }369370 pub const fn value_type(&self) -> ValType {371 match self {372 Self::Str(..) => ValType::Str,373 Self::Num(..) => ValType::Num,374 Self::Arr(..) => ValType::Arr,375 Self::Obj(..) => ValType::Obj,376 Self::Bool(_) => ValType::Bool,377 Self::Null => ValType::Null,378 Self::Func(..) => ValType::Func,379 }380 }381382 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {383 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {384 manifest.manifest(val.clone())385 }386 manifest_dyn(self, &format)387 }388389 pub fn to_string(&self) -> Result<IStr> {390 Ok(match self {391 Self::Bool(true) => "true".into(),392 Self::Bool(false) => "false".into(),393 Self::Null => "null".into(),394 Self::Str(s) => s.clone().into_flat(),395 _ => self.manifest(ToStringFormat).map(IStr::from)?,396 })397 }398399 pub fn into_indexable(self) -> Result<IndexableVal> {400 Ok(match self {401 Val::Str(s) => IndexableVal::Str(s.into_flat()),402 Val::Arr(arr) => IndexableVal::Arr(arr),403 _ => throw!(ValueIsNotIndexable(self.value_type())),404 })405 }406}407408const fn is_function_like(val: &Val) -> bool {409 matches!(val, Val::Func(_))410}411412413pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {414 Ok(match (val_a, val_b) {415 (Val::Bool(a), Val::Bool(b)) => a == b,416 (Val::Null, Val::Null) => true,417 (Val::Str(a), Val::Str(b)) => a == b,418 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,419 (Val::Arr(_), Val::Arr(_)) => {420 throw!("primitiveEquals operates on primitive types, got array")421 }422 (Val::Obj(_), Val::Obj(_)) => {423 throw!("primitiveEquals operates on primitive types, got object")424 }425 (a, b) if is_function_like(a) && is_function_like(b) => {426 throw!("cannot test equality of functions")427 }428 (_, _) => false,429 })430}431432433pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {434 if val_a.value_type() != val_b.value_type() {435 return Ok(false);436 }437 match (val_a, val_b) {438 (Val::Arr(a), Val::Arr(b)) => {439 if ArrValue::ptr_eq(a, b) {440 return Ok(true);441 }442 if a.len() != b.len() {443 return Ok(false);444 }445 for (a, b) in a.iter().zip(b.iter()) {446 if !equals(&a?, &b?)? {447 return Ok(false);448 }449 }450 Ok(true)451 }452 (Val::Obj(a), Val::Obj(b)) => {453 if ObjValue::ptr_eq(a, b) {454 return Ok(true);455 }456 let fields = a.fields(457 #[cfg(feature = "exp-preserve-order")]458 false,459 );460 if fields461 != b.fields(462 #[cfg(feature = "exp-preserve-order")]463 false,464 ) {465 return Ok(false);466 }467 for field in fields {468 if !equals(469 &a.get(field.clone())?.expect("field exists"),470 &b.get(field)?.expect("field exists"),471 )? {472 return Ok(false);473 }474 }475 Ok(true)476 }477 (a, b) => Ok(primitive_equals(a, b)?),478 }479}