569 lines
14 KiB
Go
569 lines
14 KiB
Go
package commands
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
func TestXorCommand_WillHandle(t *testing.T) {
|
|
cmd := &XorCommand{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
want bool
|
|
}{
|
|
{"old syntax WITH/GIVING", "XOR a WITH b GIVING c", true},
|
|
{"old syntax WITH/arrow", "XOR a WITH b -> c", true},
|
|
{"new syntax", "result = x ^ y", true},
|
|
{"not XOR", "ADD a TO b GIVING c", false},
|
|
{"wrong param count", "XOR a b c", false},
|
|
{"empty", "", false},
|
|
{"new syntax wrong op", "result = x + y", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
|
got := cmd.WillHandle(line)
|
|
if got != tt.want {
|
|
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestXorCommand_OldSyntax(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantAsm []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "byte XOR byte -> byte (variables)",
|
|
line: "XOR a WITH b GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor b",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "byte XOR byte -> word",
|
|
line: "XOR a WITH b GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor b",
|
|
"\tsta result",
|
|
"\tlda #0",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "word XOR word -> word",
|
|
line: "XOR x WITH y GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("y", "", compiler.KindWord, 0x5678)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda x",
|
|
"\teor y",
|
|
"\tsta result",
|
|
"\tlda x+1",
|
|
"\teor y+1",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "byte XOR literal -> byte",
|
|
line: "XOR a WITH $AA GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor #$aa",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "literal XOR byte -> byte",
|
|
line: "XOR 255 WITH b GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$ff",
|
|
"\teor b",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "constant folding: 255 XOR 170 -> byte",
|
|
line: "XOR 255 WITH 170 GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$55",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "constant folding: $FFFF XOR $AAAA -> word",
|
|
line: "XOR $FFFF WITH $AAAA GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$55",
|
|
"\tsta result",
|
|
"\tlda #$55",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "arrow syntax",
|
|
line: "XOR a WITH b -> result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor b",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "word XOR byte -> byte",
|
|
line: "XOR wval WITH bval GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor bval",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "word XOR byte -> word",
|
|
line: "XOR wval WITH bval GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor bval",
|
|
"\tsta result",
|
|
"\tlda wval+1",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "error: unknown destination variable",
|
|
line: "XOR a WITH b GIVING unknown",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "error: wrong separator",
|
|
line: "XOR a TO b GIVING result",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "error: cannot assign to constant",
|
|
line: "XOR a WITH b GIVING MAXVAL",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
cmd := &XorCommand{}
|
|
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Error("expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsmXor(asm, tt.wantAsm) {
|
|
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(asm, "\n"),
|
|
strings.Join(tt.wantAsm, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestXorCommand_NewSyntax(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantAsm []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "byte ^ byte -> byte",
|
|
line: "result = a ^ b",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor b",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "byte ^ byte -> word",
|
|
line: "result = a ^ b",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor b",
|
|
"\tsta result",
|
|
"\tlda #0",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "word ^ word -> word",
|
|
line: "result = x ^ y",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("y", "", compiler.KindWord, 0x5678)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda x",
|
|
"\teor y",
|
|
"\tsta result",
|
|
"\tlda x+1",
|
|
"\teor y+1",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "byte ^ byte_const -> word (optimization: skip eor #0)",
|
|
line: "result = b ^ 5",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("b", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda b",
|
|
"\teor #$05",
|
|
"\tsta result",
|
|
"\tlda #0",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "byte ^ word -> word (swapped to word ^ byte)",
|
|
line: "result = bval ^ wval",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor bval",
|
|
"\tsta result",
|
|
"\tlda wval+1",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "byte_const ^ byte -> word (optimization: skip eor #0)",
|
|
line: "result = 5 ^ b",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("b", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$05",
|
|
"\teor b",
|
|
"\tsta result",
|
|
"\tlda #$00",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "byte ^ word_const -> word (no optimization)",
|
|
line: "result = b ^ 300",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("b", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda b",
|
|
"\teor #$2c",
|
|
"\tsta result",
|
|
"\tlda #0",
|
|
"\teor #$01",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "word_const ^ byte -> word (optimization: skip eor #0)",
|
|
line: "result = 300 ^ b",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("b", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$2c",
|
|
"\teor b",
|
|
"\tsta result",
|
|
"\tlda #$01",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "self-assignment: word ^= byte (optimization: skip high byte entirely)",
|
|
line: "wval = wval ^ bval",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor bval",
|
|
"\tsta wval",
|
|
},
|
|
},
|
|
{
|
|
name: "self-assignment reversed: word ^= byte (optimization via swap)",
|
|
line: "wval = bval ^ wval",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor bval",
|
|
"\tsta wval",
|
|
},
|
|
},
|
|
{
|
|
name: "self-assignment: word ^= byte_const (optimization: skip high byte entirely)",
|
|
line: "wval = wval ^ 42",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor #$2a",
|
|
"\tsta wval",
|
|
},
|
|
},
|
|
{
|
|
name: "self-assignment: word ^= word_const (no optimization: high byte needed)",
|
|
line: "wval = wval ^ 300",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor #$2c",
|
|
"\tsta wval",
|
|
"\tlda wval+1",
|
|
"\teor #$01",
|
|
"\tsta wval+1",
|
|
},
|
|
},
|
|
{
|
|
name: "self-assignment: word ^= word (no optimization: both high bytes needed)",
|
|
line: "wval = wval ^ wval2",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
|
st.AddVar("wval2", "", compiler.KindWord, 0x5678)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda wval",
|
|
"\teor wval2",
|
|
"\tsta wval",
|
|
"\tlda wval+1",
|
|
"\teor wval2+1",
|
|
"\tsta wval+1",
|
|
},
|
|
},
|
|
{
|
|
name: "variable ^ literal",
|
|
line: "result = a ^ $AA",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor #$aa",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "constant folding",
|
|
line: "result = 255 ^ 170",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$55",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "constant folding word",
|
|
line: "result = $FFFF ^ $AAAA",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("result", "", compiler.KindWord, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda #$55",
|
|
"\tsta result",
|
|
"\tlda #$55",
|
|
"\tsta result+1",
|
|
},
|
|
},
|
|
{
|
|
name: "using constant in expression",
|
|
line: "result = a ^ MASK",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
|
st.AddConst("MASK", "", compiler.KindByte, 0xAA)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"\tlda a",
|
|
"\teor #$aa",
|
|
"\tsta result",
|
|
},
|
|
},
|
|
{
|
|
name: "error: unknown destination",
|
|
line: "unknown = a ^ b",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "error: wrong operator",
|
|
line: "result = a + b",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
st.AddVar("result", "", compiler.KindByte, 0)
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
cmd := &XorCommand{}
|
|
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Error("expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsmXor(asm, tt.wantAsm) {
|
|
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(asm, "\n"),
|
|
strings.Join(tt.wantAsm, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// equalAsmXor compares two assembly slices for equality
|
|
func equalAsmXor(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|