Quellcode durchsuchen

Adding IndexExpression DataType

simon vor 3 Jahren
Ursprung
Commit
0ae19e2cdf
5 geänderte Dateien mit 145 neuen und 32 gelöschten Zeilen
  1. 34 32
      code/code.go
  2. 11 0
      compiler/compiler.go
  3. 36 0
      compiler/compiler_test.go
  4. 47 0
      vm/vm.go
  5. 17 0
      vm/vm_test.go

+ 34 - 32
code/code.go

@@ -11,6 +11,39 @@ import (
 	"fmt"
 )
 
+type Opcode byte
+
+const (
+	OpConstant Opcode = iota
+	OpAdd             // +
+	OpSub             // -
+	OpMul             // *
+	OpDiv             // /
+	OpPop
+
+	OpTrue  // true
+	OpFalse // false
+
+	OpEqual       // ==
+	OpNotEqual    // !=
+	OpGreaterThan // >
+
+	OpMinus
+	OpBang
+
+	OpJumpNotTruthy
+	OpJump
+
+	OpNull
+
+	OpGetGlobal
+	OpSetGlobal
+
+	OpArray
+	OpHash
+	OpIndex
+)
+
 type Instructions []byte
 
 // String MiniDisassembler
@@ -58,38 +91,6 @@ func (ins Instructions) fmtInstructions(def *Definition, operands []int) string
 	return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
 }
 
-type Opcode byte
-
-const (
-	OpConstant Opcode = iota
-	OpAdd             // +
-	OpSub             // -
-	OpMul             // *
-	OpDiv             // /
-	OpPop
-
-	OpTrue  // true
-	OpFalse // false
-
-	OpEqual       // ==
-	OpNotEqual    // !=
-	OpGreaterThan // >
-
-	OpMinus
-	OpBang
-
-	OpJumpNotTruthy
-	OpJump
-
-	OpNull
-
-	OpGetGlobal
-	OpSetGlobal
-
-	OpArray
-	OpHash
-)
-
 // Definition For debugging and testing purposes
 //
 // it’s handy being able to look up how many operands an opcode has and what its human-readable name is.
@@ -125,6 +126,7 @@ var definitions = map[Opcode]*Definition{
 
 	OpArray: {"OpArray", []int{2}},
 	OpHash:  {"OpHash", []int{2}},
+	OpIndex: {"OpIndex", []int{}},
 }
 
 func Lookup(op byte) (*Definition, error) {

+ 11 - 0
compiler/compiler.go

@@ -215,6 +215,17 @@ 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
 }

+ 36 - 0
compiler/compiler_test.go

@@ -377,6 +377,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()
 

+ 47 - 0
vm/vm.go

@@ -155,6 +155,14 @@ func (vm *VM) Run() error {
 			if err != nil {
 				return err
 			}
+		case code.OpIndex:
+			index := vm.pop()
+			left := vm.pop()
+
+			err := vm.executeIndexExpression(left, index)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -188,6 +196,17 @@ func (vm *VM) pop() object.Object {
 	return o
 }
 
+func (vm *VM) executeIndexExpression(left, index object.Object) error {
+	switch {
+	case left.Type() == object.ArrayObj && index.Type() == object.IntegerObj:
+		return vm.executeArrayIndex(left, index)
+	case left.Type() == object.HashObj:
+		return vm.executeHashIndex(left, index)
+	default:
+		return fmt.Errorf("index operator not supported: %s", left.Type())
+	}
+}
+
 func (vm *VM) buildArray(startIndex, endIndex int) object.Object {
 	elements := make([]object.Object, endIndex-startIndex)
 
@@ -334,6 +353,34 @@ func (vm *VM) executeBangOperator() error {
 	}
 }
 
+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.Hashtable)
+	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 nativeBoolToBooleanObject(input bool) *object.Boolean {
 	if input {
 		return True

+ 17 - 0
vm/vm_test.go

@@ -122,6 +122,23 @@ func TestHashLiterals(t *testing.T) {
 	runVmTests(t, test)
 }
 
+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)
+}
+
 func runVmTests(t *testing.T, tests []vmTestCase) {
 	t.Helper()