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.go351
1 files changed, 351 insertions, 0 deletions
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
new file mode 100644
index 0000000..e8c77fa
--- /dev/null
+++ b/evaluator/evaluator_test.go
@@ -0,0 +1,351 @@
+package evaluator
+
+import (
+ "monkey/lexer"
+ "monkey/object"
+ "monkey/parser"
+ "testing"
+)
+
+func TestEvalIntegerExpression(t *testing.T) {
+ tests := []struct {
+ input string
+ expected int64
+ }{
+ {"5", 5},
+ {"10", 10},
+ {"-5", -5},
+ {"-10", -10},
+ {"5 + 5 + 5 + 5 - 10", 10},
+ {"2 * 2 * 2 * 2 * 2", 32},
+ {"-50 + 100 + -50", 0},
+ {"5 * 2 + 10", 20},
+ {"5 + 2 * 10", 25},
+ {"20 + 2 * -10", 0},
+ {"50 / 2 * 2 + 10", 60},
+ {"2 * (5 + 10)", 30},
+ {"3 * 3 * 3 + 10", 37},
+ {"3 * (3 * 3) + 10", 37},
+ {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+ testIntegerObject(t, evaluated, tt.expected)
+ }
+}
+
+func TestEvalBooleanExpression(t *testing.T) {
+ tests := []struct {
+ input string
+ expected bool
+ }{
+ {"true", true},
+ {"false", false},
+ {"1 < 2", true},
+ {"1 > 2", false},
+ {"1 < 1", false},
+ {"1 > 1", false},
+ {"1 == 1", true},
+ {"1 != 1", false},
+ {"1 == 2", false},
+ {"1 != 2", true},
+ {"true == true", true},
+ {"false == false", true},
+ {"true == false", false},
+ {"true != false", true},
+ {"false != true", true},
+ {"(1 < 2) == true", true},
+ {"(1 < 2) == false", false},
+ {"(1 > 2) == true", false},
+ {"(1 > 2) == false", true},
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+ testBooleanObject(t, evaluated, tt.expected)
+ }
+}
+
+func TestBangOperator(t *testing.T) {
+ tests := []struct {
+ input string
+ expected bool
+ }{
+ {"!true", false},
+ {"!false", true},
+ {"!5", false},
+ {"!!true", true},
+ {"!!false", false},
+ {"!!5", true},
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+ testBooleanObject(t, evaluated, tt.expected)
+ }
+}
+
+func TestIfElseExpressions(t *testing.T) {
+ tests := []struct {
+ input string
+ expected interface{}
+ }{
+ {"if (true) { 10 }", 10},
+ {"if (false) { 10 }", nil},
+ {"if (1) { 10 }", 10},
+ {"if (1 < 2) { 10 }", 10},
+ {"if (1 > 2) { 10 }", nil},
+ {"if (1 > 2) { 10 } else { 20 }", 20},
+ {"if (1 < 2) { 10 } else { 20 }", 10},
+ }
+
+ 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 TestReturnStatements(t *testing.T) {
+ tests := []struct {
+ input string
+ expected int64
+ }{
+ {"return 10;", 10},
+ {"return 10; 9;", 10},
+ {"return 2 * 5; 9;", 10},
+ {"9; return 2 * 5; 9;", 10},
+ {"if (10 > 1) { return 10; }", 10},
+ {
+ `
+if (10 > 1) {
+ if (10 > 1) {
+ return 10;
+ }
+
+ return 1;
+}
+`,
+ 10,
+ },
+ {
+ `
+let f = fn(x) {
+ return x;
+ x + 10;
+};
+f(10);`,
+ 10,
+ },
+ {
+ `
+let f = fn(x) {
+ let result = x + 10;
+ return result;
+ return 10;
+};
+f(10);`,
+ 20,
+ },
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+ testIntegerObject(t, evaluated, tt.expected)
+ }
+}
+
+func TestErrorHandling(t *testing.T) {
+ tests := []struct {
+ input string
+ expectedMessage string
+ }{
+ {
+ "5 + true;",
+ "type mismatch: INTEGER + BOOLEAN",
+ },
+ {
+ "5 + true; 5;",
+ "type mismatch: INTEGER + BOOLEAN",
+ },
+ {
+ "-true",
+ "unknown operator: -BOOLEAN",
+ },
+ {
+ "true + false;",
+ "unknown operator: BOOLEAN + BOOLEAN",
+ },
+ {
+ "true + false + true + false;",
+ "unknown operator: BOOLEAN + BOOLEAN",
+ },
+ {
+ "5; true + false; 5",
+ "unknown operator: BOOLEAN + BOOLEAN",
+ },
+ {
+ "if (10 > 1) { true + false; }",
+ "unknown operator: BOOLEAN + BOOLEAN",
+ },
+ {
+ `
+if (10 > 1) {
+ if (10 > 1) {
+ return true + false;
+ }
+
+ return 1;
+}
+`,
+ "unknown operator: BOOLEAN + BOOLEAN",
+ },
+ {
+ "foobar",
+ "identifier not found: foobar",
+ },
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+
+ errObj, ok := evaluated.(*object.Error)
+ if !ok {
+ t.Errorf("no error object returned. got=%T(%+v)",
+ evaluated, evaluated)
+ continue
+ }
+
+ if errObj.Message != tt.expectedMessage {
+ t.Errorf("wrong error message. expected=%q, got=%q",
+ tt.expectedMessage, errObj.Message)
+ }
+ }
+}
+
+func TestLetStatements(t *testing.T) {
+ tests := []struct {
+ input string
+ expected int64
+ }{
+ {"let a = 5; a;", 5},
+ {"let a = 5 * 5; a;", 25},
+ {"let a = 5; let b = a; b;", 5},
+ {"let a = 5; let b = a; let c = a + b + 5; c;", 15},
+ }
+
+ for _, tt := range tests {
+ testIntegerObject(t, testEval(tt.input), tt.expected)
+ }
+}
+
+func TestFunctionObject(t *testing.T) {
+ input := "fn(x) { x + 2; };"
+
+ evaluated := testEval(input)
+ fn, ok := evaluated.(*object.Function)
+ if !ok {
+ t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
+ }
+
+ if len(fn.Parameters) != 1 {
+ t.Fatalf("function has wrong parameters. Parameters=%+v",
+ fn.Parameters)
+ }
+
+ if fn.Parameters[0].String() != "x" {
+ t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
+ }
+
+ expectedBody := "(x + 2)"
+
+ if fn.Body.String() != expectedBody {
+ t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
+ }
+}
+
+func TestFunctionApplication(t *testing.T) {
+ tests := []struct {
+ input string
+ expected int64
+ }{
+ {"let identity = fn(x) { x; }; identity(5);", 5},
+ {"let identity = fn(x) { return x; }; identity(5);", 5},
+ {"let double = fn(x) { x * 2; }; double(5);", 10},
+ {"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
+ {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
+ {"fn(x) { x; }(5)", 5},
+ }
+
+ for _, tt := range tests {
+ testIntegerObject(t, testEval(tt.input), tt.expected)
+ }
+}
+
+func TestEnclosingEnvironments(t *testing.T) {
+ input := `
+let first = 10;
+let second = 10;
+let third = 10;
+
+let ourFunction = fn(first) {
+ let second = 20;
+
+ first + second + third;
+};
+
+ourFunction(20) + first + second;`
+
+ testIntegerObject(t, testEval(input), 70)
+}
+
+func testEval(input string) object.Object {
+ l := lexer.New(input)
+ p := parser.New(l)
+ program := p.ParseProgram()
+ env := object.NewEnvironment()
+
+ return Eval(program, env)
+}
+
+func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
+ result, ok := obj.(*object.Integer)
+ if !ok {
+ t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
+ return false
+ }
+ if result.Value != expected {
+ t.Errorf("object has wrong value. got=%d, want=%d",
+ result.Value, expected)
+ return false
+ }
+
+ return true
+}
+
+func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
+ result, ok := obj.(*object.Boolean)
+ if !ok {
+ t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
+ return false
+ }
+ if result.Value != expected {
+ t.Errorf("object has wrong value. got=%t, want=%t",
+ result.Value, expected)
+ return false
+ }
+ return true
+}
+
+func testNullObject(t *testing.T, obj object.Object) bool {
+ if obj != NULL {
+ t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
+ return false
+ }
+ return true
+}