Skip to content

Commit

Permalink
IndexGet of Array, Index, ImmutableIndex, Bytes, String, Undefined
Browse files Browse the repository at this point in the history
  • Loading branch information
geseq authored and d5 committed Feb 10, 2019
1 parent 2b517f3 commit 5e21abf
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 59 deletions.
4 changes: 1 addition & 3 deletions docs/objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 { ... }`).
Expand Down
5 changes: 5 additions & 0 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion objects/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion objects/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion objects/immautable_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion objects/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
5 changes: 5 additions & 0 deletions objects/undefined.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 72 additions & 41 deletions runtime/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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:
Expand Down
23 changes: 18 additions & 5 deletions runtime/vm_array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package runtime_test
import (
"fmt"
"testing"

"github.com/d5/tengo/objects"
)

func TestArray(t *testing.T) {
Expand All @@ -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]`
Expand All @@ -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))
}
3 changes: 3 additions & 0 deletions runtime/vm_bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package runtime_test

import (
"testing"

"github.com/d5/tengo/objects"
)

func TestBytes(t *testing.T) {
Expand All @@ -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)
}
8 changes: 7 additions & 1 deletion runtime/vm_immutable_test.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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`)
Expand All @@ -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`)
Expand Down
Loading

0 comments on commit 5e21abf

Please sign in to comment.