package compiler import ( "fmt" "github/runnignwater/monkey/ast" "github/runnignwater/monkey/code" "github/runnignwater/monkey/lexer" "github/runnignwater/monkey/object" "github/runnignwater/monkey/parser" "testing" ) type compilerTestCase struct { input string expectedConstants []interface{} expectedInstructions []code.Instructions } func TestIntegerArithmetic(t *testing.T) { tests := []compilerTestCase{ { input: "1 + 2", expectedConstants: []interface{}{1, 2}, expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 1), code.Make(code.OpAdd), code.Make(code.OpPop), }, }, { input: "1;2", expectedConstants: []interface{}{1, 2}, expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpPop), code.Make(code.OpConstant, 1), code.Make(code.OpPop), }, }, { input: "1 - 2", expectedConstants: []interface{}{1, 2}, expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 1), code.Make(code.OpSub), code.Make(code.OpPop), }, }, { input: "1 * 2", expectedConstants: []interface{}{1, 2}, expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 1), code.Make(code.OpMul), code.Make(code.OpPop), }, }, { input: "2 / 1", expectedConstants: []interface{}{2, 1}, expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 1), code.Make(code.OpDiv), code.Make(code.OpPop), }, }, { input: "5 * (2 + 10)", expectedConstants: []interface{}{5, 2, 10}, expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 1), code.Make(code.OpConstant, 2), code.Make(code.OpAdd), code.Make(code.OpMul), code.Make(code.OpPop), }, }, } runCompilerTests(t, tests) } func runCompilerTests(t *testing.T, tests []compilerTestCase) { t.Helper() for _, tt := range tests { program := parse(tt.input) compiler := New() err := compiler.Compile(program) if err != nil { t.Fatalf("compiler error: %s", err) } bytecode := compiler.ByteCode() err = testInstructions(tt.expectedInstructions, bytecode.Instructions) if err != nil { t.Fatalf("testInstructions failed: %s", err) } err = testConstants(t, tt.expectedConstants, bytecode.Constants) if err != nil { t.Fatalf("testConstants failed: %s", err) } } } func testConstants( t *testing.T, expected []interface{}, actual []object.Object, ) error { t.Helper() if len(expected) != len(actual) { return fmt.Errorf("wrong number of constants. got=%d, want=%d", len(actual), len(expected)) } for i, constant := range expected { switch constant := constant.(type) { case int: err := testIntegerObject(int64(constant), actual[i]) if err != nil { return fmt.Errorf("constant %d -- testIntegerObject failed: %s", i, err) } } } return nil } func testIntegerObject(expected int64, actual object.Object) error { result, ok := actual.(*object.Integer) if !ok { return fmt.Errorf("object is not Integer. got=%T (%+v)", actual, actual) } if result.Value != expected { return fmt.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected) } return nil } func testInstructions( expected []code.Instructions, actual code.Instructions, ) error { concatted := concatInstructions(expected) if len(actual) != len(concatted) { return fmt.Errorf("wrong instructions length.\nwant=%q\ngot=%q", concatted, actual) } for i, ins := range concatted { if actual[i] != ins { return fmt.Errorf("wrong instructions at %d.\nwant=%q\ngot=%q", i, concatted, actual) } } return nil } func concatInstructions(s []code.Instructions) code.Instructions { out := code.Instructions{} for _, ins := range s { out = append(out, ins...) } return out } func parse(input string) *ast.Program { l := lexer.New(input) p := parser.New(l) return p.ParseProgram() }