difftreelog
feat make std.map accept strings
in: master
5 files changed
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IBytes;5use jrsonnet_parser::LocExpr;67use crate::{function::FuncVal, Context, Result, Thunk, Val};89mod spec;10use spec::*;1112/// Represents a Jsonnet array value.13#[derive(Debug, Clone, Trace)]14// may contrain other ArrValue15#[trace(tracking(force))]16pub enum ArrValue {17 /// Layout optimized byte array.18 Bytes(BytesArray),19 /// Layout optimized char array.20 Chars(CharArray),21 /// Every element is lazy evaluated.22 Lazy(LazyArray),23 /// Every element is defined somewhere in source code24 Expr(ExprArray),25 /// Every field is already evaluated.26 Eager(EagerArray),27 /// Concatenation of two arrays of any kind.28 Extended(Cc<ExtendedArray>),29 /// Represents a integer array in form `[start, start + 1, ... end - 1, end]`.30 /// This kind of arrays is generated by `std.range(start, end)` call, and used for loops.31 Range(RangeArray),32 /// Sliced array view.33 Slice(Cc<SliceArray>),34 /// Reversed array view.35 /// Returned by `std.reverse(other)` call36 Reverse(Cc<ReverseArray>),37 /// Returned by `std.map` call38 Mapped(MappedArray),39 /// Returned by `std.repeat` call40 Repeated(RepeatedArray),41}4243pub trait ArrayLikeIter<T>: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator {}44impl<I, T> ArrayLikeIter<T> for I where45 I: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator46{47}4849impl ArrValue {50 pub fn empty() -> Self {51 Self::Range(RangeArray::empty())52 }5354 pub fn expr(ctx: Context, exprs: impl IntoIterator<Item = LocExpr>) -> Self {55 Self::Expr(ExprArray::new(ctx, exprs))56 }5758 pub fn lazy(thunks: Cc<Vec<Thunk<Val>>>) -> Self {59 Self::Lazy(LazyArray(thunks))60 }6162 pub fn eager(values: Vec<Val>) -> Self {63 Self::Eager(EagerArray(Cc::new(values)))64 }6566 pub fn repeated(data: ArrValue, repeats: usize) -> Option<Self> {67 Some(Self::Repeated(RepeatedArray::new(data, repeats)?))68 }6970 pub fn bytes(bytes: IBytes) -> Self {71 Self::Bytes(BytesArray(bytes))72 }73 pub fn chars(chars: impl Iterator<Item = char>) -> Self {74 Self::Chars(CharArray(Rc::new(chars.collect())))75 }7677 #[must_use]78 pub fn map(self, mapper: FuncVal) -> Self {79 Self::Mapped(MappedArray::new(self, mapper))80 }8182 pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {83 // TODO: ArrValue::Picked(inner, indexes) for large arrays84 let mut out = Vec::new();85 for i in self.iter() {86 let i = i?;87 if filter(&i)? {88 out.push(i);89 };90 }91 Ok(Self::eager(out))92 }9394 pub fn extended(a: ArrValue, b: ArrValue) -> Self {95 // TODO: benchmark for an optimal value, currently just a arbitrary choice96 const ARR_EXTEND_THRESHOLD: usize = 100;9798 if a.is_empty() {99 b100 } else if b.is_empty() {101 a102 } else if a.len() + b.len() > ARR_EXTEND_THRESHOLD {103 Self::Extended(Cc::new(ExtendedArray::new(a, b)))104 } else if let (Some(a), Some(b)) = (a.iter_cheap(), b.iter_cheap()) {105 let mut out = Vec::with_capacity(a.len() + b.len());106 out.extend(a);107 out.extend(b);108 Self::eager(out)109 } else {110 let mut out = Vec::with_capacity(a.len() + b.len());111 out.extend(a.iter_lazy());112 out.extend(b.iter_lazy());113 Self::lazy(Cc::new(out))114 }115 }116117 pub fn range_exclusive(a: i32, b: i32) -> Self {118 Self::Range(RangeArray::new_exclusive(a, b))119 }120 pub fn range_inclusive(a: i32, b: i32) -> Self {121 Self::Range(RangeArray::new_inclusive(a, b))122 }123124 #[must_use]125 pub fn slice(126 self,127 from: Option<usize>,128 to: Option<usize>,129 step: Option<usize>,130 ) -> Option<Self> {131 let len = self.len();132 let from = from.unwrap_or(0);133 let to = to.unwrap_or(len).min(len);134 let step = step.unwrap_or(1);135136 if from >= to || step == 0 {137 return None;138 }139 // match self {140 // ArrValue::Slice(slice) => {141 // return Some(Self::Slice(Cc::new(SliceArray {142 // inner: slice.inner.clone(),143 // from: slice.from + slice.step * (from as u32),144 // to: slice.from + (to as u32) * slice.step,145 // step: slice.step * step as u32,146 // })))147 // }148 // _ => {}149 // }150151 Some(Self::Slice(Cc::new(SliceArray {152 inner: self,153 from: from as u32,154 to: to as u32,155 step: step as u32,156 })))157 }158159 /// Array length.160 pub fn len(&self) -> usize {161 pass!(self.len())162 }163164 /// Is array contains no elements?165 pub fn is_empty(&self) -> bool {166 pass!(self.is_empty())167 }168169 /// Get array element by index, evaluating it, if it is lazy.170 ///171 /// Returns `None` on out-of-bounds condition.172 pub fn get(&self, index: usize) -> Result<Option<Val>> {173 pass!(self.get(index))174 }175176 /// Returns None if get is either non cheap, or out of bounds177 fn get_cheap(&self, index: usize) -> Option<Val> {178 pass!(self.get_cheap(index))179 }180181 /// Get array element by index, without evaluation.182 ///183 /// Returns `None` on out-of-bounds condition.184 pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {185 pass!(self.get_lazy(index))186 }187188 #[cfg(feature = "nightly")]189 pub fn iter(&self) -> UnknownArrayIter<'_> {190 pass_iter_call!(self.iter => UnknownArrayIter)191 }192 #[cfg(not(feature = "nightly"))]193 pub fn iter(&self) -> impl ArrayLikeIter<Result<Val>> + '_ {194 (0..self.len()).map(|i| self.get(i).transpose().expect("length checked"))195 }196197 /// Iterate over elements, returning lazy values.198 #[cfg(feature = "nightly")]199 pub fn iter_lazy(&self) -> UnknownArrayIterLazy<'_> {200 pass_iter_call!(self.iter_lazy => UnknownArrayIterLazy)201 }202 #[cfg(not(feature = "nightly"))]203 pub fn iter_lazy(&self) -> impl ArrayLikeIter<Thunk<Val>> + '_ {204 (0..self.len()).map(|i| self.get_lazy(i).expect("length checked"))205 }206207 #[cfg(feature = "nightly")]208 pub fn iter_cheap(&self) -> Option<UnknownArrayIterCheap<'_>> {209 macro_rules! question {210 ($v:expr) => {211 $v?212 };213 }214 Some(pass_iter_call!(self.iter_cheap in question => UnknownArrayIterCheap))215 }216217 #[cfg(not(feature = "nightly"))]218 pub fn iter_cheap(&self) -> Option<impl ArrayLikeIter<Val> + '_> {219 if self.is_cheap() {220 Some((0..self.len()).map(|i| self.get_cheap(i).expect("length and is_cheap checked")))221 } else {222 None223 }224 }225226 /// Return a reversed view on current array.227 #[must_use]228 pub fn reversed(self) -> Self {229 Self::Reverse(Cc::new(ReverseArray(self)))230 }231232 pub fn ptr_eq(a: &Self, b: &Self) -> bool {233 match (a, b) {234 (ArrValue::Bytes(a), ArrValue::Bytes(b)) => a.0 == b.0,235 (ArrValue::Lazy(a), ArrValue::Lazy(b)) => Cc::ptr_eq(&a.0, &b.0),236 (ArrValue::Expr(a), ArrValue::Expr(b)) => Cc::ptr_eq(&a.0, &b.0),237 (ArrValue::Eager(a), ArrValue::Eager(b)) => Cc::ptr_eq(&a.0, &b.0),238 (ArrValue::Extended(a), ArrValue::Extended(b)) => Cc::ptr_eq(a, b),239 (ArrValue::Range(a), ArrValue::Range(b)) => a == b,240 _ => false,241 }242 }243244 /// Is this vec supports `.get_cheap()?`245 pub fn is_cheap(&self) -> bool {246 match self {247 ArrValue::Eager(_) | ArrValue::Range(..) | ArrValue::Bytes(_) | ArrValue::Chars(_) => {248 true249 }250 ArrValue::Extended(v) => v.a.is_cheap() && v.b.is_cheap(),251 ArrValue::Slice(r) => r.inner.is_cheap(),252 ArrValue::Reverse(i) => i.0.is_cheap(),253 ArrValue::Repeated(v) => v.is_cheap(),254 ArrValue::Expr(_) | ArrValue::Lazy(_) | ArrValue::Mapped(_) => false,255 }256 }257}258impl From<Vec<Val>> for ArrValue {259 fn from(value: Vec<Val>) -> Self {260 Self::eager(value)261 }262}263impl From<Vec<Thunk<Val>>> for ArrValue {264 fn from(value: Vec<Thunk<Val>>) -> Self {265 Self::lazy(Cc::new(value))266 }267}268impl FromIterator<Val> for ArrValue {269 fn from_iter<T: IntoIterator<Item = Val>>(iter: T) -> Self {270 Self::eager(iter.into_iter().collect())271 }272}273274#[cfg(target_pointer_width = "64")]275static_assertions::assert_eq_size!(ArrValue, [u8; 16]);crates/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)*),
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -149,6 +149,12 @@
Arr(ArrValue),
}
impl IndexableVal {
+ pub fn to_array(self) -> ArrValue {
+ match self {
+ IndexableVal::Str(s) => ArrValue::chars(s.chars()),
+ IndexableVal::Arr(arr) => arr,
+ }
+ }
/// Slice the value.
///
/// # Implementation
crates/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)
crates/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]