aboutsummaryrefslogtreecommitdiff
path: root/evaluator
diff options
context:
space:
mode:
Diffstat (limited to 'evaluator')
-rw-r--r--evaluator/evaluator.go47
-rw-r--r--evaluator/evaluator_test.go90
2 files changed, 137 insertions, 0 deletions
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index 913138b..5e988cc 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -110,6 +110,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
case *ast.StringLiteral:
return &object.String{Value: node.Value}
+ case *ast.HashLiteral:
+ return evalHashLiteral(node, env)
+
case *ast.ReturnStatement:
val := Eval(node.ReturnValue, env)
if isError(val) {
@@ -319,6 +322,8 @@ func evalIndexExpression(left, index object.Object) object.Object {
switch {
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
return evalArrayIndexExpression(left, index)
+ case left.Type() == object.HASH_OBJ:
+ return evalHashIndexExpression(left, index)
default:
return newError("index operator not supported: %s", left.Type())
}
@@ -348,6 +353,48 @@ func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object
return newError("identifier not found: " + node.Value)
}
+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.Hashable)
+ 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 evalHashIndexExpression(hash, index object.Object) object.Object {
+ hashObject := hash.(*object.Hash)
+
+ key, ok := index.(object.Hashable)
+ if !ok {
+ return newError("unusable as hash key: %s", index.Type())
+ }
+
+ pair, ok := hashObject.Pairs[key.HashKey()]
+ if !ok {
+ return NULL
+ }
+
+ return pair.Value
+}
+
func isTruthy(obj object.Object) bool {
switch obj {
case NULL:
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index 3c72cda..f1d28d0 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -185,6 +185,10 @@ func TestErrorhandling(t *testing.T) {
`"Hello" - "World"`,
"unknown operator: STRING - STRING",
},
+ {
+ `{"name": "Monkey"}[fn(x) { x }];`,
+ "unusable as hash key: FUNCTION",
+ },
}
for _, tt := range tests {
@@ -456,3 +460,89 @@ func TestArrayIndexExpressions(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)
+ }
+}
+
+func TestHashIndexExpressions(t *testing.T) {
+ tests := []struct {
+ input string
+ expected interface{}
+ }{
+ {
+ `{"foo": 5}["foo"]`,
+ 5,
+ },
+ {
+ `{"foo": 5}["bar"]`,
+ nil,
+ },
+ {
+ `let key = "foo"; {"foo": 5}[key]`,
+ 5,
+ },
+ {
+ `{}["foo"]`,
+ nil,
+ },
+ {
+ `{5: 5}[5]`,
+ 5,
+ },
+ {
+ `{true: 5}[true]`,
+ 5,
+ },
+ {
+ `{false: 5}[false]`,
+ 5,
+ },
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+ integer, ok := tt.expected.(int)
+ if ok {
+ testIntegerObject(t, evaluated, int64(integer))
+ } else {
+ testNullObject(t, evaluated)
+ }
+ }
+}