Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Negative indexes, closes #274 #276

Merged
merged 1 commit into from
Aug 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions docs/types/array.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ notation:
array[3]
```

Accessing an index that does not exist returns null.
Accessing an index that does not exist returns `null`.

You can also access the Nth last element of an array by
using a negative index:

``` bash
["a", "b", "c", "d"][-2] # "c"
```

You can also access a range of indexes with the `[start:end]` notation:

``` bash
array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

array[0:2] // [0, 1, 2]
array[0:2] # [0, 1, 2]
```

where `start` is the starting position in the array, and `end` is
Expand All @@ -38,14 +45,14 @@ and if `end` is omitted it is assumed to be the last index in the
array:

``` bash
array[:2] // [0, 1, 2]
array[7:] // [7, 8, 9]
array[:2] # [0, 1, 2]
array[7:] # [7, 8, 9]
```

If `end` is negative, it will be converted to `length of array - end`:

``` bash
array[:-3] // [0, 1, 2, 3, 4, 5, 6]
array[:-3] # [0, 1, 2, 3, 4, 5, 6]
```

To concatenate arrays, "sum" them:
Expand Down Expand Up @@ -113,7 +120,7 @@ a # [1, 2, 3, 4, 99, 55, 66]
An array is defined as "homogeneous" when all its elements
are of a single type:

```
``` bash
[1, 2, 3] # homogeneous
[null, 0, "", {}] # heterogeneous
```
Expand Down
12 changes: 10 additions & 2 deletions docs/types/string.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ with the index notation:
"hello world"[1] # e
```

Accessing an index that does not exist returns null.
Accessing an index that does not exist returns an empty string.

You can access the Nth last character of the string using a
negative index:

``` bash
"string"[-2] # "n"
```

You can also access a range of the string with the `[start:end]` notation:

Expand All @@ -42,7 +49,8 @@ You can also access a range of the string with the `[start:end]` notation:

where `start` is the starting position in the array, and `end` is
the ending one. If `start` is not specified, it is assumed to be 0,
and if `end` is omitted it is assumed to be the character in the string:
and if `end` is omitted it is assumed to be the last character in the
string:

``` bash
"string"[0:3] // "str"
Expand Down
40 changes: 35 additions & 5 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1096,7 +1096,7 @@ func evalStringIndexExpression(tok token.Token, array, index object.Object, end
max := len(stringObject.Value) - 1

if isRange {
max += 1
max++
// A range's minimum value is 0
if idx < 0 {
idx = 0
Expand Down Expand Up @@ -1127,8 +1127,23 @@ func evalStringIndexExpression(tok token.Token, array, index object.Object, end
return &object.String{Token: tok, Value: string(stringObject.Value[idx:max])}
}

if idx < 0 || idx > max {
return NULL
// Out of bounds? Return an empty string
if idx > max {
return &object.String{Token: tok, Value: ""}
}

if idx < 0 {
length := max + 1

// Negative out of bounds? Return an empty string
if math.Abs(float64(idx)) > float64(length) {
return &object.String{Token: tok, Value: ""}
}

// Our index was negative, so the actual index is length of the string + the index
// eg 3 + (-2) = 1
// "123"[-2] = "2"
idx = length + idx
}

return &object.String{Token: tok, Value: string(stringObject.Value[idx])}
Expand All @@ -1140,7 +1155,7 @@ func evalArrayIndexExpression(tok token.Token, array, index object.Object, end o
max := len(arrayObject.Elements) - 1

if isRange {
max += 1
max++
// A range's minimum value is 0
if idx < 0 {
idx = 0
Expand Down Expand Up @@ -1171,10 +1186,25 @@ func evalArrayIndexExpression(tok token.Token, array, index object.Object, end o
return &object.Array{Token: tok, Elements: arrayObject.Elements[idx:max]}
}

if idx < 0 || idx > max {
// Out of bounds? Return a null element
if idx > max {
return NULL
}

if idx < 0 {
length := max + 1

// Negative out of bounds? Return a null element
if math.Abs(float64(idx)) > float64(length) {
return NULL
}

// Our index was negative, so the actual index is length of the string + the index
// eg 3 + (-2) = 1
// [1,2,3][-2] = 2
idx = length + idx
}

return arrayObject.Elements[idx]
}

Expand Down
40 changes: 32 additions & 8 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1377,9 +1377,25 @@ func TestArrayIndexExpressions(t *testing.T) {
nil,
},
{
"[1, 2, 3][-1]",
"[1, 2, 3][-2]",
2,
},
{
"[1, 2, 3][-10]",
nil,
},
{
"[1, 2, 3][-3]",
1,
},
{
"[1, 2, 3][-4]",
nil,
},
{
"[1, 2, 3][-0]",
1,
},
{
"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][1:-300]; a[0]",
nil,
Expand Down Expand Up @@ -1512,7 +1528,7 @@ func TestStringIndexExpressions(t *testing.T) {
}{
{
`"123"[10]`,
nil,
"",
},
{
`"123"[1]`,
Expand All @@ -1534,6 +1550,18 @@ func TestStringIndexExpressions(t *testing.T) {
`"123"[:-1]`,
"12",
},
{
`"123"[-2]`,
"2",
},
{
`"123"[-1]`,
"3",
},
{
`"123"[-10]`,
"",
},
{
`"123"[2:-10]`,
"",
Expand All @@ -1558,10 +1586,6 @@ func TestStringIndexExpressions(t *testing.T) {
`"123"[-10:{}]`,
`index ranges can only be numerical: got "{}" (type HASH)`,
},
{
`"123"[-2]`,
"",
},
{
`"123"[3]`,
"",
Expand All @@ -1575,12 +1599,12 @@ func TestStringIndexExpressions(t *testing.T) {
for _, tt := range tests {
evaluated := testEval(tt.input)
switch result := evaluated.(type) {
case *object.Null:
testNullObject(t, evaluated)
case *object.String:
testStringObject(t, evaluated, tt.expected.(string))
case *object.Error:
logErrorWithPosition(t, result.Message, tt.expected)
default:
t.Errorf("object is not the right result. got=%s ('%+v' expected)", result.Inspect(), tt.expected)
}
}
}
Expand Down