c65gm/internal/commands/subtr_test.go

510 lines
12 KiB
Go

package commands
import (
"strings"
"testing"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
)
func TestSubtractCommand_WillHandle(t *testing.T) {
cmd := &SubtractCommand{}
tests := []struct {
name string
line string
want bool
}{
{"old syntax SUBTRACT FROM/GIVING", "SUBTRACT a FROM b GIVING c", true},
{"old syntax SUBT FROM/arrow", "SUBT a FROM b -> c", true},
{"old syntax SUBT minus/arrow", "SUBT a - b -> c", true},
{"old syntax SUBTRACT minus/GIVING", "SUBTRACT a - b GIVING c", true},
{"new syntax", "result = x - y", true},
{"not SUBTRACT", "ADD a TO b GIVING c", false},
{"wrong param count", "SUBTRACT 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 TestSubtractCommand_OldSyntax_FROM(t *testing.T) {
tests := []struct {
name string
line string
setupVars func(*compiler.SymbolTable)
wantAsm []string
wantErr bool
}{
{
name: "SUBTRACT a FROM b (means b-a)",
line: "SUBTRACT 5 FROM 10 GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tlda #$05",
"\tsta result",
},
},
{
name: "SUBTRACT byte FROM byte -> byte (variables)",
line: "SUBTRACT a FROM b GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 10)
st.AddVar("b", "", compiler.KindByte, 20)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda b",
"\tsbc a",
"\tsta result",
},
},
{
name: "SUBT a FROM b -> c with arrow",
line: "SUBT a FROM b -> result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 10)
st.AddVar("b", "", compiler.KindByte, 20)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda b",
"\tsbc a",
"\tsta result",
},
},
{
name: "SUBTRACT literal FROM variable",
line: "SUBTRACT 10 FROM a GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc #$0a",
"\tsta result",
},
},
{
name: "word FROM word -> word",
line: "SUBTRACT x FROM y GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0x1000)
st.AddVar("y", "", compiler.KindWord, 0x2000)
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tsec",
"\tlda y",
"\tsbc x",
"\tsta result",
"\tlda y+1",
"\tsbc x+1",
"\tsta result+1",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
tt.setupVars(ctx.SymbolTable)
cmd := &SubtractCommand{}
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 !equalAsmSubtr(asm, tt.wantAsm) {
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
strings.Join(asm, "\n"),
strings.Join(tt.wantAsm, "\n"))
}
})
}
}
func TestSubtractCommand_OldSyntax_Minus(t *testing.T) {
tests := []struct {
name string
line string
setupVars func(*compiler.SymbolTable)
wantAsm []string
wantErr bool
}{
{
name: "SUBT a - b (means a-b, no swap)",
line: "SUBT 10 - 5 GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tlda #$05",
"\tsta result",
},
},
{
name: "SUBT byte - byte -> byte (variables)",
line: "SUBT a - b -> result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("b", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc b",
"\tsta result",
},
},
{
name: "SUBTRACT a - b GIVING c",
line: "SUBTRACT a - b GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("b", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc b",
"\tsta result",
},
},
{
name: "SUBT variable - literal",
line: "SUBT a - 10 -> result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc #$0a",
"\tsta result",
},
},
{
name: "word - word -> word",
line: "SUBT x - y -> result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0x2000)
st.AddVar("y", "", compiler.KindWord, 0x1000)
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tsec",
"\tlda x",
"\tsbc y",
"\tsta result",
"\tlda x+1",
"\tsbc y+1",
"\tsta result+1",
},
},
{
name: "byte - byte -> word",
line: "SUBT a - b GIVING result",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("b", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc b",
"\tsta result",
"\tlda #0",
"\tsbc #0",
"\tsta result+1",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
tt.setupVars(ctx.SymbolTable)
cmd := &SubtractCommand{}
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 !equalAsmSubtr(asm, tt.wantAsm) {
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
strings.Join(asm, "\n"),
strings.Join(tt.wantAsm, "\n"))
}
})
}
}
func TestSubtractCommand_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, 20)
st.AddVar("b", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc b",
"\tsta result",
},
},
{
name: "byte - byte -> word",
line: "result = a - b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("b", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc b",
"\tsta result",
"\tlda #0",
"\tsbc #0",
"\tsta result+1",
},
},
{
name: "word - word -> word",
line: "result = x - y",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("x", "", compiler.KindWord, 0x2000)
st.AddVar("y", "", compiler.KindWord, 0x1000)
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tsec",
"\tlda x",
"\tsbc y",
"\tsta result",
"\tlda x+1",
"\tsbc y+1",
"\tsta result+1",
},
},
{
name: "variable - literal",
line: "result = a - 10",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 20)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc #$0a",
"\tsta result",
},
},
{
name: "literal - variable",
line: "result = 20 - b",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("b", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda #$14",
"\tsbc b",
"\tsta result",
},
},
{
name: "constant folding",
line: "result = 100 - 25",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tlda #$4b",
"\tsta result",
},
},
{
name: "constant folding word",
line: "result = $2000 - $1000",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tlda #$00",
"\tsta result",
"\tlda #$10",
"\tsta result+1",
},
},
{
name: "using constant in expression",
line: "result = a - OFFSET",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("a", "", compiler.KindByte, 100)
st.AddConst("OFFSET", "", compiler.KindByte, 10)
st.AddVar("result", "", compiler.KindByte, 0)
},
wantAsm: []string{
"\tsec",
"\tlda a",
"\tsbc #$0a",
"\tsta result",
},
},
{
name: "word - byte -> word",
line: "result = wval - bval",
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("wval", "", compiler.KindWord, 0x1234)
st.AddVar("bval", "", compiler.KindByte, 0x10)
st.AddVar("result", "", compiler.KindWord, 0)
},
wantAsm: []string{
"\tsec",
"\tlda wval",
"\tsbc bval",
"\tsta result",
"\tlda wval+1",
"\tsbc #0",
"\tsta result+1",
},
},
{
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,
},
{
name: "error: cannot assign to constant",
line: "MAXVAL = a - b",
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 := &SubtractCommand{}
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 !equalAsmSubtr(asm, tt.wantAsm) {
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
strings.Join(asm, "\n"),
strings.Join(tt.wantAsm, "\n"))
}
})
}
}
// equalAsmSubtr compares two assembly slices for equality
func equalAsmSubtr(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}