diff --git a/code/code.go b/code/code.go index c9bf9f5..0c1d16d 100644 --- a/code/code.go +++ b/code/code.go @@ -31,6 +31,7 @@ const ( OpSetGlobal OpArray OpHash + OpIndex ) type Definition struct { @@ -59,6 +60,7 @@ var definitions = map[Opcode]*Definition{ OpSetGlobal: {"OpSetGlobal", []int{2}}, OpArray: {"OpArray", []int{2}}, OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, } func Lookup(op byte) (*Definition, error) { diff --git a/compiler/compiler.go b/compiler/compiler.go index 44304c2..5bad298 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -227,6 +227,19 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(code.OpHash, len(node.Pairs)*2) + case *ast.IndexExpression: + err := c.Compile(node.Left) + if err != nil { + return err + } + + err = c.Compile(node.Index) + if err != nil { + return err + } + + c.emit(code.OpIndex) + } return nil diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index f5df3f8..49929c3 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -390,6 +390,42 @@ func TestHashLiterals(t *testing.T) { runCompilerTests(t, tests) } +func TestIndexExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "[1, 2, 3][1 + 1]", + expectedConstants: []interface{}{1, 2, 3, 1, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpConstant, 2), + code.Make(code.OpArray, 3), + code.Make(code.OpConstant, 3), + code.Make(code.OpConstant, 4), + code.Make(code.OpAdd), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + { + input: "{1: 2}[2 - 1]", + expectedConstants: []interface{}{1, 2, 2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpHash, 2), + code.Make(code.OpConstant, 2), + code.Make(code.OpConstant, 3), + code.Make(code.OpSub), + code.Make(code.OpIndex), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + func runCompilerTests(t *testing.T, tests []compilerTestCase) { t.Helper() diff --git a/vm/vm.go b/vm/vm.go index 332c9ad..a9c1989 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -160,12 +160,60 @@ func (vm *VM) Run() error { return err } + case code.OpIndex: + index := vm.pop() + left := vm.pop() + + err := vm.executeIndexExpressions(left, index) + if err != nil { + return err + } + } } return nil } +func (vm *VM) executeIndexExpressions(left, index object.Object) error { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return vm.executeArrayIndex(left, index) + case left.Type() == object.HASH_OBJ: + return vm.executeHashIndex(left, index) + default: + return fmt.Errorf("index operator not supported: %s", left.Type()) + } +} + +func (vm *VM) executeArrayIndex(array, index object.Object) error { + arrayObject := array.(*object.Array) + i := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if i < 0 || i > max { + return vm.push(Null) + } + + return vm.push(arrayObject.Elements[i]) +} + +func (vm *VM) executeHashIndex(hash, index object.Object) error { + hashObject := hash.(*object.Hash) + + key, ok := index.(object.Hashable) + if !ok { + return fmt.Errorf("unusable as hash key: %s", index.Type()) + } + + pair, ok := hashObject.Pairs[key.HashKey()] + if !ok { + return vm.push(Null) + } + + return vm.push(pair.Value) +} + func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { hashedPairs := make(map[object.HashKey]object.HashPair) diff --git a/vm/vm_test.go b/vm/vm_test.go index 62c9f54..dca7e02 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -279,3 +279,20 @@ func TestHashLiterals(t *testing.T) { runVmTests(t, tests) } + +func TestIndexExpressions(t *testing.T) { + tests := []vmTestCase{ + {"[1, 2, 3][1]", 2}, + {"[1, 2, 3][0 + 2]", 3}, + {"[[1, 1, 1]][0][0]", 1}, + {"[][0]", Null}, + {"[1, 2, 3][99]", Null}, + {"[1][-1]", Null}, + {"{1: 1, 2: 2}[1]", 1}, + {"{1: 1, 2: 2}[2]", 2}, + {"{1: 1}[0]", Null}, + {"{}[0]", Null}, + } + + runVmTests(t, tests) +}