Add the rest of the evaluator tests.

Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
Mariano Uvalle 2025-01-13 19:46:10 -08:00
parent b68bbf01d2
commit 4eede91cfd

View file

@ -51,6 +51,15 @@ func TestEvalBooleanExpression(t *testing.T) {
{"1 != 1", false}, {"1 != 1", false},
{"1 == 2", false}, {"1 == 2", false},
{"1 != 2", true}, {"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 { for _, tt := range tests {
@ -70,15 +79,6 @@ func TestBangOperator(t *testing.T) {
{"!!true", true}, {"!!true", true},
{"!!false", false}, {"!!false", false},
{"!!5", true}, {"!!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 { for _, tt := range tests {
@ -121,6 +121,7 @@ func TestReturnStatements(t *testing.T) {
{"return 10; 9;", 10}, {"return 10; 9;", 10},
{"return 2 * 5; 9;", 10}, {"return 2 * 5; 9;", 10},
{"9; return 2 * 5; 9;", 10}, {"9; return 2 * 5; 9;", 10},
{"if (10 > 1) { return 10; }", 10},
{ {
` `
if (10 > 1) { if (10 > 1) {
@ -133,6 +134,25 @@ if (10 > 1) {
`, `,
10, 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 { for _, tt := range tests {
@ -162,10 +182,18 @@ func TestErrorHandling(t *testing.T) {
"true + false;", "true + false;",
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: BOOLEAN + BOOLEAN",
}, },
{
"true + false + true + false;",
"unknown operator: BOOLEAN + BOOLEAN",
},
{ {
"5; true + false; 5", "5; true + false; 5",
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: BOOLEAN + BOOLEAN",
}, },
{
`"Hello" - "World"`,
"unknown operator: STRING - STRING",
},
{ {
"if (10 > 1) { true + false; }", "if (10 > 1) { true + false; }",
"unknown operator: BOOLEAN + BOOLEAN", "unknown operator: BOOLEAN + BOOLEAN",
@ -186,14 +214,14 @@ if (10 > 1) {
"foobar", "foobar",
"identifier not found: foobar", "identifier not found: foobar",
}, },
{
`"Hello" - "World"`,
"unknown operator: STRING - STRING",
},
{ {
`{"name": "Monkey"}[fn(x) { x }];`, `{"name": "Monkey"}[fn(x) { x }];`,
"unusable as hash key: FUNCTION", "unusable as hash key: FUNCTION",
}, },
{
`999[1]`,
"index operator not supported: INTEGER",
},
} }
for _, tt := range tests { 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) { func TestStringLiteral(t *testing.T) {
input := `"Hello World!"` input := `"Hello World!"`
@ -303,13 +360,26 @@ func TestStringConcatenation(t *testing.T) {
func TestBuiltinFunctions(t *testing.T) { func TestBuiltinFunctions(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expected any expected interface{}
}{ }{
{`len("")`, 0}, {`len("")`, 0},
{`len("four")`, 4}, {`len("four")`, 4},
{`len("hello world")`, 11}, {`len("hello world")`, 11},
{`len(1)`, "argument to `len` not supported, got INTEGER"}, {`len(1)`, "argument to `len` not supported, got INTEGER"},
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, {`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 { for _, tt := range tests {
@ -318,6 +388,8 @@ func TestBuiltinFunctions(t *testing.T) {
switch expected := tt.expected.(type) { switch expected := tt.expected.(type) {
case int: case int:
testIntegerObject(t, evaluated, int64(expected)) testIntegerObject(t, evaluated, int64(expected))
case nil:
testNullObject(t, evaluated)
case string: case string:
errObj, ok := evaluated.(*object.Error) errObj, ok := evaluated.(*object.Error)
if !ok { if !ok {
@ -329,6 +401,22 @@ func TestBuiltinFunctions(t *testing.T) {
t.Errorf("wrong error message. expected=%q, got=%q", t.Errorf("wrong error message. expected=%q, got=%q",
expected, errObj.Message) 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) { func TestHashLiterals(t *testing.T) {
input := `let two = "two"; input := `let two = "two";
{ {
"one": 10 - 9, "one": 10 - 9,
two: 1 + 1, two: 1 + 1,
"thr" + "ee": 6 / 2, "thr" + "ee": 6 / 2,
4: 4, 4: 4,
true: 5, true: 5,
false: 6 false: 6
}` }`
evaluated := testEval(input) evaluated := testEval(input)
result, ok := evaluated.(*object.Hash) 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 { func testEval(input string) object.Object {
l := lexer.New(input) l := lexer.New(input)
p := parser.New(l) p := parser.New(l)
program := p.ParseProgram() 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 { 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 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
}