IndexGet of Array, Index, ImmutableIndex, Bytes, String, Undefined
This commit is contained in:
parent
2b517f376e
commit
5e21abfd74
13 changed files with 136 additions and 59 deletions
|
@ -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 { ... }`).
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
113
runtime/vm.go
113
runtime/vm.go
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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`)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue