Browse Source

Adding Hash dataType

simon 3 years ago
parent
commit
74829f8063
6 changed files with 138 additions and 1 deletions
  1. 2 0
      code/code.go
  2. 21 0
      compiler/compiler.go
  3. 45 0
      compiler/compiler_test.go
  4. 1 1
      object/object.go
  5. 33 0
      vm/vm.go
  6. 36 0
      vm/vm_test.go

+ 2 - 0
code/code.go

@@ -87,6 +87,7 @@ const (
 	OpSetGlobal
 
 	OpArray
+	OpHash
 )
 
 // Definition For debugging and testing purposes
@@ -123,6 +124,7 @@ var definitions = map[Opcode]*Definition{
 	OpSetGlobal: {"OpSetGlobal", []int{2}},
 
 	OpArray: {"OpArray", []int{2}},
+	OpHash:  {"OpHash", []int{2}},
 }
 
 func Lookup(op byte) (*Definition, error) {

+ 21 - 0
compiler/compiler.go

@@ -10,6 +10,7 @@ import (
 	"github/runnignwater/monkey/ast"
 	"github/runnignwater/monkey/code"
 	"github/runnignwater/monkey/object"
+	"sort"
 )
 
 type EmittedInstruction struct {
@@ -194,6 +195,26 @@ func (c *Compiler) Compile(node ast.Node) error {
 			}
 		}
 		c.emit(code.OpArray, len(node.Element))
+	case *ast.HashLiteral:
+		var keys []ast.Expression
+		for k := range node.Pairs {
+			keys = append(keys, k)
+		}
+		sort.Slice(keys, func(i, j int) bool {
+			return keys[i].String() < keys[j].String()
+		})
+
+		for _, k := range keys {
+			err := c.Compile(k)
+			if err != nil {
+				return err
+			}
+			err = c.Compile(node.Pairs[k])
+			if err != nil {
+				return err
+			}
+		}
+		c.emit(code.OpHash, len(node.Pairs)*2)
 	}
 	return nil
 }

+ 45 - 0
compiler/compiler_test.go

@@ -332,6 +332,51 @@ func TestArrayLiteral(t *testing.T) {
 	runCompilerTests(t, tests)
 }
 
+func TestHashLiterals(t *testing.T) {
+	tests := []compilerTestCase{
+		{
+			input:             "{}",
+			expectedConstants: []interface{}{},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpHash, 0),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "{1:2, 3:4, 5:6}",
+			expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpConstant, 2),
+				code.Make(code.OpConstant, 3),
+				code.Make(code.OpConstant, 4),
+				code.Make(code.OpConstant, 5),
+				code.Make(code.OpHash, 6),
+				code.Make(code.OpPop),
+			},
+		},
+		{
+			input:             "{1:2+3, 4:5*6}",
+			expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
+			expectedInstructions: []code.Instructions{
+				code.Make(code.OpConstant, 0),
+				code.Make(code.OpConstant, 1),
+				code.Make(code.OpConstant, 2),
+				code.Make(code.OpAdd),
+				code.Make(code.OpConstant, 3),
+				code.Make(code.OpConstant, 4),
+				code.Make(code.OpConstant, 5),
+				code.Make(code.OpMul),
+				code.Make(code.OpHash, 4),
+				code.Make(code.OpPop),
+			},
+		},
+	}
+
+	runCompilerTests(t, tests)
+}
+
 func runCompilerTests(t *testing.T, tests []compilerTestCase) {
 	t.Helper()
 

+ 1 - 1
object/object.go

@@ -93,7 +93,7 @@ func (f *Function) Type() ObjType { return FunctionObj }
 func (f *Function) Inspect() string {
 	var out bytes.Buffer
 
-	params := []string{}
+	var params []string
 	for _, p := range f.Parameters {
 		params = append(params, p.String())
 	}

+ 33 - 0
vm/vm.go

@@ -141,6 +141,20 @@ func (vm *VM) Run() error {
 			if err != nil {
 				return err
 			}
+		case code.OpHash:
+			numElements := int(code.ReadUint16(vm.instructions[ip+1:]))
+			ip += 2
+
+			hash, err := vm.buildHash(vm.sp-numElements, vm.sp)
+			if err != nil {
+				return err
+			}
+			vm.sp -= numElements
+
+			err = vm.push(hash)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -183,6 +197,25 @@ func (vm *VM) buildArray(startIndex, endIndex int) object.Object {
 
 	return &object.Array{Elements: elements}
 }
+
+func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) {
+	hashPairs := make(map[object.HashKey]object.HashPair)
+
+	for i := startIndex; i < endIndex; i += 2 {
+		key := vm.stack[i]
+		value := vm.stack[i+1]
+
+		pair := object.HashPair{Key: key, Value: value}
+		hashKey, ok := key.(object.Hashtable)
+		if !ok {
+			return nil, fmt.Errorf("unusable as hash key: %s", key.Type())
+		}
+		hashPairs[hashKey.HashKey()] = pair
+	}
+
+	return &object.Hash{Pairs: hashPairs}, nil
+}
+
 func (vm *VM) executeBinaryOperation(op code.Opcode) error {
 	right := vm.pop()
 	left := vm.pop()

+ 36 - 0
vm/vm_test.go

@@ -106,6 +106,22 @@ func TestArrayLiterals(t *testing.T) {
 	runVmTests(t, tests)
 }
 
+func TestHashLiterals(t *testing.T) {
+	test := []vmTestCase{
+		{"{}", map[object.HashKey]int64{}},
+		{"{1:2, 2:3}", map[object.HashKey]int64{
+			(&object.Integer{Value: 1}).HashKey(): 2,
+			(&object.Integer{Value: 2}).HashKey(): 3,
+		}},
+		{"{1+1:2*2, 3+3:4*4}", map[object.HashKey]int64{
+			(&object.Integer{Value: 2}).HashKey(): 4,
+			(&object.Integer{Value: 6}).HashKey(): 16,
+		}},
+	}
+
+	runVmTests(t, test)
+}
+
 func runVmTests(t *testing.T, tests []vmTestCase) {
 	t.Helper()
 
@@ -175,6 +191,26 @@ func testExpectedObject(
 				t.Errorf("testIntegerObject failed: %s", err)
 			}
 		}
+	case map[object.HashKey]int64:
+		hash, ok := actual.(*object.Hash)
+		if !ok {
+			t.Errorf("object is not Hash. got=%T (%+v)", actual, actual)
+			return
+		}
+		if len(hash.Pairs) != len(expected) {
+			t.Errorf("hash has wrong number of Pairs. want=%d, got=%d", len(expected), len(hash.Pairs))
+			return
+		}
+		for expectedKey, expectedValue := range expected {
+			pair, ok := hash.Pairs[expectedKey]
+			if !ok {
+				t.Errorf("no pair for given key in Pairs")
+			}
+			err := testIntegerObject(expectedValue, pair.Value)
+			if err != nil {
+				t.Errorf("testIntegerObject failed: %s", err)
+			}
+		}
 	}
 }