git.delta.rocks / jrsonnet / refs/commits / 7681558ac965

difftreelog

fix SliceArray implementation was dumb

lwnlqzpvYaroslav Bolyukin2026-03-02parent: #3421cac.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/arr/spec.rs
1use std::{any::Any, cell::RefCell, fmt::Debug, iter, mem::replace};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_parser::LocExpr;67use super::ArrValue;8use crate::{9	error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, typed::Typed,10	val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,11};1213pub trait ArrayLike: Any + Trace + Debug {14	fn len(&self) -> usize;15	fn is_empty(&self) -> bool {16		self.len() == 017	}18	fn get(&self, index: usize) -> Result<Option<Val>>;19	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;20	fn get_cheap(&self, index: usize) -> Option<Val>;2122	fn is_cheap(&self) -> bool;23}2425#[derive(Debug, Trace)]26pub struct SliceArray {27	pub(crate) inner: ArrValue,28	pub(crate) from: u32,29	pub(crate) to: u32,30	pub(crate) step: u32,31}3233impl SliceArray {34	fn iter(&self) -> impl Iterator<Item = Result<Val>> + '_ {35		self.inner36			.iter()37			.skip(self.from as usize)38			.take((self.to - self.from) as usize)39			.step_by(self.step as usize)40	}4142	fn iter_lazy(&self) -> impl Iterator<Item = Thunk<Val>> + '_ {43		self.inner44			.iter_lazy()45			.skip(self.from as usize)46			.take((self.to - self.from) as usize)47			.step_by(self.step as usize)48	}4950	fn iter_cheap(&self) -> Option<impl crate::arr::ArrayLikeIter<Val> + '_> {51		Some(52			self.inner53				.iter_cheap()?54				.skip(self.from as usize)55				.take((self.to - self.from) as usize)56				.step_by(self.step as usize),57		)58	}59}60impl ArrayLike for SliceArray {61	fn len(&self) -> usize {62		iter::repeat(())63			.take((self.to - self.from) as usize)64			.step_by(self.step as usize)65			.count()66	}6768	fn get(&self, index: usize) -> Result<Option<Val>> {69		self.iter().nth(index).transpose()70	}7172	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {73		self.iter_lazy().nth(index)74	}7576	fn get_cheap(&self, index: usize) -> Option<Val> {77		self.iter_cheap()?.nth(index)78	}79	fn is_cheap(&self) -> bool {80		self.inner.is_cheap()81	}82}8384#[derive(Trace, Debug)]85pub struct CharArray(pub Vec<char>);86impl ArrayLike for CharArray {87	fn len(&self) -> usize {88		self.0.len()89	}9091	fn get(&self, index: usize) -> Result<Option<Val>> {92		Ok(self.get_cheap(index))93	}9495	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {96		self.get_cheap(index).map(Thunk::evaluated)97	}9899	fn get_cheap(&self, index: usize) -> Option<Val> {100		self.0.get(index).map(|v| Val::string(*v))101	}102	fn is_cheap(&self) -> bool {103		true104	}105}106107#[derive(Trace, Debug)]108pub struct BytesArray(pub IBytes);109impl ArrayLike for BytesArray {110	fn len(&self) -> usize {111		self.0.len()112	}113114	fn get(&self, index: usize) -> Result<Option<Val>> {115		Ok(self.get_cheap(index))116	}117118	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {119		self.get_cheap(index).map(Thunk::evaluated)120	}121122	fn get_cheap(&self, index: usize) -> Option<Val> {123		self.0.get(index).map(|v| Val::Num((*v).into()))124	}125	fn is_cheap(&self) -> bool {126		true127	}128}129130#[derive(Debug, Trace, Clone)]131enum ArrayThunk<T: 'static + Trace> {132	Computed(Val),133	Errored(Error),134	Waiting(T),135	Pending,136}137138#[derive(Debug, Trace, Clone)]139pub struct ExprArray {140	ctx: Context,141	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,142}143impl ExprArray {144	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {145		Self {146			ctx,147			cached: Cc::new(RefCell::new(148				items.into_iter().map(ArrayThunk::Waiting).collect(),149			)),150		}151	}152}153impl ArrayLike for ExprArray {154	fn len(&self) -> usize {155		self.cached.borrow().len()156	}157	fn get(&self, index: usize) -> Result<Option<Val>> {158		if index >= self.len() {159			return Ok(None);160		}161		match &self.cached.borrow()[index] {162			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),163			ArrayThunk::Errored(e) => return Err(e.clone()),164			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),165			ArrayThunk::Waiting(..) => {}166		};167168		let ArrayThunk::Waiting(expr) =169			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)170		else {171			unreachable!()172		};173174		let new_value = match evaluate(self.ctx.clone(), &expr) {175			Ok(v) => v,176			Err(e) => {177				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());178				return Err(e);179			}180		};181		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());182		Ok(Some(new_value))183	}184	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {185		if index >= self.len() {186			return None;187		}188		match &self.cached.borrow()[index] {189			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),190			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),191			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}192		};193194		#[derive(Trace)]195		struct ExprArrThunk {196			expr: ExprArray,197			index: usize,198		}199		impl ThunkValue for ExprArrThunk {200			type Output = Val;201202			fn get(&self) -> Result<Self::Output> {203				self.expr204					.get(self.index)205					.transpose()206					.expect("index checked")207			}208		}209210		Some(Thunk::new(ExprArrThunk {211			expr: self.clone(),212			index,213		}))214	}215	fn get_cheap(&self, _index: usize) -> Option<Val> {216		None217	}218	fn is_cheap(&self) -> bool {219		false220	}221}222223#[derive(Trace, Debug)]224pub struct ExtendedArray {225	pub a: ArrValue,226	pub b: ArrValue,227	split: usize,228	len: usize,229}230impl ExtendedArray {231	pub fn new(a: ArrValue, b: ArrValue) -> Self {232		let a_len = a.len();233		let b_len = b.len();234		Self {235			a,236			b,237			split: a_len,238			len: a_len.checked_add(b_len).expect("too large array value"),239		}240	}241}242243struct WithExactSize<I>(I, usize);244impl<I, T> Iterator for WithExactSize<I>245where246	I: Iterator<Item = T>,247{248	type Item = T;249250	fn next(&mut self) -> Option<Self::Item> {251		self.0.next()252	}253	fn nth(&mut self, n: usize) -> Option<Self::Item> {254		self.0.nth(n)255	}256	fn size_hint(&self) -> (usize, Option<usize>) {257		(self.1, Some(self.1))258	}259}260impl<I> DoubleEndedIterator for WithExactSize<I>261where262	I: DoubleEndedIterator,263{264	fn next_back(&mut self) -> Option<Self::Item> {265		self.0.next_back()266	}267	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {268		self.0.nth_back(n)269	}270}271impl<I> ExactSizeIterator for WithExactSize<I>272where273	I: Iterator,274{275	fn len(&self) -> usize {276		self.1277	}278}279impl ArrayLike for ExtendedArray {280	fn get(&self, index: usize) -> Result<Option<Val>> {281		if self.split > index {282			self.a.get(index)283		} else {284			self.b.get(index - self.split)285		}286	}287	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {288		if self.split > index {289			self.a.get_lazy(index)290		} else {291			self.b.get_lazy(index - self.split)292		}293	}294295	fn len(&self) -> usize {296		self.len297	}298299	fn get_cheap(&self, index: usize) -> Option<Val> {300		if self.split > index {301			self.a.get_cheap(index)302		} else {303			self.b.get_cheap(index - self.split)304		}305	}306	fn is_cheap(&self) -> bool {307		self.a.is_cheap() && self.b.is_cheap()308	}309}310311#[derive(Trace, Debug)]312pub struct LazyArray(pub Vec<Thunk<Val>>);313impl ArrayLike for LazyArray {314	fn len(&self) -> usize {315		self.0.len()316	}317	fn get(&self, index: usize) -> Result<Option<Val>> {318		let Some(v) = self.0.get(index) else {319			return Ok(None);320		};321		v.evaluate().map(Some)322	}323	fn get_cheap(&self, _index: usize) -> Option<Val> {324		None325	}326	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {327		self.0.get(index).cloned()328	}329	fn is_cheap(&self) -> bool {330		false331	}332}333334#[derive(Trace, Debug)]335pub struct EagerArray(pub Vec<Val>);336impl ArrayLike for EagerArray {337	fn len(&self) -> usize {338		self.0.len()339	}340341	fn get(&self, index: usize) -> Result<Option<Val>> {342		Ok(self.0.get(index).cloned())343	}344345	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {346		self.0.get(index).cloned().map(Thunk::evaluated)347	}348349	fn get_cheap(&self, index: usize) -> Option<Val> {350		self.0.get(index).cloned()351	}352	fn is_cheap(&self) -> bool {353		true354	}355}356357/// Inclusive range type358#[derive(Debug, Trace, PartialEq, Eq)]359pub struct RangeArray {360	start: i32,361	end: i32,362}363impl RangeArray {364	pub fn empty() -> Self {365		Self::new_exclusive(0, 0)366	}367	pub fn new_exclusive(start: i32, end: i32) -> Self {368		end.checked_sub(1)369			.map_or_else(Self::empty, |end| Self { start, end })370	}371	pub fn new_inclusive(start: i32, end: i32) -> Self {372		Self { start, end }373	}374	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {375		WithExactSize(376			self.start..=self.end,377			(self.end as usize)378				.wrapping_sub(self.start as usize)379				.wrapping_add(1),380		)381	}382}383384impl ArrayLike for RangeArray {385	fn len(&self) -> usize {386		self.range().len()387	}388	fn is_empty(&self) -> bool {389		self.range().len() == 0390	}391392	fn get(&self, index: usize) -> Result<Option<Val>> {393		Ok(self.get_cheap(index))394	}395396	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {397		self.get_cheap(index).map(Thunk::evaluated)398	}399400	fn get_cheap(&self, index: usize) -> Option<Val> {401		self.range().nth(index).map(|i| Val::Num(i.into()))402	}403	fn is_cheap(&self) -> bool {404		true405	}406}407408#[derive(Debug, Trace)]409pub struct ReverseArray(pub ArrValue);410impl ArrayLike for ReverseArray {411	fn len(&self) -> usize {412		self.0.len()413	}414415	fn get(&self, index: usize) -> Result<Option<Val>> {416		self.0.get(self.0.len() - index - 1)417	}418419	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {420		self.0.get_lazy(self.0.len() - index - 1)421	}422423	fn get_cheap(&self, index: usize) -> Option<Val> {424		self.0.get_cheap(self.0.len() - index - 1)425	}426	fn is_cheap(&self) -> bool {427		self.0.is_cheap()428	}429}430431#[derive(Trace, Debug, Clone)]432pub struct MappedArray<const WITH_INDEX: bool> {433	inner: ArrValue,434	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,435	mapper: FuncVal,436}437impl<const WITH_INDEX: bool> MappedArray<WITH_INDEX> {438	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {439		let len = inner.len();440		Self {441			inner,442			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),443			mapper,444		}445	}446	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {447		if WITH_INDEX {448			self.mapper.evaluate_simple(&(index, value), false)449		} else {450			self.mapper.evaluate_simple(&(value,), false)451		}452	}453}454impl<const WITH_INDEX: bool> ArrayLike for MappedArray<WITH_INDEX> {455	fn len(&self) -> usize {456		self.cached.borrow().len()457	}458459	fn get(&self, index: usize) -> Result<Option<Val>> {460		if index >= self.len() {461			return Ok(None);462		}463		match &self.cached.borrow()[index] {464			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),465			ArrayThunk::Errored(e) => return Err(e.clone()),466			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),467			ArrayThunk::Waiting(..) => {}468		};469470		let ArrayThunk::Waiting(()) =471			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)472		else {473			unreachable!()474		};475476		let val = self477			.inner478			.get(index)479			.transpose()480			.expect("index checked")481			.and_then(|r| self.evaluate(index, r));482483		let new_value = match val {484			Ok(v) => v,485			Err(e) => {486				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());487				return Err(e);488			}489		};490		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());491		Ok(Some(new_value))492	}493	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {494		if index >= self.len() {495			return None;496		}497		match &self.cached.borrow()[index] {498			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),499			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),500			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}501		};502503		#[derive(Trace)]504		struct MappedArrayThunk<const WITH_INDEX: bool> {505			arr: MappedArray<WITH_INDEX>,506			index: usize,507		}508		impl<const WITH_INDEX: bool> ThunkValue for MappedArrayThunk<WITH_INDEX> {509			type Output = Val;510511			fn get(&self) -> Result<Self::Output> {512				self.arr.get(self.index).transpose().expect("index checked")513			}514		}515516		Some(Thunk::new(MappedArrayThunk {517			arr: self.clone(),518			index,519		}))520	}521522	fn get_cheap(&self, _index: usize) -> Option<Val> {523		None524	}525	fn is_cheap(&self) -> bool {526		false527	}528}529530#[derive(Trace, Debug)]531pub struct RepeatedArray {532	data: ArrValue,533	repeats: usize,534	total_len: usize,535}536impl RepeatedArray {537	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {538		let total_len = data.len().checked_mul(repeats)?;539		Some(Self {540			data,541			repeats,542			total_len,543		})544	}545}546547impl ArrayLike for RepeatedArray {548	fn len(&self) -> usize {549		self.total_len550	}551552	fn get(&self, index: usize) -> Result<Option<Val>> {553		if index > self.total_len {554			return Ok(None);555		}556		self.data.get(index % self.data.len())557	}558559	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {560		if index > self.total_len {561			return None;562		}563		self.data.get_lazy(index % self.data.len())564	}565566	fn get_cheap(&self, index: usize) -> Option<Val> {567		if index > self.total_len {568			return None;569		}570		self.data.get_cheap(index % self.data.len())571	}572	fn is_cheap(&self) -> bool {573		self.data.is_cheap()574	}575}576577#[derive(Trace, Debug)]578pub struct PickObjectValues {579	obj: ObjValue,580	keys: Vec<IStr>,581}582583impl PickObjectValues {584	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {585		Self { obj, keys }586	}587}588589impl ArrayLike for PickObjectValues {590	fn len(&self) -> usize {591		self.keys.len()592	}593594	fn get(&self, index: usize) -> Result<Option<Val>> {595		let Some(key) = self.keys.get(index) else {596			return Ok(None);597		};598		Ok(Some(self.obj.get_or_bail(key.clone())?))599	}600601	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {602		let key = self.keys.get(index)?;603		Some(self.obj.get_lazy_or_bail(key.clone()))604	}605606	fn get_cheap(&self, _index: usize) -> Option<Val> {607		None608	}609610	fn is_cheap(&self) -> bool {611		false612	}613}614615#[derive(Trace, Debug)]616pub struct PickObjectKeyValues {617	obj: ObjValue,618	keys: Vec<IStr>,619}620621impl PickObjectKeyValues {622	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {623		Self { obj, keys }624	}625}626627#[derive(Typed)]628pub struct KeyValue {629	key: IStr,630	value: Thunk<Val>,631}632633impl ArrayLike for PickObjectKeyValues {634	fn len(&self) -> usize {635		self.keys.len()636	}637638	fn get(&self, index: usize) -> Result<Option<Val>> {639		let Some(key) = self.keys.get(index) else {640			return Ok(None);641		};642		Ok(Some(643			KeyValue::into_untyped(KeyValue {644				key: key.clone(),645				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),646			})647			.expect("convertible"),648		))649	}650651	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {652		let key = self.keys.get(index)?;653		// Nothing can fail in the key part, yet value is still654		// lazy-evaluated655		Some(Thunk::evaluated(656			KeyValue::into_untyped(KeyValue {657				key: key.clone(),658				value: self.obj.get_lazy_or_bail(key.clone()),659			})660			.expect("convertible"),661		))662	}663664	fn get_cheap(&self, _index: usize) -> Option<Val> {665		None666	}667668	fn is_cheap(&self) -> bool {669		false670	}671}
after · crates/jrsonnet-evaluator/src/arr/spec.rs
1use std::{any::Any, cell::RefCell, fmt::Debug, iter, mem::replace};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_parser::LocExpr;67use super::ArrValue;8use crate::{9	error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, typed::Typed,10	val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,11};1213pub trait ArrayLike: Any + Trace + Debug {14	fn len(&self) -> usize;15	fn is_empty(&self) -> bool {16		self.len() == 017	}18	fn get(&self, index: usize) -> Result<Option<Val>>;19	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;20	fn get_cheap(&self, index: usize) -> Option<Val>;2122	fn is_cheap(&self) -> bool;23}2425#[derive(Debug, Trace)]26pub struct SliceArray {27	pub(crate) inner: ArrValue,28	pub(crate) from: u32,29	pub(crate) to: u32,30	pub(crate) step: u32,31}3233impl SliceArray {34	fn map_idx(&self, index: usize) -> usize {35		self.from as usize + self.step as usize * index36	}37}38impl ArrayLike for SliceArray {39	fn len(&self) -> usize {40		((self.to - self.from + self.step - 1) / self.step) as usize41	}4243	fn get(&self, index: usize) -> Result<Option<Val>> {44		self.inner.get(self.map_idx(index))45	}4647	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {48		self.inner.get_lazy(self.map_idx(index))49	}5051	fn get_cheap(&self, index: usize) -> Option<Val> {52		self.inner.get_cheap(self.map_idx(index))53	}54	fn is_cheap(&self) -> bool {55		self.inner.is_cheap()56	}57}5859#[derive(Trace, Debug)]60pub struct CharArray(pub Vec<char>);61impl ArrayLike for CharArray {62	fn len(&self) -> usize {63		self.0.len()64	}6566	fn get(&self, index: usize) -> Result<Option<Val>> {67		Ok(self.get_cheap(index))68	}6970	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {71		self.get_cheap(index).map(Thunk::evaluated)72	}7374	fn get_cheap(&self, index: usize) -> Option<Val> {75		self.0.get(index).map(|v| Val::string(*v))76	}77	fn is_cheap(&self) -> bool {78		true79	}80}8182#[derive(Trace, Debug)]83pub struct BytesArray(pub IBytes);84impl ArrayLike for BytesArray {85	fn len(&self) -> usize {86		self.0.len()87	}8889	fn get(&self, index: usize) -> Result<Option<Val>> {90		Ok(self.get_cheap(index))91	}9293	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {94		self.get_cheap(index).map(Thunk::evaluated)95	}9697	fn get_cheap(&self, index: usize) -> Option<Val> {98		self.0.get(index).map(|v| Val::Num((*v).into()))99	}100	fn is_cheap(&self) -> bool {101		true102	}103}104105#[derive(Debug, Trace, Clone)]106enum ArrayThunk<T: 'static + Trace> {107	Computed(Val),108	Errored(Error),109	Waiting(T),110	Pending,111}112113#[derive(Debug, Trace, Clone)]114pub struct ExprArray {115	ctx: Context,116	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,117}118impl ExprArray {119	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {120		Self {121			ctx,122			cached: Cc::new(RefCell::new(123				items.into_iter().map(ArrayThunk::Waiting).collect(),124			)),125		}126	}127}128impl ArrayLike for ExprArray {129	fn len(&self) -> usize {130		self.cached.borrow().len()131	}132	fn get(&self, index: usize) -> Result<Option<Val>> {133		if index >= self.len() {134			return Ok(None);135		}136		match &self.cached.borrow()[index] {137			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),138			ArrayThunk::Errored(e) => return Err(e.clone()),139			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),140			ArrayThunk::Waiting(..) => {}141		};142143		let ArrayThunk::Waiting(expr) =144			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)145		else {146			unreachable!()147		};148149		let new_value = match evaluate(self.ctx.clone(), &expr) {150			Ok(v) => v,151			Err(e) => {152				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());153				return Err(e);154			}155		};156		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());157		Ok(Some(new_value))158	}159	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {160		if index >= self.len() {161			return None;162		}163		match &self.cached.borrow()[index] {164			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),165			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),166			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}167		};168169		#[derive(Trace)]170		struct ExprArrThunk {171			expr: ExprArray,172			index: usize,173		}174		impl ThunkValue for ExprArrThunk {175			type Output = Val;176177			fn get(&self) -> Result<Self::Output> {178				self.expr179					.get(self.index)180					.transpose()181					.expect("index checked")182			}183		}184185		Some(Thunk::new(ExprArrThunk {186			expr: self.clone(),187			index,188		}))189	}190	fn get_cheap(&self, _index: usize) -> Option<Val> {191		None192	}193	fn is_cheap(&self) -> bool {194		false195	}196}197198#[derive(Trace, Debug)]199pub struct ExtendedArray {200	pub a: ArrValue,201	pub b: ArrValue,202	split: usize,203	len: usize,204}205impl ExtendedArray {206	pub fn new(a: ArrValue, b: ArrValue) -> Self {207		let a_len = a.len();208		let b_len = b.len();209		Self {210			a,211			b,212			split: a_len,213			len: a_len.checked_add(b_len).expect("too large array value"),214		}215	}216}217218struct WithExactSize<I>(I, usize);219impl<I, T> Iterator for WithExactSize<I>220where221	I: Iterator<Item = T>,222{223	type Item = T;224225	fn next(&mut self) -> Option<Self::Item> {226		self.0.next()227	}228	fn nth(&mut self, n: usize) -> Option<Self::Item> {229		self.0.nth(n)230	}231	fn size_hint(&self) -> (usize, Option<usize>) {232		(self.1, Some(self.1))233	}234}235impl<I> DoubleEndedIterator for WithExactSize<I>236where237	I: DoubleEndedIterator,238{239	fn next_back(&mut self) -> Option<Self::Item> {240		self.0.next_back()241	}242	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {243		self.0.nth_back(n)244	}245}246impl<I> ExactSizeIterator for WithExactSize<I>247where248	I: Iterator,249{250	fn len(&self) -> usize {251		self.1252	}253}254impl ArrayLike for ExtendedArray {255	fn get(&self, index: usize) -> Result<Option<Val>> {256		if self.split > index {257			self.a.get(index)258		} else {259			self.b.get(index - self.split)260		}261	}262	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {263		if self.split > index {264			self.a.get_lazy(index)265		} else {266			self.b.get_lazy(index - self.split)267		}268	}269270	fn len(&self) -> usize {271		self.len272	}273274	fn get_cheap(&self, index: usize) -> Option<Val> {275		if self.split > index {276			self.a.get_cheap(index)277		} else {278			self.b.get_cheap(index - self.split)279		}280	}281	fn is_cheap(&self) -> bool {282		self.a.is_cheap() && self.b.is_cheap()283	}284}285286#[derive(Trace, Debug)]287pub struct LazyArray(pub Vec<Thunk<Val>>);288impl ArrayLike for LazyArray {289	fn len(&self) -> usize {290		self.0.len()291	}292	fn get(&self, index: usize) -> Result<Option<Val>> {293		let Some(v) = self.0.get(index) else {294			return Ok(None);295		};296		v.evaluate().map(Some)297	}298	fn get_cheap(&self, _index: usize) -> Option<Val> {299		None300	}301	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {302		self.0.get(index).cloned()303	}304	fn is_cheap(&self) -> bool {305		false306	}307}308309#[derive(Trace, Debug)]310pub struct EagerArray(pub Vec<Val>);311impl ArrayLike for EagerArray {312	fn len(&self) -> usize {313		self.0.len()314	}315316	fn get(&self, index: usize) -> Result<Option<Val>> {317		Ok(self.0.get(index).cloned())318	}319320	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {321		self.0.get(index).cloned().map(Thunk::evaluated)322	}323324	fn get_cheap(&self, index: usize) -> Option<Val> {325		self.0.get(index).cloned()326	}327	fn is_cheap(&self) -> bool {328		true329	}330}331332/// Inclusive range type333#[derive(Debug, Trace, PartialEq, Eq)]334pub struct RangeArray {335	start: i32,336	end: i32,337}338impl RangeArray {339	pub fn empty() -> Self {340		Self::new_exclusive(0, 0)341	}342	pub fn new_exclusive(start: i32, end: i32) -> Self {343		end.checked_sub(1)344			.map_or_else(Self::empty, |end| Self { start, end })345	}346	pub fn new_inclusive(start: i32, end: i32) -> Self {347		Self { start, end }348	}349	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {350		WithExactSize(351			self.start..=self.end,352			(self.end as usize)353				.wrapping_sub(self.start as usize)354				.wrapping_add(1),355		)356	}357}358359impl ArrayLike for RangeArray {360	fn len(&self) -> usize {361		self.range().len()362	}363	fn is_empty(&self) -> bool {364		self.range().len() == 0365	}366367	fn get(&self, index: usize) -> Result<Option<Val>> {368		Ok(self.get_cheap(index))369	}370371	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {372		self.get_cheap(index).map(Thunk::evaluated)373	}374375	fn get_cheap(&self, index: usize) -> Option<Val> {376		self.range().nth(index).map(|i| Val::Num(i.into()))377	}378	fn is_cheap(&self) -> bool {379		true380	}381}382383#[derive(Debug, Trace)]384pub struct ReverseArray(pub ArrValue);385impl ArrayLike for ReverseArray {386	fn len(&self) -> usize {387		self.0.len()388	}389390	fn get(&self, index: usize) -> Result<Option<Val>> {391		self.0.get(self.0.len() - index - 1)392	}393394	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {395		self.0.get_lazy(self.0.len() - index - 1)396	}397398	fn get_cheap(&self, index: usize) -> Option<Val> {399		self.0.get_cheap(self.0.len() - index - 1)400	}401	fn is_cheap(&self) -> bool {402		self.0.is_cheap()403	}404}405406#[derive(Trace, Debug, Clone)]407pub struct MappedArray<const WITH_INDEX: bool> {408	inner: ArrValue,409	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,410	mapper: FuncVal,411}412impl<const WITH_INDEX: bool> MappedArray<WITH_INDEX> {413	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {414		let len = inner.len();415		Self {416			inner,417			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),418			mapper,419		}420	}421	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {422		if WITH_INDEX {423			self.mapper.evaluate_simple(&(index, value), false)424		} else {425			self.mapper.evaluate_simple(&(value,), false)426		}427	}428}429impl<const WITH_INDEX: bool> ArrayLike for MappedArray<WITH_INDEX> {430	fn len(&self) -> usize {431		self.cached.borrow().len()432	}433434	fn get(&self, index: usize) -> Result<Option<Val>> {435		if index >= self.len() {436			return Ok(None);437		}438		match &self.cached.borrow()[index] {439			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),440			ArrayThunk::Errored(e) => return Err(e.clone()),441			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),442			ArrayThunk::Waiting(..) => {}443		};444445		let ArrayThunk::Waiting(()) =446			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)447		else {448			unreachable!()449		};450451		let val = self452			.inner453			.get(index)454			.transpose()455			.expect("index checked")456			.and_then(|r| self.evaluate(index, r));457458		let new_value = match val {459			Ok(v) => v,460			Err(e) => {461				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());462				return Err(e);463			}464		};465		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());466		Ok(Some(new_value))467	}468	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {469		if index >= self.len() {470			return None;471		}472		match &self.cached.borrow()[index] {473			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),474			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),475			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}476		};477478		#[derive(Trace)]479		struct MappedArrayThunk<const WITH_INDEX: bool> {480			arr: MappedArray<WITH_INDEX>,481			index: usize,482		}483		impl<const WITH_INDEX: bool> ThunkValue for MappedArrayThunk<WITH_INDEX> {484			type Output = Val;485486			fn get(&self) -> Result<Self::Output> {487				self.arr.get(self.index).transpose().expect("index checked")488			}489		}490491		Some(Thunk::new(MappedArrayThunk {492			arr: self.clone(),493			index,494		}))495	}496497	fn get_cheap(&self, _index: usize) -> Option<Val> {498		None499	}500	fn is_cheap(&self) -> bool {501		false502	}503}504505#[derive(Trace, Debug)]506pub struct RepeatedArray {507	data: ArrValue,508	repeats: usize,509	total_len: usize,510}511impl RepeatedArray {512	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {513		let total_len = data.len().checked_mul(repeats)?;514		Some(Self {515			data,516			repeats,517			total_len,518		})519	}520}521522impl ArrayLike for RepeatedArray {523	fn len(&self) -> usize {524		self.total_len525	}526527	fn get(&self, index: usize) -> Result<Option<Val>> {528		if index > self.total_len {529			return Ok(None);530		}531		self.data.get(index % self.data.len())532	}533534	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {535		if index > self.total_len {536			return None;537		}538		self.data.get_lazy(index % self.data.len())539	}540541	fn get_cheap(&self, index: usize) -> Option<Val> {542		if index > self.total_len {543			return None;544		}545		self.data.get_cheap(index % self.data.len())546	}547	fn is_cheap(&self) -> bool {548		self.data.is_cheap()549	}550}551552#[derive(Trace, Debug)]553pub struct PickObjectValues {554	obj: ObjValue,555	keys: Vec<IStr>,556}557558impl PickObjectValues {559	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {560		Self { obj, keys }561	}562}563564impl ArrayLike for PickObjectValues {565	fn len(&self) -> usize {566		self.keys.len()567	}568569	fn get(&self, index: usize) -> Result<Option<Val>> {570		let Some(key) = self.keys.get(index) else {571			return Ok(None);572		};573		Ok(Some(self.obj.get_or_bail(key.clone())?))574	}575576	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {577		let key = self.keys.get(index)?;578		Some(self.obj.get_lazy_or_bail(key.clone()))579	}580581	fn get_cheap(&self, _index: usize) -> Option<Val> {582		None583	}584585	fn is_cheap(&self) -> bool {586		false587	}588}589590#[derive(Trace, Debug)]591pub struct PickObjectKeyValues {592	obj: ObjValue,593	keys: Vec<IStr>,594}595596impl PickObjectKeyValues {597	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {598		Self { obj, keys }599	}600}601602#[derive(Typed)]603pub struct KeyValue {604	key: IStr,605	value: Thunk<Val>,606}607608impl ArrayLike for PickObjectKeyValues {609	fn len(&self) -> usize {610		self.keys.len()611	}612613	fn get(&self, index: usize) -> Result<Option<Val>> {614		let Some(key) = self.keys.get(index) else {615			return Ok(None);616		};617		Ok(Some(618			KeyValue::into_untyped(KeyValue {619				key: key.clone(),620				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),621			})622			.expect("convertible"),623		))624	}625626	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {627		let key = self.keys.get(index)?;628		// Nothing can fail in the key part, yet value is still629		// lazy-evaluated630		Some(Thunk::evaluated(631			KeyValue::into_untyped(KeyValue {632				key: key.clone(),633				value: self.obj.get_lazy_or_bail(key.clone()),634			})635			.expect("convertible"),636		))637	}638639	fn get_cheap(&self, _index: usize) -> Option<Val> {640		None641	}642643	fn is_cheap(&self) -> bool {644		false645	}646}