git.delta.rocks / jrsonnet / refs/commits / 068a27ea4069

difftreelog

feat make std.map accept strings

Yaroslav Bolyukin2023-05-03parent: #bd9176f.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -1,3 +1,5 @@
+use std::rc::Rc;
+
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IBytes;
 use jrsonnet_parser::LocExpr;
@@ -14,6 +16,8 @@
 pub enum ArrValue {
 	/// Layout optimized byte array.
 	Bytes(BytesArray),
+	/// Layout optimized char array.
+	Chars(CharArray),
 	/// Every element is lazy evaluated.
 	Lazy(LazyArray),
 	/// Every element is defined somewhere in source code
@@ -66,6 +70,9 @@
 	pub fn bytes(bytes: IBytes) -> Self {
 		Self::Bytes(BytesArray(bytes))
 	}
+	pub fn chars(chars: impl Iterator<Item = char>) -> Self {
+		Self::Chars(CharArray(Rc::new(chars.collect())))
+	}
 
 	#[must_use]
 	pub fn map(self, mapper: FuncVal) -> Self {
@@ -237,7 +244,9 @@
 	/// Is this vec supports `.get_cheap()?`
 	pub fn is_cheap(&self) -> bool {
 		match self {
-			ArrValue::Eager(_) | ArrValue::Range(..) | ArrValue::Bytes(_) => true,
+			ArrValue::Eager(_) | ArrValue::Range(..) | ArrValue::Bytes(_) | ArrValue::Chars(_) => {
+				true
+			}
 			ArrValue::Extended(v) => v.a.is_cheap() && v.b.is_cheap(),
 			ArrValue::Slice(r) => r.inner.is_cheap(),
 			ArrValue::Reverse(i) => i.0.is_cheap(),
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -1,15 +1,18 @@
 //! Those implementations are a bit sketchy, as this is mostly performance experiments
 //! of not yet finished nightly rust features
 
-use std::{cell::RefCell, iter, mem::replace};
+use std::{cell::RefCell, iter, mem::replace, rc::Rc};
 
 use jrsonnet_gcmodule::{Cc, Trace};
-use jrsonnet_interner::IBytes;
+use jrsonnet_interner::{IBytes, IStr};
 use jrsonnet_parser::LocExpr;
 
 use super::ArrValue;
 use crate::{
-	error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, val::ThunkValue,
+	error::ErrorKind::InfiniteRecursionDetected,
+	evaluate,
+	function::FuncVal,
+	val::{StrValue, ThunkValue},
 	Context, Error, Result, Thunk, Val,
 };
 
@@ -154,6 +157,69 @@
 }
 
 #[derive(Trace, Debug, Clone)]
+pub struct CharArray(pub Rc<Vec<char>>);
+#[cfg(feature = "nightly")]
+type CharArrayIter<'t> = impl DoubleEndedIterator<Item = Result<Val>> + ExactSizeIterator + 't;
+#[cfg(feature = "nightly")]
+type CharArrayLazyIter<'t> = impl DoubleEndedIterator<Item = Thunk<Val>> + ExactSizeIterator + 't;
+#[cfg(feature = "nightly")]
+type CharArrayCheapIter<'t> = impl DoubleEndedIterator<Item = Val> + ExactSizeIterator + 't;
+impl ArrayLike for CharArray {
+	#[cfg(feature = "nightly")]
+	type Iter<'t> = CharArrayIter<'t>;
+	#[cfg(feature = "nightly")]
+	type IterLazy<'t> = CharArrayLazyIter<'t>;
+	#[cfg(feature = "nightly")]
+	type IterCheap<'t> = CharArrayCheapIter<'t>;
+
+	fn len(&self) -> usize {
+		self.0.len()
+	}
+
+	fn get(&self, index: usize) -> Result<Option<Val>> {
+		Ok(self.get_cheap(index))
+	}
+
+	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+		self.get_cheap(index).map(Thunk::evaluated)
+	}
+
+	fn get_cheap(&self, index: usize) -> Option<Val> {
+		self.0
+			.get(index)
+			.map(|v| Val::Str(StrValue::Flat(IStr::from(*v))))
+	}
+
+	#[cfg(feature = "nightly")]
+	fn iter(&self) -> CharArrayIter<'_> {
+		self.0
+			.iter()
+			.map(|v| Ok(Val::Str(StrValue::Flat(IStr::from(*v)))))
+	}
+
+	#[cfg(feature = "nightly")]
+	fn iter_lazy(&self) -> CharArrayLazyIter<'_> {
+		self.0
+			.iter()
+			.map(|v| Thunk::evaluated(Val::Str(StrValue::Flat(IStr::from(*v)))))
+	}
+
+	#[cfg(feature = "nightly")]
+	fn iter_cheap(&self) -> Option<CharArrayCheapIter<'_>> {
+		Some(
+			self.0
+				.iter()
+				.map(|v| Val::Str(StrValue::Flat(IStr::from(*v)))),
+		)
+	}
+}
+impl From<CharArray> for ArrValue {
+	fn from(value: CharArray) -> Self {
+		ArrValue::Chars(value)
+	}
+}
+
+#[derive(Trace, Debug, Clone)]
 pub struct BytesArray(pub IBytes);
 #[cfg(feature = "nightly")]
 type BytesArrayIter<'t> = impl DoubleEndedIterator<Item = Result<Val>> + ExactSizeIterator + 't;
