c65gm/internal/commands/let_test.go

425 lines
9 KiB
Go

package commands
import (
"strings"
"testing"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
)
func TestLetCommand_WillHandle(t *testing.T) {
cmd := &LetCommand{}
tests := []struct {
name string
line string
want bool
}{
{"old syntax LET/GET", "LET a GET b", true},
{"old syntax LET/equals", "LET a = 10", true},
{"new syntax", "result = value", true},
{"not LET - arithmetic", "result = a + b", false}, // 5 params
{"not LET - keyword", "ADD a TO b GIVING c", false},
{"wrong param count", "LET a b", false},
{"empty", "", 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 TestLetCommand_OldSyntax(t *testing.T) {
tests := []struct {
name string
line string
setupVars func(*compiler.SymbolTable)
wantAsm []string
wantErr bool
}{
{
name: "LET byte GET byte",
line: "LET a GET b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
st.AddVar("b", "", compiler.KindByte, 10)
},
wantAsm: []string{
"\tlda b",
"\tsta a",
},
},
{
name: "LET byte = literal",
line: "LET a = 100",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tlda #$64",
"\tsta a",
},
},
{
name: "LET word GET word",
line: "LET x GET y",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
st.AddVar("y", "", compiler.KindWord, 0x1234)
},
wantAsm: []string{
"\tlda y",
"\tsta x",
"\tlda y+1",
"\tsta x+1",
},
},
{
name: "LET word = literal",
line: "LET x = $1234",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$34",
"\tsta x",
"\tlda #$12",
"\tsta x+1",
},
},
{
name: "LET word GET byte (zero-extend)",
line: "LET x GET b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
st.AddVar("b", "", compiler.KindByte, 100)
},
wantAsm: []string{
"\tlda b",
"\tsta x",
"\tlda #0",
"\tsta x+1",
},
},
{
name: "LET byte GET word (take low byte)",
line: "LET b GET x",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0)
st.AddVar("x", "", compiler.KindWord, 0x1234)
},
wantAsm: []string{
"\tlda x",
"\tsta b",
},
},
{
name: "LET word = $0000 (optimization)",
line: "LET x = 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$00",
"\tsta x",
"\tsta x+1",
},
},
{
name: "LET word = $FFFF (optimization)",
line: "LET x = $FFFF",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$ff",
"\tsta x",
"\tsta x+1",
},
},
{
name: "LET with constant",
line: "LET a = MAXVAL",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
},
wantAsm: []string{
"\tlda #$ff",
"\tsta a",
},
},
{
name: "error: unknown destination",
line: "LET unknown GET a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
},
wantErr: true,
},
{
name: "error: wrong separator",
line: "LET a TO b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
st.AddVar("b", "", compiler.KindByte, 0)
},
wantErr: true,
},
{
name: "error: cannot assign to constant",
line: "LET MAXVAL = 100",
setupVars: func(st *compiler.SymbolTable) {
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 := &LetCommand{}
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 !equalAsmLet(asm, tt.wantAsm) {
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
strings.Join(asm, "\n"),
strings.Join(tt.wantAsm, "\n"))
}
})
}
}
func TestLetCommand_NewSyntax(t *testing.T) {
tests := []struct {
name string
line string
setupVars func(*compiler.SymbolTable)
wantAsm []string
wantErr bool
}{
{
name: "byte = byte",
line: "a = b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
st.AddVar("b", "", compiler.KindByte, 10)
},
wantAsm: []string{
"\tlda b",
"\tsta a",
},
},
{
name: "byte = literal",
line: "a = 100",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tlda #$64",
"\tsta a",
},
},
{
name: "word = word",
line: "x = y",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
st.AddVar("y", "", compiler.KindWord, 0x1234)
},
wantAsm: []string{
"\tlda y",
"\tsta x",
"\tlda y+1",
"\tsta x+1",
},
},
{
name: "word = literal",
line: "x = $1234",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$34",
"\tsta x",
"\tlda #$12",
"\tsta x+1",
},
},
{
name: "word = byte (zero-extend)",
line: "x = b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
st.AddVar("b", "", compiler.KindByte, 100)
},
wantAsm: []string{
"\tlda b",
"\tsta x",
"\tlda #0",
"\tsta x+1",
},
},
{
name: "byte = word (take low byte)",
line: "b = x",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 0)
st.AddVar("x", "", compiler.KindWord, 0x1234)
},
wantAsm: []string{
"\tlda x",
"\tsta b",
},
},
{
name: "word = 0 (optimization)",
line: "x = 0",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$00",
"\tsta x",
"\tsta x+1",
},
},
{
name: "word = $FFFF (optimization)",
line: "x = 65535",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$ff",
"\tsta x",
"\tsta x+1",
},
},
{
name: "word = $0102 (different bytes, no optimization)",
line: "x = $0102",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$02",
"\tsta x",
"\tlda #$01",
"\tsta x+1",
},
},
{
name: "using constant",
line: "a = MAXVAL",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
},
wantAsm: []string{
"\tlda #$ff",
"\tsta a",
},
},
{
name: "expression with constant",
line: "a = 10+5",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tlda #$0f",
"\tsta a",
},
},
{
name: "error: unknown destination",
line: "unknown = a",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 0)
},
wantErr: true,
},
{
name: "error: cannot assign to constant",
line: "MAXVAL = 100",
setupVars: func(st *compiler.SymbolTable) {
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 := &LetCommand{}
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 !equalAsmLet(asm, tt.wantAsm) {
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
strings.Join(asm, "\n"),
strings.Join(tt.wantAsm, "\n"))
}
})
}
}
// equalAsmLet compares two assembly slices for equality
func equalAsmLet(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}