Files
monkey/internal/compiler/compiler_test.go
csmith 862119e90e
Some checks failed
Build / build (push) Successful in 9m50s
Publish Image / publish (push) Failing after 49s
Test / build (push) Successful in 10m55s
lots o fixes
2024-04-01 18:18:45 -04:00

1256 lines
31 KiB
Go

package compiler
import (
"fmt"
"github.com/stretchr/testify/assert"
"monkey/internal/ast"
"monkey/internal/code"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
input: "1 << 2",
expectedConstants: []interface{}{1, 2},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpLeftShift),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "4 >> 2",
expectedConstants: []interface{}{4, 2},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpRightShift),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "-1",
expectedConstants: []interface{}{1},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpMinus),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
input: "false",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpFalse),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "null",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpNull),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
input: "true == false",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpTrue),
code.Make(code.OpFalse),
code.Make(code.OpEqual),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "true != false",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpTrue),
code.Make(code.OpFalse),
code.Make(code.OpNotEqual),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "true && false",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpTrue),
code.Make(code.OpFalse),
code.Make(code.OpAnd),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "true || false",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpTrue),
code.Make(code.OpFalse),
code.Make(code.OpOr),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "!true",
expectedConstants: []interface{}{},
expectedInstructions: []code.Instructions{
code.Make(code.OpTrue),
code.Make(code.OpNot),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
}
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\n0016 OpHalt\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\n0018 OpHalt\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\n0037 OpHalt\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\n0014 OpHalt\n",
},
{
input: `
one := 1;
one;
`,
constants: []interface{}{1},
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpGetGlobal 0\n0010 OpPop\n0011 OpHalt\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\n0018 OpHalt\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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
}
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\n0005 OpHalt\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\n0005 OpHalt\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\n0012 OpHalt\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\n0007 OpHalt\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\n0014 OpHalt\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\n0017 OpHalt\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\n0023 OpHalt\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\n0014 OpHalt\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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
runCompilerTests(t, tests)
}
func TestBuiltins(t *testing.T) {
tests := []compilerTestCase{
{
input: `
len([]);
push([], 1);
`,
expectedConstants: []interface{}{1},
expectedInstructions: []code.Instructions{
code.Make(code.OpGetBuiltin, 22),
code.Make(code.OpArray, 0),
code.Make(code.OpCall, 1),
code.Make(code.OpPop),
code.Make(code.OpGetBuiltin, 34),
code.Make(code.OpArray, 0),
code.Make(code.OpConstant, 0),
code.Make(code.OpCall, 2),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: `fn() { return len([]) }`,
expectedConstants: []interface{}{
[]code.Instructions{
code.Make(code.OpGetBuiltin, 22),
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),
code.Make(code.OpHalt),
},
},
}
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),
code.Make(code.OpHalt),
},
},
{
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),
code.Make(code.OpHalt),
},
},
}
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),
// 0013
code.Make(code.OpHalt),
},
},
}
runCompilerTests(t, tests)
}
func TestImportExpressions(t *testing.T) {
tests := []compilerTestCase2{
{
input: `import("foo")`,
constants: []interface{}{"foo"},
instructions: "0000 OpConstant 0\n0003 OpLoadModule\n0004 OpPop\n0005 OpHalt\n",
},
}
runCompilerTests2(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("<text", 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
}