@@ -935,6 +1001,7 @@
 	($t:ident.$m:ident($($ident:ident),*)) => {
 		match $t {
 			Self::Bytes(e) => e.$m($($ident)*),
+			Self::Chars(e) => e.$m($($ident)*),
 			Self::Expr(e) => e.$m($($ident)*),
 			Self::Lazy(e) => e.$m($($ident)*),
 			Self::Eager(e) => e.$m($($ident)*),
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) else {76			unreachable!();77		};78		let new_value = match value.0.get() {79			Ok(v) => v,80			Err(e) => {81				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());82				return Err(e);83			}84		};85		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());86		Ok(new_value)87	}88}8990type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);9192#[derive(Trace, Clone)]93pub struct CachedUnbound<I, T>94where95	I: Unbound<Bound = T>,96	T: Trace,97{98	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,99	value: I,100}101impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {102	pub fn new(value: I) -> Self {103		Self {104			cache: Cc::new(RefCell::new(GcHashMap::new())),105			value,106		}107	}108}109impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {110	type Bound = T;111	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {112		let cache_key = (113			sup.as_ref().map(|s| s.clone().downgrade()),114			this.as_ref().map(|t| t.clone().downgrade()),115		);116		{117			if let Some(t) = self.cache.borrow().get(&cache_key) {118				return Ok(t.clone());119			}120		}121		let bound = self.value.bind(sup, this)?;122123		{124			let mut cache = self.cache.borrow_mut();125			cache.insert(cache_key, bound.clone());126		}127128		Ok(bound)129	}130}131132impl<T: Debug + Trace> Debug for Thunk<T> {133	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134		write!(f, "Lazy")135	}136}137impl<T: Trace> PartialEq for Thunk<T> {138	fn eq(&self, other: &Self) -> bool {139		Cc::ptr_eq(&self.0, &other.0)140	}141}142143/// Represents a Jsonnet value, which can be sliced or indexed (string or array).144#[allow(clippy::module_name_repetitions)]145pub enum IndexableVal {146	/// String.147	Str(IStr),148	/// Array.149	Arr(ArrValue),150}151impl IndexableVal {152	/// Slice the value.153	///154	/// # Implementation155	///156	/// For strings, will create a copy of specified interval.157	///158	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.159	pub fn slice(160		self,161		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,162		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,163		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,164	) -> Result<Self> {165		match &self {166			IndexableVal::Str(s) => {167				let index = index.as_deref().copied().unwrap_or(0);168				let end = end.as_deref().copied().unwrap_or(usize::MAX);169				let step = step.as_deref().copied().unwrap_or(1);170171				if index >= end {172					return Ok(Self::Str("".into()));173				}174175				Ok(Self::Str(176					(s.chars()177						.skip(index)178						.take(end - index)179						.step_by(step)180						.collect::<String>())181					.into(),182				))183			}184			IndexableVal::Arr(arr) => {185				let index = index.as_deref().copied().unwrap_or(0);186				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());187				let step = step.as_deref().copied().unwrap_or(1);188189				if index >= end {190					return Ok(Self::Arr(ArrValue::empty()));191				}192193				Ok(Self::Arr(194					arr.clone()195						.slice(Some(index), Some(end), Some(step))196						.expect("arguments checked"),197				))198			}199		}200	}201}202203#[derive(Debug, Clone, Trace)]204pub enum StrValue {205	Flat(IStr),206	Tree(Rc<(StrValue, StrValue, usize)>),207}208impl StrValue {209	pub fn concat(a: StrValue, b: StrValue) -> Self {210		// TODO: benchmark for an optimal value, currently just a arbitrary choice211		const STRING_EXTEND_THRESHOLD: usize = 100;212213		if a.is_empty() {214			b215		} else if b.is_empty() {216			a217		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {218			Self::Flat(format!("{a}{b}").into())219		} else {220			let len = a.len() + b.len();221			Self::Tree(Rc::new((a, b, len)))222		}223	}224	pub fn into_flat(self) -> IStr {225		#[cold]226		fn write_buf(s: &StrValue, out: &mut String) {227			match s {228				StrValue::Flat(f) => out.push_str(f),229				StrValue::Tree(t) => {230					write_buf(&t.0, out);231					write_buf(&t.1, out);232				}233			}234		}235		match self {236			StrValue::Flat(f) => f,237			StrValue::Tree(_) => {238				let mut buf = String::with_capacity(self.len());239				write_buf(&self, &mut buf);240				buf.into()241			}242		}243	}244	pub fn len(&self) -> usize {245		match self {246			StrValue::Flat(v) => v.len(),247			StrValue::Tree(t) => t.2,248		}249	}250	pub fn is_empty(&self) -> bool {251		match self {252			Self::Flat(v) => v.is_empty(),253			// Can't create non-flat empty string254			Self::Tree(_) => false,255		}256	}257}258impl From<&str> for StrValue {259	fn from(value: &str) -> Self {260		Self::Flat(value.into())261	}262}263impl From<String> for StrValue {264	fn from(value: String) -> Self {265		Self::Flat(value.into())266	}267}268impl Display for StrValue {269	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {270		match self {271			StrValue::Flat(v) => write!(f, "{v}"),272			StrValue::Tree(t) => {273				write!(f, "{}", t.0)?;274				write!(f, "{}", t.1)275			}276		}277	}278}279impl PartialEq for StrValue {280	fn eq(&self, other: &Self) -> bool {281		let a = self.clone().into_flat();282		let b = other.clone().into_flat();283		a == b284	}285}286impl Eq for StrValue {}287impl PartialOrd for StrValue {288	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {289		let a = self.clone().into_flat();290		let b = other.clone().into_flat();291		Some(a.cmp(&b))292	}293}294impl Ord for StrValue {295	fn cmp(&self, other: &Self) -> std::cmp::Ordering {296		self.partial_cmp(other)297			.expect("partial_cmp always returns Some")298	}299}300301/// Represents any valid Jsonnet value.302#[derive(Debug, Clone, Trace)]303pub enum Val {304	/// Represents a Jsonnet boolean.305	Bool(bool),306	/// Represents a Jsonnet null value.307	Null,308	/// Represents a Jsonnet string.309	Str(StrValue),310	/// Represents a Jsonnet number.311	/// Should be finite, and not NaN312	/// This restriction isn't enforced by enum, as enum field can't be marked as private313	Num(f64),314	/// Experimental bigint315	#[cfg(feature = "exp-bigint")]316	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),317	/// Represents a Jsonnet array.318	Arr(ArrValue),319	/// Represents a Jsonnet object.320	Obj(ObjValue),321	/// Represents a Jsonnet function.322	Func(FuncVal),323}324325#[cfg(target_pointer_width = "64")]326static_assertions::assert_eq_size!(Val, [u8; 24]);327328impl From<IndexableVal> for Val {329	fn from(v: IndexableVal) -> Self {330		match v {331			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),332			IndexableVal::Arr(a) => Self::Arr(a),333		}334	}335}336337impl Val {338	pub const fn as_bool(&self) -> Option<bool> {339		match self {340			Self::Bool(v) => Some(*v),341			_ => None,342		}343	}344	pub const fn as_null(&self) -> Option<()> {345		match self {346			Self::Null => Some(()),347			_ => None,348		}349	}350	pub fn as_str(&self) -> Option<IStr> {351		match self {352			Self::Str(s) => Some(s.clone().into_flat()),353			_ => None,354		}355	}356	pub const fn as_num(&self) -> Option<f64> {357		match self {358			Self::Num(n) => Some(*n),359			_ => None,360		}361	}362	pub fn as_arr(&self) -> Option<ArrValue> {363		match self {364			Self::Arr(a) => Some(a.clone()),365			_ => None,366		}367	}368	pub fn as_obj(&self) -> Option<ObjValue> {369		match self {370			Self::Obj(o) => Some(o.clone()),371			_ => None,372		}373	}374	pub fn as_func(&self) -> Option<FuncVal> {375		match self {376			Self::Func(f) => Some(f.clone()),377			_ => None,378		}379	}380381	/// Creates `Val::Num` after checking for numeric overflow.382	/// As numbers are `f64`, we can just check for their finity.383	pub fn new_checked_num(num: f64) -> Result<Self> {384		if num.is_finite() {385			Ok(Self::Num(num))386		} else {387			throw!("overflow")388		}389	}390391	pub const fn value_type(&self) -> ValType {392		match self {393			Self::Str(..) => ValType::Str,394			Self::Num(..) => ValType::Num,395			#[cfg(feature = "exp-bigint")]396			Self::BigInt(..) => ValType::BigInt,397			Self::Arr(..) => ValType::Arr,398			Self::Obj(..) => ValType::Obj,399			Self::Bool(_) => ValType::Bool,400			Self::Null => ValType::Null,401			Self::Func(..) => ValType::Func,402		}403	}404405	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {406		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {407			manifest.manifest(val.clone())408		}409		manifest_dyn(self, &format)410	}411412	pub fn to_string(&self) -> Result<IStr> {413		Ok(match self {414			Self::Bool(true) => "true".into(),415			Self::Bool(false) => "false".into(),416			Self::Null => "null".into(),417			Self::Str(s) => s.clone().into_flat(),418			_ => self.manifest(ToStringFormat).map(IStr::from)?,419		})420	}421422	pub fn into_indexable(self) -> Result<IndexableVal> {423		Ok(match self {424			Val::Str(s) => IndexableVal::Str(s.into_flat()),425			Val::Arr(arr) => IndexableVal::Arr(arr),426			_ => throw!(ValueIsNotIndexable(self.value_type())),427		})428	}429}430431const fn is_function_like(val: &Val) -> bool {432	matches!(val, Val::Func(_))433}434435/// Native implementation of `std.primitiveEquals`436pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {437	Ok(match (val_a, val_b) {438		(Val::Bool(a), Val::Bool(b)) => a == b,439		(Val::Null, Val::Null) => true,440		(Val::Str(a), Val::Str(b)) => a == b,441		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,442		(Val::Arr(_), Val::Arr(_)) => {443			throw!("primitiveEquals operates on primitive types, got array")444		}445		(Val::Obj(_), Val::Obj(_)) => {446			throw!("primitiveEquals operates on primitive types, got object")447		}448		(a, b) if is_function_like(a) && is_function_like(b) => {449			throw!("cannot test equality of functions")450		}451		(_, _) => false,452	})453}454455/// Native implementation of `std.equals`456pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {457	if val_a.value_type() != val_b.value_type() {458		return Ok(false);459	}460	match (val_a, val_b) {461		(Val::Arr(a), Val::Arr(b)) => {462			if ArrValue::ptr_eq(a, b) {463				return Ok(true);464			}465			if a.len() != b.len() {466				return Ok(false);467			}468			for (a, b) in a.iter().zip(b.iter()) {469				if !equals(&a?, &b?)? {470					return Ok(false);471				}472			}473			Ok(true)474		}475		(Val::Obj(a), Val::Obj(b)) => {476			if ObjValue::ptr_eq(a, b) {477				return Ok(true);478			}479			let fields = a.fields(480				#[cfg(feature = "exp-preserve-order")]481				false,482			);483			if fields484				!= b.fields(485					#[cfg(feature = "exp-preserve-order")]486					false,487				) {488				return Ok(false);489			}490			for field in fields {491				if !equals(492					&a.get(field.clone())?.expect("field exists"),493					&b.get(field)?.expect("field exists"),494				)? {495					return Ok(false);496				}497			}498			Ok(true)499		}500		(a, b) => Ok(primitive_equals(a, b)?),501	}502}
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) else {76			unreachable!();77		};78		let new_value = match value.0.get() {79			Ok(v) => v,80			Err(e) => {81				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());82				return Err(e);83			}84		};85		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());86		Ok(new_value)87	}88}8990type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);9192#[derive(Trace, Clone)]93pub struct CachedUnbound<I, T>94where95	I: Unbound<Bound = T>,96	T: Trace,97{98	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,99	value: I,100}101impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {102	pub fn new(value: I) -> Self {103		Self {104			cache: Cc::new(RefCell::new(GcHashMap::new())),105			value,106		}107	}108}109impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {110	type Bound = T;111	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {112		let cache_key = (113			sup.as_ref().map(|s| s.clone().downgrade()),114			this.as_ref().map(|t| t.clone().downgrade()),115		);116		{117			if let Some(t) = self.cache.borrow().get(&cache_key) {118				return Ok(t.clone());119			}120		}121		let bound = self.value.bind(sup, this)?;122123		{124			let mut cache = self.cache.borrow_mut();125			cache.insert(cache_key, bound.clone());126		}127128		Ok(bound)129	}130}131132impl<T: Debug + Trace> Debug for Thunk<T> {133	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {134		write!(f, "Lazy")135	}136}137impl<T: Trace> PartialEq for Thunk<T> {138	fn eq(&self, other: &Self) -> bool {139		Cc::ptr_eq(&self.0, &other.0)140	}141}142143/// Represents a Jsonnet value, which can be sliced or indexed (string or array).144#[allow(clippy::module_name_repetitions)]145pub enum IndexableVal {146	/// String.147	Str(IStr),148	/// Array.149	Arr(ArrValue),150}151impl IndexableVal {152	pub fn to_array(self) -> ArrValue {153		match self {154			IndexableVal::Str(s) => ArrValue::chars(s.chars()),155			IndexableVal::Arr(arr) => arr,156		}157	}158	/// Slice the value.159	///160	/// # Implementation161	///162	/// For strings, will create a copy of specified interval.163	///164	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.165	pub fn slice(166		self,167		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,168		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,169		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,170	) -> Result<Self> {171		match &self {172			IndexableVal::Str(s) => {173				let index = index.as_deref().copied().unwrap_or(0);174				let end = end.as_deref().copied().unwrap_or(usize::MAX);175				let step = step.as_deref().copied().unwrap_or(1);176177				if index >= end {178					return Ok(Self::Str("".into()));179				}180181				Ok(Self::Str(182					(s.chars()183						.skip(index)184						.take(end - index)185						.step_by(step)186						.collect::<String>())187					.into(),188				))189			}190			IndexableVal::Arr(arr) => {191				let index = index.as_deref().copied().unwrap_or(0);192				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());193				let step = step.as_deref().copied().unwrap_or(1);194195				if index >= end {196					return Ok(Self::Arr(ArrValue::empty()));197				}198199				Ok(Self::Arr(200					arr.clone()201						.slice(Some(index), Some(end), Some(step))202						.expect("arguments checked"),203				))204			}205		}206	}207}208209#[derive(Debug, Clone, Trace)]210pub enum StrValue {211	Flat(IStr),212	Tree(Rc<(StrValue, StrValue, usize)>),213}214impl StrValue {215	pub fn concat(a: StrValue, b: StrValue) -> Self {216		// TODO: benchmark for an optimal value, currently just a arbitrary choice217		const STRING_EXTEND_THRESHOLD: usize = 100;218219		if a.is_empty() {220			b221		} else if b.is_empty() {222			a223		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {224			Self::Flat(format!("{a}{b}").into())225		} else {226			let len = a.len() + b.len();227			Self::Tree(Rc::new((a, b, len)))228		}229	}230	pub fn into_flat(self) -> IStr {231		#[cold]232		fn write_buf(s: &StrValue, out: &mut String) {233			match s {234				StrValue::Flat(f) => out.push_str(f),235				StrValue::Tree(t) => {236					write_buf(&t.0, out);237					write_buf(&t.1, out);238				}239			}240		}241		match self {242			StrValue::Flat(f) => f,243			StrValue::Tree(_) => {244				let mut buf = String::with_capacity(self.len());245				write_buf(&self, &mut buf);246				buf.into()247			}248		}249	}250	pub fn len(&self) -> usize {251		match self {252			StrValue::Flat(v) => v.len(),253			StrValue::Tree(t) => t.2,254		}255	}256	pub fn is_empty(&self) -> bool {257		match self {258			Self::Flat(v) => v.is_empty(),259			// Can't create non-flat empty string260			Self::Tree(_) => false,261		}262	}263}264impl From<&str> for StrValue {265	fn from(value: &str) -> Self {266		Self::Flat(value.into())267	}268}269impl From<String> for StrValue {270	fn from(value: String) -> Self {271		Self::Flat(value.into())272	}273}274impl Display for StrValue {275	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {276		match self {277			StrValue::Flat(v) => write!(f, "{v}"),278			StrValue::Tree(t) => {279				write!(f, "{}", t.0)?;280				write!(f, "{}", t.1)281			}282		}283	}284}285impl PartialEq for StrValue {286	fn eq(&self, other: &Self) -> bool {287		let a = self.clone().into_flat();288		let b = other.clone().into_flat();289		a == b290	}291}292impl Eq for StrValue {}293impl PartialOrd for StrValue {294	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {295		let a = self.clone().into_flat();296		let b = other.clone().into_flat();297		Some(a.cmp(&b))298	}299}300impl Ord for StrValue {301	fn cmp(&self, other: &Self) -> std::cmp::Ordering {302		self.partial_cmp(other)303			.expect("partial_cmp always returns Some")304	}305}306307/// Represents any valid Jsonnet value.308#[derive(Debug, Clone, Trace)]309pub enum Val {310	/// Represents a Jsonnet boolean.311	Bool(bool),312	/// Represents a Jsonnet null value.313	Null,314	/// Represents a Jsonnet string.315	Str(StrValue),316	/// Represents a Jsonnet number.317	/// Should be finite, and not NaN318	/// This restriction isn't enforced by enum, as enum field can't be marked as private319	Num(f64),320	/// Experimental bigint321	#[cfg(feature = "exp-bigint")]322	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),323	/// Represents a Jsonnet array.324	Arr(ArrValue),325	/// Represents a Jsonnet object.326	Obj(ObjValue),327	/// Represents a Jsonnet function.328	Func(FuncVal),329}330331#[cfg(target_pointer_width = "64")]332static_assertions::assert_eq_size!(Val, [u8; 24]);333334impl From<IndexableVal> for Val {335	fn from(v: IndexableVal) -> Self {336		match v {337			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),338			IndexableVal::Arr(a) => Self::Arr(a),339		}340	}341}342343impl Val {344	pub const fn as_bool(&self) -> Option<bool> {345		match self {346			Self::Bool(v) => Some(*v),347			_ => None,348		}349	}350	pub const fn as_null(&self) -> Option<()> {351		match self {352			Self::Null => Some(()),353			_ => None,354		}355	}356	pub fn as_str(&self) -> Option<IStr> {357		match self {358			Self::Str(s) => Some(s.clone().into_flat()),359			_ => None,360		}361	}362	pub const fn as_num(&self) -> Option<f64> {363		match self {364			Self::Num(n) => Some(*n),365			_ => None,366		}367	}368	pub fn as_arr(&self) -> Option<ArrValue> {369		match self {370			Self::Arr(a) => Some(a.clone()),371			_ => None,372		}373	}374	pub fn as_obj(&self) -> Option<ObjValue> {375		match self {376			Self::Obj(o) => Some(o.clone()),377			_ => None,378		}379	}380	pub fn as_func(&self) -> Option<FuncVal> {381		match self {382			Self::Func(f) => Some(f.clone()),383			_ => None,384		}385	}386387	/// Creates `Val::Num` after checking for numeric overflow.388	/// As numbers are `f64`, we can just check for their finity.389	pub fn new_checked_num(num: f64) -> Result<Self> {390		if num.is_finite() {391			Ok(Self::Num(num))392		} else {393			throw!("overflow")394		}395	}396397	pub const fn value_type(&self) -> ValType {398		match self {399			Self::Str(..) => ValType::Str,400			Self::Num(..) => ValType::Num,401			#[cfg(feature = "exp-bigint")]402			Self::BigInt(..) => ValType::BigInt,403			Self::Arr(..) => ValType::Arr,404			Self::Obj(..) => ValType::Obj,405			Self::Bool(_) => ValType::Bool,406			Self::Null => ValType::Null,407			Self::Func(..) => ValType::Func,408		}409	}410411	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {412		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {413			manifest.manifest(val.clone())414		}415		manifest_dyn(self, &format)416	}417418	pub fn to_string(&self) -> Result<IStr> {419		Ok(match self {420			Self::Bool(true) => "true".into(),421			Self::Bool(false) => "false".into(),422			Self::Null => "null".into(),423			Self::Str(s) => s.clone().into_flat(),424			_ => self.manifest(ToStringFormat).map(IStr::from)?,425		})426	}427428	pub fn into_indexable(self) -> Result<IndexableVal> {429		Ok(match self {430			Val::Str(s) => IndexableVal::Str(s.into_flat()),431			Val::Arr(arr) => IndexableVal::Arr(arr),432			_ => throw!(ValueIsNotIndexable(self.value_type())),433		})434	}435}436437const fn is_function_like(val: &Val) -> bool {438	matches!(val, Val::Func(_))439}440441/// Native implementation of `std.primitiveEquals`442pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {443	Ok(match (val_a, val_b) {444		(Val::Bool(a), Val::Bool(b)) => a == b,445		(Val::Null, Val::Null) => true,446		(Val::Str(a), Val::Str(b)) => a == b,447		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,448		(Val::Arr(_), Val::Arr(_)) => {449			throw!("primitiveEquals operates on primitive types, got array")450		}451		(Val::Obj(_), Val::Obj(_)) => {452			throw!("primitiveEquals operates on primitive types, got object")453		}454		(a, b) if is_function_like(a) && is_function_like(b) => {455			throw!("cannot test equality of functions")456		}457		(_, _) => false,458	})459}460461/// Native implementation of `std.equals`462pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {463	if val_a.value_type() != val_b.value_type() {464		return Ok(false);465	}466	match (val_a, val_b) {467		(Val::Arr(a), Val::Arr(b)) => {468			if ArrValue::ptr_eq(a, b) {469				return Ok(true);470			}471			if a.len() != b.len() {472				return Ok(false);473			}474			for (a, b) in a.iter().zip(b.iter()) {475				if !equals(&a?, &b?)? {476					return Ok(false);477				}478			}479			Ok(true)480		}481		(Val::Obj(a), Val::Obj(b)) => {482			if ObjValue::ptr_eq(a, b) {483				return Ok(true);484			}485			let fields = a.fields(486				#[cfg(feature = "exp-preserve-order")]487				false,488			);489			if fields490				!= b.fields(491					#[cfg(feature = "exp-preserve-order")]492					false,493				) {494				return Ok(false);495			}496			for field in fields {497				if !equals(498					&a.get(field.clone())?.expect("field exists"),499					&b.get(field)?.expect("field exists"),500				)? {501					return Ok(false);502				}503			}504			Ok(true)505		}506		(a, b) => Ok(primitive_equals(a, b)?),507	}508}
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -205,6 +205,12 @@
 		s.as_str().into()
 	}
 }
+impl From<char> for IStr {
+	fn from(value: char) -> Self {
+		let mut buf = [0; 5];
+		Self::from(&*value.encode_utf8(&mut buf))
+	}
+}
 impl From<&[u8]> for IBytes {
 	fn from(v: &[u8]) -> Self {
 		intern_bytes(v)
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -45,8 +45,9 @@
 }
 
 #[builtin]
-pub fn builtin_map(func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
-	Ok(arr.map(func))
+pub fn builtin_map(func: FuncVal, arr: IndexableVal) -> ArrValue {
+	let arr = arr.to_array();
+	arr.map(func)
 }
 
 #[builtin]