difftreelog
feat impl Default for Thunk
in: master
1 file changed
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use 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/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42 pub fn evaluated(val: T) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44 }45 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47 }48 pub fn errored(e: Error) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50 }51}5253impl<T> Thunk<T>54where55 T: Clone + Trace,56{57 pub fn force(&self) -> Result<()> {58 self.evaluate()?;59 Ok(())60 }6162 /// Evaluate thunk, or return cached value63 ///64 /// # Errors65 ///66 /// - Lazy value evaluation returned error67 /// - This method was called during inner value evaluation68 pub fn evaluate(&self) -> Result<T> {69 match &*self.0.borrow() {70 ThunkInner::Computed(v) => return Ok(v.clone()),71 ThunkInner::Errored(e) => return Err(e.clone()),72 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73 ThunkInner::Waiting(..) => (),74 };75 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)76 else {77 unreachable!();78 };79 let new_value = match value.0.get() {80 Ok(v) => v,81 Err(e) => {82 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());83 return Err(e);84 }85 };86 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());87 Ok(new_value)88 }89}9091pub trait ThunkMapper<Input>: Trace {92 type Output;93 fn map(self, from: Input) -> Result<Self::Output>;94}95impl<Input> Thunk<Input>96where97 Input: Trace + Clone,98{99 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>100 where101 M: ThunkMapper<Input>,102 M::Output: Trace,103 {104 #[derive(Trace)]105 struct Mapped<Input: Trace, Mapper: Trace> {106 inner: Thunk<Input>,107 mapper: Mapper,108 }109 impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>110 where111 Input: Trace + Clone,112 Mapper: ThunkMapper<Input>,113 {114 type Output = Mapper::Output;115116 fn get(self: Box<Self>) -> Result<Self::Output> {117 let value = self.inner.evaluate()?;118 let mapped = self.mapper.map(value)?;119 Ok(mapped)120 }121 }122123 Thunk::new(Mapped::<Input, M> {124 inner: self,125 mapper,126 })127 }128}129130impl<T: Trace> From<Result<T>> for Thunk<T> {131 fn from(value: Result<T>) -> Self {132 match value {133 Ok(o) => Self::evaluated(o),134 Err(e) => Self::errored(e),135 }136 }137}138139type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);140141#[derive(Trace, Clone)]142pub struct CachedUnbound<I, T>143where144 I: Unbound<Bound = T>,145 T: Trace,146{147 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,148 value: I,149}150impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {151 pub fn new(value: I) -> Self {152 Self {153 cache: Cc::new(RefCell::new(GcHashMap::new())),154 value,155 }156 }157}158impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {159 type Bound = T;160 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {161 let cache_key = (162 sup.as_ref().map(|s| s.clone().downgrade()),163 this.as_ref().map(|t| t.clone().downgrade()),164 );165 {166 if let Some(t) = self.cache.borrow().get(&cache_key) {167 return Ok(t.clone());168 }169 }170 let bound = self.value.bind(sup, this)?;171172 {173 let mut cache = self.cache.borrow_mut();174 cache.insert(cache_key, bound.clone());175 }176177 Ok(bound)178 }179}180181impl<T: Debug + Trace> Debug for Thunk<T> {182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {183 write!(f, "Lazy")184 }185}186impl<T: Trace> PartialEq for Thunk<T> {187 fn eq(&self, other: &Self) -> bool {188 Cc::ptr_eq(&self.0, &other.0)189 }190}191192/// Represents a Jsonnet value, which can be sliced or indexed (string or array).193#[allow(clippy::module_name_repetitions)]194pub enum IndexableVal {195 /// String.196 Str(IStr),197 /// Array.198 Arr(ArrValue),199}200impl IndexableVal {201 pub fn to_array(self) -> ArrValue {202 match self {203 IndexableVal::Str(s) => ArrValue::chars(s.chars()),204 IndexableVal::Arr(arr) => arr,205 }206 }207 /// Slice the value.208 ///209 /// # Implementation210 ///211 /// For strings, will create a copy of specified interval.212 ///213 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.214 pub fn slice(215 self,216 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,217 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,218 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,219 ) -> Result<Self> {220 match &self {221 IndexableVal::Str(s) => {222 let index = index.as_deref().copied().unwrap_or(0);223 let end = end.as_deref().copied().unwrap_or(usize::MAX);224 let step = step.as_deref().copied().unwrap_or(1);225226 if index >= end {227 return Ok(Self::Str("".into()));228 }229230 Ok(Self::Str(231 (s.chars()232 .skip(index)233 .take(end - index)234 .step_by(step)235 .collect::<String>())236 .into(),237 ))238 }239 IndexableVal::Arr(arr) => {240 let index = index.as_deref().copied().unwrap_or(0);241 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());242 let step = step.as_deref().copied().unwrap_or(1);243244 if index >= end {245 return Ok(Self::Arr(ArrValue::empty()));246 }247248 Ok(Self::Arr(249 arr.clone()250 .slice(Some(index), Some(end), Some(step))251 .expect("arguments checked"),252 ))253 }254 }255 }256}257258#[derive(Debug, Clone, Trace)]259pub enum StrValue {260 Flat(IStr),261 Tree(Rc<(StrValue, StrValue, usize)>),262}263impl StrValue {264 pub fn concat(a: StrValue, b: StrValue) -> Self {265 // TODO: benchmark for an optimal value, currently just a arbitrary choice266 const STRING_EXTEND_THRESHOLD: usize = 100;267268 if a.is_empty() {269 b270 } else if b.is_empty() {271 a272 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {273 Self::Flat(format!("{a}{b}").into())274 } else {275 let len = a.len() + b.len();276 Self::Tree(Rc::new((a, b, len)))277 }278 }279 pub fn into_flat(self) -> IStr {280 #[cold]281 fn write_buf(s: &StrValue, out: &mut String) {282 match s {283 StrValue::Flat(f) => out.push_str(f),284 StrValue::Tree(t) => {285 write_buf(&t.0, out);286 write_buf(&t.1, out);287 }288 }289 }290 match self {291 StrValue::Flat(f) => f,292 StrValue::Tree(_) => {293 let mut buf = String::with_capacity(self.len());294 write_buf(&self, &mut buf);295 buf.into()296 }297 }298 }299 pub fn len(&self) -> usize {300 match self {301 StrValue::Flat(v) => v.len(),302 StrValue::Tree(t) => t.2,303 }304 }305 pub fn is_empty(&self) -> bool {306 match self {307 Self::Flat(v) => v.is_empty(),308 // Can't create non-flat empty string309 Self::Tree(_) => false,310 }311 }312}313impl From<&str> for StrValue {314 fn from(value: &str) -> Self {315 Self::Flat(value.into())316 }317}318impl From<String> for StrValue {319 fn from(value: String) -> Self {320 Self::Flat(value.into())321 }322}323impl From<IStr> for StrValue {324 fn from(value: IStr) -> Self {325 Self::Flat(value)326 }327}328impl Display for StrValue {329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {330 match self {331 StrValue::Flat(v) => write!(f, "{v}"),332 StrValue::Tree(t) => {333 write!(f, "{}", t.0)?;334 write!(f, "{}", t.1)335 }336 }337 }338}339impl PartialEq for StrValue {340 fn eq(&self, other: &Self) -> bool {341 let a = self.clone().into_flat();342 let b = other.clone().into_flat();343 a == b344 }345}346impl Eq for StrValue {}347impl PartialOrd for StrValue {348 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {349 Some(self.cmp(other))350 }351}352impl Ord for StrValue {353 fn cmp(&self, other: &Self) -> std::cmp::Ordering {354 let a = self.clone().into_flat();355 let b = other.clone().into_flat();356 a.cmp(&b)357 }358}359360/// Represents any valid Jsonnet value.361#[derive(Debug, Clone, Trace)]362pub enum Val {363 /// Represents a Jsonnet boolean.364 Bool(bool),365 /// Represents a Jsonnet null value.366 Null,367 /// Represents a Jsonnet string.368 Str(StrValue),369 /// Represents a Jsonnet number.370 /// Should be finite, and not NaN371 /// This restriction isn't enforced by enum, as enum field can't be marked as private372 Num(f64),373 /// Experimental bigint374 #[cfg(feature = "exp-bigint")]375 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),376 /// Represents a Jsonnet array.377 Arr(ArrValue),378 /// Represents a Jsonnet object.379 Obj(ObjValue),380 /// Represents a Jsonnet function.381 Func(FuncVal),382}383384#[cfg(target_pointer_width = "64")]385static_assertions::assert_eq_size!(Val, [u8; 24]);386387impl From<IndexableVal> for Val {388 fn from(v: IndexableVal) -> Self {389 match v {390 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),391 IndexableVal::Arr(a) => Self::Arr(a),392 }393 }394}395396impl Val {397 pub const fn as_bool(&self) -> Option<bool> {398 match self {399 Self::Bool(v) => Some(*v),400 _ => None,401 }402 }403 pub const fn as_null(&self) -> Option<()> {404 match self {405 Self::Null => Some(()),406 _ => None,407 }408 }409 pub fn as_str(&self) -> Option<IStr> {410 match self {411 Self::Str(s) => Some(s.clone().into_flat()),412 _ => None,413 }414 }415 pub const fn as_num(&self) -> Option<f64> {416 match self {417 Self::Num(n) => Some(*n),418 _ => None,419 }420 }421 pub fn as_arr(&self) -> Option<ArrValue> {422 match self {423 Self::Arr(a) => Some(a.clone()),424 _ => None,425 }426 }427 pub fn as_obj(&self) -> Option<ObjValue> {428 match self {429 Self::Obj(o) => Some(o.clone()),430 _ => None,431 }432 }433 pub fn as_func(&self) -> Option<FuncVal> {434 match self {435 Self::Func(f) => Some(f.clone()),436 _ => None,437 }438 }439440 /// Creates `Val::Num` after checking for numeric overflow.441 /// As numbers are `f64`, we can just check for their finity.442 pub fn new_checked_num(num: f64) -> Result<Self> {443 if num.is_finite() {444 Ok(Self::Num(num))445 } else {446 throw!("overflow")447 }448 }449450 pub const fn value_type(&self) -> ValType {451 match self {452 Self::Str(..) => ValType::Str,453 Self::Num(..) => ValType::Num,454 #[cfg(feature = "exp-bigint")]455 Self::BigInt(..) => ValType::BigInt,456 Self::Arr(..) => ValType::Arr,457 Self::Obj(..) => ValType::Obj,458 Self::Bool(_) => ValType::Bool,459 Self::Null => ValType::Null,460 Self::Func(..) => ValType::Func,461 }462 }463464 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {465 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {466 manifest.manifest(val.clone())467 }468 manifest_dyn(self, &format)469 }470471 pub fn to_string(&self) -> Result<IStr> {472 Ok(match self {473 Self::Bool(true) => "true".into(),474 Self::Bool(false) => "false".into(),475 Self::Null => "null".into(),476 Self::Str(s) => s.clone().into_flat(),477 _ => self.manifest(ToStringFormat).map(IStr::from)?,478 })479 }480481 pub fn into_indexable(self) -> Result<IndexableVal> {482 Ok(match self {483 Val::Str(s) => IndexableVal::Str(s.into_flat()),484 Val::Arr(arr) => IndexableVal::Arr(arr),485 _ => throw!(ValueIsNotIndexable(self.value_type())),486 })487 }488}489490const fn is_function_like(val: &Val) -> bool {491 matches!(val, Val::Func(_))492}493494/// Native implementation of `std.primitiveEquals`495pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {496 Ok(match (val_a, val_b) {497 (Val::Bool(a), Val::Bool(b)) => a == b,498 (Val::Null, Val::Null) => true,499 (Val::Str(a), Val::Str(b)) => a == b,500 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,501 #[cfg(feature = "exp-bigint")]502 (Val::BigInt(a), Val::BigInt(b)) => a == b,503 (Val::Arr(_), Val::Arr(_)) => {504 throw!("primitiveEquals operates on primitive types, got array")505 }506 (Val::Obj(_), Val::Obj(_)) => {507 throw!("primitiveEquals operates on primitive types, got object")508 }509 (a, b) if is_function_like(a) && is_function_like(b) => {510 throw!("cannot test equality of functions")511 }512 (_, _) => false,513 })514}515516/// Native implementation of `std.equals`517pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {518 if val_a.value_type() != val_b.value_type() {519 return Ok(false);520 }521 match (val_a, val_b) {522 (Val::Arr(a), Val::Arr(b)) => {523 if ArrValue::ptr_eq(a, b) {524 return Ok(true);525 }526 if a.len() != b.len() {527 return Ok(false);528 }529 for (a, b) in a.iter().zip(b.iter()) {530 if !equals(&a?, &b?)? {531 return Ok(false);532 }533 }534 Ok(true)535 }536 (Val::Obj(a), Val::Obj(b)) => {537 if ObjValue::ptr_eq(a, b) {538 return Ok(true);539 }540 let fields = a.fields(541 #[cfg(feature = "exp-preserve-order")]542 false,543 );544 if fields545 != b.fields(546 #[cfg(feature = "exp-preserve-order")]547 false,548 ) {549 return Ok(false);550 }551 for field in fields {552 if !equals(553 &a.get(field.clone())?.expect("field exists"),554 &b.get(field)?.expect("field exists"),555 )? {556 return Ok(false);557 }558 }559 Ok(true)560 }561 (a, b) => Ok(primitive_equals(a, b)?),562 }563}