IndexGet of Array, Index, ImmutableIndex, Bytes, String, Undefined

This commit is contained in:
earncef 2019-02-10 01:45:21 +01:00 committed by Daniel
parent 2b517f376e
commit 5e21abfd74
13 changed files with 136 additions and 59 deletions

View file

@ -84,7 +84,7 @@ If `IndexGet` returns an error (`err`), the VM will treat it as a run-time error
Array and Map implementation forces the type of index Object to be Int and String respectively, but, it's not a required behavior of the VM. It is completely okay to take various index types as long as it is consistent.
By convention, Array or Array-like types return `ErrIndexOutOfBounds` error (as a runtime error) when the index is invalid (out of the bounds), and, Map or Map-like types return `Undefined` value (instead of a run-time error) when the key does not exist. But, again, this is not a required behavior.
By convention, Array or Array-like types and Map or Map-like types return `Undefined` value when the key does not exist. But, again, this is not a required behavior.
### Index-Assignable Interface
@ -98,8 +98,6 @@ type IndexAssignable interface {
Array and Map implementation forces the type of index Object to be Int and String respectively, but, it's not a required behavior of the VM. It is completely okay to take various index types as long as it is consistent.
By convention, Array or Array-like types return `ErrIndexOutOfBounds` error (as a runtime error) when the index is invalid (out of the bounds).
### Iterable Interface
If the type implements [Iterable](https://godoc.org/github.com/d5/tengo/objects#Iterable) interface, its values can be used in `for-in` statements (`for key, value in object { ... }`).

View file

@ -68,6 +68,8 @@ _See [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.
You can use the dot selector (`.`) and indexer (`[]`) operator to read or write elements of arrays, strings, or maps.
Reading a nonexistent index returns `Undefined` value.
```golang
["one", "two", "three"][1] // == "two"
@ -80,6 +82,8 @@ m.a // == 1
m["b"][1] // == 3
m.c() // == 10
m.x = 5 // add 'x' to map 'm'
m["b"][5] // == undefined
m["b"][5].d // == undefined
//m.b[5] = 0 // but this is an error: index out of bounds
```
> [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c)
@ -91,6 +95,7 @@ a := [1, 2, 3, 4, 5][1:3] // == [2, 3]
b := [1, 2, 3, 4, 5][3:] // == [4, 5]
c := [1, 2, 3, 4, 5][:3] // == [1, 2, 3]
d := "hello world"[2:10] // == "llo worl"
c := [1, 2, 3, 4, 5][-1:10] // == [1, 2, 3, 4, 5]
```
> [Run in Playground](https://tengolang.com/?s=214ab490bb24549578770984985f6b161aed915d)

View file

@ -94,7 +94,7 @@ func (o *Array) IndexGet(index Object) (res Object, err error) {
idxVal := int(intIdx.Value)
if idxVal < 0 || idxVal >= len(o.Value) {
err = ErrIndexOutOfBounds
res = UndefinedValue
return
}

View file

@ -66,7 +66,7 @@ func (o *Bytes) IndexGet(index Object) (res Object, err error) {
idxVal := int(intIdx.Value)
if idxVal < 0 || idxVal >= len(o.Value) {
err = ErrIndexOutOfBounds
res = UndefinedValue
return
}

View file

@ -91,7 +91,7 @@ func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) {
idxVal := int(intIdx.Value)
if idxVal < 0 || idxVal >= len(o.Value) {
err = ErrIndexOutOfBounds
res = UndefinedValue
return
}

View file

@ -73,7 +73,7 @@ func (o *String) IndexGet(index Object) (res Object, err error) {
}
if idxVal < 0 || idxVal >= len(o.runeStr) {
err = ErrIndexOutOfBounds
res = UndefinedValue
return
}

View file

@ -35,3 +35,8 @@ func (o *Undefined) IsFalsy() bool {
func (o *Undefined) Equals(x Object) bool {
return o == x
}
// IndexGet returns an element at a given index.
func (o *Undefined) IndexGet(index Object) (Object, error) {
return UndefinedValue, nil
}

View file

@ -631,57 +631,69 @@ func (v *VM) Run() error {
}
}
var highIdx int64
if *high == objects.UndefinedValue {
highIdx = -1
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
}
switch left := (*left).(type) {
case *objects.Array:
numElements := int64(len(left.Value))
if lowIdx < 0 || lowIdx > numElements {
return fmt.Errorf("index out of bounds: %d", lowIdx)
}
if highIdx < 0 {
var highIdx int64
if *high == objects.UndefinedValue {
highIdx = numElements
} else if highIdx < 0 || highIdx > numElements {
return fmt.Errorf("index out of bounds: %d", highIdx)
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
}
if lowIdx < 0 {
lowIdx = 0
} else if lowIdx > numElements {
lowIdx = numElements
}
if highIdx < 0 {
highIdx = 0
} else if highIdx > numElements {
highIdx = numElements
}
if v.sp >= StackSize {
return ErrStackOverflow
}
var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]}
v.stack[v.sp] = &val
v.sp++
case *objects.ImmutableArray:
numElements := int64(len(left.Value))
if lowIdx < 0 || lowIdx > numElements {
return fmt.Errorf("index out of bounds: %d", lowIdx)
}
if highIdx < 0 {
var highIdx int64
if *high == objects.UndefinedValue {
highIdx = numElements
} else if highIdx < 0 || highIdx > numElements {
return fmt.Errorf("index out of bounds: %d", highIdx)
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
}
if lowIdx < 0 {
lowIdx = 0
} else if lowIdx > numElements {
lowIdx = numElements
}
if highIdx < 0 {
highIdx = 0
} else if highIdx > numElements {
highIdx = numElements
}
if v.sp >= StackSize {
return ErrStackOverflow
}
@ -693,20 +705,31 @@ func (v *VM) Run() error {
case *objects.String:
numElements := int64(len(left.Value))
if lowIdx < 0 || lowIdx > numElements {
return fmt.Errorf("index out of bounds: %d", lowIdx)
}
if highIdx < 0 {
var highIdx int64
if *high == objects.UndefinedValue {
highIdx = numElements
} else if highIdx < 0 || highIdx > numElements {
return fmt.Errorf("index out of bounds: %d", highIdx)
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
}
if lowIdx < 0 {
lowIdx = 0
} else if lowIdx > numElements {
lowIdx = numElements
}
if highIdx < 0 {
highIdx = 0
} else if highIdx > numElements {
highIdx = numElements
}
if v.sp >= StackSize {
return ErrStackOverflow
}
@ -718,20 +741,31 @@ func (v *VM) Run() error {
case *objects.Bytes:
numElements := int64(len(left.Value))
if lowIdx < 0 || lowIdx >= numElements {
return fmt.Errorf("index out of bounds: %d", lowIdx)
}
if highIdx < 0 {
var highIdx int64
if *high == objects.UndefinedValue {
highIdx = numElements
} else if highIdx < 0 || highIdx > numElements {
return fmt.Errorf("index out of bounds: %d", highIdx)
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
}
if lowIdx < 0 {
lowIdx = 0
} else if lowIdx > numElements {
lowIdx = numElements
}
if highIdx < 0 {
highIdx = 0
} else if highIdx > numElements {
highIdx = numElements
}
if v.sp >= StackSize {
return ErrStackOverflow
}
@ -740,9 +774,6 @@ func (v *VM) Run() error {
v.stack[v.sp] = &val
v.sp++
default:
return fmt.Errorf("cannot slice %s", left.TypeName())
}
case compiler.OpCall:

View file

@ -3,6 +3,8 @@ package runtime_test
import (
"fmt"
"testing"
"github.com/d5/tengo/objects"
)
func TestArray(t *testing.T) {
@ -12,6 +14,9 @@ func TestArray(t *testing.T) {
expect(t, `a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2`, ARR{5, 2, 3})
expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, ARR{5, 2, 3})
// array index set
expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`)
// index operator
arr := ARR{1, 2, 3, 4, 5, 6}
arrStr := `[1, 2, 3, 4, 5, 6]`
@ -22,21 +27,29 @@ func TestArray(t *testing.T) {
expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", arrStr, idx), arr[idx])
expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, arrStr), arr[idx])
}
expectError(t, fmt.Sprintf("%s[%d]", arrStr, -1))
expectError(t, fmt.Sprintf("%s[%d]", arrStr, arrLen))
expect(t, fmt.Sprintf("%s[%d]", arrStr, -1), objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), objects.UndefinedValue)
// slice operator
for low := 0; low < arrLen; low++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, low), ARR{})
for high := low; high <= arrLen; high++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, high), arr[low:high])
expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", arrStr, low, high), arr[low:high])
expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", arrStr, low, high), arr[low:high])
expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, high), arr[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), arr[low:])
expect(t, fmt.Sprintf("out = %s[:]", arrStr), arr[:])
}
}
expectError(t, fmt.Sprintf("%s[%d:]", arrStr, -1))
expectError(t, fmt.Sprintf("%s[:%d]", arrStr, arrLen+1))
expect(t, fmt.Sprintf("out = %s[:]", arrStr), arr)
expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), arr)
expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), arr)
expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), ARR{})
expectError(t, fmt.Sprintf("out = %s[:%d]", arrStr, -1))
expectError(t, fmt.Sprintf("out = %s[%d:]", arrStr, arrLen+1))
expectError(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 0, -1))
expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1))
}

