anglais/core/compiler_test.go
Neemek 7879184d56
All checks were successful
/ test (push) Successful in 51s
fix wasm and tests
2026-07-15 21:46:14 +02:00

701 lines
12 KiB
Go

package core
import (
"fmt"
"testing"
)
func TestNewCompiler(t *testing.T) {
c := NewCompiler([]rune{})
if c == nil {
t.Fatal("NewCompiler returned nil")
}
if c.ip != 0 {
t.Error("compiler ip doesn't start at zero")
}
if c.Chunk == nil {
t.Error("compiler chunk initialized to nil")
}
}
func BenchmarkNewCompiler(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewCompiler([]rune{})
}
}
type CompileTestData struct {
program *Program
expectedStack []Value
expectedScope []map[string]Value
}
func GetCompileTestData() map[string]CompileTestData {
return map[string]CompileTestData{
"constant_string": {
&Program{
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
true,
0, 0,
},
&ReferenceNode{"a", 0, 0},
},
0, 0,
},
"",
},
[]Value{&StringValue{"Hello world!"}},
[]map[string]Value{},
},
/* these tests are so fucking unmaintainable
"conditional_false": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
false,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
nil,
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FloatValue{0},
0,
},
},
},
"conditional_true": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
true,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
nil,
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FloatValue{1},
0,
},
},
},
"conditional_else_false": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
false,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
2,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FloatValue{2},
0,
},
},
},
"conditional_else_true": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
true,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
2,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FloatValue{1},
0,
},
},
},
"addition": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&BinaryNode{
BinaryAddition,
&FloatNode{
1,
0, 0,
},
&FloatNode{
2,
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FloatValue{3},
0,
},
},
},
"sum_function": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{name: "sum"},
&FunctionNode{
"sum",
[]FunctionParameter{
{
"a",
&FloatSignature{},
},
{
"b",
&FloatSignature{},
},
},
&FloatSignature{},
&BlockNode{
[]Node{
&ReturnNode{
&BinaryNode{
BinaryAddition,
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"sum",
&FunctionValue{
"sum",
[]FunctionParameter{
{
"a",
&FloatSignature{},
},
{
"b",
&FloatSignature{},
},
},
&FloatSignature{},
NewChunk(
[]Bytecode{
InstructionDescend,
InstructionGetLocal, 0,
InstructionGetLocal, 1,
InstructionAddFloat,
InstructionReturn,
InstructionAscend,
},
[]Value{
&StringValue{"a"}, &StringValue{"b"},
},
),
nil,
},
0,
},
},
},
"remove_func_vars": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"a", 0, 0},
&FunctionNode{
"a",
[]FunctionParameter{},
&FloatSignature{},
&BlockNode{
[]Node{
&AssignNode{
&ReferenceNode{"b", 0, 0},
&FloatNode{
1,
0, 0,
},
true,
0, 0,
},
&ReturnNode{
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
&CallNode{
&ReferenceNode{
"a",
0, 0,
},
[]Node{},
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FunctionValue{
"a",
[]FunctionParameter{},
&FloatSignature{},
NewChunk(
[]Bytecode{
InstructionDescend,
InstructionConstant, 0,
InstructionDeclareLocal, 1,
InstructionGetLocal, 1,
InstructionReturn,
InstructionAscend,
},
[]Value{
&FloatValue{1}, &StringValue{"b"},
},
),
nil,
},
0,
},
},
},
"two_lists": {
program: &Program{
[]Import{},
&BlockNode{
statements: []Node{
&AssignNode{
dest: &ReferenceNode{"a", 0, 0},
value: &ListNode{
items: []Node{
&FloatNode{value: 1},
&FloatNode{value: 2},
},
},
declare: true,
},
&AssignNode{
dest: &ReferenceNode{"b", 0, 0},
value: &ListNode{
items: []Node{
&StringNode{value: "Hello"},
&StringNode{value: "world"},
},
},
declare: true,
},
},
},
"",
},
expectedStack: []Value{
&VariableValue{
name: "a",
value: &ListValue{
Items: []Value{
&FloatValue{1},
&FloatValue{2},
},
},
scope: 0,
},
&VariableValue{
name: "b",
value: &ListValue{
Items: []Value{
&StringValue{"Hello"},
&StringValue{"world"},
},
},
scope: 0,
},
},
},
"unary/negate": {
program: &Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{
UnaryNegate,
&FloatNode{
1,
0, 0,
},
0, 0,
},
declare: true,
},
},
0, 0,
},
"",
},
expectedStack: []Value{
&VariableValue{
name: "a",
value: &FloatValue{-1},
scope: 0,
},
},
},
"unary/not": {
program: &Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{
UnaryNot,
&BooleanNode{
value: true,
},
0, 0,
},
declare: true,
},
},
0, 0,
},
"",
},
expectedStack: []Value{
&VariableValue{
name: "a",
value: &BoolValue{false},
scope: 0,
},
},
},
*/
}
}
func printChunk(t *testing.T, name string, chunk *Chunk) {
t.Logf("=v= %s =v=", name)
for i, bc := range chunk.Bytecode {
t.Logf("i=%d \t%d \t(%s)", i, bc, bc)
}
t.Logf("=-= constants =-=")
for i, ct := range chunk.Constants {
t.Logf("c=%d \t%s", i, ct.DebugString())
f, ok := ct.(*FunctionValue)
if ok {
printChunk(t, f.Name, f.Chunk)
}
}
t.Logf("=^= %s =^=", name)
}
func TestCompile(t *testing.T) {
data := GetCompileTestData()
for name, testCase := range data {
t.Run(name, func(t *testing.T) {
t.Log("Initializing compiler")
c := NewCompiler([]rune(testCase.program.String()))
t.Log("Compiling node tree")
_, err := c.Compile(testCase.program)
if err != nil {
t.Fatalf("Compiling failed: %v", err)
}
t.Log("Initializing vm")
vm := NewVM(c.Chunk, 256, 256)
t.Log("Printing chunk for debug")
printChunk(t, name, c.Chunk)
t.Log("Executing bytecode")
for vm.Next() {
}
t.Log("Executed bytecode")
CompareStacks(t, testCase.expectedStack, vm.Stack)
CompareScope(t, testCase.expectedScope, vm.scope)
})
}
}
func BenchmarkCompile(b *testing.B) {
data := GetCompileTestData()
for name, testCase := range data {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
c := NewCompiler([]rune{})
_, _ = c.Compile(testCase.program)
}
})
}
}
func TestCompiler_AddU16(t *testing.T) {
for i := 0; i <= 0xffff; i++ {
t.Run(fmt.Sprint(i), func(t *testing.T) {
c := NewCompiler([]rune{})
c.addU16(uint16(i))
if c.Chunk.Bytecode[0] != Bytecode(i>>8) {
t.Errorf("first 8 bits don't match (got %s, expected %b)", c.Chunk.Bytecode[0], byte(i>>8))
}
if c.Chunk.Bytecode[1] != Bytecode(i&0xff) {
t.Errorf("last 8 bits don't match (got %s, expected %b)", c.Chunk.Bytecode[1], byte(i&0xff))
}
})
}
}
func TestCompiler_CleanStack(t *testing.T) {
cases := GetCompileTestData()
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
c := NewCompiler([]rune(tc.program.String()))
_, err := c.Compile(tc.program)
if err != nil {
t.Fatalf("Compiling failed: %v", err)
}
vm := NewVM(c.Chunk, 256, 256)
for vm.Next() {
}
// make sure stack has only assigned values
for i := 1; i < int(vm.Stack.Current); i++ {
v := vm.Stack.items[i]
if v == nil {
t.Errorf("Unclean stack! value %v at %d on the stack is intermediary", v.String(), i)
}
}
})
}
}