aboutsummaryrefslogtreecommitdiff
path: root/evaluator/evaluator_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'evaluator/evaluator_test.go')
-rw-r--r--evaluator/evaluator_test.go278
1 files changed, 278 insertions, 0 deletions
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)