Hashes
This commit is contained in:
@@ -114,6 +114,10 @@ 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
|
||||
@@ -345,11 +349,29 @@ 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())
|
||||
}
|
||||
}
|
||||
|
||||
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 evalArrayIndexExpression(array, index object.Object) object.Object {
|
||||
arrayObject := array.(*object.Array)
|
||||
idx := index.(*object.Integer).Value
|
||||
@@ -361,3 +383,32 @@ func evalArrayIndexExpression(array, index object.Object) object.Object {
|
||||
|
||||
return arrayObject.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.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}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,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 {
|
||||
@@ -434,6 +438,93 @@ 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 pair. 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testEval(input string) object.Object {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
|
||||
Reference in New Issue
Block a user