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

difftreelog

feat impl Default for Thunk

Yaroslav Bolyukin2023-08-06parent: #a1d4291.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
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;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}
after · crates/jrsonnet-evaluator/src/val.rs
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;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}138139impl<T: Trace + Default> Default for Thunk<T> {140	fn default() -> Self {141		Self::evaluated(T::default())142	}143}144145type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);146147#[derive(Trace, Clone)]148pub struct CachedUnbound<I, T>149where150	I: Unbound<Bound = T>,151	T: Trace,152{153	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,154	value: I,155}156impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {157	pub fn new(value: I) -> Self {158		Self {159			cache: Cc::new(RefCell::new(GcHashMap::new())),160			value,161		}162	}163}164impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {165	type Bound = T;166	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {167		let cache_key = (168			sup.as_ref().map(|s| s.clone().downgrade()),169			this.as_ref().map(|t| t.clone().downgrade()),170		);171		{172			if let Some(t) = self.cache.borrow().get(&cache_key) {173				return Ok(t.clone());174			}175		}176		let bound = self.value.bind(sup, this)?;177178		{179			let mut cache = self.cache.borrow_mut();180			cache.insert(cache_key, bound.clone());181		}182183		Ok(bound)184	}185}186187impl<T: Debug + Trace> Debug for Thunk<T> {188	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {189		write!(f, "Lazy")190	}191}192impl<T: Trace> PartialEq for Thunk<T> {193	fn eq(&self, other: &Self) -> bool {194		Cc::ptr_eq(&self.0, &other.0)195	}196}197198/// Represents a Jsonnet value, which can be sliced or indexed (string or array).199#[allow(clippy::module_name_repetitions)]200pub enum IndexableVal {201	/// String.202	Str(IStr),203	/// Array.204	Arr(ArrValue),205}206impl IndexableVal {207	pub fn to_array(self) -> ArrValue {208		match self {209			IndexableVal::Str(s) => ArrValue::chars(s.chars()),210			IndexableVal::Arr(arr) => arr,211		}212	}213	/// Slice the value.214	///215	/// # Implementation216	///217	/// For strings, will create a copy of specified interval.218	///219	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.220	pub fn slice(221		self,222		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,223		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,224		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,225	) -> Result<Self> {226		match &self {227			IndexableVal::Str(s) => {228				let index = index.as_deref().copied().unwrap_or(0);229				let end = end.as_deref().copied().unwrap_or(usize::MAX);230				let step = step.as_deref().copied().unwrap_or(1);231232				if index >= end {233					return Ok(Self::Str("".into()));234				}235236				Ok(Self::Str(237					(s.chars()238						.skip(index)239						.take(end - index)240						.step_by(step)241						.collect::<String>())242					.into(),243				))244			}245			IndexableVal::Arr(arr) => {246				let index = index.as_deref().copied().unwrap_or(0);247				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());248				let step = step.as_deref().copied().unwrap_or(1);249250				if index >= end {251					return Ok(Self::Arr(ArrValue::empty()));252				}253254				Ok(Self::Arr(255					arr.clone()256						.slice(Some(index), Some(end), Some(step))257						.expect("arguments checked"),258				))259			}260		}261	}262}263264#[derive(Debug, Clone, Trace)]265pub enum StrValue {266	Flat(IStr),267	Tree(Rc<(StrValue, StrValue, usize)>),268}269impl StrValue {270	pub fn concat(a: StrValue, b: StrValue) -> Self {271		// TODO: benchmark for an optimal value, currently just a arbitrary choice272		const STRING_EXTEND_THRESHOLD: usize = 100;273274		if a.is_empty() {275			b276		} else if b.is_empty() {277			a278		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {279			Self::Flat(format!("{a}{b}").into())280		} else {281			let len = a.len() + b.len();282			Self::Tree(Rc::new((a, b, len)))283		}284	}285	pub fn into_flat(self) -> IStr {286		#[cold]287		fn write_buf(s: &StrValue, out: &mut String) {288			match s {289				StrValue::Flat(f) => out.push_str(f),290				StrValue::Tree(t) => {291					write_buf(&t.0, out);292					write_buf(&t.1, out);293				}294			}295		}296		match self {297			StrValue::Flat(f) => f,298			StrValue::Tree(_) => {299				let mut buf = String::with_capacity(self.len());300				write_buf(&self, &mut buf);301				buf.into()302			}303		}304	}305	pub fn len(&self) -> usize {306		match self {307			StrValue::Flat(v) => v.len(),308			StrValue::Tree(t) => t.2,309		}310	}311	pub fn is_empty(&self) -> bool {312		match self {313			Self::Flat(v) => v.is_empty(),314			// Can't create non-flat empty string315			Self::Tree(_) => false,316		}317	}318}319impl From<&str> for StrValue {320	fn from(value: &str) -> Self {321		Self::Flat(value.into())322	}323}324impl From<String> for StrValue {325	fn from(value: String) -> Self {326		Self::Flat(value.into())327	}328}329impl From<IStr> for StrValue {330	fn from(value: IStr) -> Self {331		Self::Flat(value)332	}333}334impl Display for StrValue {335	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {336		match self {337			StrValue::Flat(v) => write!(f, "{v}"),338			StrValue::Tree(t) => {339				write!(f, "{}", t.0)?;340				write!(f, "{}", t.1)341			}342		}343	}344}345impl PartialEq for StrValue {346	fn eq(&self, other: &Self) -> bool {347		let a = self.clone().into_flat();348		let b = other.clone().into_flat();349		a == b350	}351}352impl Eq for StrValue {}353impl PartialOrd for StrValue {354	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {355		Some(self.cmp(other))356	}357}358impl Ord for StrValue {359	fn cmp(&self, other: &Self) -> std::cmp::Ordering {360		let a = self.clone().into_flat();361		let b = other.clone().into_flat();362		a.cmp(&b)363	}364}365366/// Represents any valid Jsonnet value.367#[derive(Debug, Clone, Trace, Default)]368pub enum Val {369	/// Represents a Jsonnet boolean.370	Bool(bool),371	/// Represents a Jsonnet null value.372	#[default]373	Null,374	/// Represents a Jsonnet string.375	Str(StrValue),376	/// Represents a Jsonnet number.377	/// Should be finite, and not NaN378	/// This restriction isn't enforced by enum, as enum field can't be marked as private379	Num(f64),380	/// Experimental bigint381	#[cfg(feature = "exp-bigint")]382	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),383	/// Represents a Jsonnet array.384	Arr(ArrValue),385	/// Represents a Jsonnet object.386	Obj(ObjValue),387	/// Represents a Jsonnet function.388	Func(FuncVal),389}390391#[cfg(target_pointer_width = "64")]392static_assertions::assert_eq_size!(Val, [u8; 24]);393394impl From<IndexableVal> for Val {395	fn from(v: IndexableVal) -> Self {396		match v {397			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),398			IndexableVal::Arr(a) => Self::Arr(a),399		}400	}401}402403impl Val {404	pub const fn as_bool(&self) -> Option<bool> {405		match self {406			Self::Bool(v) => Some(*v),407			_ => None,408		}409	}410	pub const fn as_null(&self) -> Option<()> {411		match self {412			Self::Null => Some(()),413			_ => None,414		}415	}416	pub fn as_str(&self) -> Option<IStr> {417		match self {418			Self::Str(s) => Some(s.clone().into_flat()),419			_ => None,420		}421	}422	pub const fn as_num(&self) -> Option<f64> {423		match self {424			Self::Num(n) => Some(*n),425			_ => None,426		}427	}428	pub fn as_arr(&self) -> Option<ArrValue> {429		match self {430			Self::Arr(a) => Some(a.clone()),431			_ => None,432		}433	}434	pub fn as_obj(&self) -> Option<ObjValue> {435		match self {436			Self::Obj(o) => Some(o.clone()),437			_ => None,438		}439	}440	pub fn as_func(&self) -> Option<FuncVal> {441		match self {442			Self::Func(f) => Some(f.clone()),443			_ => None,444		}445	}446447	/// Creates `Val::Num` after checking for numeric overflow.448	/// As numbers are `f64`, we can just check for their finity.449	pub fn new_checked_num(num: f64) -> Result<Self> {450		if num.is_finite() {451			Ok(Self::Num(num))452		} else {453			throw!("overflow")454		}455	}456457	pub const fn value_type(&self) -> ValType {458		match self {459			Self::Str(..) => ValType::Str,460			Self::Num(..) => ValType::Num,461			#[cfg(feature = "exp-bigint")]462			Self::BigInt(..) => ValType::BigInt,463			Self::Arr(..) => ValType::Arr,464			Self::Obj(..) => ValType::Obj,465			Self::Bool(_) => ValType::Bool,466			Self::Null => ValType::Null,467			Self::Func(..) => ValType::Func,468		}469	}470471	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {472		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {473			manifest.manifest(val.clone())474		}475		manifest_dyn(self, &format)476	}477478	pub fn to_string(&self) -> Result<IStr> {479		Ok(match self {480			Self::Bool(true) => "true".into(),481			Self::Bool(false) => "false".into(),482			Self::Null => "null".into(),483			Self::Str(s) => s.clone().into_flat(),484			_ => self.manifest(ToStringFormat).map(IStr::from)?,485		})486	}487488	pub fn into_indexable(self) -> Result<IndexableVal> {489		Ok(match self {490			Val::Str(s) => IndexableVal::Str(s.into_flat()),491			Val::Arr(arr) => IndexableVal::Arr(arr),492			_ => throw!(ValueIsNotIndexable(self.value_type())),493		})494	}495}496497const fn is_function_like(val: &Val) -> bool {498	matches!(val, Val::Func(_))499}500501/// Native implementation of `std.primitiveEquals`502pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {503	Ok(match (val_a, val_b) {504		(Val::Bool(a), Val::Bool(b)) => a == b,505		(Val::Null, Val::Null) => true,506		(Val::Str(a), Val::Str(b)) => a == b,507		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,508		#[cfg(feature = "exp-bigint")]509		(Val::BigInt(a), Val::BigInt(b)) => a == b,510		(Val::Arr(_), Val::Arr(_)) => {511			throw!("primitiveEquals operates on primitive types, got array")512		}513		(Val::Obj(_), Val::Obj(_)) => {514			throw!("primitiveEquals operates on primitive types, got object")515		}516		(a, b) if is_function_like(a) && is_function_like(b) => {517			throw!("cannot test equality of functions")518		}519		(_, _) => false,520	})521}522523/// Native implementation of `std.equals`524pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {525	if val_a.value_type() != val_b.value_type() {526		return Ok(false);527	}528	match (val_a, val_b) {529		(Val::Arr(a), Val::Arr(b)) => {530			if ArrValue::ptr_eq(a, b) {531				return Ok(true);532			}533			if a.len() != b.len() {534				return Ok(false);535			}536			for (a, b) in a.iter().zip(b.iter()) {537				if !equals(&a?, &b?)? {538					return Ok(false);539				}540			}541			Ok(true)542		}543		(Val::Obj(a), Val::Obj(b)) => {544			if ObjValue::ptr_eq(a, b) {545				return Ok(true);546			}547			let fields = a.fields(548				#[cfg(feature = "exp-preserve-order")]549				false,550			);551			if fields552				!= b.fields(553					#[cfg(feature = "exp-preserve-order")]554					false,555				) {556				return Ok(false);557			}558			for field in fields {559				if !equals(560					&a.get(field.clone())?.expect("field exists"),561					&b.get(field)?.expect("field exists"),562				)? {563					return Ok(false);564				}565			}566			Ok(true)567		}568		(a, b) => Ok(primitive_equals(a, b)?),569	}570}