From 4eede91cfd779cc606b96cefe72031d935db7cce Mon Sep 17 00:00:00 2001 From: jmug Date: Mon, 13 Jan 2025 19:46:10 -0800 Subject: [PATCH] Add the rest of the evaluator tests. Signed-off-by: jmug --- pkg/evaluator/evaluator_test.go | 152 +++++++++++++++++++++++++------- 1 file changed, 120 insertions(+), 32 deletions(-) diff --git a/pkg/evaluator/evaluator_test.go b/pkg/evaluator/evaluator_test.go index ac29cdf..35f9993 100644 --- a/pkg/evaluator/evaluator_test.go +++ b/pkg/evaluator/evaluator_test.go @@ -51,6 +51,15 @@ func TestEvalBooleanExpression(t *testing.T) { {"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 { @@ -70,15 +79,6 @@ func TestBangOperator(t *testing.T) { {"!!true", true}, {"!!false", false}, {"!!5", 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 { @@ -121,6 +121,7 @@ func TestReturnStatements(t *testing.T) { {"return 10; 9;", 10}, {"return 2 * 5; 9;", 10}, {"9; return 2 * 5; 9;", 10}, + {"if (10 > 1) { return 10; }", 10}, { ` if (10 > 1) { @@ -133,6 +134,25 @@ if (10 > 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 { @@ -162,10 +182,18 @@ func TestErrorHandling(t *testing.T) { "true + false;", "unknown operator: BOOLEAN + BOOLEAN", }, + { + "true + false + true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, { "5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN", }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, { "if (10 > 1) { true + false; }", "unknown operator: BOOLEAN + BOOLEAN", @@ -186,14 +214,14 @@ if (10 > 1) { "foobar", "identifier not found: foobar", }, - { - `"Hello" - "World"`, - "unknown operator: STRING - STRING", - }, { `{"name": "Monkey"}[fn(x) { x }];`, "unusable as hash key: FUNCTION", }, + { + `999[1]`, + "index operator not supported: INTEGER", + }, } for _, tt := range tests { @@ -272,6 +300,35 @@ func TestFunctionApplication(t *testing.T) { } } +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 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!"` @@ -303,13 +360,26 @@ func TestStringConcatenation(t *testing.T) { func TestBuiltinFunctions(t *testing.T) { tests := []struct { input string - expected any + 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, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, } for _, tt := range tests { @@ -318,6 +388,8 @@ func TestBuiltinFunctions(t *testing.T) { 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 { @@ -329,6 +401,22 @@ func TestBuiltinFunctions(t *testing.T) { 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)) + } } } } @@ -412,14 +500,14 @@ 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 - }` + { + "one": 10 - 9, + two: 1 + 1, + "thr" + "ee": 6 / 2, + 4: 4, + true: 5, + false: 6 + }` evaluated := testEval(input) result, ok := evaluated.(*object.Hash) @@ -495,21 +583,13 @@ func TestHashIndexExpressions(t *testing.T) { } } } - -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 -} - func testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) program := p.ParseProgram() + env := object.NewEnvironment() - return Eval(program, object.NewEnvironment()) + return Eval(program, env) } func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { @@ -540,3 +620,11 @@ func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { } 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 +}