Done with the book! Added hash maps and the puts builtin.
Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
parent
fa9f450278
commit
fb25a86b91
11 changed files with 373 additions and 0 deletions
|
|
@ -1,12 +1,21 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.jmug.me/jmug/interpreter-in-go/pkg/object"
|
||||
)
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"puts": {
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
return _NULL
|
||||
},
|
||||
},
|
||||
"len": {
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
|
|
|
|||
|
|
@ -91,6 +91,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
|
||||
}
|
||||
|
|
@ -251,6 +253,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())
|
||||
}
|
||||
|
|
@ -265,6 +269,40 @@ func evalArrayIndexExpression(arrayObj, indexObj object.Object) object.Object {
|
|||
return array[index]
|
||||
}
|
||||
|
||||
func evalHashIndexExpression(hashObj, index object.Object) object.Object {
|
||||
hash := hashObj.(*object.Hash)
|
||||
hashable, okHash := index.(object.Hashable)
|
||||
if !okHash {
|
||||
return newError("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
pair, okPair := hash.Pairs[hashable.HashKey()]
|
||||
if !okPair {
|
||||
return _NULL
|
||||
}
|
||||
return pair.Value
|
||||
}
|
||||
|
||||
func evalHashLiteral(hash *ast.HashLiteral, env *object.Environment) object.Object {
|
||||
pairs := map[object.HashKey]object.HashPair{}
|
||||
for ke, ve := range hash.Pairs {
|
||||
k := Eval(ke, env)
|
||||
if isError(k) {
|
||||
return k
|
||||
}
|
||||
hashable, ok := k.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", k.Type())
|
||||
}
|
||||
v := Eval(ve, env)
|
||||
if isError(v) {
|
||||
return v
|
||||
}
|
||||
hashKey := hashable.HashKey()
|
||||
pairs[hashKey] = object.HashPair{Key: k, Value: v}
|
||||
}
|
||||
return &object.Hash{Pairs: pairs}
|
||||
}
|
||||
|
||||
func applyFunction(fnObj object.Object, args []object.Object) object.Object {
|
||||
switch fn := fnObj.(type) {
|
||||
case *object.Function:
|
||||
|
|
|
|||
|
|
@ -190,6 +190,10 @@ if (10 > 1) {
|
|||
`"Hello" - "World"`,
|
||||
"unknown operator: STRING - STRING",
|
||||
},
|
||||
{
|
||||
`{"name": "Monkey"}[fn(x) { x }];`,
|
||||
"unusable as hash key: FUNCTION",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -406,6 +410,92 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testNullObject(t *testing.T, obj object.Object) bool {
|
||||
if obj != _NULL {
|
||||
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue