Skip to content

Commit e1b9c1e

Browse files
committed
feat(lang): support negative indexes in slices
1 parent 28fd5e7 commit e1b9c1e

File tree

4 files changed

+105
-42
lines changed

4 files changed

+105
-42
lines changed

rsjsonnet-lang/src/program/eval/expr.rs

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::cell::{Cell, OnceCell};
22

33
use super::super::{
4-
ir, FuncData, FuncKind, ImportError, ObjectData, ObjectLayer, ThunkEnv, ThunkEnvData, ValueData,
4+
ir, ArrayData, FuncData, FuncKind, ImportError, ObjectData, ObjectLayer, ThunkEnv,
5+
ThunkEnvData, ValueData,
56
};
67
use super::{EvalErrorKind, EvalErrorValueType, EvalResult, Evaluator, State, TraceItem};
78
use crate::gc::{Gc, GcView};
@@ -577,25 +578,99 @@ impl<'p> Evaluator<'_, 'p> {
577578
is_func: bool,
578579
span: Option<SpanId>,
579580
) -> EvalResult<()> {
581+
match indexable {
582+
ValueData::String(s) => self.do_slice_string(&s, start, end, step, span),
583+
ValueData::Array(array) => self.do_array_string(&array.view(), start, end, step, span),
584+
_ => {
585+
if is_func {
586+
Err(self.report_error(EvalErrorKind::InvalidStdFuncArgType {
587+
func_name: "slice".into(),
588+
arg_index: 0,
589+
expected_types: vec![EvalErrorValueType::String, EvalErrorValueType::Array],
590+
got_type: EvalErrorValueType::from_value(&indexable),
591+
}))
592+
} else {
593+
Err(self.report_error(EvalErrorKind::InvalidSlicedType {
594+
span: span.unwrap(),
595+
got_type: EvalErrorValueType::from_value(&indexable),
596+
}))
597+
}
598+
}
599+
}
600+
}
601+
602+
pub(super) fn do_slice_string(
603+
&mut self,
604+
string: &str,
605+
start: Option<f64>,
606+
end: Option<f64>,
607+
step: Option<f64>,
608+
span: Option<SpanId>,
609+
) -> EvalResult<()> {
610+
let (start, end, step) =
611+
self.get_slice_range(string.chars().count(), start, end, step, span)?;
612+
613+
let r: String = string
614+
.chars()
615+
.skip(start)
616+
.take(end - start)
617+
.step_by(step)
618+
.collect();
619+
self.value_stack.push(ValueData::String(r.into()));
620+
Ok(())
621+
}
622+
623+
pub(super) fn do_array_string(
624+
&mut self,
625+
array: &GcView<ArrayData<'p>>,
626+
start: Option<f64>,
627+
end: Option<f64>,
628+
step: Option<f64>,
629+
span: Option<SpanId>,
630+
) -> EvalResult<()> {
631+
let (start, end, step) = self.get_slice_range(array.len(), start, end, step, span)?;
632+
633+
let result = self.program.slice_array(array, start, end, step);
634+
self.value_stack.push(ValueData::Array(result));
635+
Ok(())
636+
}
637+
638+
pub(super) fn get_slice_range(
639+
&mut self,
640+
indexable_len: usize,
641+
start: Option<f64>,
642+
end: Option<f64>,
643+
step: Option<f64>,
644+
span: Option<SpanId>,
645+
) -> EvalResult<(usize, usize, usize)> {
580646
let start = if let Some(start) = start {
581-
if !start.is_finite() || start.trunc() != start || start < 0.0 {
647+
if !start.is_finite() || start.trunc() != start {
582648
return Err(self.report_error(EvalErrorKind::Other {
583649
span,
584-
message: format!("slice start {start} is not a non-negative integer"),
650+
message: format!("slice start {start} is not an integer"),
585651
}));
586652
}
587-
start as usize
653+
if start < 0.0 {
654+
indexable_len.saturating_sub(-start as usize)
655+
} else {
656+
start as usize
657+
}
588658
} else {
589659
0
590660
};
591661
let end = if let Some(end) = end {
592-
if !end.is_finite() || end.trunc() != end || end < 0.0 {
662+
if !end.is_finite() || end.trunc() != end {
593663
return Err(self.report_error(EvalErrorKind::Other {
594664
span,
595-
message: format!("slice end {end} is not a non-negative integer"),
665+
message: format!("slice end {end} is not an integer"),
596666
}));
597667
}
598-
(end as usize).max(start)
668+
let end = if end < 0.0 {
669+
indexable_len.saturating_sub(-end as usize)
670+
} else {
671+
end as usize
672+
};
673+
end.max(start)
599674
} else {
600675
usize::MAX
601676
};
@@ -611,39 +686,7 @@ impl<'p> Evaluator<'_, 'p> {
611686
1
612687
};
613688

614-
match indexable {
615-
ValueData::String(s) => {
616-
let r: String = s
617-
.chars()
618-
.skip(start)
619-
.take(end - start)
620-
.step_by(step)
621-
.collect();
622-
self.value_stack.push(ValueData::String(r.into()));
623-
Ok(())
624-
}
625-
ValueData::Array(array) => {
626-
let array = array.view();
627-
let result = self.program.slice_array(&array, start, end, step);
628-
self.value_stack.push(ValueData::Array(result));
629-
Ok(())
630-
}
631-
_ => {
632-
if is_func {
633-
Err(self.report_error(EvalErrorKind::InvalidStdFuncArgType {
634-
func_name: "slice".into(),
635-
arg_index: 0,
636-
expected_types: vec![EvalErrorValueType::String, EvalErrorValueType::Array],
637-
got_type: EvalErrorValueType::from_value(&indexable),
638-
}))
639-
} else {
640-
Err(self.report_error(EvalErrorKind::InvalidSlicedType {
641-
span: span.unwrap(),
642-
got_type: EvalErrorValueType::from_value(&indexable),
643-
}))
644-
}
645-
}
646-
}
689+
Ok((start, end, step))
647690
}
648691

649692
pub(super) fn do_binary_op(

ui-tests/fail/slice/invalid_end.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: slice end 1.25 is not a non-negative integer
1+
error: slice end 1.25 is not an integer
22
note: while evaluating call to `slice`
33
--> invalid_end.jsonnet:1:1
44
|

ui-tests/fail/slice/invalid_start.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: slice start 1.25 is not a non-negative integer
1+
error: slice start 1.25 is not an integer
22
note: while evaluating call to `slice`
33
--> invalid_start.jsonnet:1:1
44
|

ui-tests/pass/slice.jsonnet

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ test("string", null, null, null, "string") &&
99
test("string", 0, null, null, "string") &&
1010
test("string", 1, null, null, "tring") &&
1111
test("string", 2, null, null, "ring") &&
12+
test("string", 20, null, null, "") &&
1213

1314
test("string", null, 4, null, "stri") &&
1415
test("string", null, 5, null, "strin") &&
1516
test("string", null, 6, null, "string") &&
17+
test("string", null, 20, null, "string") &&
1618

1719
test("string", null, null, 1, "string") &&
1820
test("string", null, null, 2, "srn") &&
@@ -34,6 +36,14 @@ test("string", 0, 6, 1, "string") &&
3436
test("string", 1, 6, 2, "tig") &&
3537
test("string", 1, 5, 2, "ti") &&
3638

39+
test("string", -3, null, 1, "ing") &&
40+
test("string", -4, null, 1, "ring") &&
41+
test("string", -20, null, 1, "string") &&
42+
43+
test("string", null, -3, 1, "str") &&
44+
test("string", null, -4, 1, "st") &&
45+
test("string", null, -20, 1, "") &&
46+
3747
std.assertEqual("string"[::], "string") &&
3848
std.assertEqual("string"[1::], "tring") &&
3949
std.assertEqual("string"[:5:], "strin") &&
@@ -51,10 +61,12 @@ test(arr, null, null, null, arr) &&
5161
test(arr, 0, null, null, arr) &&
5262
test(arr, 1, null, null, ['r', 'r', 'a', 'y', '.']) &&
5363
test(arr, 2, null, null, ['r', 'a', 'y', '.']) &&
64+
test(arr, 20, null, null, []) &&
5465

5566
test(arr, null, 4, null, ['a', 'r', 'r', 'a']) &&
5667
test(arr, null, 5, null, ['a', 'r', 'r', 'a', 'y']) &&
5768
test(arr, null, 6, null, arr) &&
69+
test(arr, null, 20, null, arr) &&
5870

5971
test(arr, null, null, 1, arr) &&
6072
test(arr, null, null, 2, ['a', 'r', 'y']) &&
@@ -76,6 +88,14 @@ test(arr, 0, 6, 1, arr) &&
7688
test(arr, 1, 6, 2, ['r', 'a', '.']) &&
7789
test(arr, 1, 5, 2, ['r', 'a']) &&
7890

91+
test(arr, -3, null, 1, ['a', 'y', '.']) &&
92+
test(arr, -4, null, 1, ['r', 'a', 'y', '.']) &&
93+
test(arr, -20, null, 1, arr) &&
94+
95+
test(arr, null, -3, 1, ['a', 'r', 'r']) &&
96+
test(arr, null, -4, 1, ['a', 'r']) &&
97+
test(arr, null, -20, 1, []) &&
98+
7999
std.assertEqual(arr[::], ['a', 'r', 'r', 'a', 'y', '.']) &&
80100
std.assertEqual(arr[1::], ['r', 'r', 'a', 'y', '.']) &&
81101
std.assertEqual(arr[:5:], ['a', 'r', 'r', 'a', 'y']) &&

0 commit comments

Comments
 (0)