From 62ffdd8c48798acebda9b535a00f0d3115bb383a Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 03 Dec 2022 20:43:55 +0000 Subject: [PATCH] refactor: more array specializations --- --- a/bindings/jsonnet/src/val_make.rs +++ b/bindings/jsonnet/src/val_make.rs @@ -46,7 +46,7 @@ /// Assign elements with [`jsonnet_json_array_append`]. #[no_mangle] pub extern "C" fn jsonnet_json_make_array(_vm: &VM) -> *mut Val { - Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Cc::new(Vec::new()))))) + Box::into_raw(Box::new(Val::Arr(ArrValue::eager(Cc::new(Vec::new()))))) } /// Make a `JsonnetJsonValue` representing an object. --- a/bindings/jsonnet/src/val_modify.rs +++ b/bindings/jsonnet/src/val_modify.rs @@ -25,7 +25,7 @@ } new.push(Thunk::evaluated(val.clone())); - *arr = Val::Arr(ArrValue::Lazy(Cc::new(new))); + *arr = Val::Arr(ArrValue::lazy(Cc::new(new))); } _ => panic!("should receive array"), } --- a/crates/jrsonnet-cli/src/manifest.rs +++ b/crates/jrsonnet-cli/src/manifest.rs @@ -50,7 +50,7 @@ /// Preserve order in object manifestification #[cfg(feature = "exp-preserve-order")] #[clap(long)] - preserve_order: bool, + pub preserve_order: bool, } impl ConfigureState for ManifestOpts { type Guards = Box; --- /dev/null +++ b/crates/jrsonnet-evaluator/src/arr/mod.rs @@ -0,0 +1,222 @@ +use jrsonnet_gcmodule::{Cc, Trace}; +use jrsonnet_interner::IBytes; +use jrsonnet_parser::LocExpr; + +use crate::{function::FuncVal, Context, Result, Thunk, Val}; + +mod spec; +use spec::*; + +/// Represents a Jsonnet array value. +#[derive(Debug, Clone, Trace)] +// may contrain other ArrValue +#[trace(tracking(force))] +pub enum ArrValue { + /// Layout optimized byte array. + Bytes(BytesArray), + /// Every element is lazy evaluated. + Lazy(LazyArray), + /// Every element is defined somewhere in source code + Expr(ExprArray), + /// Every field is already evaluated. + Eager(EagerArray), + /// Concatenation of two arrays of any kind. + Extended(Cc), + /// Represents a integer array in form `[start, start + 1, ... end - 1, end]`. + /// This kind of arrays is generated by `std.range(start, end)` call, and used for loops. + Range(RangeArray), + /// Sliced array view. + Slice(Box), + /// Reversed array view. + /// Returned by `std.reverse(other)` call + Reverse(Box), + /// Returned by `std.map` call + Mapped(MappedArray), +} + +impl ArrValue { + pub fn empty() -> Self { + Self::Range(RangeArray::empty()) + } + + pub fn expr(ctx: Context, exprs: impl IntoIterator) -> Self { + Self::Expr(ExprArray::new(ctx, exprs)) + } + + pub fn lazy(thunks: Cc>>) -> Self { + Self::Lazy(LazyArray(thunks)) + } + + pub fn eager(values: Cc>) -> Self { + Self::Eager(EagerArray(values)) + } + + pub fn bytes(bytes: IBytes) -> Self { + Self::Bytes(BytesArray(bytes)) + } + + #[must_use] + pub fn map(self, mapper: FuncVal) -> Self { + Self::Mapped(MappedArray::new(self, mapper)) + } + + pub fn filter(self, filter: impl Fn(&Val) -> Result) -> Result { + // TODO: ArrValue::Picked(inner, indexes) for large arrays + let mut out = Vec::new(); + for i in self.iter() { + let i = i?; + if filter(&i)? { + out.push(i); + }; + } + Ok(Self::eager(Cc::new(out))) + } + + pub fn extended(a: ArrValue, b: ArrValue) -> Self { + // TODO: benchmark for an optimal value, currently just a arbitrary choice + const ARR_EXTEND_THRESHOLD: usize = 100; + + if a.len() + b.len() > ARR_EXTEND_THRESHOLD { + Self::Extended(Cc::new(ExtendedArray::new(a, b))) + } else if let (Some(a), Some(b)) = (a.iter_cheap(), b.iter_cheap()) { + let mut out = Vec::with_capacity(a.len() + b.len()); + out.extend(a); + out.extend(b); + Self::eager(Cc::new(out)) + } else { + let mut out = Vec::with_capacity(a.len() + b.len()); + out.extend(a.iter_lazy()); + out.extend(b.iter_lazy()); + Self::lazy(Cc::new(out)) + } + } + + pub fn range_exclusive(a: i32, b: i32) -> Self { + Self::Range(RangeArray::new_exclusive(a, b)) + } + pub fn range_inclusive(a: i32, b: i32) -> Self { + Self::Range(RangeArray::new_inclusive(a, b)) + } + + #[must_use] + pub fn slice( + self, + from: Option, + to: Option, + step: Option, + ) -> Option { + let len = self.len(); + let from = from.unwrap_or(0); + let to = to.unwrap_or(len).min(len); + let step = step.unwrap_or(1); + if from >= to || step == 0 { + return None; + } + + Some(Self::Slice(Box::new(SliceArray { + inner: self, + from: from as u32, + to: to as u32, + step: step as u32, + }))) + } + + /// Array length. + pub fn len(&self) -> usize { + pass!(self.len()) + } + + /// Is array contains no elements? + pub fn is_empty(&self) -> bool { + pass!(self.is_empty()) + } + + /// Get array element by index, evaluating it, if it is lazy. + /// + /// Returns `None` on out-of-bounds condition. + pub fn get(&self, index: usize) -> Result> { + pass!(self.get(index)) + } + + /// Returns None if get is either non cheap, or out of bounds + fn get_cheap(&self, index: usize) -> Option { + pass!(self.get_cheap(index)) + } + + /// Get array element by index, without evaluation. + /// + /// Returns `None` on out-of-bounds condition. + pub fn get_lazy(&self, index: usize) -> Option> { + pass!(self.get_lazy(index)) + } + + /// Evaluate all array elements, returning new array. + pub fn evaluatedcc(&self) -> Result>> { + self.evaluated().map(Cc::new) + } + pub fn evaluated(&self) -> Result> { + pass!(self.evaluated()) + } + + /// Iterate over elements, evaluating them. + pub fn iter(&self) -> UnknownArrayIter<'_> { + pass_iter_call!(self.iter => UnknownArrayIter) + } + + /// Iterate over elements, returning lazy values. + pub fn iter_lazy(&self) -> UnknownArrayIterLazy<'_> { + pass_iter_call!(self.iter_lazy => UnknownArrayIterLazy) + } + + pub fn iter_cheap(&self) -> Option> { + macro_rules! question { + ($v:expr) => { + $v? + }; + } + Some(pass_iter_call!(self.iter_cheap in question => UnknownArrayIterCheap)) + } + + /// Return a reversed view on current array. + #[must_use] + pub fn reversed(self) -> Self { + Self::Reverse(Box::new(ReverseArray(self))) + } + + pub fn ptr_eq(a: &Self, b: &Self) -> bool { + match (a, b) { + (ArrValue::Bytes(a), ArrValue::Bytes(b)) => a.0 == b.0, + (ArrValue::Lazy(a), ArrValue::Lazy(b)) => Cc::ptr_eq(&a.0, &b.0), + (ArrValue::Expr(a), ArrValue::Expr(b)) => Cc::ptr_eq(&a.0, &b.0), + (ArrValue::Eager(a), ArrValue::Eager(b)) => Cc::ptr_eq(&a.0, &b.0), + (ArrValue::Extended(a), ArrValue::Extended(b)) => Cc::ptr_eq(&a, &b), + (ArrValue::Range(a), ArrValue::Range(b)) => a == b, + (ArrValue::Slice(_), ArrValue::Slice(_)) => false, + (ArrValue::Reverse(_), ArrValue::Reverse(_)) => false, + _ => false, + } + } + + pub fn is_cheap(&self) -> bool { + match self { + ArrValue::Eager(_) | ArrValue::Range(..) | ArrValue::Bytes(_) => 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(), + ArrValue::Expr(_) | ArrValue::Lazy(_) | ArrValue::Mapped(_) => false, + } + } +} +impl From> for ArrValue { + fn from(value: Vec) -> Self { + Self::eager(Cc::new(value)) + } +} +impl From>> for ArrValue { + fn from(value: Vec>) -> Self { + Self::lazy(Cc::new(value)) + } +} + +#[cfg(target_pointer_width = "64")] +static_assertions::assert_eq_size!(ArrValue, [u8; 16]); --- /dev/null +++ b/crates/jrsonnet-evaluator/src/arr/spec.rs @@ -0,0 +1,841 @@ +use std::{ + cell::RefCell, + iter::{self, Rev}, + mem::replace, +}; + +use jrsonnet_gcmodule::{Cc, Trace}; +use jrsonnet_interner::IBytes; +use jrsonnet_parser::LocExpr; + +use super::ArrValue; +use crate::{ + error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, tb, typed::Any, + val::ThunkValue, Context, Error, Result, Thunk, Val, +}; + +pub trait ArrayLike { + type Iter<'t> + where + Self: 't; + type IterLazy<'t> + where + Self: 't; + type IterCheap<'t> + where + Self: 't; + + fn len(&self) -> usize; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn get(&self, index: usize) -> Result>; + fn get_lazy(&self, index: usize) -> Option>; + fn get_cheap(&self, index: usize) -> Option; + fn evaluated(&self) -> Result>; + #[allow(clippy::iter_not_returning_iterator)] + fn iter(&self) -> Self::Iter<'_>; + fn iter_lazy(&self) -> Self::IterLazy<'_>; + fn iter_cheap(&self) -> Option>; +} + +#[derive(Debug, Clone, Trace)] +pub struct SliceArray { + pub(crate) inner: ArrValue, + pub(crate) from: u32, + pub(crate) to: u32, + pub(crate) step: u32, +} +type SliceArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type SliceArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type SliceArrayCheapIter<'t> = impl DoubleEndedIterator + ExactSizeIterator + 't; +impl ArrayLike for SliceArray { + type Iter<'t> = SliceArrayIter<'t>; + + type IterLazy<'t> = SliceArrayLazyIter<'t>; + + type IterCheap<'t> = SliceArrayCheapIter<'t>; + + fn len(&self) -> usize { + iter::repeat(()) + .take((self.to - self.from) as usize) + .step_by(self.step as usize) + .count() + } + + fn get(&self, index: usize) -> Result> { + self.iter().nth(index).transpose() + } + + fn get_lazy(&self, index: usize) -> Option> { + self.iter_lazy().nth(index) + } + + fn get_cheap(&self, index: usize) -> Option { + self.iter_cheap()?.nth(index) + } + + fn evaluated(&self) -> Result> { + self.iter().collect() + } + + fn iter(&self) -> SliceArrayIter<'_> { + self.inner + .iter() + .skip(self.from as usize) + .take((self.to - self.from) as usize) + .step_by(self.step as usize) + } + + fn iter_lazy(&self) -> SliceArrayLazyIter<'_> { + self.inner + .iter_lazy() + .skip(self.from as usize) + .take((self.to - self.from) as usize) + .step_by(self.step as usize) + } + + fn iter_cheap(&self) -> Option> { + Some( + self.inner + .iter_cheap()? + .skip(self.from as usize) + .take((self.to - self.from) as usize) + .step_by(self.step as usize), + ) + } +} + +#[derive(Trace, Debug, Clone)] +pub struct BytesArray(pub IBytes); +type BytesArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type BytesArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type BytesArrayCheapIter<'t> = impl DoubleEndedIterator + ExactSizeIterator + 't; +impl ArrayLike for BytesArray { + type Iter<'t> = BytesArrayIter<'t>; + + type IterLazy<'t> = BytesArrayLazyIter<'t>; + + type IterCheap<'t> = BytesArrayCheapIter<'t>; + + fn len(&self) -> usize { + self.0.len() + } + + fn get(&self, index: usize) -> Result> { + Ok(self.get_cheap(index)) + } + + fn get_lazy(&self, index: usize) -> Option> { + self.get_cheap(index).map(Thunk::evaluated) + } + + fn get_cheap(&self, index: usize) -> Option { + self.0.get(index).map(|v| Val::Num(f64::from(*v))) + } + + fn evaluated(&self) -> Result> { + self.iter().collect() + } + + fn iter(&self) -> BytesArrayIter<'_> { + self.0.iter().map(|v| Ok(Val::Num(f64::from(*v)))) + } + + fn iter_lazy(&self) -> BytesArrayLazyIter<'_> { + self.0 + .iter() + .map(|v| Thunk::evaluated(Val::Num(f64::from(*v)))) + } + + fn iter_cheap(&self) -> Option> { + Some(self.0.iter().map(|v| Val::Num(f64::from(*v)))) + } +} + +#[derive(Debug, Trace, Clone)] +enum ArrayThunk { + Computed(Val), + Errored(Error), + Waiting(T), + Pending, +} + +#[derive(Debug, Trace)] +pub struct ExprArrayInner { + ctx: Context, + cached: RefCell>>, +} +#[derive(Debug, Trace, Clone)] +pub struct ExprArray(pub Cc); +type ExprArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type ExprArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type ExprArrayCheapIter<'t> = iter::Empty; +impl ExprArray { + pub fn new(ctx: Context, items: impl IntoIterator) -> Self { + Self(Cc::new(ExprArrayInner { + ctx, + cached: RefCell::new(items.into_iter().map(ArrayThunk::Waiting).collect()), + })) + } +} +impl ArrayLike for ExprArray { + type Iter<'t> = ExprArrayIter<'t>; + + type IterLazy<'t> = ExprArrayLazyIter<'t>; + + type IterCheap<'t> = ExprArrayCheapIter<'t>; + + fn len(&self) -> usize { + self.0.cached.borrow().len() + } + fn get(&self, index: usize) -> Result> { + if index >= self.len() { + return Ok(None); + } + match &self.0.cached.borrow()[index] { + ArrayThunk::Computed(c) => return Ok(Some(c.clone())), + ArrayThunk::Errored(e) => return Err(e.clone()), + ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()), + ArrayThunk::Waiting(..) => {} + }; + + let ArrayThunk::Waiting(expr) = replace(&mut self.0.cached.borrow_mut()[index], ArrayThunk::Pending) else { + unreachable!() + }; + + let new_value = match evaluate(self.0.ctx.clone(), &expr) { + Ok(v) => v, + Err(e) => { + self.0.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone()); + return Err(e); + } + }; + self.0.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone()); + Ok(Some(new_value)) + } + fn get_lazy(&self, index: usize) -> Option> { + #[derive(Trace)] + struct ArrayElement { + arr_thunk: ExprArray, + index: usize, + } + + impl ThunkValue for ArrayElement { + type Output = Val; + + fn get(self: Box) -> Result { + self.arr_thunk + .get(self.index) + .transpose() + .expect("index checked") + } + } + + if index >= self.len() { + return None; + } + match &self.0.cached.borrow()[index] { + ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())), + ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())), + ArrayThunk::Waiting(_) | ArrayThunk::Pending => {} + }; + + Some(Thunk::new(tb!(ArrayElement { + arr_thunk: self.clone(), + index, + }))) + } + fn get_cheap(&self, _index: usize) -> Option { + None + } + + fn iter(&self) -> ExprArrayIter<'_> { + (0..self.len()).map(|i| self.get(i).transpose().expect("index checked")) + } + fn iter_lazy(&self) -> ExprArrayLazyIter<'_> { + (0..self.len()).map(|i| self.get_lazy(i).expect("index checked")) + } + fn iter_cheap(&self) -> Option> { + None + } + + fn evaluated(&self) -> Result> { + self.iter().collect() + } +} + +#[derive(Trace, Debug, Clone)] +pub struct ExtendedArray { + pub a: ArrValue, + pub b: ArrValue, + split: usize, + len: usize, +} +type ExtendedArrayIter<'t> = impl DoubleEndedIterator> + 't; +type ExtendedArrayLazyIter<'t> = impl DoubleEndedIterator> + 't; +type ExtendedArrayCheapIter<'t> = impl DoubleEndedIterator + 't; +impl ExtendedArray { + pub fn new(a: ArrValue, b: ArrValue) -> Self { + let a_len = a.len(); + let b_len = b.len(); + Self { + a, + b, + split: a_len, + len: a_len.checked_add(b_len).expect("too large array value"), + } + } +} +impl ArrayLike for ExtendedArray { + type Iter<'t> = ExtendedArrayIter<'t>; + + type IterLazy<'t> = ExtendedArrayLazyIter<'t>; + + type IterCheap<'t> = ExtendedArrayCheapIter<'t>; + + fn get(&self, index: usize) -> Result> { + if self.split > index { + self.a.get(index) + } else { + self.b.get(index - self.split) + } + } + fn get_lazy(&self, index: usize) -> Option> { + if self.split > index { + self.a.get_lazy(index) + } else { + self.b.get_lazy(index - self.split) + } + } + + fn len(&self) -> usize { + self.len + } + + fn get_cheap(&self, index: usize) -> Option { + if self.split > index { + self.a.get_cheap(index) + } else { + self.b.get_cheap(index - self.split) + } + } + + fn evaluated(&self) -> Result> { + let mut out = self.a.evaluated()?; + out.extend(self.b.evaluated()?.into_iter()); + Ok(out) + } + + fn iter(&self) -> ExtendedArrayIter<'_> { + self.a.iter().chain(self.b.iter()) + } + fn iter_lazy(&self) -> ExtendedArrayLazyIter<'_> { + self.a.iter_lazy().chain(self.b.iter_lazy()) + } + fn iter_cheap(&self) -> Option> { + let a = self.a.iter_cheap()?; + let b = self.b.iter_cheap()?; + Some(a.chain(b)) + } +} + +#[derive(Trace, Debug, Clone)] +pub struct LazyArray(pub Cc>>); +type LazyArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type LazyArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type LazyArrayCheapIter<'t> = iter::Empty; +impl ArrayLike for LazyArray { + type Iter<'t> = LazyArrayIter<'t>; + + type IterLazy<'t> = LazyArrayLazyIter<'t>; + + type IterCheap<'t> = LazyArrayCheapIter<'t>; + + fn len(&self) -> usize { + self.0.len() + } + fn get(&self, index: usize) -> Result> { + let Some(v) = self.0.get(index) else { + return Ok(None); + }; + v.evaluate().map(Some) + } + fn get_cheap(&self, _index: usize) -> Option { + None + } + fn get_lazy(&self, index: usize) -> Option> { + self.0.get(index).cloned() + } + fn evaluated(&self) -> Result> { + let mut out = Vec::with_capacity(self.len()); + for i in self.0.iter() { + out.push(i.evaluate()?); + } + Ok(out) + } + fn iter(&self) -> LazyArrayIter<'_> { + self.0.iter().map(Thunk::evaluate) + } + fn iter_lazy(&self) -> LazyArrayLazyIter<'_> { + self.0.iter().cloned() + } + fn iter_cheap(&self) -> Option> { + None + } +} + +#[derive(Trace, Debug, Clone)] +pub struct EagerArray(pub Cc>); +type EagerArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type EagerArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type EagerArrayCheapIter<'t> = impl DoubleEndedIterator + ExactSizeIterator + 't; +impl ArrayLike for EagerArray { + type Iter<'t> = EagerArrayIter<'t>; + + type IterLazy<'t> = EagerArrayLazyIter<'t>; + + type IterCheap<'t> = EagerArrayCheapIter<'t>; + + fn len(&self) -> usize { + self.0.len() + } + + fn get(&self, index: usize) -> Result> { + Ok(self.0.get(index).cloned()) + } + + fn get_lazy(&self, index: usize) -> Option> { + self.0.get(index).cloned().map(Thunk::evaluated) + } + + fn get_cheap(&self, index: usize) -> Option { + self.0.get(index).cloned() + } + + fn evaluated(&self) -> Result> { + Ok((*self.0).clone()) + } + + fn iter(&self) -> EagerArrayIter<'_> { + self.0.iter().cloned().map(Ok) + } + + fn iter_lazy(&self) -> EagerArrayLazyIter<'_> { + self.0.iter().cloned().map(Thunk::evaluated) + } + + fn iter_cheap(&self) -> Option> { + Some(self.0.iter().cloned()) + } +} + +/// Inclusive range type +#[derive(Debug, Trace, Clone, PartialEq, Eq)] +pub struct RangeArray { + start: i32, + end: i32, +} +struct RangeIter { + start: i32, + end: i32, +} +impl RangeIter { + fn finished(&self) -> bool { + self.end < self.start + } + fn finish(&mut self) { + self.start = 0; + self.end = -1; + } +} +impl Iterator for RangeIter { + type Item = i32; + + fn next(&mut self) -> Option { + if self.finished() { + return None; + } + let v = self.start; + if v == self.end { + self.finish(); + } else { + self.start = v + 1; + } + Some(v) + } + fn nth(&mut self, n: usize) -> Option { + let v = (self.start as usize) + n; + if v > self.end as usize { + self.finish(); + None + } else { + self.start = v as i32; + self.next() + } + } + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} +impl DoubleEndedIterator for RangeIter { + fn next_back(&mut self) -> Option { + if self.finished() { + return None; + } + let v = self.end; + if v == self.start { + self.finish(); + } else { + self.end = v - 1; + } + Some(v) + } + fn nth_back(&mut self, n: usize) -> Option { + let v = (self.end as usize) - n; + if v < self.start as usize { + self.finish(); + None + } else { + self.end = v as i32; + self.next_back() + } + } +} +impl ExactSizeIterator for RangeIter { + fn len(&self) -> usize { + if self.finished() { + 0 + } else { + (self.end as isize - self.start as isize + 1) as usize + } + } +} +impl RangeArray { + pub fn empty() -> Self { + Self::new_exclusive(0, 0) + } + pub fn new_exclusive(start: i32, end: i32) -> Self { + end.checked_sub(1) + .map_or_else(Self::empty, |end| Self { start, end }) + } + pub fn new_inclusive(start: i32, end: i32) -> Self { + Self { start, end } + } + fn range(&self) -> RangeIter { + RangeIter { + start: self.start, + end: self.end, + } + } +} + +type RangeArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type RangeArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type RangeArrayCheapIter<'t> = impl DoubleEndedIterator + ExactSizeIterator + 't; +impl ArrayLike for RangeArray { + type Iter<'t> = RangeArrayIter<'t>; + + type IterLazy<'t> = RangeArrayLazyIter<'t>; + + type IterCheap<'t> = RangeArrayCheapIter<'t>; + + fn len(&self) -> usize { + self.range().len() + } + fn is_empty(&self) -> bool { + self.range().finished() + } + + fn get(&self, index: usize) -> Result> { + Ok(self.get_cheap(index)) + } + + fn get_lazy(&self, index: usize) -> Option> { + self.get_cheap(index).map(Thunk::evaluated) + } + + fn get_cheap(&self, index: usize) -> Option { + self.range().nth(index).map(|i| Val::Num(f64::from(i))) + } + + fn evaluated(&self) -> Result> { + Ok(self.range().map(|i| Val::Num(f64::from(i))).collect()) + } + + fn iter(&self) -> RangeArrayIter<'_> { + self.range().map(|i| Ok(Val::Num(f64::from(i)))) + } + + fn iter_lazy(&self) -> RangeArrayLazyIter<'_> { + self.range() + .map(|i| Thunk::evaluated(Val::Num(f64::from(i)))) + } + + fn iter_cheap(&self) -> Option> { + Some(self.range().map(|i| Val::Num(f64::from(i)))) + } +} + +#[derive(Debug, Trace, Clone)] +pub struct ReverseArray(pub ArrValue); +impl ArrayLike for ReverseArray { + type Iter<'t> = Rev>; + + type IterLazy<'t> = Rev>; + + type IterCheap<'t> = Rev>; + + fn len(&self) -> usize { + self.0.len() + } + + fn get(&self, index: usize) -> Result> { + self.0.get(self.0.len() - index - 1) + } + + fn get_lazy(&self, index: usize) -> Option> { + self.0.get_lazy(self.0.len() - index - 1) + } + + fn get_cheap(&self, index: usize) -> Option { + self.0.get_cheap(self.0.len() - index - 1) + } + + fn evaluated(&self) -> Result> { + let mut v = self.0.evaluated()?; + v.reverse(); + Ok(v) + } + + fn iter(&self) -> Rev> { + self.0.iter().rev() + } + + fn iter_lazy(&self) -> Rev> { + self.0.iter_lazy().rev() + } + + fn iter_cheap(&self) -> Option>> { + Some(self.0.iter_cheap()?.rev()) + } +} + +#[derive(Trace, Debug)] +pub struct MappedArrayInner { + inner: ArrValue, + cached: RefCell>>, + mapper: FuncVal, +} +#[derive(Trace, Debug, Clone)] +pub struct MappedArray(Cc); +impl MappedArray { + pub fn new(inner: ArrValue, mapper: FuncVal) -> Self { + let len = inner.len(); + Self(Cc::new(MappedArrayInner { + inner, + cached: RefCell::new(vec![ArrayThunk::Waiting(()); len]), + mapper, + })) + } +} +type MappedArrayIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type MappedArrayLazyIter<'t> = impl DoubleEndedIterator> + ExactSizeIterator + 't; +type MappedArrayCheapIter<'t> = iter::Empty; +impl ArrayLike for MappedArray { + type Iter<'t> = MappedArrayIter<'t>; + type IterLazy<'t> = MappedArrayLazyIter<'t>; + type IterCheap<'t> = MappedArrayCheapIter<'t>; + + fn len(&self) -> usize { + self.0.cached.borrow().len() + } + + fn get(&self, index: usize) -> Result> { + if index >= self.len() { + return Ok(None); + } + match &self.0.cached.borrow()[index] { + ArrayThunk::Computed(c) => return Ok(Some(c.clone())), + ArrayThunk::Errored(e) => return Err(e.clone()), + ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()), + ArrayThunk::Waiting(..) => {} + }; + + let ArrayThunk::Waiting(_) = replace(&mut self.0.cached.borrow_mut()[index], ArrayThunk::Pending) else { + unreachable!() + }; + + let val = self + .0 + .inner + .get(index) + .transpose() + .expect("index checked") + .and_then(|r| self.0.mapper.evaluate_simple(&(Any(r),))); + + let new_value = match val { + Ok(v) => v, + Err(e) => { + self.0.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone()); + return Err(e); + } + }; + self.0.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone()); + Ok(Some(new_value)) + } + fn get_lazy(&self, index: usize) -> Option> { + #[derive(Trace)] + struct ArrayElement { + arr_thunk: MappedArray, + index: usize, + } + + impl ThunkValue for ArrayElement { + type Output = Val; + + fn get(self: Box) -> Result { + self.arr_thunk + .get(self.index) + .transpose() + .expect("index checked") + } + } + + if index >= self.len() { + return None; + } + match &self.0.cached.borrow()[index] { + ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())), + ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())), + ArrayThunk::Waiting(_) | ArrayThunk::Pending => {} + }; + + Some(Thunk::new(tb!(ArrayElement { + arr_thunk: self.clone(), + index, + }))) + } + + fn get_cheap(&self, _index: usize) -> Option { + None + } + + fn evaluated(&self) -> Result> { + self.iter().collect() + } + + fn iter(&self) -> MappedArrayIter<'_> { + (0..self.len()).map(|i| self.get(i).transpose().expect("length checked")) + } + + fn iter_lazy(&self) -> MappedArrayLazyIter<'_> { + (0..self.len()).map(|i| self.get_lazy(i).expect("length checked")) + } + + fn iter_cheap(&self) -> Option> { + None + } +} +// impl MappedArray + +macro_rules! impl_iter_enum { + ($n:ident => $v:ident) => { + pub enum $n<'t> { + Bytes(::$v<'t>), + Expr(::$v<'t>), + Lazy(::$v<'t>), + Eager(::$v<'t>), + Range(::$v<'t>), + Slice(Box<::$v<'t>>), + Extended(Box<::$v<'t>>), + Reverse(Box<::$v<'t>>), + Mapped(Box<::$v<'t>>), + } + }; +} + +macro_rules! pass { + ($t:ident.$m:ident($($ident:ident),*)) => { + match $t { + Self::Bytes(e) => e.$m($($ident)*), + Self::Expr(e) => e.$m($($ident)*), + Self::Lazy(e) => e.$m($($ident)*), + Self::Eager(e) => e.$m($($ident)*), + Self::Range(e) => e.$m($($ident)*), + Self::Slice(e) => e.$m($($ident)*), + Self::Extended(e) => e.$m($($ident)*), + Self::Reverse(e) => e.$m($($ident)*), + Self::Mapped(e) => e.$m($($ident)*), + } + }; +} +pub(super) use pass; + +macro_rules! pass_iter_call { + ($t:ident.$c:ident $(in $wrap:ident)? => $e:ident) => { + match $t { + ArrValue::Bytes(e) => $e::Bytes($($wrap!)?(e.$c())), + ArrValue::Lazy(e) => $e::Lazy($($wrap!)?(e.$c())), + ArrValue::Expr(e) => $e::Expr($($wrap!)?(e.$c())), + ArrValue::Eager(e) => $e::Eager($($wrap!)?(e.$c())), + ArrValue::Range(e) => $e::Range($($wrap!)?(e.$c())), + ArrValue::Slice(e) => $e::Slice(Box::new($($wrap!)?(e.$c()))), + ArrValue::Extended(e) => $e::Extended(Box::new($($wrap!)?(e.$c()))), + ArrValue::Reverse(e) => $e::Reverse(Box::new($($wrap!)?(e.$c()))), + ArrValue::Mapped(e) => $e::Mapped(Box::new($($wrap!)?(e.$c()))), + } + }; +} +pub(super) use pass_iter_call; + +macro_rules! impl_iter { + ($t:ident => $out:ty) => { + impl Iterator for $t<'_> { + type Item = $out; + + fn next(&mut self) -> Option { + pass!(self.next()) + } + fn nth(&mut self, count: usize) -> Option { + pass!(self.nth(count)) + } + fn size_hint(&self) -> (usize, Option) { + pass!(self.size_hint()) + } + } + impl DoubleEndedIterator for $t<'_> { + fn next_back(&mut self) -> Option { + pass!(self.next_back()) + } + fn nth_back(&mut self, count: usize) -> Option { + pass!(self.nth_back(count)) + } + } + impl ExactSizeIterator for $t<'_> { + fn len(&self) -> usize { + match self { + Self::Bytes(e) => e.len(), + Self::Expr(e) => e.len(), + Self::Lazy(e) => e.len(), + Self::Eager(e) => e.len(), + Self::Range(e) => e.len(), + Self::Slice(e) => e.len(), + Self::Extended(e) => { + e.size_hint().1.expect("overflow is checked in constructor") + } + Self::Reverse(e) => e.len(), + Self::Mapped(e) => e.len(), + } + } + } + }; +} + +impl_iter_enum!(UnknownArrayIter => Iter); +impl_iter_enum!(UnknownArrayIterLazy => IterLazy); +impl_iter_enum!(UnknownArrayIterCheap => IterCheap); +impl_iter!(UnknownArrayIter => Result); +impl_iter!(UnknownArrayIterLazy => Thunk); +impl_iter!(UnknownArrayIterCheap => Val); --- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -32,7 +32,7 @@ Destruct::Array { start, rest, end } => { use jrsonnet_parser::DestructRest; - use crate::val::ArrValue; + use crate::arr::ArrValue; #[derive(Trace)] struct DataThunk { @@ -110,7 +110,10 @@ fn get(self: Box) -> Result { let full = self.full.evaluate()?; let to = full.len() - self.end; - Ok(Val::Arr(full.slice(Some(self.start), Some(to), None))) + Ok(Val::Arr( + full.slice(Some(self.start), Some(to), None) + .expect("arguments checked"), + )) } } --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -10,19 +10,53 @@ use self::destructure::destruct; use crate::{ + arr::ArrValue, destructure::evaluate_dest, error::ErrorKind::*, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal}, tb, throw, typed::Typed, - val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue}, + val::{CachedUnbound, IndexableVal, Thunk, ThunkValue}, Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State, Unbound, Val, }; pub mod destructure; pub mod operator; +pub fn evaluate_trivial(expr: &LocExpr) -> Option { + fn is_trivial(expr: &LocExpr) -> bool { + match &*expr.0 { + Expr::Str(_) + | Expr::Num(_) + | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true, + Expr::Arr(a) => a.iter().all(is_trivial), + Expr::Parened(e) => is_trivial(e), + _ => false, + } + } + Some(match &*expr.0 { + Expr::Str(s) => Val::Str(s.clone()), + Expr::Num(n) => Val::Num(*n), + Expr::Literal(LiteralType::False) => Val::Bool(false), + Expr::Literal(LiteralType::True) => Val::Bool(true), + Expr::Literal(LiteralType::Null) => Val::Null, + Expr::Arr(n) => { + if n.iter().any(|e| !is_trivial(e)) { + return None; + } + Val::Arr(ArrValue::eager(Cc::new( + n.iter() + .map(evaluate_trivial) + .map(|e| e.expect("checked trivial")) + .collect(), + ))) + } + Expr::Parened(e) => evaluate_trivial(e)?, + _ => return None, + }) +} + pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val { Val::Func(FuncVal::Normal(Cc::new(FuncDesc { name, @@ -100,7 +134,7 @@ let fctx = Pending::new(); let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint()); - let value = Thunk::evaluated(Val::Arr(ArrValue::Lazy(Cc::new(vec![ + let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(Cc::new(vec![ Thunk::evaluated(Val::Str(field.clone())), Thunk::new(tb!(ObjectFieldThunk { field: field.clone(), @@ -380,6 +414,9 @@ #[allow(clippy::too_many_lines)] pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result { use Expr::*; + if let Some(trivial) = evaluate_trivial(&expr) { + return Ok(trivial); + } let LocExpr(expr, loc) = expr; Ok(match &**expr { Literal(LiteralType::This) => { @@ -507,9 +544,9 @@ evaluate(ctx, &returned.clone())? } Arr(items) => { - let mut out = Vec::with_capacity(items.len()); - for item in items { - // TODO: Implement ArrValue::Lazy with same context for every element? + if items.is_empty() { + Val::Arr(ArrValue::empty()) + } else if items.len() == 1 { #[derive(Trace)] struct ArrayElement { ctx: Context, @@ -521,12 +558,15 @@ evaluate(self.ctx, &self.item) } } - out.push(Thunk::new(tb!(ArrayElement { - ctx: ctx.clone(), - item: item.clone(), - }))); + Val::Arr(ArrValue::lazy(Cc::new(vec![Thunk::new(tb!( + ArrayElement { + ctx, + item: items[0].clone(), + } + ))]))) + } else { + Val::Arr(ArrValue::expr(ctx, items.iter().cloned())) } - Val::Arr(out.into()) } ArrComp(expr, comp_specs) => { let mut out = Vec::new(); @@ -534,7 +574,7 @@ out.push(evaluate(ctx, expr)?); Ok(()) })?; - Val::Arr(ArrValue::Eager(Cc::new(out))) + Val::Arr(ArrValue::eager(Cc::new(out))) } Obj(body) => Val::Obj(evaluate_object(ctx, body)?), ObjExtend(a, b) => evaluate_add_op( @@ -615,7 +655,7 @@ || s.import_resolved(resolved_path), )?, ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?), - ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)), + ImportBin(_) => Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?)), _ => unreachable!(), } } --- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -3,8 +3,8 @@ use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType}; use crate::{ - error::ErrorKind::*, evaluate, stdlib::std_format, throw, typed::Typed, val::equals, Context, - Result, Val, + arr::ArrValue, error::ErrorKind::*, evaluate, stdlib::std_format, throw, typed::Typed, + val::equals, Context, Result, Val, }; pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result { @@ -18,6 +18,8 @@ }) } +/// Arbitrary threshold + pub fn evaluate_add_op(a: &Val, b: &Val) -> Result { use Val::*; Ok(match (a, b) { @@ -34,12 +36,8 @@ (o, Str(a)) => Str(format!("{}{a}", o.clone().to_string()?).into()), (Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())), - (Arr(a), Arr(b)) => { - let mut out = Vec::with_capacity(a.len() + b.len()); - out.extend(a.iter_lazy()); - out.extend(b.iter_lazy()); - Arr(out.into()) - } + (Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())), + (Num(v1), Num(v2)) => Val::new_checked_num(v1 + v2)?, _ => throw!(BinaryOperatorDoesNotOperateOnValues( BinaryOpType::Add, @@ -82,7 +80,7 @@ }) } -pub fn evaluate_compare_op(a: &Val, op: BinaryOpType, b: &Val) -> Result { +pub fn evaluate_compare_op(a: &Val, b: &Val, op: BinaryOpType) -> Result { use Val::*; Ok(match (a, b) { (Str(a), Str(b)) => a.cmp(b), @@ -92,7 +90,7 @@ let bi = b.iter(); for (a, b) in ai.zip(bi) { - let ord = evaluate_compare_op(&a?, op, &b?)?; + let ord = evaluate_compare_op(&a?, &b?, op)?; if !ord.is_eq() { return Ok(ord); } @@ -117,10 +115,10 @@ (a, Eq, b) => Bool(equals(a, b)?), (a, Neq, b) => Bool(!equals(a, b)?), - (a, Lt, b) => Bool(evaluate_compare_op(a, Lt, b)?.is_lt()), - (a, Gt, b) => Bool(evaluate_compare_op(a, Gt, b)?.is_gt()), - (a, Lte, b) => Bool(evaluate_compare_op(a, Lte, b)?.is_le()), - (a, Gte, b) => Bool(evaluate_compare_op(a, Gte, b)?.is_ge()), + (a, Lt, b) => Bool(evaluate_compare_op(a, b, Lt)?.is_lt()), + (a, Gt, b) => Bool(evaluate_compare_op(a, b, Gt)?.is_gt()), + (a, Lte, b) => Bool(evaluate_compare_op(a, b, Lte)?.is_le()), + (a, Gte, b) => Bool(evaluate_compare_op(a, b, Gte)?.is_ge()), (Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone(), true)), (a, Mod, b) => evaluate_mod_op(a, b)?, --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -7,7 +7,7 @@ Deserialize, Serialize, }; -use crate::{error::Result, val::ArrValue, ObjValueBuilder, State, Val}; +use crate::{arr::ArrValue, error::Result, ObjValueBuilder, State, Val}; impl<'de> Deserialize<'de> for Val { fn deserialize(deserializer: D) -> Result @@ -77,7 +77,7 @@ where E: serde::de::Error, { - Ok(Val::Arr(ArrValue::Bytes(v.into()))) + Ok(Val::Arr(ArrValue::bytes(v.into()))) } fn visit_none(self) -> Result @@ -117,7 +117,7 @@ out.push(val); } - Ok(Val::Arr(ArrValue::Eager(Cc::new(out)))) + Ok(Val::Arr(ArrValue::eager(Cc::new(out)))) } fn visit_map(self, mut map: A) -> Result --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -1,5 +1,6 @@ //! jsonnet interpreter implementation #![cfg_attr(feature = "nightly", feature(thread_local))] +#![feature(type_alias_impl_trait)] #![deny(unsafe_op_in_unsafe_fn)] #![warn( clippy::all, @@ -43,6 +44,7 @@ // For jrsonnet-macros extern crate self as jrsonnet_evaluator; +mod arr; mod ctx; mod dynamic; pub mod error; --- a/crates/jrsonnet-evaluator/src/stdlib/mod.rs +++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs @@ -14,7 +14,7 @@ || format!("std.format of {str}"), || { Ok(match vals { - Val::Arr(vals) => format_arr(&str, &vals.evaluated()?)?, + Val::Arr(vals) => format_arr(&str, &vals.evaluatedcc()?)?, Val::Obj(obj) => format_obj(&str, &obj)?, o => format_arr(&str, &[o])?, }) --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -6,11 +6,12 @@ use jrsonnet_types::{ComplexValType, ValType}; use crate::{ + arr::ArrValue, error::Result, function::{native::NativeDesc, FuncDesc, FuncVal}, throw, typed::CheckType, - val::{ArrValue, IndexableVal}, + val::IndexableVal, ObjValue, ObjValueBuilder, Val, }; @@ -30,6 +31,8 @@ fn from_untyped(untyped: Val) -> Result; } +const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64; + macro_rules! impl_int { ($($ty:ty)*) => {$( impl Typed for $ty { @@ -155,12 +158,11 @@ } } impl Typed for usize { - // It is possible to store 54 bits of precision in f64, but leaving u32::MAX here for compatibility const TYPE: &'static ComplexValType = - &ComplexValType::BoundedNumber(Some(0.0), Some(u32::MAX as f64)); + &ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER)); fn into_untyped(value: Self) -> Result { - if value > u32::MAX as Self { + if value > MAX_SAFE_INTEGER as Self { throw!("number is too large") } Ok(Val::Num(value as f64)) @@ -282,13 +284,13 @@ const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr); fn into_untyped(value: Self) -> Result { - Ok(Val::Arr(ArrValue::Eager(value.0))) + Ok(Val::Arr(ArrValue::eager(value.0))) } fn from_untyped(value: Val) -> Result { ::TYPE.check(&value)?; match value { - Val::Arr(a) => Ok(Self(a.evaluated()?)), + Val::Arr(a) => Ok(Self(a.evaluatedcc()?)), _ => unreachable!(), } } @@ -300,12 +302,12 @@ &ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0))); fn into_untyped(value: Self) -> Result { - Ok(Val::Arr(ArrValue::Bytes(value))) + Ok(Val::Arr(ArrValue::bytes(value))) } fn from_untyped(value: Val) -> Result { if let Val::Arr(ArrValue::Bytes(bytes)) = value { - return Ok(bytes); + return Ok(bytes.0); } ::TYPE.check(&value)?; match value { --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,9 +1,10 @@ -use std::{cell::RefCell, fmt::Debug}; +use std::{cell::RefCell, fmt::Debug, mem::replace}; use jrsonnet_gcmodule::{Cc, Trace}; -use jrsonnet_interner::{IBytes, IStr}; +use jrsonnet_interner::IStr; use jrsonnet_types::ValType; +pub use crate::arr::ArrValue; use crate::{ error::{Error, ErrorKind::*}, function::FuncVal, @@ -31,16 +32,22 @@ #[derive(Clone, Trace)] pub struct Thunk(Cc>>); +impl Thunk { + pub fn evaluated(val: T) -> Self { + Self(Cc::new(RefCell::new(ThunkInner::Computed(val)))) + } + pub fn new(f: TraceBox>) -> Self { + Self(Cc::new(RefCell::new(ThunkInner::Waiting(f)))) + } + pub fn errored(e: Error) -> Self { + Self(Cc::new(RefCell::new(ThunkInner::Errored(e)))) + } +} + impl Thunk where T: Clone + Trace, { - pub fn new(f: TraceBox>) -> Self { - Self(Cc::new(RefCell::new(ThunkInner::Waiting(f)))) - } - pub fn evaluated(val: T) -> Self { - Self(Cc::new(RefCell::new(ThunkInner::Computed(val)))) - } pub fn force(&self) -> Result<()> { self.evaluate()?; Ok(()) @@ -52,7 +59,7 @@ ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()), ThunkInner::Waiting(..) => (), }; - let ThunkInner::Waiting(value) = std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else { + let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else { unreachable!(); }; let new_value = match value.0.get() { @@ -118,337 +125,8 @@ fn eq(&self, other: &Self) -> bool { Cc::ptr_eq(&self.0, &other.0) } -} - -#[derive(Debug, Clone, Trace)] -pub struct Slice { - pub(crate) inner: ArrValue, - pub(crate) from: u32, - pub(crate) to: u32, - pub(crate) step: u32, } -impl Slice { - const fn from(&self) -> usize { - self.from as usize - } - const fn to(&self) -> usize { - self.to as usize - } - const fn step(&self) -> usize { - self.step as usize - } - const fn len(&self) -> usize { - // TODO: use div_ceil - let diff = self.to() - self.from(); - let rem = diff % self.step(); - let div = diff / self.step(); - - if rem == 0 { - div - } else { - div + 1 - } - } -} - -/// Represents a Jsonnet array value. -#[derive(Debug, Clone, Trace)] -// may contrain other ArrValue -#[trace(tracking(force))] -pub enum ArrValue { - /// Layout optimized byte array. - Bytes(#[trace(skip)] IBytes), - /// Every element is lazy evaluated. - Lazy(Cc>>), - /// Every field is already evaluated. - Eager(Cc>), - /// Concatenation of two arrays of any kind. - Extended(Box<(Self, Self)>), - /// Represents a integer array in form `[start, start + 1, ... end - 1, end]`. - /// This kind of arrays is generated by `std.range(start, end)` call, and used for loops. - Range(i32, i32), - /// Sliced array view. - Slice(Box), - /// Reversed array view. - /// Returned by `std.reverse(other)` call - Reversed(Box), -} - -#[cfg(target_pointer_width = "64")] -static_assertions::assert_eq_size!(ArrValue, [u8; 16]); - -impl ArrValue { - pub fn new_eager() -> Self { - Self::Eager(Cc::new(Vec::new())) - } - pub fn empty() -> Self { - Self::new_range(0, 0) - } - - /// # Panics - /// If a > b - #[inline] - pub fn new_range(a: i32, b: i32) -> Self { - assert!(a <= b); - Self::Range(a, b) - } - - /// # Panics - /// If passed numbers are incorrect - #[must_use] - pub fn slice(self, from: Option, to: Option, step: Option) -> Self { - let len = self.len(); - let from = from.unwrap_or(0); - let to = to.unwrap_or(len).min(len); - let step = step.unwrap_or(1); - assert!(from < to); - assert!(step > 0); - - Self::Slice(Box::new(Slice { - inner: self, - from: from as u32, - to: to as u32, - step: step as u32, - })) - } - /// Array length. - pub fn len(&self) -> usize { - match self { - Self::Bytes(i) => i.len(), - Self::Lazy(l) => l.len(), - Self::Eager(e) => e.len(), - Self::Extended(v) => v.0.len() + v.1.len(), - Self::Range(a, b) => a.abs_diff(*b) as usize + 1, - Self::Reversed(i) => i.len(), - Self::Slice(s) => s.len(), - } - } - - /// Is array contains no elements? - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Get array element by index, evaluating it, if it is lazy. - /// - /// Returns `None` on out-of-bounds condition. - pub fn get(&self, index: usize) -> Result> { - match self { - Self::Bytes(i) => i - .get(index) - .map_or(Ok(None), |v| Ok(Some(Val::Num(f64::from(*v))))), - Self::Lazy(vec) => { - if let Some(v) = vec.get(index) { - Ok(Some(v.evaluate()?)) - } else { - Ok(None) - } - } - Self::Eager(vec) => Ok(vec.get(index).cloned()), - Self::Extended(v) => { - let a_len = v.0.len(); - if a_len > index { - v.0.get(index) - } else { - v.1.get(index - a_len) - } - } - Self::Range(a, _) => { - if index >= self.len() { - return Ok(None); - } - Ok(Some(Val::Num(((*a as isize) + index as isize) as f64))) - } - Self::Reversed(v) => { - let len = v.len(); - if index >= len { - return Ok(None); - } - v.get(len - index - 1) - } - Self::Slice(v) => { - let index = v.from() + index * v.step(); - if index >= v.to() { - return Ok(None); - } - v.inner.get(index) - } - } - } - - /// Get array element by index, without evaluation. - /// - /// Returns `None` on out-of-bounds condition. - pub fn get_lazy(&self, index: usize) -> Option> { - match self { - Self::Bytes(i) => i - .get(index) - .map(|b| Thunk::evaluated(Val::Num(f64::from(*b)))), - Self::Lazy(vec) => vec.get(index).cloned(), - Self::Eager(vec) => vec.get(index).cloned().map(Thunk::evaluated), - Self::Extended(v) => { - let a_len = v.0.len(); - if a_len > index { - v.0.get_lazy(index) - } else { - v.1.get_lazy(index - a_len) - } - } - Self::Range(a, _) => { - if index >= self.len() { - return None; - } - Some(Thunk::evaluated(Val::Num( - ((*a as isize) + index as isize) as f64, - ))) - } - Self::Reversed(v) => { - let len = v.len(); - if index >= len { - return None; - } - v.get_lazy(len - index - 1) - } - Self::Slice(s) => { - let index = s.from() + index * s.step(); - if index >= s.to() { - return None; - } - s.inner.get_lazy(index) - } - } - } - - /// Evaluate all array elements, returning new array. - pub fn evaluated(&self) -> Result>> { - Ok(match self { - Self::Bytes(i) => { - let mut out = Vec::with_capacity(i.len()); - for v in i.iter() { - out.push(Val::Num(f64::from(*v))); - } - Cc::new(out) - } - Self::Lazy(vec) => { - let mut out = Vec::with_capacity(vec.len()); - for item in vec.iter() { - out.push(item.evaluate()?); - } - Cc::new(out) - } - Self::Eager(vec) => vec.clone(), - Self::Extended(_v) => { - let mut out = Vec::with_capacity(self.len()); - for item in self.iter() { - out.push(item?); - } - Cc::new(out) - } - Self::Range(a, b) => { - let mut out = Vec::with_capacity(self.len()); - for i in *a..=*b { - out.push(Val::Num(f64::from(i))); - } - Cc::new(out) - } - Self::Reversed(r) => { - let mut r = r.evaluated()?; - Cc::update_with(&mut r, |v| v.reverse()); - r - } - Self::Slice(v) => { - let mut out = Vec::with_capacity(v.inner.len()); - for v in v - .inner - .iter_lazy() - .skip(v.from()) - .take(v.to() - v.from()) - .step_by(v.step()) - { - out.push(v.evaluate()?); - } - Cc::new(out) - } - }) - } - - /// Iterate over elements, evaluating them. - pub fn iter(&self) -> impl DoubleEndedIterator> + '_ { - (0..self.len()).map(move |idx| match self { - Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))), - Self::Lazy(l) => l[idx].evaluate(), - Self::Eager(e) => Ok(e[idx].clone()), - Self::Extended(..) | Self::Range(..) | Self::Reversed(..) | Self::Slice(..) => { - self.get(idx).map(|e| e.expect("idx < len")) - } - }) - } - - /// Iterate over elements, returning lazy values. - pub fn iter_lazy(&self) -> impl DoubleEndedIterator> + '_ { - (0..self.len()).map(move |idx| match self { - Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))), - Self::Lazy(l) => l[idx].clone(), - Self::Eager(e) => Thunk::evaluated(e[idx].clone()), - Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => { - self.get_lazy(idx).expect("idx < len") - } - }) - } - - /// Return a reversed view on current array. - #[must_use] - pub fn reversed(self) -> Self { - Self::Reversed(Box::new(self)) - } - - /// Return a new array, produced by passing every element of current array to specified callback function. - pub fn map(self, mapper: impl Fn(Val) -> Result) -> Result { - let mut out = Vec::with_capacity(self.len()); - - for value in self.iter() { - out.push(mapper(value?)?); - } - - Ok(Self::Eager(Cc::new(out))) - } - - /// Return a new array, produced from current array by removing every value, for which specified callback function returns false. - pub fn filter(self, filter: impl Fn(&Val) -> Result) -> Result { - let mut out = Vec::with_capacity(self.len()); - - for value in self.iter() { - let value = value?; - if filter(&value)? { - out.push(value); - } - } - - Ok(Self::Eager(Cc::new(out))) - } - - pub fn ptr_eq(a: &Self, b: &Self) -> bool { - match (a, b) { - (Self::Lazy(a), Self::Lazy(b)) => Cc::ptr_eq(a, b), - (Self::Eager(a), Self::Eager(b)) => Cc::ptr_eq(a, b), - _ => false, - } - } -} - -impl From>> for ArrValue { - fn from(v: Vec>) -> Self { - Self::Lazy(Cc::new(v)) - } -} - -impl From> for ArrValue { - fn from(v: Vec) -> Self { - Self::Eager(Cc::new(v)) - } -} - /// Represents a Jsonnet value, which can be spliced or indexed (string or array). #[allow(clippy::module_name_repetitions)] pub enum IndexableVal { @@ -496,15 +174,14 @@ let step = step.as_deref().copied().unwrap_or(1); if index >= end { - return Ok(Self::Arr(ArrValue::new_eager())); + return Ok(Self::Arr(ArrValue::empty())); } - Ok(Self::Arr(ArrValue::Slice(Box::new(Slice { - inner: arr.clone(), - from: index as u32, - to: end as u32, - step: step as u32, - })))) + Ok(Self::Arr( + arr.clone() + .slice(Some(index), Some(end), Some(step)) + .expect("arguments checked"), + )) } } } @@ -539,10 +216,6 @@ } } } - -// Broken between stable and nightly, as there is new layout size optimization -// #[cfg(target_pointer_width = "64")] -// static_assertions::assert_eq_size!(Val, [u8; 24]); impl Val { pub const fn as_bool(&self) -> Option { --- a/crates/jrsonnet-stdlib/src/arrays.rs +++ b/crates/jrsonnet-stdlib/src/arrays.rs @@ -28,8 +28,8 @@ } #[builtin] -pub fn builtin_map(func: NativeFn<((Any,), Any)>, arr: ArrValue) -> Result { - arr.map(|val| Ok(func(Any(val))?.0)) +pub fn builtin_map(func: FuncVal, arr: ArrValue) -> Result { + Ok(arr.map(func)) } #[builtin] @@ -94,9 +94,9 @@ #[builtin] pub fn builtin_range(from: i32, to: i32) -> Result { if to < from { - return Ok(ArrValue::new_eager()); + return Ok(ArrValue::empty()); } - Ok(ArrValue::new_range(from, to)) + Ok(ArrValue::range_inclusive(from, to)) } #[builtin] --- a/crates/jrsonnet-stdlib/src/misc.rs +++ b/crates/jrsonnet-stdlib/src/misc.rs @@ -77,7 +77,7 @@ } else if b.len() == a.len() { return equals(&Val::Arr(a), &Val::Arr(b)); } else { - for (a, b) in a.slice(None, Some(b.len()), None).iter().zip(b.iter()) { + for (a, b) in a.iter().take(b.len()).zip(b.iter()) { let a = a?; let b = b?; if !equals(&a, &b)? { @@ -103,11 +103,7 @@ return equals(&Val::Arr(a), &Val::Arr(b)); } else { let a_len = a.len(); - for (a, b) in a - .slice(Some(a_len - b.len()), None, None) - .iter() - .zip(b.iter()) - { + for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) { let a = a?; let b = b?; if !equals(&a, &b)? { --- a/crates/jrsonnet-stdlib/src/sort.rs +++ b/crates/jrsonnet-stdlib/src/sort.rs @@ -106,9 +106,9 @@ if arr.len() <= 1 { return Ok(arr); } - Ok(ArrValue::Eager(super::sort::sort( + Ok(ArrValue::eager(super::sort::sort( ctx, - arr.evaluated()?, + arr.evaluatedcc()?, keyF.unwrap_or_else(FuncVal::identity), )?)) } -- gitstuff