1173 lines
29 KiB
Go
1173 lines
29 KiB
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"monkey/ast"
|
|
"monkey/code"
|
|
"monkey/lexer"
|
|
"monkey/object"
|
|
"monkey/parser"
|
|
"testing"
|
|
)
|
|
|
|
type Instructions string
|
|
|
|
type compilerTestCase struct {
|
|
input string
|
|
expectedConstants []interface{}
|
|
expectedInstructions []code.Instructions
|
|
}
|
|
|
|
type compilerTestCase2 struct {
|
|
input string
|
|
constants []interface{}
|
|
instructions string
|
|
}
|
|
|
|
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",
|
|
expectedConstants: []interface{}{5, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpMod),
|
|
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),
|
|
},
|
|
},
|
|
{
|
|
input: "5 | 2",
|
|
expectedConstants: []interface{}{5, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpBitwiseOR),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "5 ^ 2",
|
|
expectedConstants: []interface{}{5, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpBitwiseXOR),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "5 & 2",
|
|
expectedConstants: []interface{}{5, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpBitwiseAND),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestBooleanExpressions(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: "null",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpNull),
|
|
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.OpGreaterThanEqual),
|
|
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.OpGreaterThanEqual),
|
|
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 && false",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpFalse),
|
|
code.Make(code.OpAnd),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "true || false",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpFalse),
|
|
code.Make(code.OpOr),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "!true",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpNot),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestConditionals(t *testing.T) {
|
|
tests := []compilerTestCase2{
|
|
{
|
|
input: `
|
|
if (true) { 10 }; 3333;
|
|
`,
|
|
constants: []interface{}{10, 3333},
|
|
instructions: "0000 OpTrue\n0001 OpJumpNotTruthy 10\n0004 OpConstant 0\n0007 OpJump 11\n0010 OpNull\n0011 OpPop\n0012 OpConstant 1\n0015 OpPop\n",
|
|
}, {
|
|
input: `
|
|
if (true) { 10 } else { 20 }; 3333;
|
|
`,
|
|
constants: []interface{}{10, 20, 3333},
|
|
instructions: "0000 OpTrue\n0001 OpJumpNotTruthy 10\n0004 OpConstant 0\n0007 OpJump 13\n0010 OpConstant 1\n0013 OpPop\n0014 OpConstant 2\n0017 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
x := 0; if (true) { x = 1; }; if (false) { x = 2; }
|
|
`,
|
|
constants: []interface{}{0, 1, 2},
|
|
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpTrue\n0008 OpJumpNotTruthy 20\n0011 OpConstant 1\n0014 OpAssignGlobal 0\n0017 OpJump 21\n0020 OpNull\n0021 OpPop\n0022 OpFalse\n0023 OpJumpNotTruthy 35\n0026 OpConstant 2\n0029 OpAssignGlobal 0\n0032 OpJump 36\n0035 OpNull\n0036 OpPop\n",
|
|
},
|
|
}
|
|
|
|
runCompilerTests2(t, tests)
|
|
}
|
|
|
|
func TestGlobalBindExpressions(t *testing.T) {
|
|
tests := []compilerTestCase2{
|
|
{
|
|
input: `
|
|
one := 1;
|
|
two := 2;
|
|
`,
|
|
constants: []interface{}{1, 2},
|
|
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpConstant 1\n0010 OpSetGlobal 1\n0013 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
one := 1;
|
|
one;
|
|
`,
|
|
constants: []interface{}{1},
|
|
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpGetGlobal 0\n0010 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
one := 1;
|
|
two := one;
|
|
two;
|
|
`,
|
|
constants: []interface{}{1},
|
|
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpGetGlobal 0\n0010 OpSetGlobal 1\n0013 OpPop\n0014 OpGetGlobal 1\n0017 OpPop\n",
|
|
},
|
|
}
|
|
|
|
runCompilerTests2(t, tests)
|
|
}
|
|
|
|
func TestStringExpressions(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `"monkey"`,
|
|
expectedConstants: []interface{}{"monkey"},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `"mon" + "key"`,
|
|
expectedConstants: []interface{}{"mon", "key"},
|
|
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 TestArrayLiterals(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.OpGetItem),
|
|
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.OpGetItem),
|
|
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.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 2, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `fn() { 1; return 2 }`,
|
|
expectedConstants: []interface{}{
|
|
1,
|
|
2,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 2, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestFunctionsWithoutReturn(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `fn() { }`,
|
|
expectedConstants: []interface{}{
|
|
[]code.Instructions{
|
|
code.Make(code.OpNull),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 0, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestClosures(t *testing.T) {
|
|
tests := []compilerTestCase2{
|
|
{
|
|
input: `
|
|
fn(a) {
|
|
return fn(b) {
|
|
return a + b
|
|
}
|
|
}
|
|
`,
|
|
constants: []interface{}{
|
|
Instructions("0000 OpGetFree 0\n0002 OpGetLocal 0\n0004 OpAdd\n0005 OpReturn\n"),
|
|
Instructions("0000 OpGetLocal 0\n0002 OpClosure 0 1\n0006 OpReturn\n"),
|
|
},
|
|
instructions: "0000 OpClosure 1 0\n0004 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
fn(a) {
|
|
return fn(b) {
|
|
return fn(c) {
|
|
return a + b + c
|
|
}
|
|
}
|
|
};
|
|
`,
|
|
constants: []interface{}{
|
|
Instructions("0000 OpGetFree 0\n0002 OpGetFree 1\n0004 OpAdd\n0005 OpGetLocal 0\n0007 OpAdd\n0008 OpReturn\n"),
|
|
Instructions("0000 OpGetFree 0\n0002 OpGetLocal 0\n0004 OpClosure 0 2\n0008 OpReturn\n"),
|
|
Instructions("0000 OpGetLocal 0\n0002 OpClosure 1 1\n0006 OpReturn\n"),
|
|
},
|
|
instructions: "0000 OpClosure 2 0\n0004 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
global := 55;
|
|
|
|
fn() {
|
|
a := 66;
|
|
|
|
return fn() {
|
|
b := 77;
|
|
|
|
return fn() {
|
|
c := 88;
|
|
|
|
return global + a + b + c;
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
constants: []interface{}{
|
|
55,
|
|
66,
|
|
77,
|
|
88,
|
|
Instructions("0000 OpConstant 3\n0003 OpSetLocal 0\n0005 OpPop\n0006 OpGetGlobal 0\n0009 OpGetFree 0\n0011 OpAdd\n0012 OpGetFree 1\n0014 OpAdd\n0015 OpGetLocal 0\n0017 OpAdd\n0018 OpReturn\n"),
|
|
Instructions("0000 OpConstant 2\n0003 OpSetLocal 0\n0005 OpPop\n0006 OpGetFree 0\n0008 OpGetLocal 0\n0010 OpClosure 4 2\n0014 OpReturn\n"),
|
|
Instructions("0000 OpConstant 1\n0003 OpSetLocal 0\n0005 OpPop\n0006 OpGetLocal 0\n0008 OpClosure 5 1\n0012 OpReturn\n"),
|
|
},
|
|
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpClosure 6 0\n0011 OpPop\n",
|
|
},
|
|
}
|
|
|
|
runCompilerTests2(t, tests)
|
|
}
|
|
|
|
func TestCompilerScopes(t *testing.T) {
|
|
compiler := New()
|
|
if compiler.scopeIndex != 0 {
|
|
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
|
|
}
|
|
|
|
compiler.emit(code.OpMul)
|
|
|
|
compiler.enterScope()
|
|
if compiler.scopeIndex != 1 {
|
|
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1)
|
|
}
|
|
|
|
compiler.emit(code.OpSub)
|
|
|
|
if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 {
|
|
t.Errorf("instructions length is wrong. got=%d", len(compiler.scopes[compiler.scopeIndex].instructions))
|
|
}
|
|
|
|
last := compiler.scopes[compiler.scopeIndex].lastInstruction
|
|
if last.Opcode != code.OpSub {
|
|
t.Errorf("lastInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpSub)
|
|
}
|
|
|
|
compiler.leaveScope()
|
|
if compiler.scopeIndex != 0 {
|
|
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
|
|
}
|
|
|
|
compiler.emit(code.OpAdd)
|
|
|
|
if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 {
|
|
t.Errorf("instructions length is 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.OpSub)
|
|
}
|
|
|
|
previous := compiler.scopes[compiler.scopeIndex].previousInstruction
|
|
if previous.Opcode != code.OpMul {
|
|
t.Errorf("previousInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpMul)
|
|
}
|
|
}
|
|
|
|
func TestFunctionCalls(t *testing.T) {
|
|
tests := []compilerTestCase2{
|
|
{
|
|
input: `fn() { return 24 }();`,
|
|
constants: []interface{}{
|
|
24,
|
|
Instructions("0000 OpConstant 0\n0003 OpReturn\n"),
|
|
},
|
|
instructions: "0000 OpClosure 1 0\n0004 OpCall 0\n0006 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
noArg := fn() { return 24 };
|
|
noArg();
|
|
`,
|
|
constants: []interface{}{
|
|
24,
|
|
Instructions("0000 OpConstant 0\n0003 OpReturn\n"),
|
|
},
|
|
instructions: "0000 OpClosure 1 0\n0004 OpSetGlobal 0\n0007 OpPop\n0008 OpGetGlobal 0\n0011 OpCall 0\n0013 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
oneArg := fn(a) { return a };
|
|
oneArg(24);
|
|
`,
|
|
constants: []interface{}{
|
|
Instructions("0000 OpGetLocal 0\n0002 OpReturn\n"),
|
|
24,
|
|
},
|
|
instructions: "0000 OpClosure 0 0\n0004 OpSetGlobal 0\n0007 OpPop\n0008 OpGetGlobal 0\n0011 OpConstant 1\n0014 OpCall 1\n0016 OpPop\n",
|
|
},
|
|
{
|
|
input: `
|
|
manyArg := fn(a, b, c) { a; b; return c };
|
|
manyArg(24, 25, 26);
|
|
`,
|
|
constants: []interface{}{
|
|
Instructions("0000 OpGetLocal 0\n0002 OpPop\n0003 OpGetLocal 1\n0005 OpPop\n0006 OpGetLocal 2\n0008 OpReturn\n"),
|
|
24,
|
|
25,
|
|
26,
|
|
},
|
|
instructions: "0000 OpClosure 0 0\n0004 OpSetGlobal 0\n0007 OpPop\n0008 OpGetGlobal 0\n0011 OpConstant 1\n0014 OpConstant 2\n0017 OpConstant 3\n0020 OpCall 3\n0022 OpPop\n",
|
|
},
|
|
}
|
|
|
|
runCompilerTests2(t, tests)
|
|
}
|
|
|
|
func TestAssignmentExpressions(t *testing.T) {
|
|
tests := []compilerTestCase2{
|
|
{
|
|
input: `
|
|
x := 1
|
|
x = 2
|
|
`,
|
|
constants: []interface{}{1, 2},
|
|
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpConstant 1\n0010 OpAssignGlobal 0\n0013 OpPop\n",
|
|
},
|
|
}
|
|
|
|
runCompilerTests2(t, tests)
|
|
}
|
|
|
|
func TestAssignmentStatementScopes(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
num := 0;
|
|
fn() { num = 55; }
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
0,
|
|
55,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAssignGlobal, 0),
|
|
code.Make(code.OpNull),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpClosure, 2, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
fn() { num := 0; num = 55; }
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
0,
|
|
55,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetLocal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAssignLocal, 0),
|
|
code.Make(code.OpNull),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 2, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestLetStatementScopes(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
num := 55;
|
|
fn() { return num }
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
55,
|
|
[]code.Instructions{
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpClosure, 1, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
fn() {
|
|
num := 55;
|
|
return num
|
|
}
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
55,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetLocal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetLocal, 0),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 1, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
fn() {
|
|
a := 55;
|
|
b := 77;
|
|
return a + b
|
|
}
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
55,
|
|
77,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetLocal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpSetLocal, 1),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetLocal, 0),
|
|
code.Make(code.OpGetLocal, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 2, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
a := 0;
|
|
a := a + 1;
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
0,
|
|
1,
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestBuiltins(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
len([]);
|
|
push([], 1);
|
|
`,
|
|
expectedConstants: []interface{}{1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpGetBuiltin, 10),
|
|
code.Make(code.OpArray, 0),
|
|
code.Make(code.OpCall, 1),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetBuiltin, 14),
|
|
code.Make(code.OpArray, 0),
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpCall, 2),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `fn() { return len([]) }`,
|
|
expectedConstants: []interface{}{
|
|
[]code.Instructions{
|
|
code.Make(code.OpGetBuiltin, 10),
|
|
code.Make(code.OpArray, 0),
|
|
code.Make(code.OpCall, 1),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 0, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestRecursiveFunctions(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
countDown := fn(x) { return countDown(x - 1); };
|
|
countDown(1);
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
1,
|
|
[]code.Instructions{
|
|
code.Make(code.OpCurrentClosure),
|
|
code.Make(code.OpGetLocal, 0),
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSub),
|
|
code.Make(code.OpCall, 1),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
1,
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 1, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpCall, 1),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
wrapper := fn() {
|
|
countDown := fn(x) { return countDown(x - 1); };
|
|
return countDown(1);
|
|
};
|
|
wrapper();
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
1,
|
|
[]code.Instructions{
|
|
code.Make(code.OpCurrentClosure),
|
|
code.Make(code.OpGetLocal, 0),
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSub),
|
|
code.Make(code.OpCall, 1),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
1,
|
|
[]code.Instructions{
|
|
code.Make(code.OpClosure, 1, 0),
|
|
code.Make(code.OpSetLocal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetLocal, 0),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpCall, 1),
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpClosure, 3, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpCall, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestIteration(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
while (true) { 10 };
|
|
`,
|
|
expectedConstants: []interface{}{10},
|
|
expectedInstructions: []code.Instructions{
|
|
// 0000
|
|
code.Make(code.OpTrue),
|
|
// 0001
|
|
code.Make(code.OpJumpNotTruthy, 11),
|
|
// 0004
|
|
code.Make(code.OpConstant, 0),
|
|
// 0007
|
|
code.Make(code.OpPop),
|
|
// 0008
|
|
code.Make(code.OpJump, 0),
|
|
// 0011
|
|
code.Make(code.OpNull),
|
|
// 0012
|
|
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 runCompilerTests2(t *testing.T, tests []compilerTestCase2) {
|
|
t.Helper()
|
|
|
|
assert := assert.New(t)
|
|
|
|
for _, tt := range tests {
|
|
program := parse(tt.input)
|
|
|
|
compiler := New()
|
|
err := compiler.Compile(program)
|
|
assert.NoError(err)
|
|
|
|
bytecode := compiler.Bytecode()
|
|
assert.Equal(tt.instructions, bytecode.Instructions.String())
|
|
|
|
testConstants2(t, tt.constants, bytecode.Constants)
|
|
}
|
|
}
|
|
|
|
func parse(input string) *ast.Program {
|
|
l := lexer.New(input)
|
|
p := parser.New(l)
|
|
return p.ParseProgram()
|
|
}
|
|
|
|
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 instruction 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 testConstants(t *testing.T, expected []interface{}, actual []object.Object) error {
|
|
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.CompiledFunction)
|
|
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 testConstants2(t *testing.T, expected []interface{}, actual []object.Object) {
|
|
assert := assert.New(t)
|
|
|
|
assert.Equal(len(expected), len(actual))
|
|
|
|
for i, constant := range expected {
|
|
switch constant := constant.(type) {
|
|
|
|
case Instructions:
|
|
assert.Equal(
|
|
string(constant),
|
|
actual[i].(*object.CompiledFunction).Instructions.String(),
|
|
)
|
|
|
|
case string:
|
|
assert.Equal(constant, actual[i].(*object.String).Value)
|
|
|
|
case int:
|
|
assert.Equal(int64(constant), actual[i].(*object.Integer).Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testIntegerObject(expected int64, actual object.Object) interface{} {
|
|
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 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=%q, want=%q", result.Value, expected)
|
|
}
|
|
|
|
return nil
|
|
}
|