git.delta.rocks / jrsonnet / refs/commits / 62ffdd8c4879

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs8.9 KiBsourcehistory
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}129130/// Represents a Jsonnet value, which can be spliced or indexed (string or array).131#[allow(clippy::module_name_repetitions)]132pub enum IndexableVal {133	/// String.134	Str(IStr),135	/// Array.136	Arr(ArrValue),137}138impl IndexableVal {139	/// Slice the value.140	///141	/// # Implementation142	///143	/// For strings, will create a copy of specified interval.144	///145	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.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}189190/// Represents any valid Jsonnet value.191#[derive(Debug, Clone, Trace)]192pub enum Val {193	/// Represents a Jsonnet boolean.194	Bool(bool),195	/// Represents a Jsonnet null value.196	Null,197	/// Represents a Jsonnet string.198	Str(IStr),199	/// Represents a Jsonnet number.200	/// Should be finite, and not NaN201	/// This restriction isn't enforced by enum, as enum field can't be marked as private202	Num(f64),203	/// Represents a Jsonnet array.204	Arr(ArrValue),205	/// Represents a Jsonnet object.206	Obj(ObjValue),207	/// Represents a Jsonnet function.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	/// Creates `Val::Num` after checking for numeric overflow.265	/// As numbers are `f64`, we can just check for their finity.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}315316/// Native implementation of `std.primitiveEquals`317pub 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}335336/// Native implementation of `std.equals`337pub 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}