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:
Mariano Uvalle 2025-01-10 21:09:44 -08:00
parent fa9f450278
commit fb25a86b91
11 changed files with 373 additions and 0 deletions

View file

@ -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 {

View file

@ -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:

View file

@ -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)