425 lines
9 KiB
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
|
|
}
|