diff --git a/crates/jrsonnet-evaluator/src/val.rs b/crates/jrsonnet-evaluator/src/val.rs index 5ccf978b..f9e0071f 100644 --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -234,14 +234,34 @@ impl IndexableVal { /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned. pub fn slice( self, - index: Option>, - end: Option>, + index: Option, + end: Option, step: Option>, ) -> Result { match &self { IndexableVal::Str(s) => { - let index = index.as_deref().copied().unwrap_or(0); - let end = end.as_deref().copied().unwrap_or(usize::MAX); + let mut computed_len = None; + let mut get_len = || { + computed_len.map_or_else( + || { + let len = s.chars().count(); + let _ = computed_len.insert(len); + len + }, + |len| len, + ) + }; + let mut get_idx = |pos: Option, default| { + match pos { + Some(v) if v < 0 => get_len().saturating_sub((-v) as usize), + // No need to clamp, as iterator interface is used + Some(v) => v as usize, + None => default, + } + }; + + let index = get_idx(index, 0); + let end = get_idx(end, usize::MAX); let step = step.as_deref().copied().unwrap_or(1); if index >= end { @@ -258,8 +278,13 @@ impl IndexableVal { )) } IndexableVal::Arr(arr) => { - let index = index.as_deref().copied().unwrap_or(0); - let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len()); + let get_idx = |pos: Option, len: usize, default| match pos { + Some(v) if v < 0 => len.saturating_sub((-v) as usize), + Some(v) => (v as usize).min(len), + None => default, + }; + let index = get_idx(index, arr.len(), 0); + let end = get_idx(end, arr.len(), arr.len()); let step = step.as_deref().copied().unwrap_or(1); if index >= end { diff --git a/crates/jrsonnet-stdlib/src/arrays.rs b/crates/jrsonnet-stdlib/src/arrays.rs index 11fd7c72..39a73e88 100644 --- a/crates/jrsonnet-stdlib/src/arrays.rs +++ b/crates/jrsonnet-stdlib/src/arrays.rs @@ -47,8 +47,8 @@ pub fn builtin_repeat(what: Either![IStr, ArrValue], count: usize) -> Result>, - end: Option>, + index: Option, + end: Option, step: Option>, ) -> Result { indexable.slice(index, end, step).map(Val::from)