| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776 |
- 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),
- },
- },
- {
- input: "-1",
- expectedConstants: []interface{}{1},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpMinus),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestBooleanExpression(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: "true",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpTrue),
- code.Make(code.OpPop),
- },
- },
- {
- input: "false",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpFalse),
- 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.OpGreaterThan),
- code.Make(code.OpPop),
- },
- },
- {
- input: "1 < 2",
- expectedConstants: []interface{}{2, 1},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpGreaterThan),
- 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.OpEqual),
- 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.OpNotEqual),
- code.Make(code.OpPop),
- },
- },
- {
- input: "true == false",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpTrue),
- code.Make(code.OpFalse),
- code.Make(code.OpEqual),
- code.Make(code.OpPop),
- },
- },
- {
- input: "true != false",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpTrue),
- code.Make(code.OpFalse),
- code.Make(code.OpNotEqual),
- code.Make(code.OpPop),
- },
- },
- {
- input: "!true",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpTrue),
- code.Make(code.OpBang),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestConditionals(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: "if (true) {10} ; 3333;",
- expectedConstants: []interface{}{10, 3333},
- expectedInstructions: []code.Instructions{
- // 0000
- code.Make(code.OpTrue),
- // 0001
- code.Make(code.OpJumpNotTruthy, 10),
- // 0004
- code.Make(code.OpConstant, 0),
- // 0007
- code.Make(code.OpJump, 11),
- // 0010
- code.Make(code.OpNull),
- // 0011
- code.Make(code.OpPop),
- // 0012
- code.Make(code.OpConstant, 1),
- // 0015
- code.Make(code.OpPop),
- },
- },
- {
- input: "if(true) {10} else {20}; 3333",
- expectedConstants: []interface{}{10, 20, 3333},
- expectedInstructions: []code.Instructions{
- // 0000
- code.Make(code.OpTrue),
- // 0001
- code.Make(code.OpJumpNotTruthy, 10),
- // 0004
- code.Make(code.OpConstant, 0),
- // 0007
- code.Make(code.OpJump, 13),
- // 0010
- code.Make(code.OpConstant, 1),
- // 0013
- code.Make(code.OpPop),
- // 0014
- code.Make(code.OpConstant, 2),
- // 0017
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestGlobalLetStatements(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: `let one = 1; let two = 2;`,
- expectedConstants: []interface{}{1, 2},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpSetGlobal, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpSetGlobal, 1),
- },
- },
- {
- input: `let one = 1; one;`,
- expectedConstants: []interface{}{1},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpSetGlobal, 0),
- code.Make(code.OpGetGlobal, 0),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestLetStatement(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: `let num = 55; fn(){num};`,
- expectedConstants: []interface{}{
- 55,
- []code.Instructions{
- code.Make(code.OpGetGlobal, 0),
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpSetGlobal, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpPop),
- },
- },
- {
- input: `
- fn () {
- let num = 55;
- num;
- }
- `,
- expectedConstants: []interface{}{
- 55,
- []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpSetLocal, 0),
- code.Make(code.OpGetLocal, 0),
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 1),
- code.Make(code.OpPop),
- },
- },
- {
- input: `
- fn () {
- let a = 55;
- let b = 77;
- a + b;
- }
- `,
- expectedConstants: []interface{}{
- 55, 77,
- []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpSetLocal, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpSetLocal, 1),
- code.Make(code.OpGetLocal, 0),
- code.Make(code.OpGetLocal, 1),
- code.Make(code.OpAdd),
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 2),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestStringExpression(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: `"monkey"`,
- expectedConstants: []interface{}{"monkey"},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpPop),
- },
- },
- {
- input: `"monkey" + "language"`,
- expectedConstants: []interface{}{"monkey", "language"},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpAdd),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestArrayLiteral(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: "[]",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpArray, 0),
- code.Make(code.OpPop),
- },
- },
- {
- input: "[1, 2, 3]",
- expectedConstants: []interface{}{1, 2, 3},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpConstant, 2),
- code.Make(code.OpArray, 3),
- code.Make(code.OpPop),
- },
- },
- {
- input: "[1+2, 3 - 4, 5 *6]",
- expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpAdd),
- code.Make(code.OpConstant, 2),
- code.Make(code.OpConstant, 3),
- code.Make(code.OpSub),
- code.Make(code.OpConstant, 4),
- code.Make(code.OpConstant, 5),
- code.Make(code.OpMul),
- code.Make(code.OpArray, 3),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestHashLiterals(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: "{}",
- expectedConstants: []interface{}{},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpHash, 0),
- code.Make(code.OpPop),
- },
- },
- {
- input: "{1:2, 3:4, 5:6}",
- expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpConstant, 2),
- code.Make(code.OpConstant, 3),
- code.Make(code.OpConstant, 4),
- code.Make(code.OpConstant, 5),
- code.Make(code.OpHash, 6),
- code.Make(code.OpPop),
- },
- },
- {
- input: "{1:2+3, 4:5*6}",
- expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
- 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.OpConstant, 3),
- code.Make(code.OpConstant, 4),
- code.Make(code.OpConstant, 5),
- code.Make(code.OpMul),
- code.Make(code.OpHash, 4),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestIndexExpressions(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: "[1,2,3][1+1]",
- expectedConstants: []interface{}{1, 2, 3, 1, 1},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpConstant, 2),
- code.Make(code.OpArray, 3),
- code.Make(code.OpConstant, 3),
- code.Make(code.OpConstant, 4),
- code.Make(code.OpAdd),
- code.Make(code.OpIndex),
- code.Make(code.OpPop),
- },
- },
- {
- input: "{1:2}[2-1]",
- expectedConstants: []interface{}{1, 2, 2, 1},
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpHash, 2),
- code.Make(code.OpConstant, 2),
- code.Make(code.OpConstant, 3),
- code.Make(code.OpSub),
- code.Make(code.OpIndex),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestFunctions(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: `fn() {return 5 + 10; }`,
- expectedConstants: []interface{}{
- 5,
- 10,
- []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpAdd),
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 2),
- code.Make(code.OpPop),
- },
- },
- {
- input: `fn(){5+10}`,
- expectedConstants: []interface{}{
- 5, 10,
- []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpAdd),
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 2),
- code.Make(code.OpPop),
- },
- },
- {
- input: `fn(){1;2}`,
- expectedConstants: []interface{}{
- 1, 2,
- []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpPop),
- code.Make(code.OpConstant, 1),
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 2),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestCompilerScopes(t *testing.T) {
- compiler := New()
- if compiler.scopeIndex != 0 {
- t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
- }
- globalSymbolTable := compiler.symbolTable
- compiler.emit(code.OpMul)
- // enterScope ======================================================================================================
- compiler.enterScope()
- if compiler.scopeIndex != 1 {
- t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
- }
- compiler.emit(code.OpSub)
- if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 {
- t.Errorf("instructions length wrong. got=%d, want = 1",
- len(compiler.scopes[compiler.scopeIndex].instructions))
- }
- last := compiler.scopes[compiler.scopeIndex].lastInstruction
- if last.Opcode != code.OpSub {
- t.Errorf("lastInstruction.Opcde wrong. got=%d, want=%d", last.Opcode, code.OpSub)
- }
- if compiler.symbolTable.Outer != globalSymbolTable {
- t.Errorf("compiler did not enclose symboTable")
- }
- compiler.leaveScope()
- if compiler.scopeIndex != 0 {
- t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
- }
- // leaveScope ======================================================================================================
- if compiler.symbolTable != globalSymbolTable {
- t.Errorf("compiler did not restore global symbol table")
- }
- if compiler.symbolTable.Outer != nil {
- t.Errorf("compiler modified global symbol table incorrectly")
- }
- compiler.emit(code.OpAdd)
- if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 {
- t.Errorf("instructions length wrong. got=%d",
- len(compiler.scopes[compiler.scopeIndex].instructions))
- }
- last = compiler.scopes[compiler.scopeIndex].lastInstruction
- if last.Opcode != code.OpAdd {
- t.Errorf("lastInstruction.Opcode wrong. got=%d, want=%d", last.Opcode, code.OpAdd)
- }
- previous := compiler.scopes[compiler.scopeIndex].previousInstruction
- if previous.Opcode != code.OpMul {
- t.Errorf("previousInstruction.Opcode wrong. got=%d, want=%d", previous.Opcode, code.OpMul)
- }
- }
- func TestFunctionWithoutReturnValue(t *testing.T) {
- tests := []compilerTestCase{
- {input: `fn() {}`,
- expectedConstants: []interface{}{
- []code.Instructions{code.Make(code.OpReturn)},
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpPop),
- },
- },
- }
- runCompilerTests(t, tests)
- }
- func TestFunctionCalls(t *testing.T) {
- tests := []compilerTestCase{
- {
- input: `fn () { 24 } ()`,
- expectedConstants: []interface{}{
- 24,
- []code.Instructions{
- code.Make(code.OpConstant, 0), // The literal "24"
- code.Make(code.OpReturnValue),
- },
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 1), // The compiled function
- code.Make(code.OpCall),
- code.Make(code.OpPop),
- },
- },
- {
- input: `let noArg = fn(){24};noArg();`,
- expectedConstants: []interface{}{
- 24,
- []code.Instructions{
- code.Make(code.OpConstant, 0),
- code.Make(code.OpReturnValue)},
- },
- expectedInstructions: []code.Instructions{
- code.Make(code.OpConstant, 1), // The compiled function
- code.Make(code.OpSetGlobal, 0),
- code.Make(code.OpGetGlobal, 0),
- code.Make(code.OpCall),
- 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)
- }
- case string:
- err := testStringObject(constant, actual[i])
- if err != nil {
- return fmt.Errorf("constant %d - testStringObject failed: %s", i, err)
- }
- case []code.Instructions:
- fn, ok := actual[i].(*object.CompileFunction)
- if !ok {
- return fmt.Errorf("constant %d -- not a function: %T", i, actual[i])
- }
- err := testInstructions(constant, fn.Instructions)
- if err != nil {
- return fmt.Errorf("constant %d -- testInstructions failed: %s", i, err)
- }
- }
- }
- return nil
- }
- func testStringObject(expected string, actual object.Object) error {
- result, ok := actual.(*object.String)
- if !ok {
- return fmt.Errorf("object is not String. got=%T (%+v)", actual, actual)
- }
- if result.Value != expected {
- return fmt.Errorf("object has wrong value. got=%s, want=%q", result.Value, expected)
- }
- 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\n got=%q", concatted, actual)
- }
- for i, ins := range concatted {
- if actual[i] != ins {
- return fmt.Errorf("wrong instructions at %d.\nwant=%q\n got=%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()
- }
|