Selaa lähdekoodia

Evaluating Hash Literals

simon 3 vuotta sitten
vanhempi
commit
9e5f0e2008
4 muutettua tiedostoa jossa 139 lisäystä ja 2 poistoa
  1. 22 0
      evaluator/evaluator.go
  2. 39 0
      evaluator/evaluator_test.go
  3. 58 2
      object/object.go
  4. 20 0
      object/object_test.go

+ 22 - 0
evaluator/evaluator.go

@@ -96,6 +96,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
 			return index
 		}
 		return evalIndexExpression(left, index)
+	case *ast.HashLiteral:
+		return evalHashLiteral(node, env)
 	}
 
 	return nil
@@ -311,7 +313,27 @@ func evalArrayIndexExpression(array, index object.Object) object.Object {
 	}
 	return arrayObj.Elements[idx]
 }
+func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Object {
+	pairs := make(map[object.HashKey]object.HashPair)
 
+	for keyNode, valueNode := range node.Pairs {
+		key := Eval(keyNode, env)
+		if isError(key) {
+			return key
+		}
+		hashKey, ok := key.(object.Hashtable)
+		if !ok {
+			return newError("unusable as hash key: %s", key.Type())
+		}
+		value := Eval(valueNode, env)
+		if isError(value) {
+			return value
+		}
+		hashed := hashKey.HashKey()
+		pairs[hashed] = object.HashPair{Key: key, Value: value}
+	}
+	return &object.Hash{Pairs: pairs}
+}
 func isTruthy(obj object.Object) bool {
 	switch obj {
 	case NULL:

+ 39 - 0
evaluator/evaluator_test.go

@@ -430,3 +430,42 @@ func TestArrayIndexExpression(t *testing.T) {
 		}
 	}
 }
+
+func TestHashLiterals(t *testing.T) {
+	input := `let two = "two"; 
+    { 
+	  "one" : 10 -9,
+      "two" : 1 + 1,
+	  "thr"+"ee" : 6 / 2,
+       4 : 4,
+       true: 5,
+       false :6
+	}
+    `
+
+	evaluated := testEval(input)
+	result, ok := evaluated.(*object.Hash)
+	if !ok {
+		t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
+	}
+
+	expected := map[object.HashKey]int64{
+		(&object.String{Value: "one"}).HashKey():   1,
+		(&object.String{Value: "two"}).HashKey():   2,
+		(&object.String{Value: "three"}).HashKey(): 3,
+		(&object.Integer{Value: 4}).HashKey():      4,
+		TRUE.HashKey():                             5,
+		FALSE.HashKey():                            6,
+	}
+	if len(result.Pairs) != len(expected) {
+		t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
+	}
+
+	for expectedKey, expectedValue := range expected {
+		pair, ok := result.Pairs[expectedKey]
+		if !ok {
+			t.Errorf("no pair for given key in Pairs")
+		}
+		testIntegerObject(t, pair.Value, expectedValue)
+	}
+}

+ 58 - 2
object/object.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"fmt"
 	"github/runnignwater/monkey/ast"
+	"hash/fnv"
 	"strings"
 )
 
@@ -20,6 +21,7 @@ const (
 	StringObj      = "STRING"
 	BuiltinObj     = "BUILTIN"
 	ArrayObj       = "ARRAY"
+	HashObj        = "HASH"
 )
 
 // Object source code as an Object
@@ -30,6 +32,10 @@ type Object interface {
 
 // BuiltinFunction 内建函数数据
 type BuiltinFunction func(args ...Object) Object
+type HashKey struct {
+	Type  ObjType
+	Value uint64
+}
 
 type Integer struct {
 	Value int64
@@ -37,6 +43,9 @@ type Integer struct {
 
 func (i *Integer) Type() ObjType   { return IntegerObj }
 func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
+func (i *Integer) HashKey() HashKey {
+	return HashKey{Type: i.Type(), Value: uint64(i.Value)}
+}
 
 type Boolean struct {
 	Value bool
@@ -44,6 +53,16 @@ type Boolean struct {
 
 func (b *Boolean) Type() ObjType   { return BooleanObj }
 func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
+func (b *Boolean) HashKey() HashKey {
+	var value uint64
+
+	if b.Value {
+		value = 1
+	} else {
+		value = 0
+	}
+	return HashKey{Type: b.Type(), Value: value}
+}
 
 type Null struct{}
 
@@ -95,6 +114,14 @@ type String struct {
 
 func (s *String) Type() ObjType   { return StringObj }
 func (s *String) Inspect() string { return s.Value }
+func (s *String) HashKey() HashKey {
+	h := fnv.New64a()
+	_, err := h.Write([]byte(s.Value))
+	if err != nil {
+		return HashKey{Type: NullObj}
+	}
+	return HashKey{Type: s.Type(), Value: h.Sum64()}
+}
 
 type Array struct {
 	Elements []Object
@@ -120,6 +147,35 @@ type Builtin struct {
 	Fn BuiltinFunction
 }
 
-func (b Builtin) Type() ObjType { return BuiltinObj }
+func (b *Builtin) Type() ObjType { return BuiltinObj }
+
+func (b *Builtin) Inspect() string { return "builtin function" }
 
-func (b Builtin) Inspect() string { return "builtin function" }
+type HashPair struct {
+	Key   Object
+	Value Object
+}
+type Hash struct {
+	Pairs map[HashKey]HashPair
+}
+
+func (h *Hash) Type() ObjType { return HashObj }
+func (h *Hash) Inspect() string {
+	var out bytes.Buffer
+
+	pairs := []string{}
+	for _, pair := range h.Pairs {
+		pairs = append(pairs, fmt.Sprintf("%s %s",
+			pair.Key.Inspect(), pair.Value.Inspect()))
+	}
+
+	out.WriteString("{")
+	out.WriteString(strings.Join(pairs, ", "))
+	out.WriteString("}")
+
+	return out.String()
+}
+
+type Hashtable interface {
+	HashKey() HashKey
+}

+ 20 - 0
object/object_test.go

@@ -0,0 +1,20 @@
+package object
+
+import "testing"
+
+func TestStringHashKey(t *testing.T) {
+	hello1 := &String{Value: "Hello World"}
+	hello2 := &String{Value: "Hello World"}
+	diff1 := &String{Value: "My name is johnny"}
+	diff2 := &String{Value: "My name is johnny"}
+
+	if hello1.HashKey() != hello2.HashKey() {
+		t.Errorf("strings with same content have different hash keys")
+	}
+	if diff1.HashKey() != diff2.HashKey() {
+		t.Errorf("strings with same content have different hash keys")
+	}
+	if hello1.HashKey() == diff1.HashKey() {
+		t.Errorf("strings with different content have same hash keys")
+	}
+}