| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- package evaluator
- import (
- "github/runnignwater/monkey/lexer"
- "github/runnignwater/monkey/object"
- "github/runnignwater/monkey/parser"
- "log"
- "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},
- }
- 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", true},
- {"1 != 1", false},
- {"1 == 2", false},
- {"1 != 2", true},
- {"true == true", true},
- {"false == false", true},
- {"(1 < 2) == true", 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 TestIfElseExpression(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}", 10},
- {"if(1>2){10} else {20}", 20},
- }
- 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 3 *5; 9;", 15},
- {
- `if ( 10 > 1) {
- if( 10 > 1) {
- return 10;
- }
- return 1;
- }
- `,
- 10,
- },
- }
- for _, tt := range tests {
- evaluated := testEval(tt.input)
- testIntegerObject(t, evaluated, tt.expected)
- }
- }
- func TestErrorHandling(t *testing.T) {
- tests := []struct {
- input string
- expectedMsg string
- }{
- {
- "5 + true",
- "type mismatch: INTEGER + BOOLEAN",
- },
- {
- "5 + true; 5;",
- "type mismatch: INTEGER + BOOLEAN",
- },
- {
- "-true",
- "unknown operator: -BOOLEAN",
- },
- {
- "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",
- },
- {
- `"Hello" - "World"`,
- "unknown operator: STRING - STRING",
- },
- }
- 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.Msg != tt.expectedMsg {
- t.Errorf("wrong error message. expected=%q, got=%q", tt.expectedMsg, errObj.Msg)
- }
- }
- }
- func TestLetStatement(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 identify = fn(x){return x;};identify(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 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 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 testEval(input string) object.Object {
- l := lexer.New(input)
- p := parser.New(l)
- program := p.ParseProgram()
- env := object.NewEnvironment()
- return Eval(program, env)
- }
- 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)
- }
- }
- // ------------------------------------------------------------------------Built-in Function
- 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"},
- }
- for _, tt := range tests {
- evaluated := testEval(tt.input)
- switch expected := tt.expected.(type) {
- case int:
- testIntegerObject(t, evaluated, int64(expected))
- case string:
- errObj, ok := evaluated.(*object.Error)
- if !ok {
- t.Errorf("object is not Error. got=%T (%+v)", evaluated, evaluated)
- }
- if errObj.Msg != expected {
- t.Errorf("wrong error message. expected=%q, got=%q", expected, errObj.Msg)
- }
- }
- }
- }
- func TestArrayLiterals(t *testing.T) {
- input := "[1, 2 * 2, 3 + 5]"
- 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 {
- log.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], 8)
- }
- func TestArrayIndexExpression(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)
- }
- }
|