aboutsummaryrefslogtreecommitdiff
path: root/evaluator
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2017-05-20 16:28:10 +0200
committerDimitri Sokolyuk <demon@dim13.org>2017-05-20 16:28:10 +0200
commit523f40968b9c1a23da1f4a1c2f125197d7611fef (patch)
tree36518bed0260cb3cede13b59fc7051a9e262d403 /evaluator
parent69fc902f8f5fd8f36db0991f6ba4faeabb3090fa (diff)
04
Diffstat (limited to 'evaluator')
-rw-r--r--evaluator/builtins.go117
-rw-r--r--evaluator/evaluator.go137
-rw-r--r--evaluator/evaluator_test.go278
3 files changed, 522 insertions, 10 deletions
diff --git a/evaluator/builtins.go b/evaluator/builtins.go
new file mode 100644
index 0000000..68eadcd
--- /dev/null
+++ b/evaluator/builtins.go
@@ -0,0 +1,117 @@
+package evaluator
+
+import (
+ "fmt"
+ "monkey/object"
+)
+
+var builtins = map[string]*object.Builtin{
+ "len": &object.Builtin{Fn: func(args ...object.Object) object.Object {
+ if len(args) != 1 {
+ return newError("wrong number of arguments. got=%d, want=1",
+ len(args))
+ }
+
+ switch arg := args[0].(type) {
+ case *object.Array:
+ return &object.Integer{Value: int64(len(arg.Elements))}
+ case *object.String:
+ return &object.Integer{Value: int64(len(arg.Value))}
+ default:
+ return newError("argument to `len` not supported, got %s",
+ args[0].Type())
+ }
+ },
+ },
+ "puts": &object.Builtin{
+ Fn: func(args ...object.Object) object.Object {
+ for _, arg := range args {
+ fmt.Println(arg.Inspect())
+ }
+
+ return NULL
+ },
+ },
+ "first": &object.Builtin{
+ Fn: func(args ...object.Object) object.Object {
+ if len(args) != 1 {
+ return newError("wrong number of arguments. got=%d, want=1",
+ len(args))
+ }
+ if args[0].Type() != object.ARRAY_OBJ {
+ return newError("argument to `first` must be ARRAY, got %s",
+ args[0].Type())
+ }
+
+ arr := args[0].(*object.Array)
+ if len(arr.Elements) > 0 {
+ return arr.Elements[0]
+ }
+
+ return NULL
+ },
+ },
+ "last": &object.Builtin{
+ Fn: func(args ...object.Object) object.Object {
+ if len(args) != 1 {
+ return newError("wrong number of arguments. got=%d, want=1",
+ len(args))
+ }
+ if args[0].Type() != object.ARRAY_OBJ {
+ return newError("argument to `last` must be ARRAY, got %s",
+ args[0].Type())
+ }
+
+ arr := args[0].(*object.Array)
+ length := len(arr.Elements)
+ if length > 0 {
+ return arr.Elements[length-1]
+ }
+
+ return NULL
+ },
+ },
+ "rest": &object.Builtin{
+ Fn: func(args ...object.Object) object.Object {
+ if len(args) != 1 {
+ return newError("wrong number of arguments. got=%d, want=1",
+ len(args))
+ }
+ if args[0].Type() != object.ARRAY_OBJ {
+ return newError("argument to `rest` must be ARRAY, got %s",
+ args[0].Type())
+ }
+
+ arr := args[0].(*object.Array)
+ length := len(arr.Elements)
+ if length > 0 {
+ newElements := make([]object.Object, length-1, length-1)
+ copy(newElements, arr.Elements[1:length])
+ return &object.Array{Elements: newElements}
+ }
+
+ return NULL
+ },
+ },
+ "push": &object.Builtin{
+ Fn: func(args ...object.Object) object.Object {
+ if len(args) != 2 {
+ return newError("wrong number of arguments. got=%d, want=1",
+ len(args))
+ }
+ if args[0].Type() != object.ARRAY_OBJ {
+ return newError("argument to `push` must be ARRAY, got %s",
+ args[0].Type())
+ }
+
+ arr := args[0].(*object.Array)
+ length := len(arr.Elements)
+
+ newElements := make([]object.Object, length+1, length+1)
+ copy(newElements, arr.Elements)
+ newElements[length] = args[1]
+
+ return &object.Array{Elements: newElements}
+ },
+ },
+}
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index c8fa09a..50b2ab5 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -43,6 +43,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}
+ case *ast.StringLiteral:
+ return &object.String{Value: node.Value}
+
case *ast.Boolean:
return nativeBoolToBooleanObject(node.Value)
@@ -89,6 +92,28 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
}
return applyFunction(function, args)
+
+ case *ast.ArrayLiteral:
+ elements := evalExpressions(node.Elements, env)
+ if len(elements) == 1 && isError(elements[0]) {
+ return elements[0]
+ }
+ return &object.Array{Elements: elements}
+
+ case *ast.IndexExpression:
+ left := Eval(node.Left, env)
+ if isError(left) {
+ return left
+ }
+ index := Eval(node.Index, env)
+ if isError(index) {
+ return index
+ }
+ return evalIndexExpression(left, index)
+
+ case *ast.HashLiteral:
+ return evalHashLiteral(node, env)
+
}
return nil
@@ -156,6 +181,8 @@ func evalInfixExpression(
switch {
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right)
+ case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
+ return evalStringInfixExpression(operator, left, right)
case operator == "==":
return nativeBoolToBooleanObject(left == right)
case operator == "!=":
@@ -221,6 +248,20 @@ func evalIntegerInfixExpression(
}
}
+func evalStringInfixExpression(
+ operator string,
+ left, right object.Object,
+) object.Object {
+ if operator != "+" {
+ return newError("unknown operator: %s %s %s",
+ left.Type(), operator, right.Type())
+ }
+
+ leftVal := left.(*object.String).Value
+ rightVal := right.(*object.String).Value
+ return &object.String{Value: leftVal + rightVal}
+}
+
func evalIfExpression(
ie *ast.IfExpression,
env *object.Environment,
@@ -243,12 +284,15 @@ func evalIdentifier(
node *ast.Identifier,
env *object.Environment,
) object.Object {
- val, ok := env.Get(node.Value)
- if !ok {
- return newError("identifier not found: " + node.Value)
+ if val, ok := env.Get(node.Value); ok {
+ return val
+ }
+
+ if builtin, ok := builtins[node.Value]; ok {
+ return builtin
}
- return val
+ return newError("identifier not found: " + node.Value)
}
func isTruthy(obj object.Object) bool {
@@ -293,14 +337,19 @@ func evalExpressions(
}
func applyFunction(fn object.Object, args []object.Object) object.Object {
- function, ok := fn.(*object.Function)
- if !ok {
+ switch fn := fn.(type) {
+
+ case *object.Function:
+ extendedEnv := extendFunctionEnv(fn, args)
+ evaluated := Eval(fn.Body, extendedEnv)
+ return unwrapReturnValue(evaluated)
+
+ case *object.Builtin:
+ return fn.Fn(args...)
+
+ default:
return newError("not a function: %s", fn.Type())
}
-
- extendedEnv := extendFunctionEnv(function, args)
- evaluated := Eval(function.Body, extendedEnv)
- return unwrapReturnValue(evaluated)
}
func extendFunctionEnv(
@@ -323,3 +372,71 @@ func unwrapReturnValue(obj object.Object) object.Object {
return obj
}
+
+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 evalArrayIndexExpression(array, index object.Object) object.Object {
+ arrayObject := array.(*object.Array)
+ idx := index.(*object.Integer).Value
+ max := int64(len(arrayObject.Elements) - 1)
+
+ if idx < 0 || idx > max {
+ return NULL
+ }
+
+ 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}
+}
+
+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
+}
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index e8c77fa..932b07f 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -190,6 +190,10 @@ func TestErrorHandling(t *testing.T) {
"unknown operator: BOOLEAN + BOOLEAN",
},
{
+ `"Hello" - "World"`,
+ "unknown operator: STRING - STRING",
+ },
+ {
"if (10 > 1) { true + false; }",
"unknown operator: BOOLEAN + BOOLEAN",
},
@@ -209,6 +213,14 @@ if (10 > 1) {
"foobar",
"identifier not found: foobar",
},
+ {
+ `{"name": "Monkey"}[fn(x) { x }];`,
+ "unusable as hash key: FUNCTION",
+ },
+ {
+ `999[1]`,
+ "index operator not supported: INTEGER",
+ },
}
for _, tt := range tests {
@@ -304,6 +316,272 @@ ourFunction(20) + first + second;`
testIntegerObject(t, testEval(input), 70)
}
+func TestClosures(t *testing.T) {
+ input := `
+let newAdder = fn(x) {
+ fn(y) { x + y };
+};
+
+let addTwo = newAdder(2);
+addTwo(2);`
+
+ testIntegerObject(t, testEval(input), 4)
+}
+
+func TestStringLiteral(t *testing.T) {
+ input := `"Hello World!"`
+
+ evaluated := testEval(input)
+ str, ok := evaluated.(*object.String)
+ if !ok {
+ t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
+ }
+
+ if str.Value != "Hello World!" {
+ t.Errorf("String has wrong value. got=%q", str.Value)
+ }
+}
+
+func TestStringConcatenation(t *testing.T) {
+ input := `"Hello" + " " + "World!"`
+
+ evaluated := testEval(input)
+ str, ok := evaluated.(*object.String)
+ if !ok {
+ t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
+ }
+
+ if str.Value != "Hello World!" {
+ t.Errorf("String has wrong value. got=%q", str.Value)
+ }
+}
+
+func TestBuiltinFunctions(t *testing.T) {
+ tests := []struct {
+ input string
+ expected interface{}
+ }{
+ {`len("")`, 0},
+ {`len("four")`, 4},
+ {`len("hello world")`, 11},
+ {`len(1)`, "argument to `len` not supported, got INTEGER"},
+ {`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
+ {`len([1, 2, 3])`, 3},
+ {`len([])`, 0},
+ {`puts("hello", "world!")`, nil},
+ {`first([1, 2, 3])`, 1},
+ {`first([])`, nil},
+ {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
+ {`last([1, 2, 3])`, 3},
+ {`last([])`, nil},
+ {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
+ {`rest([1, 2, 3])`, []int{2, 3}},
+ {`rest([])`, nil},
+ {`push([], 1)`, []int{1}},
+ {`push(1)`, "argument to `push` must be ARRAY, got INTEGER"},
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+
+ switch expected := tt.expected.(type) {
+ case int:
+ testIntegerObject(t, evaluated, int64(expected))
+ case nil:
+ testNullObject(t, evaluated)
+ case string:
+ errObj, ok := evaluated.(*object.Error)
+ if !ok {
+ t.Errorf("object is not Error. got=%T (%+v)",
+ evaluated, evaluated)
+ continue
+ }
+ if errObj.Message != expected {
+ t.Errorf("wrong error message. expected=%q, got=%q",
+ expected, errObj.Message)
+ }
+ case []int:
+ array, ok := evaluated.(*object.Array)
+ if !ok {
+ t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated)
+ continue
+ }
+
+ if len(array.Elements) != len(expected) {
+ t.Errorf("wrong num of elements. want=%d, got=%d",
+ len(expected), len(array.Elements))
+ continue
+ }
+
+ for i, expectedElem := range expected {
+ testIntegerObject(t, array.Elements[i], int64(expectedElem))
+ }
+ }
+ }
+}
+
+func TestArrayLiterals(t *testing.T) {
+ input := "[1, 2 * 2, 3 + 3]"
+
+ evaluated := testEval(input)
+ result, ok := evaluated.(*object.Array)
+ if !ok {
+ t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
+ }
+
+ if len(result.Elements) != 3 {
+ t.Fatalf("array has wrong num of elements. got=%d",
+ len(result.Elements))
+ }
+
+ testIntegerObject(t, result.Elements[0], 1)
+ testIntegerObject(t, result.Elements[1], 4)
+ testIntegerObject(t, result.Elements[2], 6)
+}
+
+func TestArrayIndexExpressions(t *testing.T) {
+ tests := []struct {
+ input string
+ expected interface{}
+ }{
+ {
+ "[1, 2, 3][0]",
+ 1,
+ },
+ {
+ "[1, 2, 3][1]",
+ 2,
+ },
+ {
+ "[1, 2, 3][2]",
+ 3,
+ },
+ {
+ "let i = 0; [1][i];",
+ 1,
+ },
+ {
+ "[1, 2, 3][1 + 1];",
+ 3,
+ },
+ {
+ "let myArray = [1, 2, 3]; myArray[2];",
+ 3,
+ },
+ {
+ "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
+ 6,
+ },
+ {
+ "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
+ 2,
+ },
+ {
+ "[1, 2, 3][3]",
+ nil,
+ },
+ {
+ "[1, 2, 3][-1]",
+ nil,
+ },
+ }
+
+ 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 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 testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)