Allow to omit indices in index range expansions

Missing range limits in, say $PATH[..] default to the first/last
element, just like Python/Go/Rust slices.
This commit is contained in:
Johannes Altmanninger 2020-02-07 16:33:44 +01:00
parent 4c66e69cd9
commit be06f842a2
4 changed files with 68 additions and 9 deletions

View file

@ -5,6 +5,7 @@
### Syntax changes and new commands
### Scripting improvements
- Range limits in index range expansions like `$x[$start..$end]` may be omitted: `$start` and `$end` default to 1 and -1 (the last item) respectively.
### Interactive improvements

View file

@ -661,6 +661,8 @@ Sequences of elements can be written with the range operator '``..``'. A range '
If the end is smaller than the start, or the start is larger than the end, range expansion will go in reverse. This is unless exactly one of the given indices is negative, so the direction doesn't change if the list has fewer elements than expected.
A missing starting index in a range defaults to 1. This is allowed if the range is the first index expression of the sequence. Similarly, a missing ending index, defaulting to -1 is allowed for the last index range in the sequence.
Some examples::
@ -672,6 +674,9 @@ Some examples::
# Uses elements from 2 to 5
# Output is: 2 3 4 5
echo (seq 10)[7..]
# Prints: 7 8 9 10
# Use overlapping ranges:
echo (seq 10)[2..5 1..3]
# Takes elements from 2 to 5 and then elements from 1 to 3

View file

@ -188,11 +188,20 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
}
const wchar_t *end;
long tmp = fish_wcstol(&in[pos], &end);
// We don't test `*end` as is typically done because we expect it to not be the null char.
// Ignore the case of errno==-1 because it means the end char wasn't the null char.
if (errno > 0) {
return pos;
long tmp;
if (idx.empty() && in[pos] == L'.' && in[pos + 1] == L'.') {
// If we are at the first index expression, a missing start index means the range starts
// at the first item.
tmp = 1; // first index
end = &in[pos];
} else {
tmp = fish_wcstol(&in[pos], &end);
if (errno > 0) {
// We don't test `*end` as is typically done because we expect it to not be the null
// char. Ignore the case of errno==-1 because it means the end char wasn't the null
// char.
return pos;
}
}
long i1 = tmp > -1 ? tmp : size + tmp + 1;
@ -201,11 +210,20 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
if (in[pos] == L'.' && in[pos + 1] == L'.') {
pos += 2;
while (in[pos] == INTERNAL_SEPARATOR) pos++;
while (iswspace(in[pos])) pos++; // Allow the space in "[.. ]".
long tmp1 = fish_wcstol(&in[pos], &end);
// Ignore the case of errno==-1 because it means the end char wasn't the null char.
if (errno > 0) {
return pos;
long tmp1;
// Check if we are at the last index range expression, a missing end index means the
// range spans until the last item.
if (in[pos] == L']') {
tmp1 = -1; // last index
end = &in[pos];
} else {
tmp1 = fish_wcstol(&in[pos], &end);
// Ignore the case of errno==-1 because it means the end char wasn't the null char.
if (errno > 0) {
return pos;
}
}
pos = end - in;

View file

@ -3,6 +3,8 @@ set n 10
set test (seq $n)
echo $test[1..$n] # normal range
#CHECK: 1 2 3 4 5 6 7 8 9 10
echo $test[1 .. 2] # spaces are allowed
#CHECK: 1 2
echo $test[$n..1] # inverted range
#CHECK: 10 9 8 7 6 5 4 3 2 1
echo $test[2..5 8..6] # several ranges
@ -31,3 +33,36 @@ echo $test[(count $test)..1]
#CHECK: 10 9 8 7 6 5 4 3 2 1
echo $test[1..(count $test)]
#CHECK: 1 2 3 4 5 6 7 8 9 10
echo $test[ .. ]
#CHECK: 1 2 3 4 5 6 7 8 9 10
echo $test[ ..3]
#CHECK: 1 2 3
echo $test[8.. ]
#CHECK: 8 9 10
echo $test[..2 5]
# CHECK: 1 2 5
echo $test[2 9..]
# CHECK: 2 9 10
# missing start, cannot use implied range
echo $test[1..2..]
#CHECKERR: {{.*}}: Invalid index value
#CHECKERR: echo $test[1..2..]
#CHECKERR: ^
echo $test[..1..2]
#CHECKERR: {{.*}}: Invalid index value
#CHECKERR: echo $test[..1..2]
#CHECKERR: ^
set -l empty
echo $test[ $empty..]
#CHECK:
echo $test[.."$empty"]
#CHECK: 1 2 3 4 5 6 7 8 9 10
echo $test["$empty"..]
#CHECK: 1 2 3 4 5 6 7 8 9 10
echo $test[ (true)..3]
#CHECK:
echo $test[ (string join \n 1 2 3)..3 ]
#CHECK: 1 2 3 2 3 3