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

difftreelog

source

crates/jrsonnet-evaluator/src/val.rs14.2 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}145impl<T, V: Trace> From<T> for Thunk<V>146where147	T: ThunkValue<Output = V>,148{149	fn from(value: T) -> Self {150		Thunk::new(value)151	}152}153154impl<T: Trace + Default> Default for Thunk<T> {155	fn default() -> Self {156		Self::evaluated(T::default())157	}158}159160type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);161162#[derive(Trace, Clone)]163pub struct CachedUnbound<I, T>164where165	I: Unbound<Bound = T>,166	T: Trace,167{168	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,169	value: I,170}171impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {172	pub fn new(value: I) -> Self {173		Self {174			cache: Cc::new(RefCell::new(GcHashMap::new())),175			value,176		}177	}178}179impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {180	type Bound = T;181	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {182		let cache_key = (183			sup.as_ref().map(|s| s.clone().downgrade()),184			this.as_ref().map(|t| t.clone().downgrade()),185		);186		{187			if let Some(t) = self.cache.borrow().get(&cache_key) {188				return Ok(t.clone());189			}190		}191		let bound = self.value.bind(sup, this)?;192193		{194			let mut cache = self.cache.borrow_mut();195			cache.insert(cache_key, bound.clone());196		}197198		Ok(bound)199	}200}201202impl<T: Debug + Trace> Debug for Thunk<T> {203	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {204		write!(f, "Lazy")205	}206}207impl<T: Trace> PartialEq for Thunk<T> {208	fn eq(&self, other: &Self) -> bool {209		Cc::ptr_eq(&self.0, &other.0)210	}211}212213/// Represents a Jsonnet value, which can be sliced or indexed (string or array).214#[allow(clippy::module_name_repetitions)]215pub enum IndexableVal {216	/// String.217	Str(IStr),218	/// Array.219	Arr(ArrValue),220}221impl IndexableVal {222	pub fn to_array(self) -> ArrValue {223		match self {224			IndexableVal::Str(s) => ArrValue::chars(s.chars()),225			IndexableVal::Arr(arr) => arr,226		}227	}228	/// Slice the value.229	///230	/// # Implementation231	///232	/// For strings, will create a copy of specified interval.233	///234	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.235	pub fn slice(236		self,237		index: Option<i32>,238		end: Option<i32>,239		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,240	) -> Result<Self> {241		match &self {242			IndexableVal::Str(s) => {243				let mut computed_len = None;244				let mut get_len = || {245					computed_len.map_or_else(246						|| {247							let len = s.chars().count();248							let _ = computed_len.insert(len);249							len250						},251						|len| len,252					)253				};254				let mut get_idx = |pos: Option<i32>, default| {255					match pos {256						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),257						// No need to clamp, as iterator interface is used258						Some(v) => v as usize,259						None => default,260					}261				};262263				let index = get_idx(index, 0);264				let end = get_idx(end, usize::MAX);265				let step = step.as_deref().copied().unwrap_or(1);266267				if index >= end {268					return Ok(Self::Str("".into()));269				}270271				Ok(Self::Str(272					(s.chars()273						.skip(index)274						.take(end - index)275						.step_by(step)276						.collect::<String>())277					.into(),278				))279			}280			IndexableVal::Arr(arr) => {281				let get_idx = |pos: Option<i32>, len: usize, default| match pos {282					Some(v) if v < 0 => len.saturating_sub((-v) as usize),283					Some(v) => (v as usize).min(len),284					None => default,285				};286				let index = get_idx(index, arr.len(), 0);287				let end = get_idx(end, arr.len(), arr.len());288				let step = step.as_deref().copied().unwrap_or(1);289290				if index >= end {291					return Ok(Self::Arr(ArrValue::empty()));292				}293294				Ok(Self::Arr(295					arr.clone()296						.slice(Some(index), Some(end), Some(step))297						.expect("arguments checked"),298				))299			}300		}301	}302}303304#[derive(Debug, Clone, Trace)]305pub enum StrValue {306	Flat(IStr),307	Tree(Rc<(StrValue, StrValue, usize)>),308}309impl StrValue {310	pub fn concat(a: StrValue, b: StrValue) -> Self {311		// TODO: benchmark for an optimal value, currently just a arbitrary choice312		const STRING_EXTEND_THRESHOLD: usize = 100;313314		if a.is_empty() {315			b316		} else if b.is_empty() {317			a318		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {319			Self::Flat(format!("{a}{b}").into())320		} else {321			let len = a.len() + b.len();322			Self::Tree(Rc::new((a, b, len)))323		}324	}325	pub fn into_flat(self) -> IStr {326		#[cold]327		fn write_buf(s: &StrValue, out: &mut String) {328			match s {329				StrValue::Flat(f) => out.push_str(f),330				StrValue::Tree(t) => {331					write_buf(&t.0, out);332					write_buf(&t.1, out);333				}334			}335		}336		match self {337			StrValue::Flat(f) => f,338			StrValue::Tree(_) => {339				let mut buf = String::with_capacity(self.len());340				write_buf(&self, &mut buf);341				buf.into()342			}343		}344	}345	pub fn len(&self) -> usize {346		match self {347			StrValue::Flat(v) => v.len(),348			StrValue::Tree(t) => t.2,349		}350	}351	pub fn is_empty(&self) -> bool {352		match self {353			Self::Flat(v) => v.is_empty(),354			// Can't create non-flat empty string355			Self::Tree(_) => false,356		}357	}358}359impl<T> From<T> for StrValue360where361	IStr: From<T>,362{363	fn from(value: T) -> Self {364		Self::Flat(IStr::from(value))365	}366}367impl Display for StrValue {368	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {369		match self {370			StrValue::Flat(v) => write!(f, "{v}"),371			StrValue::Tree(t) => {372				write!(f, "{}", t.0)?;373				write!(f, "{}", t.1)374			}375		}376	}377}378impl PartialEq for StrValue {379	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.380	#[allow(clippy::unconditional_recursion)]381	fn eq(&self, other: &Self) -> bool {382		let a = self.clone().into_flat();383		let b = other.clone().into_flat();384		a == b385	}386}387impl Eq for StrValue {}388impl PartialOrd for StrValue {389	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {390		Some(self.cmp(other))391	}392}393impl Ord for StrValue {394	fn cmp(&self, other: &Self) -> std::cmp::Ordering {395		let a = self.clone().into_flat();396		let b = other.clone().into_flat();397		a.cmp(&b)398	}399}400401/// Represents any valid Jsonnet value.402#[derive(Debug, Clone, Trace, Default)]403pub enum Val {404	/// Represents a Jsonnet boolean.405	Bool(bool),406	/// Represents a Jsonnet null value.407	#[default]408	Null,409	/// Represents a Jsonnet string.410	Str(StrValue),411	/// Represents a Jsonnet number.412	/// Should be finite, and not NaN413	/// This restriction isn't enforced by enum, as enum field can't be marked as private414	Num(f64),415	/// Experimental bigint416	#[cfg(feature = "exp-bigint")]417	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),418	/// Represents a Jsonnet array.419	Arr(ArrValue),420	/// Represents a Jsonnet object.421	Obj(ObjValue),422	/// Represents a Jsonnet function.423	Func(FuncVal),424}425426#[cfg(target_pointer_width = "64")]427static_assertions::assert_eq_size!(Val, [u8; 24]);428429impl From<IndexableVal> for Val {430	fn from(v: IndexableVal) -> Self {431		match v {432			IndexableVal::Str(s) => Self::string(s),433			IndexableVal::Arr(a) => Self::Arr(a),434		}435	}436}437438impl Val {439	pub const fn as_bool(&self) -> Option<bool> {440		match self {441			Self::Bool(v) => Some(*v),442			_ => None,443		}444	}445	pub const fn as_null(&self) -> Option<()> {446		match self {447			Self::Null => Some(()),448			_ => None,449		}450	}451	pub fn as_str(&self) -> Option<IStr> {452		match self {453			Self::Str(s) => Some(s.clone().into_flat()),454			_ => None,455		}456	}457	pub const fn as_num(&self) -> Option<f64> {458		match self {459			Self::Num(n) => Some(*n),460			_ => None,461		}462	}463	pub fn as_arr(&self) -> Option<ArrValue> {464		match self {465			Self::Arr(a) => Some(a.clone()),466			_ => None,467		}468	}469	pub fn as_obj(&self) -> Option<ObjValue> {470		match self {471			Self::Obj(o) => Some(o.clone()),472			_ => None,473		}474	}475	pub fn as_func(&self) -> Option<FuncVal> {476		match self {477			Self::Func(f) => Some(f.clone()),478			_ => None,479		}480	}481482	/// Creates `Val::Num` after checking for numeric overflow.483	/// As numbers are `f64`, we can just check for their finity.484	pub fn new_checked_num(num: f64) -> Result<Self> {485		if num.is_finite() {486			Ok(Self::Num(num))487		} else {488			bail!("overflow")489		}490	}491492	pub const fn value_type(&self) -> ValType {493		match self {494			Self::Str(..) => ValType::Str,495			Self::Num(..) => ValType::Num,496			#[cfg(feature = "exp-bigint")]497			Self::BigInt(..) => ValType::BigInt,498			Self::Arr(..) => ValType::Arr,499			Self::Obj(..) => ValType::Obj,500			Self::Bool(_) => ValType::Bool,501			Self::Null => ValType::Null,502			Self::Func(..) => ValType::Func,503		}504	}505506	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {507		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {508			manifest.manifest(val.clone())509		}510		manifest_dyn(self, &format)511	}512513	pub fn to_string(&self) -> Result<IStr> {514		Ok(match self {515			Self::Bool(true) => "true".into(),516			Self::Bool(false) => "false".into(),517			Self::Null => "null".into(),518			Self::Str(s) => s.clone().into_flat(),519			_ => self.manifest(ToStringFormat).map(IStr::from)?,520		})521	}522523	pub fn into_indexable(self) -> Result<IndexableVal> {524		Ok(match self {525			Val::Str(s) => IndexableVal::Str(s.into_flat()),526			Val::Arr(arr) => IndexableVal::Arr(arr),527			_ => bail!(ValueIsNotIndexable(self.value_type())),528		})529	}530531	pub fn function(function: impl Into<FuncVal>) -> Self {532		Self::Func(function.into())533	}534	pub fn string(string: impl Into<StrValue>) -> Self {535		Self::Str(string.into())536	}537}538539impl From<IStr> for Val {540	fn from(value: IStr) -> Self {541		Self::string(value)542	}543}544impl From<String> for Val {545	fn from(value: String) -> Self {546		Self::string(value)547	}548}549impl From<&str> for Val {550	fn from(value: &str) -> Self {551		Self::string(value)552	}553}554impl From<ObjValue> for Val {555	fn from(value: ObjValue) -> Self {556		Self::Obj(value)557	}558}559560const fn is_function_like(val: &Val) -> bool {561	matches!(val, Val::Func(_))562}563564/// Native implementation of `std.primitiveEquals`565pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {566	Ok(match (val_a, val_b) {567		(Val::Bool(a), Val::Bool(b)) => a == b,568		(Val::Null, Val::Null) => true,569		(Val::Str(a), Val::Str(b)) => a == b,570		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,571		#[cfg(feature = "exp-bigint")]572		(Val::BigInt(a), Val::BigInt(b)) => a == b,573		(Val::Arr(_), Val::Arr(_)) => {574			bail!("primitiveEquals operates on primitive types, got array")575		}576		(Val::Obj(_), Val::Obj(_)) => {577			bail!("primitiveEquals operates on primitive types, got object")578		}579		(a, b) if is_function_like(a) && is_function_like(b) => {580			bail!("cannot test equality of functions")581		}582		(_, _) => false,583	})584}585586/// Native implementation of `std.equals`587pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {588	if val_a.value_type() != val_b.value_type() {589		return Ok(false);590	}591	match (val_a, val_b) {592		(Val::Arr(a), Val::Arr(b)) => {593			if ArrValue::ptr_eq(a, b) {594				return Ok(true);595			}596			if a.len() != b.len() {597				return Ok(false);598			}599			for (a, b) in a.iter().zip(b.iter()) {600				if !equals(&a?, &b?)? {601					return Ok(false);602				}603			}604			Ok(true)605		}606		(Val::Obj(a), Val::Obj(b)) => {607			if ObjValue::ptr_eq(a, b) {608				return Ok(true);609			}610			let fields = a.fields(611				#[cfg(feature = "exp-preserve-order")]612				false,613			);614			if fields615				!= b.fields(616					#[cfg(feature = "exp-preserve-order")]617					false,618				) {619				return Ok(false);620			}621			for field in fields {622				if !equals(623					&a.get(field.clone())?.expect("field exists"),624					&b.get(field)?.expect("field exists"),625				)? {626					return Ok(false);627				}628			}629			Ok(true)630		}631		(a, b) => Ok(primitive_equals(a, b)?),632	}633}