View file

@ -2,6 +2,8 @@ package runtime_test
import (
"testing"
"github.com/d5/tengo/objects"
)
func TestBytes(t *testing.T) {
@ -12,4 +14,5 @@ func TestBytes(t *testing.T) {
expect(t, `out = bytes("abcde")[0]`, 97)
expect(t, `out = bytes("abcde")[1]`, 98)
expect(t, `out = bytes("abcde")[4]`, 101)
expect(t, `out = bytes("abcde")[10]`, objects.UndefinedValue)
}

View file

@ -1,6 +1,10 @@
package runtime_test
import "testing"
import (
"testing"
"github.com/d5/tengo/objects"
)
func TestImmutable(t *testing.T) {
// primitive types are already immutable values
@ -26,6 +30,7 @@ func TestImmutable(t *testing.T) {
expect(t, `out = immutable([1, 2, 3, 4])[1]`, 2)
expect(t, `out = immutable([1, 2, 3, 4])[1:3]`, ARR{2, 3})
expect(t, `a := immutable([1,2,3]); a = 5; out = a`, 5)
expect(t, `a := immutable([1, 2, 3]); out = a[5]`, objects.UndefinedValue)
// map
expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`)
@ -42,6 +47,7 @@ func TestImmutable(t *testing.T) {
expect(t, `out = immutable({a:1,b:2}).b`, 2)
expect(t, `out = immutable({a:1,b:2})["b"]`, 2)
expect(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, 5)
expect(t, `a := immutable({a:1,b:2}); out = a.c`, objects.UndefinedValue)
expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, 5)
expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`)

View file

@ -3,6 +3,8 @@ package runtime_test
import (
"fmt"
"testing"
"github.com/d5/tengo/objects"
)
func TestString(t *testing.T) {
@ -24,22 +26,31 @@ func TestString(t *testing.T) {
expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), str[idx])
expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), str[idx])
}
expectError(t, fmt.Sprintf("%s[%d]", strStr, -1))
expectError(t, fmt.Sprintf("%s[%d]", strStr, strLen))
expect(t, fmt.Sprintf("%s[%d]", strStr, -1), objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), objects.UndefinedValue)
// slice operator
for low := 0; low <= strLen; low++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), "")
for high := low; high <= strLen; high++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), str[low:high])
expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), str[low:high])
expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", strStr, low, high), str[low:high])
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), str[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), str[low:])
expect(t, fmt.Sprintf("out = %s[:]", strStr), str[:])
}
}
expectError(t, fmt.Sprintf("%s[%d:]", strStr, -1))
expectError(t, fmt.Sprintf("%s[:%d]", strStr, strLen+1))
expect(t, fmt.Sprintf("out = %s[:]", strStr), str[:])
expect(t, fmt.Sprintf("out = %s[:]", strStr), str)
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), str)
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), str)
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "")
expectError(t, fmt.Sprintf("out = %s[:%d]", strStr, -1))
expectError(t, fmt.Sprintf("out = %s[%d:]", strStr, strLen+1))
expectError(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 0, -1))
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1))
// string concatenation with other types

View file

@ -8,6 +8,11 @@ import (
func TestUndefined(t *testing.T) {
expect(t, `out = undefined`, objects.UndefinedValue)
expect(t, `out = undefined.a`, objects.UndefinedValue)
expect(t, `out = undefined[1]`, objects.UndefinedValue)
expect(t, `out = undefined.a.b`, objects.UndefinedValue)
expect(t, `out = undefined[1][2]`, objects.UndefinedValue)
expect(t, `out = undefined ? 1 : 2`, 2)
expect(t, `out = undefined == undefined`, true)
expect(t, `out = undefined == 1`, false)
expect(t, `out = 1 == undefined`, false)