diff options
Diffstat (limited to 'evaluator')
| -rw-r--r-- | evaluator/evaluator.go | 47 | ||||
| -rw-r--r-- | evaluator/evaluator_test.go | 90 |
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) + } + } +} |
