minijinja/value/
ops.rs

1use crate::error::{Error, ErrorKind};
2use crate::value::{DynObject, ObjectRepr, Value, ValueKind, ValueRepr};
3
4const MIN_I128_AS_POS_U128: u128 = 170141183460469231731687303715884105728;
5
6pub enum CoerceResult<'a> {
7    I128(i128, i128),
8    F64(f64, f64),
9    Str(&'a str, &'a str),
10}
11
12pub(crate) fn as_f64(value: &Value, lossy: bool) -> Option<f64> {
13    macro_rules! checked {
14        ($expr:expr, $ty:ty) => {{
15            let rv = $expr as f64;
16            return if lossy || rv as $ty == $expr {
17                Some(rv)
18            } else {
19                None
20            };
21        }};
22    }
23
24    Some(match value.0 {
25        ValueRepr::Bool(x) => x as i64 as f64,
26        ValueRepr::U64(x) => checked!(x, u64),
27        ValueRepr::U128(x) => checked!(x.0, u128),
28        ValueRepr::I64(x) => checked!(x, i64),
29        ValueRepr::I128(x) => checked!(x.0, i128),
30        ValueRepr::F64(x) => x,
31        _ => return None,
32    })
33}
34
35pub fn coerce<'x>(a: &'x Value, b: &'x Value, lossy: bool) -> Option<CoerceResult<'x>> {
36    match (&a.0, &b.0) {
37        // equal mappings are trivial
38        (ValueRepr::U64(a), ValueRepr::U64(b)) => Some(CoerceResult::I128(*a as i128, *b as i128)),
39        (ValueRepr::U128(a), ValueRepr::U128(b)) => {
40            Some(CoerceResult::I128(a.0 as i128, b.0 as i128))
41        }
42        (ValueRepr::String(a, _), ValueRepr::String(b, _)) => Some(CoerceResult::Str(a, b)),
43        (ValueRepr::SmallStr(a), ValueRepr::SmallStr(b)) => {
44            Some(CoerceResult::Str(a.as_str(), b.as_str()))
45        }
46        (ValueRepr::SmallStr(a), ValueRepr::String(b, _)) => Some(CoerceResult::Str(a.as_str(), b)),
47        (ValueRepr::String(a, _), ValueRepr::SmallStr(b)) => Some(CoerceResult::Str(a, b.as_str())),
48        (ValueRepr::I64(a), ValueRepr::I64(b)) => Some(CoerceResult::I128(*a as i128, *b as i128)),
49        (ValueRepr::I128(a), ValueRepr::I128(b)) => Some(CoerceResult::I128(a.0, b.0)),
50        (ValueRepr::F64(a), ValueRepr::F64(b)) => Some(CoerceResult::F64(*a, *b)),
51
52        // are floats involved?
53        (ValueRepr::F64(a), _) => Some(CoerceResult::F64(*a, some!(as_f64(b, lossy)))),
54        (_, ValueRepr::F64(b)) => Some(CoerceResult::F64(some!(as_f64(a, lossy)), *b)),
55
56        // everything else goes up to i128
57        _ => Some(CoerceResult::I128(
58            some!(i128::try_from(a.clone()).ok()),
59            some!(i128::try_from(b.clone()).ok()),
60        )),
61    }
62}
63
64fn get_offset_and_len<F: FnOnce() -> usize>(
65    start: Option<i64>,
66    stop: Option<i64>,
67    end: F,
68) -> (usize, usize) {
69    let start = start.unwrap_or(0);
70    if start < 0 || stop.map_or(true, |x| x < 0) {
71        let end = end();
72        let start = if start < 0 {
73            (end as i64 + start) as usize
74        } else {
75            start as usize
76        };
77        let stop = match stop {
78            None => end,
79            Some(x) if x < 0 => (end as i64 + x) as usize,
80            Some(x) => x as usize,
81        };
82        (start, stop.saturating_sub(start))
83    } else {
84        (
85            start as usize,
86            (stop.unwrap() as usize).saturating_sub(start as usize),
87        )
88    }
89}
90
91fn range_step_backwards(
92    start: Option<i64>,
93    stop: Option<i64>,
94    step: usize,
95    end: usize,
96) -> impl Iterator<Item = usize> {
97    let start = match start {
98        None => end.saturating_sub(1),
99        Some(start) if start >= end as i64 => end.saturating_sub(1),
100        Some(start) if start >= 0 => start as usize,
101        Some(start) => (end as i64 + start).max(0) as usize,
102    };
103    let stop = match stop {
104        None => 0,
105        Some(stop) if stop < 0 => (end as i64 + stop).max(0) as usize,
106        Some(stop) => stop as usize,
107    };
108    let length = if stop == 0 {
109        (start + step) / step
110    } else {
111        (start - stop + step - 1) / step
112    };
113    (stop..=start).rev().step_by(step).take(length)
114}
115
116pub fn slice(value: Value, start: Value, stop: Value, step: Value) -> Result<Value, Error> {
117    let start = if start.is_none() {
118        None
119    } else {
120        Some(ok!(start.try_into()))
121    };
122    let stop = if stop.is_none() {
123        None
124    } else {
125        Some(ok!(i64::try_from(stop)))
126    };
127    let step = if step.is_none() {
128        1i64
129    } else {
130        ok!(i64::try_from(step))
131    };
132    if step == 0 {
133        return Err(Error::new(
134            ErrorKind::InvalidOperation,
135            "cannot slice by step size of 0",
136        ));
137    }
138
139    let kind = value.kind();
140    let error = Err(Error::new(
141        ErrorKind::InvalidOperation,
142        format!("value of type {} cannot be sliced", kind),
143    ));
144
145    match value.0 {
146        ValueRepr::String(..) | ValueRepr::SmallStr(_) => {
147            let s = value.as_str().unwrap();
148            if step > 0 {
149                let (start, len) = get_offset_and_len(start, stop, || s.chars().count());
150                Ok(Value::from(
151                    s.chars()
152                        .skip(start)
153                        .take(len)
154                        .step_by(step as usize)
155                        .collect::<String>(),
156                ))
157            } else {
158                let chars: Vec<char> = s.chars().collect();
159                Ok(Value::from(
160                    range_step_backwards(start, stop, -step as usize, chars.len())
161                        .map(move |i| chars[i])
162                        .collect::<String>(),
163                ))
164            }
165        }
166        ValueRepr::Bytes(ref b) => {
167            if step > 0 {
168                let (start, len) = get_offset_and_len(start, stop, || b.len());
169                Ok(Value::from_bytes(
170                    b.iter()
171                        .skip(start)
172                        .take(len)
173                        .step_by(step as usize)
174                        .copied()
175                        .collect(),
176                ))
177            } else {
178                Ok(Value::from_bytes(
179                    range_step_backwards(start, stop, -step as usize, b.len())
180                        .map(|i| b[i])
181                        .collect::<Vec<u8>>(),
182                ))
183            }
184        }
185        ValueRepr::Undefined(_) | ValueRepr::None => Ok(Value::from(Vec::<Value>::new())),
186        ValueRepr::Object(obj) if matches!(obj.repr(), ObjectRepr::Seq | ObjectRepr::Iterable) => {
187            if step > 0 {
188                let len = obj.enumerator_len().unwrap_or_default();
189                let (start, len) = get_offset_and_len(start, stop, || len);
190                Ok(Value::make_object_iterable(obj, move |obj| {
191                    if let Some(iter) = obj.try_iter() {
192                        Box::new(iter.skip(start).take(len).step_by(step as usize))
193                    } else {
194                        Box::new(None.into_iter())
195                    }
196                }))
197            } else {
198                Ok(Value::make_object_iterable(obj.clone(), move |obj| {
199                    if let Some(iter) = obj.try_iter() {
200                        let vec: Vec<Value> = iter.collect();
201                        Box::new(
202                            range_step_backwards(start, stop, -step as usize, vec.len())
203                                .map(move |i| vec[i].clone()),
204                        )
205                    } else {
206                        Box::new(None.into_iter())
207                    }
208                }))
209            }
210        }
211        _ => error,
212    }
213}
214
215fn int_as_value(val: i128) -> Value {
216    if val as i64 as i128 == val {
217        (val as i64).into()
218    } else {
219        val.into()
220    }
221}
222
223fn impossible_op(op: &str, lhs: &Value, rhs: &Value) -> Error {
224    Error::new(
225        ErrorKind::InvalidOperation,
226        format!(
227            "tried to use {} operator on unsupported types {} and {}",
228            op,
229            lhs.kind(),
230            rhs.kind()
231        ),
232    )
233}
234
235fn failed_op(op: &str, lhs: &Value, rhs: &Value) -> Error {
236    Error::new(
237        ErrorKind::InvalidOperation,
238        format!("unable to calculate {lhs} {op} {rhs}"),
239    )
240}
241
242macro_rules! math_binop {
243    ($name:ident, $int:ident, $float:tt) => {
244        pub fn $name(lhs: &Value, rhs: &Value) -> Result<Value, Error> {
245            match coerce(lhs, rhs, true) {
246                Some(CoerceResult::I128(a, b)) => match a.$int(b) {
247                    Some(val) => Ok(int_as_value(val)),
248                    None => Err(failed_op(stringify!($float), lhs, rhs))
249                },
250                Some(CoerceResult::F64(a, b)) => Ok((a $float b).into()),
251                _ => Err(impossible_op(stringify!($float), lhs, rhs))
252            }
253        }
254    }
255}
256
257pub fn add(lhs: &Value, rhs: &Value) -> Result<Value, Error> {
258    if matches!(lhs.kind(), ValueKind::Seq | ValueKind::Iterable)
259        && matches!(rhs.kind(), ValueKind::Seq | ValueKind::Iterable)
260    {
261        let lhs = lhs.clone();
262        let rhs = rhs.clone();
263        return Ok(Value::make_iterable(move || {
264            if let Ok(lhs) = lhs.try_iter() {
265                if let Ok(rhs) = rhs.try_iter() {
266                    return Box::new(lhs.chain(rhs))
267                        as Box<dyn Iterator<Item = Value> + Send + Sync>;
268                }
269            }
270            Box::new(None.into_iter()) as Box<dyn Iterator<Item = Value> + Send + Sync>
271        }));
272    }
273    match coerce(lhs, rhs, true) {
274        Some(CoerceResult::I128(a, b)) => a
275            .checked_add(b)
276            .ok_or_else(|| failed_op("+", lhs, rhs))
277            .map(int_as_value),
278        Some(CoerceResult::F64(a, b)) => Ok((a + b).into()),
279        Some(CoerceResult::Str(a, b)) => Ok(Value::from([a, b].concat())),
280        _ => Err(impossible_op("+", lhs, rhs)),
281    }
282}
283
284math_binop!(sub, checked_sub, -);
285math_binop!(rem, checked_rem_euclid, %);
286
287pub fn mul(lhs: &Value, rhs: &Value) -> Result<Value, Error> {
288    if let Some((s, n)) = lhs
289        .as_str()
290        .map(|s| (s, rhs))
291        .or_else(|| rhs.as_str().map(|s| (s, lhs)))
292    {
293        return Ok(Value::from(s.repeat(ok!(n.as_usize().ok_or_else(|| {
294            Error::new(
295                ErrorKind::InvalidOperation,
296                "strings can only be multiplied with integers",
297            )
298        })))));
299    } else if let Some((seq, n)) = lhs
300        .as_object()
301        .map(|s| (s, rhs))
302        .or_else(|| rhs.as_object().map(|s| (s, lhs)))
303        .filter(|x| matches!(x.0.repr(), ObjectRepr::Iterable | ObjectRepr::Seq))
304    {
305        return repeat_iterable(n, seq);
306    }
307
308    match coerce(lhs, rhs, true) {
309        Some(CoerceResult::I128(a, b)) => match a.checked_mul(b) {
310            Some(val) => Ok(int_as_value(val)),
311            None => Err(failed_op(stringify!(*), lhs, rhs)),
312        },
313        Some(CoerceResult::F64(a, b)) => Ok((a * b).into()),
314        _ => Err(impossible_op(stringify!(*), lhs, rhs)),
315    }
316}
317
318fn repeat_iterable(n: &Value, seq: &DynObject) -> Result<Value, Error> {
319    struct LenIterWrap<I: Send + Sync>(usize, I);
320
321    impl<I: Iterator<Item = Value> + Send + Sync> Iterator for LenIterWrap<I> {
322        type Item = Value;
323
324        #[inline(always)]
325        fn next(&mut self) -> Option<Self::Item> {
326            self.1.next()
327        }
328
329        #[inline(always)]
330        fn size_hint(&self) -> (usize, Option<usize>) {
331            (self.0, Some(self.0))
332        }
333    }
334
335    let n = ok!(n.as_usize().ok_or_else(|| {
336        Error::new(
337            ErrorKind::InvalidOperation,
338            "sequences and iterables can only be multiplied with integers",
339        )
340    }));
341
342    let len = ok!(seq.enumerator_len().ok_or_else(|| {
343        Error::new(
344            ErrorKind::InvalidOperation,
345            "cannot repeat unsized iterables",
346        )
347    }));
348
349    // This is not optimal.  We only query the enumerator for the length once
350    // but we support repeated iteration.  We could both lie about our length
351    // here and we could actually deal with an object that changes how much
352    // data it returns.  This is not really permissible so we won't try to
353    // improve on this here.
354    Ok(Value::make_object_iterable(seq.clone(), move |seq| {
355        Box::new(LenIterWrap(
356            len * n,
357            (0..n).flat_map(move |_| {
358                seq.try_iter().unwrap_or_else(|| {
359                    Box::new(
360                        std::iter::repeat(Value::from(Error::new(
361                            ErrorKind::InvalidOperation,
362                            "iterable did not iterate against expectations",
363                        )))
364                        .take(len),
365                    )
366                })
367            }),
368        ))
369    }))
370}
371
372pub fn div(lhs: &Value, rhs: &Value) -> Result<Value, Error> {
373    fn do_it(lhs: &Value, rhs: &Value) -> Option<Value> {
374        let a = some!(as_f64(lhs, true));
375        let b = some!(as_f64(rhs, true));
376        Some((a / b).into())
377    }
378    do_it(lhs, rhs).ok_or_else(|| impossible_op("/", lhs, rhs))
379}
380
381pub fn int_div(lhs: &Value, rhs: &Value) -> Result<Value, Error> {
382    match coerce(lhs, rhs, true) {
383        Some(CoerceResult::I128(a, b)) => {
384            if b != 0 {
385                a.checked_div_euclid(b)
386                    .ok_or_else(|| failed_op("//", lhs, rhs))
387                    .map(int_as_value)
388            } else {
389                Err(failed_op("//", lhs, rhs))
390            }
391        }
392        Some(CoerceResult::F64(a, b)) => Ok(a.div_euclid(b).into()),
393        _ => Err(impossible_op("//", lhs, rhs)),
394    }
395}
396
397/// Implements a binary `pow` operation on values.
398pub fn pow(lhs: &Value, rhs: &Value) -> Result<Value, Error> {
399    match coerce(lhs, rhs, true) {
400        Some(CoerceResult::I128(a, b)) => {
401            match TryFrom::try_from(b).ok().and_then(|b| a.checked_pow(b)) {
402                Some(val) => Ok(int_as_value(val)),
403                None => Err(failed_op("**", lhs, rhs)),
404            }
405        }
406        Some(CoerceResult::F64(a, b)) => Ok((a.powf(b)).into()),
407        _ => Err(impossible_op("**", lhs, rhs)),
408    }
409}
410
411/// Implements an unary `neg` operation on value.
412pub fn neg(val: &Value) -> Result<Value, Error> {
413    if val.kind() == ValueKind::Number {
414        match val.0 {
415            ValueRepr::F64(x) => Ok((-x).into()),
416            // special case for the largest i128 that can still be
417            // represented.
418            ValueRepr::U128(x) if x.0 == MIN_I128_AS_POS_U128 => {
419                Ok(Value::from(MIN_I128_AS_POS_U128))
420            }
421            _ => {
422                if let Ok(x) = i128::try_from(val.clone()) {
423                    x.checked_mul(-1)
424                        .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "overflow"))
425                        .map(int_as_value)
426                } else {
427                    Err(Error::from(ErrorKind::InvalidOperation))
428                }
429            }
430        }
431    } else {
432        Err(Error::from(ErrorKind::InvalidOperation))
433    }
434}
435
436/// Attempts a string concatenation.
437pub fn string_concat(left: Value, right: &Value) -> Value {
438    Value::from(format!("{left}{right}"))
439}
440
441/// Implements a containment operation on values.
442pub fn contains(container: &Value, value: &Value) -> Result<Value, Error> {
443    // Special case where if the container is undefined, it cannot hold
444    // values.  For strict containment checks the vm has a special case.
445    if container.is_undefined() {
446        return Ok(Value::from(false));
447    }
448    let rv = if let Some(s) = container.as_str() {
449        if let Some(s2) = value.as_str() {
450            s.contains(s2)
451        } else {
452            s.contains(&value.to_string())
453        }
454    } else if let ValueRepr::Object(ref obj) = container.0 {
455        match obj.repr() {
456            ObjectRepr::Plain => false,
457            ObjectRepr::Map => obj.get_value(value).is_some(),
458            ObjectRepr::Seq | ObjectRepr::Iterable => {
459                obj.try_iter().into_iter().flatten().any(|v| &v == value)
460            }
461        }
462    } else {
463        return Err(Error::new(
464            ErrorKind::InvalidOperation,
465            "cannot perform a containment check on this value",
466        ));
467    };
468    Ok(Value::from(rv))
469}
470
471#[cfg(test)]
472mod tests {
473    use super::*;
474
475    use similar_asserts::assert_eq;
476
477    #[test]
478    fn test_neg() {
479        let err = neg(&Value::from(i128::MIN)).unwrap_err();
480        assert_eq!(err.to_string(), "invalid operation: overflow");
481    }
482
483    #[test]
484    fn test_adding() {
485        let err = add(&Value::from("a"), &Value::from(42)).unwrap_err();
486        assert_eq!(
487            err.to_string(),
488            "invalid operation: tried to use + operator on unsupported types string and number"
489        );
490
491        assert_eq!(
492            add(&Value::from(1), &Value::from(2)).unwrap(),
493            Value::from(3)
494        );
495        assert_eq!(
496            add(&Value::from("foo"), &Value::from("bar")).unwrap(),
497            Value::from("foobar")
498        );
499
500        let err = add(&Value::from(i128::MAX), &Value::from(1)).unwrap_err();
501        assert_eq!(
502            err.to_string(),
503            "invalid operation: unable to calculate 170141183460469231731687303715884105727 + 1"
504        );
505    }
506
507    #[test]
508    fn test_subtracting() {
509        let err = sub(&Value::from("a"), &Value::from(42)).unwrap_err();
510        assert_eq!(
511            err.to_string(),
512            "invalid operation: tried to use - operator on unsupported types string and number"
513        );
514
515        let err = sub(&Value::from("foo"), &Value::from("bar")).unwrap_err();
516        assert_eq!(
517            err.to_string(),
518            "invalid operation: tried to use - operator on unsupported types string and string"
519        );
520
521        assert_eq!(
522            sub(&Value::from(2), &Value::from(1)).unwrap(),
523            Value::from(1)
524        );
525    }
526
527    #[test]
528    fn test_dividing() {
529        let err = div(&Value::from("a"), &Value::from(42)).unwrap_err();
530        assert_eq!(
531            err.to_string(),
532            "invalid operation: tried to use / operator on unsupported types string and number"
533        );
534
535        let err = div(&Value::from("foo"), &Value::from("bar")).unwrap_err();
536        assert_eq!(
537            err.to_string(),
538            "invalid operation: tried to use / operator on unsupported types string and string"
539        );
540
541        assert_eq!(
542            div(&Value::from(100), &Value::from(2)).unwrap(),
543            Value::from(50.0)
544        );
545
546        let err = int_div(&Value::from(i128::MIN), &Value::from(-1i128)).unwrap_err();
547        assert_eq!(
548            err.to_string(),
549            "invalid operation: unable to calculate -170141183460469231731687303715884105728 // -1"
550        );
551    }
552
553    #[test]
554    fn test_concat() {
555        assert_eq!(
556            string_concat(Value::from("foo"), &Value::from(42)),
557            Value::from("foo42")
558        );
559        assert_eq!(
560            string_concat(Value::from(23), &Value::from(42)),
561            Value::from("2342")
562        );
563    }
564
565    #[test]
566    fn test_slicing() {
567        let v = Value::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
568
569        // [::] - full slice
570        assert_eq!(
571            slice(v.clone(), Value::from(()), Value::from(()), Value::from(())).unwrap(),
572            Value::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
573        );
574
575        // [::2] - every 2nd element
576        assert_eq!(
577            slice(v.clone(), Value::from(()), Value::from(()), Value::from(2)).unwrap(),
578            Value::from(vec![0, 2, 4, 6, 8])
579        );
580
581        // [1:2:2] - slice with start, stop, step
582        assert_eq!(
583            slice(v.clone(), Value::from(1), Value::from(2), Value::from(2)).unwrap(),
584            Value::from(vec![1])
585        );
586
587        // [::-2] - reverse with step of 2
588        assert_eq!(
589            slice(v.clone(), Value::from(()), Value::from(()), Value::from(-2)).unwrap(),
590            Value::from(vec![9, 7, 5, 3, 1])
591        );
592
593        // [2::-2] - from index 2 to start, reverse with step of 2
594        assert_eq!(
595            slice(v.clone(), Value::from(2), Value::from(()), Value::from(-2)).unwrap(),
596            Value::from(vec![2, 0])
597        );
598
599        // [4:2:-2] - from index 4 to 2, reverse with step of 2
600        assert_eq!(
601            slice(v.clone(), Value::from(4), Value::from(2), Value::from(-2)).unwrap(),
602            Value::from(vec![4])
603        );
604
605        // [8:3:-2] - from index 8 to 3, reverse with step of 2
606        assert_eq!(
607            slice(v.clone(), Value::from(8), Value::from(3), Value::from(-2)).unwrap(),
608            Value::from(vec![8, 6, 4])
609        );
610    }
611
612    #[test]
613    fn test_string_slicing() {
614        let s = Value::from("abcdefghij");
615
616        // [::] - full slice
617        assert_eq!(
618            slice(s.clone(), Value::from(()), Value::from(()), Value::from(())).unwrap(),
619            Value::from("abcdefghij")
620        );
621
622        // [::2] - every 2nd character
623        assert_eq!(
624            slice(s.clone(), Value::from(()), Value::from(()), Value::from(2)).unwrap(),
625            Value::from("acegi")
626        );
627
628        // [1:2:2] - slice with start, stop, step
629        assert_eq!(
630            slice(s.clone(), Value::from(1), Value::from(2), Value::from(2)).unwrap(),
631            Value::from("b")
632        );
633
634        // [::-2] - reverse with step of 2
635        assert_eq!(
636            slice(s.clone(), Value::from(()), Value::from(()), Value::from(-2)).unwrap(),
637            Value::from("jhfdb")
638        );
639
640        // [2::-2] - from index 2 to start, reverse with step of 2
641        assert_eq!(
642            slice(s.clone(), Value::from(2), Value::from(()), Value::from(-2)).unwrap(),
643            Value::from("ca")
644        );
645
646        // [4:2:-2] - from index 4 to 2, reverse with step of 2
647        assert_eq!(
648            slice(s.clone(), Value::from(4), Value::from(2), Value::from(-2)).unwrap(),
649            Value::from("e")
650        );
651
652        // [8:3:-2] - from index 8 to 3, reverse with step of 2
653        assert_eq!(
654            slice(s.clone(), Value::from(8), Value::from(3), Value::from(-2)).unwrap(),
655            Value::from("ige")
656        );
657    }
658
659    #[test]
660    fn test_bytes_slicing() {
661        let s = Value::from_bytes(b"abcdefghij".to_vec());
662
663        // [::] - full slice
664        assert_eq!(
665            slice(s.clone(), Value::from(()), Value::from(()), Value::from(())).unwrap(),
666            Value::from_bytes(b"abcdefghij".to_vec())
667        );
668
669        // [::2] - every 2nd character
670        assert_eq!(
671            slice(s.clone(), Value::from(()), Value::from(()), Value::from(2)).unwrap(),
672            Value::from_bytes(b"acegi".to_vec())
673        );
674
675        // [1:2:2] - slice with start, stop, step
676        assert_eq!(
677            slice(s.clone(), Value::from(1), Value::from(2), Value::from(2)).unwrap(),
678            Value::from_bytes(b"b".to_vec())
679        );
680
681        // [::-2] - reverse with step of 2
682        assert_eq!(
683            slice(s.clone(), Value::from(()), Value::from(()), Value::from(-2)).unwrap(),
684            Value::from_bytes(b"jhfdb".to_vec())
685        );
686
687        // [2::-2] - from index 2 to start, reverse with step of 2
688        assert_eq!(
689            slice(s.clone(), Value::from(2), Value::from(()), Value::from(-2)).unwrap(),
690            Value::from_bytes(b"ca".to_vec())
691        );
692
693        // [4:2:-2] - from index 4 to 2, reverse with step of 2
694        assert_eq!(
695            slice(s.clone(), Value::from(4), Value::from(2), Value::from(-2)).unwrap(),
696            Value::from_bytes(b"e".to_vec())
697        );
698
699        // [8:3:-2] - from index 8 to 3, reverse with step of 2
700        assert_eq!(
701            slice(s.clone(), Value::from(8), Value::from(3), Value::from(-2)).unwrap(),
702            Value::from_bytes(b"ige".to_vec())
703        );
704    }
705}