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

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs13.9 KiBsourcehistory
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	num::NonZeroU32,6	rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::IStr;11use jrsonnet_types::ValType;1213pub use crate::arr::{ArrValue, ArrayLike};14use crate::{15	bail,16	error::{Error, ErrorKind::*},17	function::FuncVal,18	gc::{GcHashMap, TraceBox},19	manifest::{ManifestFormat, ToStringFormat},20	tb,21	typed::BoundedUsize,22	ObjValue, Result, Unbound, WeakObjValue,23};2425pub trait ThunkValue: Trace {26	type Output;27	fn get(self: Box<Self>) -> Result<Self::Output>;28}2930#[derive(Trace)]31enum ThunkInner<T: Trace> {32	Computed(T),33	Errored(Error),34	Waiting(TraceBox<dyn ThunkValue<Output = T>>),35	Pending,36}3738/// Lazily evaluated value39#[allow(clippy::module_name_repetitions)]40#[derive(Clone, Trace)]41pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4243impl<T: Trace> Thunk<T> {44	pub fn evaluated(val: T) -> Self {45		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))46	}47	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {48		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))49	}50	pub fn errored(e: Error) -> Self {51		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))52	}53	pub fn result(res: Result<T, Error>) -> Self {54		match res {55			Ok(o) => Self::evaluated(o),56			Err(e) => Self::errored(e),57		}58	}59}6061impl<T> Thunk<T>62where63	T: Clone + Trace,64{65	pub fn force(&self) -> Result<()> {66		self.evaluate()?;67		Ok(())68	}6970	/// Evaluate thunk, or return cached value71	///72	/// # Errors73	///74	/// - Lazy value evaluation returned error75	/// - This method was called during inner value evaluation76	pub fn evaluate(&self) -> Result<T> {77		match &*self.0.borrow() {78			ThunkInner::Computed(v) => return Ok(v.clone()),79			ThunkInner::Errored(e) => return Err(e.clone()),80			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),81			ThunkInner::Waiting(..) => (),82		};83		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)84		else {85			unreachable!();86		};87		let new_value = match value.0.get() {88			Ok(v) => v,89			Err(e) => {90				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());91				return Err(e);92			}93		};94		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());95		Ok(new_value)96	}97}9899pub trait ThunkMapper<Input>: Trace {100	type Output;101	fn map(self, from: Input) -> Result<Self::Output>;102}103impl<Input> Thunk<Input>104where105	Input: Trace + Clone,106{107	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>108	where109		M: ThunkMapper<Input>,110		M::Output: Trace,111	{112		#[derive(Trace)]113		struct Mapped<Input: Trace, Mapper: Trace> {114			inner: Thunk<Input>,115			mapper: Mapper,116		}117		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>118		where119			Input: Trace + Clone,120			Mapper: ThunkMapper<Input>,121		{122			type Output = Mapper::Output;123124			fn get(self: Box<Self>) -> Result<Self::Output> {125				let value = self.inner.evaluate()?;126				let mapped = self.mapper.map(value)?;127				Ok(mapped)128			}129		}130131		Thunk::new(Mapped::<Input, M> {132			inner: self,133			mapper,134		})135	}136}137138impl<T: Trace> From<Result<T>> for Thunk<T> {139	fn from(value: Result<T>) -> Self {140		match value {141			Ok(o) => Self::evaluated(o),142			Err(e) => Self::errored(e),143		}144	}145}146impl<T, V: Trace> From<T> for Thunk<V>147where148	T: ThunkValue<Output = V>,149{150	fn from(value: T) -> Self {151		Self::new(value)152	}153}154155impl<T: Trace + Default> Default for Thunk<T> {156	fn default() -> Self {157		Self::evaluated(T::default())158	}159}160161type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);162163#[derive(Trace, Clone)]164pub struct CachedUnbound<I, T>165where166	I: Unbound<Bound = T>,167	T: Trace,168{169	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,170	value: I,171}172impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {173	pub fn new(value: I) -> Self {174		Self {175			cache: Cc::new(RefCell::new(GcHashMap::new())),176			value,177		}178	}179}180impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {181	type Bound = T;182	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {183		let cache_key = (184			sup.as_ref().map(|s| s.clone().downgrade()),185			this.as_ref().map(|t| t.clone().downgrade()),186		);187		{188			if let Some(t) = self.cache.borrow().get(&cache_key) {189				return Ok(t.clone());190			}191		}192		let bound = self.value.bind(sup, this)?;193194		{195			let mut cache = self.cache.borrow_mut();196			cache.insert(cache_key, bound.clone());197		}198199		Ok(bound)200	}201}202203impl<T: Debug + Trace> Debug for Thunk<T> {204	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {205		write!(f, "Lazy")206	}207}208impl<T: Trace> PartialEq for Thunk<T> {209	fn eq(&self, other: &Self) -> bool {210		Cc::ptr_eq(&self.0, &other.0)211	}212}213214/// Represents a Jsonnet value, which can be sliced or indexed (string or array).215#[allow(clippy::module_name_repetitions)]216pub enum IndexableVal {217	/// String.218	Str(IStr),219	/// Array.220	Arr(ArrValue),221}222impl IndexableVal {223	pub fn is_empty(&self) -> bool {224		match self {225			Self::Str(s) => s.is_empty(),226			Self::Arr(s) => s.is_empty(),227		}228	}229230	pub fn to_array(self) -> ArrValue {231		match self {232			Self::Str(s) => ArrValue::chars(s.chars()),233			Self::Arr(arr) => arr,234		}235	}236	/// Slice the value.237	///238	/// # Implementation239	///240	/// For strings, will create a copy of specified interval.241	///242	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.243	pub fn slice(244		self,245		index: Option<i32>,246		end: Option<i32>,247		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,248	) -> Result<Self> {249		match &self {250			Self::Str(s) => {251				let mut computed_len = None;252				let mut get_len = || {253					computed_len.map_or_else(254						|| {255							let len = s.chars().count();256							let _ = computed_len.insert(len);257							len258						},259						|len| len,260					)261				};262				let mut get_idx = |pos: Option<i32>, default| {263					match pos {264						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),265						// No need to clamp, as iterator interface is used266						Some(v) => v as usize,267						None => default,268					}269				};270271				let index = get_idx(index, 0);272				let end = get_idx(end, usize::MAX);273				let step = step.as_deref().copied().unwrap_or(1);274275				if index >= end {276					return Ok(Self::Str("".into()));277				}278279				Ok(Self::Str(280					(s.chars()281						.skip(index)282						.take(end - index)283						.step_by(step)284						.collect::<String>())285					.into(),286				))287			}288			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(289				index,290				end,291				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),292			))),293		}294	}295}296297#[derive(Debug, Clone, Trace)]298pub enum StrValue {299	Flat(IStr),300	Tree(Rc<(StrValue, StrValue, usize)>),301}302impl StrValue {303	pub fn concat(a: Self, b: Self) -> Self {304		// TODO: benchmark for an optimal value, currently just a arbitrary choice305		const STRING_EXTEND_THRESHOLD: usize = 100;306307		if a.is_empty() {308			b309		} else if b.is_empty() {310			a311		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {312			Self::Flat(format!("{a}{b}").into())313		} else {314			let len = a.len() + b.len();315			Self::Tree(Rc::new((a, b, len)))316		}317	}318	pub fn into_flat(self) -> IStr {319		#[cold]320		fn write_buf(s: &StrValue, out: &mut String) {321			match s {322				StrValue::Flat(f) => out.push_str(f),323				StrValue::Tree(t) => {324					write_buf(&t.0, out);325					write_buf(&t.1, out);326				}327			}328		}329		match self {330			Self::Flat(f) => f,331			Self::Tree(_) => {332				let mut buf = String::with_capacity(self.len());333				write_buf(&self, &mut buf);334				buf.into()335			}336		}337	}338	pub fn len(&self) -> usize {339		match self {340			Self::Flat(v) => v.len(),341			Self::Tree(t) => t.2,342		}343	}344	pub fn is_empty(&self) -> bool {345		match self {346			Self::Flat(v) => v.is_empty(),347			// Can't create non-flat empty string348			Self::Tree(_) => false,349		}350	}351}352impl<T> From<T> for StrValue353where354	IStr: From<T>,355{356	fn from(value: T) -> Self {357		Self::Flat(IStr::from(value))358	}359}360impl Display for StrValue {361	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {362		match self {363			Self::Flat(v) => write!(f, "{v}"),364			Self::Tree(t) => {365				write!(f, "{}", t.0)?;366				write!(f, "{}", t.1)367			}368		}369	}370}371impl PartialEq for StrValue {372	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.373	#[allow(clippy::unconditional_recursion)]374	fn eq(&self, other: &Self) -> bool {375		let a = self.clone().into_flat();376		let b = other.clone().into_flat();377		a == b378	}379}380impl Eq for StrValue {}381impl PartialOrd for StrValue {382	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {383		Some(self.cmp(other))384	}385}386impl Ord for StrValue {387	fn cmp(&self, other: &Self) -> std::cmp::Ordering {388		let a = self.clone().into_flat();389		let b = other.clone().into_flat();390		a.cmp(&b)391	}392}393394/// Represents any valid Jsonnet value.395#[derive(Debug, Clone, Trace, Default)]396pub enum Val {397	/// Represents a Jsonnet boolean.398	Bool(bool),399	/// Represents a Jsonnet null value.400	#[default]401	Null,402	/// Represents a Jsonnet string.403	Str(StrValue),404	/// Represents a Jsonnet number.405	/// Should be finite, and not NaN406	/// This restriction isn't enforced by enum, as enum field can't be marked as private407	Num(f64),408	/// Experimental bigint409	#[cfg(feature = "exp-bigint")]410	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),411	/// Represents a Jsonnet array.412	Arr(ArrValue),413	/// Represents a Jsonnet object.414	Obj(ObjValue),415	/// Represents a Jsonnet function.416	Func(FuncVal),417}418419#[cfg(target_pointer_width = "64")]420static_assertions::assert_eq_size!(Val, [u8; 24]);421422impl From<IndexableVal> for Val {423	fn from(v: IndexableVal) -> Self {424		match v {425			IndexableVal::Str(s) => Self::string(s),426			IndexableVal::Arr(a) => Self::Arr(a),427		}428	}429}430431impl Val {432	pub const fn as_bool(&self) -> Option<bool> {433		match self {434			Self::Bool(v) => Some(*v),435			_ => None,436		}437	}438	pub const fn as_null(&self) -> Option<()> {439		match self {440			Self::Null => Some(()),441			_ => None,442		}443	}444	pub fn as_str(&self) -> Option<IStr> {445		match self {446			Self::Str(s) => Some(s.clone().into_flat()),447			_ => None,448		}449	}450	pub const fn as_num(&self) -> Option<f64> {451		match self {452			Self::Num(n) => Some(*n),453			_ => None,454		}455	}456	pub fn as_arr(&self) -> Option<ArrValue> {457		match self {458			Self::Arr(a) => Some(a.clone()),459			_ => None,460		}461	}462	pub fn as_obj(&self) -> Option<ObjValue> {463		match self {464			Self::Obj(o) => Some(o.clone()),465			_ => None,466		}467	}468	pub fn as_func(&self) -> Option<FuncVal> {469		match self {470			Self::Func(f) => Some(f.clone()),471			_ => None,472		}473	}474475	/// Creates `Val::Num` after checking for numeric overflow.476	/// As numbers are `f64`, we can just check for their finity.477	pub fn new_checked_num(num: f64) -> Result<Self> {478		if num.is_finite() {479			Ok(Self::Num(num))480		} else {481			bail!("overflow")482		}483	}484485	pub const fn value_type(&self) -> ValType {486		match self {487			Self::Str(..) => ValType::Str,488			Self::Num(..) => ValType::Num,489			#[cfg(feature = "exp-bigint")]490			Self::BigInt(..) => ValType::BigInt,491			Self::Arr(..) => ValType::Arr,492			Self::Obj(..) => ValType::Obj,493			Self::Bool(_) => ValType::Bool,494			Self::Null => ValType::Null,495			Self::Func(..) => ValType::Func,496		}497	}498499	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {500		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {501			manifest.manifest(val.clone())502		}503		manifest_dyn(self, &format)504	}505506	pub fn to_string(&self) -> Result<IStr> {507		Ok(match self {508			Self::Bool(true) => "true".into(),509			Self::Bool(false) => "false".into(),510			Self::Null => "null".into(),511			Self::Str(s) => s.clone().into_flat(),512			_ => self.manifest(ToStringFormat).map(IStr::from)?,513		})514	}515516	pub fn into_indexable(self) -> Result<IndexableVal> {517		Ok(match self {518			Self::Str(s) => IndexableVal::Str(s.into_flat()),519			Self::Arr(arr) => IndexableVal::Arr(arr),520			_ => bail!(ValueIsNotIndexable(self.value_type())),521		})522	}523524	pub fn function(function: impl Into<FuncVal>) -> Self {525		Self::Func(function.into())526	}527	pub fn string(string: impl Into<StrValue>) -> Self {528		Self::Str(string.into())529	}530}531532impl From<IStr> for Val {533	fn from(value: IStr) -> Self {534		Self::string(value)535	}536}537impl From<String> for Val {538	fn from(value: String) -> Self {539		Self::string(value)540	}541}542impl From<&str> for Val {543	fn from(value: &str) -> Self {544		Self::string(value)545	}546}547impl From<ObjValue> for Val {548	fn from(value: ObjValue) -> Self {549		Self::Obj(value)550	}551}552553const fn is_function_like(val: &Val) -> bool {554	matches!(val, Val::Func(_))555}556557/// Native implementation of `std.primitiveEquals`558pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {559	Ok(match (val_a, val_b) {560		(Val::Bool(a), Val::Bool(b)) => a == b,561		(Val::Null, Val::Null) => true,562		(Val::Str(a), Val::Str(b)) => a == b,563		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,564		#[cfg(feature = "exp-bigint")]565		(Val::BigInt(a), Val::BigInt(b)) => a == b,566		(Val::Arr(_), Val::Arr(_)) => {567			bail!("primitiveEquals operates on primitive types, got array")568		}569		(Val::Obj(_), Val::Obj(_)) => {570			bail!("primitiveEquals operates on primitive types, got object")571		}572		(a, b) if is_function_like(a) && is_function_like(b) => {573			bail!("cannot test equality of functions")574		}575		(_, _) => false,576	})577}578579/// Native implementation of `std.equals`580pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {581	if val_a.value_type() != val_b.value_type() {582		return Ok(false);583	}584	match (val_a, val_b) {585		(Val::Arr(a), Val::Arr(b)) => {586			if ArrValue::ptr_eq(a, b) {587				return Ok(true);588			}589			if a.len() != b.len() {590				return Ok(false);591			}592			for (a, b) in a.iter().zip(b.iter()) {593				if !equals(&a?, &b?)? {594					return Ok(false);595				}596			}597			Ok(true)598		}599		(Val::Obj(a), Val::Obj(b)) => {600			if ObjValue::ptr_eq(a, b) {601				return Ok(true);602			}603			let fields = a.fields(604				#[cfg(feature = "exp-preserve-order")]605				false,606			);607			if fields608				!= b.fields(609					#[cfg(feature = "exp-preserve-order")]610					false,611				) {612				return Ok(false);613			}614			for field in fields {615				if !equals(616					&a.get(field.clone())?.expect("field exists"),617					&b.get(field)?.expect("field exists"),618				)? {619					return Ok(false);620				}621			}622			Ok(true)623		}624		(a, b) => Ok(primitive_equals(a, b)?),625	}626}