git.delta.rocks / jrsonnet / refs/commits / dad6c32364f5

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs13.1 KiBsourcehistory
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, ArrayLike};13use crate::{14	bail,15	error::{Error, ErrorKind::*},16	function::FuncVal,17	gc::{GcHashMap, TraceBox},18	manifest::{ManifestFormat, ToStringFormat},19	tb,20	typed::BoundedUsize,21	ObjValue, Result, Unbound, WeakObjValue,22};2324pub trait ThunkValue: Trace {25	type Output;26	fn get(self: Box<Self>) -> Result<Self::Output>;27}2829#[derive(Trace)]30enum ThunkInner<T: Trace> {31	Computed(T),32	Errored(Error),33	Waiting(TraceBox<dyn ThunkValue<Output = T>>),34	Pending,35}3637/// Lazily evaluated value38#[allow(clippy::module_name_repetitions)]39#[derive(Clone, Trace)]40pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4142impl<T: Trace> Thunk<T> {43	pub fn evaluated(val: T) -> Self {44		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))45	}46	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {47		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))48	}49	pub fn errored(e: Error) -> Self {50		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))51	}52	pub fn result(res: Result<T, Error>) -> Self {53		match res {54			Ok(o) => Self::evaluated(o),55			Err(e) => Self::errored(e),56		}57	}58}5960impl<T> Thunk<T>61where62	T: Clone + Trace,63{64	pub fn force(&self) -> Result<()> {65		self.evaluate()?;66		Ok(())67	}6869	/// Evaluate thunk, or return cached value70	///71	/// # Errors72	///73	/// - Lazy value evaluation returned error74	/// - This method was called during inner value evaluation75	pub fn evaluate(&self) -> Result<T> {76		match &*self.0.borrow() {77			ThunkInner::Computed(v) => return Ok(v.clone()),78			ThunkInner::Errored(e) => return Err(e.clone()),79			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),80			ThunkInner::Waiting(..) => (),81		};82		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)83		else {84			unreachable!();85		};86		let new_value = match value.0.get() {87			Ok(v) => v,88			Err(e) => {89				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());90				return Err(e);91			}92		};93		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());94		Ok(new_value)95	}96}9798pub trait ThunkMapper<Input>: Trace {99	type Output;100	fn map(self, from: Input) -> Result<Self::Output>;101}102impl<Input> Thunk<Input>103where104	Input: Trace + Clone,105{106	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>107	where108		M: ThunkMapper<Input>,109		M::Output: Trace,110	{111		#[derive(Trace)]112		struct Mapped<Input: Trace, Mapper: Trace> {113			inner: Thunk<Input>,114			mapper: Mapper,115		}116		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>117		where118			Input: Trace + Clone,119			Mapper: ThunkMapper<Input>,120		{121			type Output = Mapper::Output;122123			fn get(self: Box<Self>) -> Result<Self::Output> {124				let value = self.inner.evaluate()?;125				let mapped = self.mapper.map(value)?;126				Ok(mapped)127			}128		}129130		Thunk::new(Mapped::<Input, M> {131			inner: self,132			mapper,133		})134	}135}136137impl<T: Trace> From<Result<T>> for Thunk<T> {138	fn from(value: Result<T>) -> Self {139		match value {140			Ok(o) => Self::evaluated(o),141			Err(e) => Self::errored(e),142		}143	}144}145146impl<T: Trace + Default> Default for Thunk<T> {147	fn default() -> Self {148		Self::evaluated(T::default())149	}150}151152type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);153154#[derive(Trace, Clone)]155pub struct CachedUnbound<I, T>156where157	I: Unbound<Bound = T>,158	T: Trace,159{160	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,161	value: I,162}163impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {164	pub fn new(value: I) -> Self {165		Self {166			cache: Cc::new(RefCell::new(GcHashMap::new())),167			value,168		}169	}170}171impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {172	type Bound = T;173	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {174		let cache_key = (175			sup.as_ref().map(|s| s.clone().downgrade()),176			this.as_ref().map(|t| t.clone().downgrade()),177		);178		{179			if let Some(t) = self.cache.borrow().get(&cache_key) {180				return Ok(t.clone());181			}182		}183		let bound = self.value.bind(sup, this)?;184185		{186			let mut cache = self.cache.borrow_mut();187			cache.insert(cache_key, bound.clone());188		}189190		Ok(bound)191	}192}193194impl<T: Debug + Trace> Debug for Thunk<T> {195	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {196		write!(f, "Lazy")197	}198}199impl<T: Trace> PartialEq for Thunk<T> {200	fn eq(&self, other: &Self) -> bool {201		Cc::ptr_eq(&self.0, &other.0)202	}203}204205/// Represents a Jsonnet value, which can be sliced or indexed (string or array).206#[allow(clippy::module_name_repetitions)]207pub enum IndexableVal {208	/// String.209	Str(IStr),210	/// Array.211	Arr(ArrValue),212}213impl IndexableVal {214	pub fn to_array(self) -> ArrValue {215		match self {216			IndexableVal::Str(s) => ArrValue::chars(s.chars()),217			IndexableVal::Arr(arr) => arr,218		}219	}220	/// Slice the value.221	///222	/// # Implementation223	///224	/// For strings, will create a copy of specified interval.225	///226	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.227	pub fn slice(228		self,229		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,230		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,231		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,232	) -> Result<Self> {233		match &self {234			IndexableVal::Str(s) => {235				let index = index.as_deref().copied().unwrap_or(0);236				let end = end.as_deref().copied().unwrap_or(usize::MAX);237				let step = step.as_deref().copied().unwrap_or(1);238239				if index >= end {240					return Ok(Self::Str("".into()));241				}242243				Ok(Self::Str(244					(s.chars()245						.skip(index)246						.take(end - index)247						.step_by(step)248						.collect::<String>())249					.into(),250				))251			}252			IndexableVal::Arr(arr) => {253				let index = index.as_deref().copied().unwrap_or(0);254				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());255				let step = step.as_deref().copied().unwrap_or(1);256257				if index >= end {258					return Ok(Self::Arr(ArrValue::empty()));259				}260261				Ok(Self::Arr(262					arr.clone()263						.slice(Some(index), Some(end), Some(step))264						.expect("arguments checked"),265				))266			}267		}268	}269}270271#[derive(Debug, Clone, Trace)]272pub enum StrValue {273	Flat(IStr),274	Tree(Rc<(StrValue, StrValue, usize)>),275}276impl StrValue {277	pub fn concat(a: StrValue, b: StrValue) -> Self {278		// TODO: benchmark for an optimal value, currently just a arbitrary choice279		const STRING_EXTEND_THRESHOLD: usize = 100;280281		if a.is_empty() {282			b283		} else if b.is_empty() {284			a285		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {286			Self::Flat(format!("{a}{b}").into())287		} else {288			let len = a.len() + b.len();289			Self::Tree(Rc::new((a, b, len)))290		}291	}292	pub fn into_flat(self) -> IStr {293		#[cold]294		fn write_buf(s: &StrValue, out: &mut String) {295			match s {296				StrValue::Flat(f) => out.push_str(f),297				StrValue::Tree(t) => {298					write_buf(&t.0, out);299					write_buf(&t.1, out);300				}301			}302		}303		match self {304			StrValue::Flat(f) => f,305			StrValue::Tree(_) => {306				let mut buf = String::with_capacity(self.len());307				write_buf(&self, &mut buf);308				buf.into()309			}310		}311	}312	pub fn len(&self) -> usize {313		match self {314			StrValue::Flat(v) => v.len(),315			StrValue::Tree(t) => t.2,316		}317	}318	pub fn is_empty(&self) -> bool {319		match self {320			Self::Flat(v) => v.is_empty(),321			// Can't create non-flat empty string322			Self::Tree(_) => false,323		}324	}325}326impl From<&str> for StrValue {327	fn from(value: &str) -> Self {328		Self::Flat(value.into())329	}330}331impl From<String> for StrValue {332	fn from(value: String) -> Self {333		Self::Flat(value.into())334	}335}336impl From<IStr> for StrValue {337	fn from(value: IStr) -> Self {338		Self::Flat(value)339	}340}341impl Display for StrValue {342	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {343		match self {344			StrValue::Flat(v) => write!(f, "{v}"),345			StrValue::Tree(t) => {346				write!(f, "{}", t.0)?;347				write!(f, "{}", t.1)348			}349		}350	}351}352impl PartialEq for StrValue {353	fn eq(&self, other: &Self) -> bool {354		let a = self.clone().into_flat();355		let b = other.clone().into_flat();356		a == b357	}358}359impl Eq for StrValue {}360impl PartialOrd for StrValue {361	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {362		Some(self.cmp(other))363	}364}365impl Ord for StrValue {366	fn cmp(&self, other: &Self) -> std::cmp::Ordering {367		let a = self.clone().into_flat();368		let b = other.clone().into_flat();369		a.cmp(&b)370	}371}372373/// Represents any valid Jsonnet value.374#[derive(Debug, Clone, Trace, Default)]375pub enum Val {376	/// Represents a Jsonnet boolean.377	Bool(bool),378	/// Represents a Jsonnet null value.379	#[default]380	Null,381	/// Represents a Jsonnet string.382	Str(StrValue),383	/// Represents a Jsonnet number.384	/// Should be finite, and not NaN385	/// This restriction isn't enforced by enum, as enum field can't be marked as private386	Num(f64),387	/// Experimental bigint388	#[cfg(feature = "exp-bigint")]389	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),390	/// Represents a Jsonnet array.391	Arr(ArrValue),392	/// Represents a Jsonnet object.393	Obj(ObjValue),394	/// Represents a Jsonnet function.395	Func(FuncVal),396}397398#[cfg(target_pointer_width = "64")]399static_assertions::assert_eq_size!(Val, [u8; 24]);400401impl From<IndexableVal> for Val {402	fn from(v: IndexableVal) -> Self {403		match v {404			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),405			IndexableVal::Arr(a) => Self::Arr(a),406		}407	}408}409410impl Val {411	pub const fn as_bool(&self) -> Option<bool> {412		match self {413			Self::Bool(v) => Some(*v),414			_ => None,415		}416	}417	pub const fn as_null(&self) -> Option<()> {418		match self {419			Self::Null => Some(()),420			_ => None,421		}422	}423	pub fn as_str(&self) -> Option<IStr> {424		match self {425			Self::Str(s) => Some(s.clone().into_flat()),426			_ => None,427		}428	}429	pub const fn as_num(&self) -> Option<f64> {430		match self {431			Self::Num(n) => Some(*n),432			_ => None,433		}434	}435	pub fn as_arr(&self) -> Option<ArrValue> {436		match self {437			Self::Arr(a) => Some(a.clone()),438			_ => None,439		}440	}441	pub fn as_obj(&self) -> Option<ObjValue> {442		match self {443			Self::Obj(o) => Some(o.clone()),444			_ => None,445		}446	}447	pub fn as_func(&self) -> Option<FuncVal> {448		match self {449			Self::Func(f) => Some(f.clone()),450			_ => None,451		}452	}453454	/// Creates `Val::Num` after checking for numeric overflow.455	/// As numbers are `f64`, we can just check for their finity.456	pub fn new_checked_num(num: f64) -> Result<Self> {457		if num.is_finite() {458			Ok(Self::Num(num))459		} else {460			bail!("overflow")461		}462	}463464	pub const fn value_type(&self) -> ValType {465		match self {466			Self::Str(..) => ValType::Str,467			Self::Num(..) => ValType::Num,468			#[cfg(feature = "exp-bigint")]469			Self::BigInt(..) => ValType::BigInt,470			Self::Arr(..) => ValType::Arr,471			Self::Obj(..) => ValType::Obj,472			Self::Bool(_) => ValType::Bool,473			Self::Null => ValType::Null,474			Self::Func(..) => ValType::Func,475		}476	}477478	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {479		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {480			manifest.manifest(val.clone())481		}482		manifest_dyn(self, &format)483	}484485	pub fn to_string(&self) -> Result<IStr> {486		Ok(match self {487			Self::Bool(true) => "true".into(),488			Self::Bool(false) => "false".into(),489			Self::Null => "null".into(),490			Self::Str(s) => s.clone().into_flat(),491			_ => self.manifest(ToStringFormat).map(IStr::from)?,492		})493	}494495	pub fn into_indexable(self) -> Result<IndexableVal> {496		Ok(match self {497			Val::Str(s) => IndexableVal::Str(s.into_flat()),498			Val::Arr(arr) => IndexableVal::Arr(arr),499			_ => bail!(ValueIsNotIndexable(self.value_type())),500		})501	}502}503504const fn is_function_like(val: &Val) -> bool {505	matches!(val, Val::Func(_))506}507508/// Native implementation of `std.primitiveEquals`509pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {510	Ok(match (val_a, val_b) {511		(Val::Bool(a), Val::Bool(b)) => a == b,512		(Val::Null, Val::Null) => true,513		(Val::Str(a), Val::Str(b)) => a == b,514		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,515		#[cfg(feature = "exp-bigint")]516		(Val::BigInt(a), Val::BigInt(b)) => a == b,517		(Val::Arr(_), Val::Arr(_)) => {518			bail!("primitiveEquals operates on primitive types, got array")519		}520		(Val::Obj(_), Val::Obj(_)) => {521			bail!("primitiveEquals operates on primitive types, got object")522		}523		(a, b) if is_function_like(a) && is_function_like(b) => {524			bail!("cannot test equality of functions")525		}526		(_, _) => false,527	})528}529530/// Native implementation of `std.equals`531pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {532	if val_a.value_type() != val_b.value_type() {533		return Ok(false);534	}535	match (val_a, val_b) {536		(Val::Arr(a), Val::Arr(b)) => {537			if ArrValue::ptr_eq(a, b) {538				return Ok(true);539			}540			if a.len() != b.len() {541				return Ok(false);542			}543			for (a, b) in a.iter().zip(b.iter()) {544				if !equals(&a?, &b?)? {545					return Ok(false);546				}547			}548			Ok(true)549		}550		(Val::Obj(a), Val::Obj(b)) => {551			if ObjValue::ptr_eq(a, b) {552				return Ok(true);553			}554			let fields = a.fields(555				#[cfg(feature = "exp-preserve-order")]556				false,557			);558			if fields559				!= b.fields(560					#[cfg(feature = "exp-preserve-order")]561					false,562				) {563				return Ok(false);564			}565			for field in fields {566				if !equals(567					&a.get(field.clone())?.expect("field exists"),568					&b.get(field)?.expect("field exists"),569				)? {570					return Ok(false);571				}572			}573			Ok(true)574		}575		(a, b) => Ok(primitive_equals(a, b)?),576	}577}