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. 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 ### 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. 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 ### 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 { ... }`). 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. 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 ```golang
["one", "two", "three"][1] // == "two" ["one", "two", "three"][1] // == "two"
@ -80,6 +82,8 @@ m.a // == 1
m["b"][1] // == 3 m["b"][1] // == 3
m.c() // == 10 m.c() // == 10
m.x = 5 // add 'x' to map 'm' 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 //m.b[5] = 0 // but this is an error: index out of bounds
``` ```
> [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c) > [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] b := [1, 2, 3, 4, 5][3:] // == [4, 5]
c := [1, 2, 3, 4, 5][:3] // == [1, 2, 3] c := [1, 2, 3, 4, 5][:3] // == [1, 2, 3]
d := "hello world"[2:10] // == "llo worl" 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) > [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) idxVal := int(intIdx.Value)
if idxVal < 0 || idxVal >= len(o.Value) { if idxVal < 0 || idxVal >= len(o.Value) {
err = ErrIndexOutOfBounds res = UndefinedValue
return return
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,8 @@ package runtime_test
import ( import (
"fmt" "fmt"
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestArray(t *testing.T) { 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, `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}) 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 // index operator
arr := ARR{1, 2, 3, 4, 5, 6} arr := ARR{1, 2, 3, 4, 5, 6}
arrStr := `[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("out = %s[1 + %d - 1]", arrStr, idx), arr[idx])
expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, arrStr), 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 // slice operator
for low := 0; low < arrLen; low++ { for low := 0; low < arrLen; low++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, low), ARR{})
for high := low; high <= arrLen; high++ { 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[%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[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[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, high), arr[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), arr[low:]) 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)) expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1))
} }

View file

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

View file

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

View file

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

View file

@ -8,6 +8,11 @@ import (
func TestUndefined(t *testing.T) { func TestUndefined(t *testing.T) {
expect(t, `out = undefined`, objects.UndefinedValue) 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 == undefined`, true)
expect(t, `out = undefined == 1`, false) expect(t, `out = undefined == 1`, false)
expect(t, `out = 1 == undefined`, false) expect(t, `out = 1 == undefined`, false)