264 lines
5.4 KiB
Go
264 lines
5.4 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
func TestElseCommand_WillHandle(t *testing.T) {
|
|
cmd := &ElseCommand{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
want bool
|
|
}{
|
|
{"ELSE", "ELSE", true},
|
|
{"not ELSE", "IF 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 TestEndIfCommand_WillHandle(t *testing.T) {
|
|
cmd := &EndIfCommand{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
want bool
|
|
}{
|
|
{"ENDIF", "ENDIF", true},
|
|
{"not ENDIF", "IF 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 TestIfElseEndif_Integration(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
lines []string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantAsm []string
|
|
}{
|
|
{
|
|
name: "IF...ENDIF (no ELSE)",
|
|
lines: []string{
|
|
"IF a = b",
|
|
"ENDIF",
|
|
},
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"; IF a = b",
|
|
"\tlda a",
|
|
"\tcmp b",
|
|
"\tbne _I1",
|
|
"; ENDIF",
|
|
"_I1",
|
|
},
|
|
},
|
|
{
|
|
name: "IF...ELSE...ENDIF",
|
|
lines: []string{
|
|
"IF a = b",
|
|
"ELSE",
|
|
"ENDIF",
|
|
},
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"; IF a = b",
|
|
"\tlda a",
|
|
"\tcmp b",
|
|
"\tbne _I1",
|
|
"; ELSE",
|
|
"\tjmp _I2",
|
|
"_I1",
|
|
"; ENDIF",
|
|
"_I2",
|
|
},
|
|
},
|
|
{
|
|
name: "nested IF statements",
|
|
lines: []string{
|
|
"IF a = 10",
|
|
"IF b = 20",
|
|
"ENDIF",
|
|
"ENDIF",
|
|
},
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("a", "", compiler.KindByte, 0)
|
|
st.AddVar("b", "", compiler.KindByte, 0)
|
|
},
|
|
wantAsm: []string{
|
|
"; IF a = 10",
|
|
"\tlda a",
|
|
"\tcmp #$0a",
|
|
"\tbne _I1",
|
|
"; IF b = 20",
|
|
"\tlda b",
|
|
"\tcmp #$14",
|
|
"\tbne _I2",
|
|
"; ENDIF",
|
|
"_I2",
|
|
"; ENDIF",
|
|
"_I1",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
var allAsm []string
|
|
|
|
for _, lineText := range tt.lines {
|
|
line := preproc.Line{Text: lineText, Kind: preproc.Source, PragmaSetIndex: 0}
|
|
|
|
// Determine which command to use
|
|
var cmd compiler.Command
|
|
if strings.HasPrefix(strings.ToUpper(lineText), "IF") {
|
|
cmd = &IfCommand{}
|
|
} else if strings.ToUpper(lineText) == "ELSE" {
|
|
cmd = &ElseCommand{}
|
|
} else if strings.ToUpper(lineText) == "ENDIF" {
|
|
cmd = &EndIfCommand{}
|
|
} else {
|
|
t.Fatalf("unknown command: %s", lineText)
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err != nil {
|
|
t.Fatalf("Interpret(%q) error = %v", lineText, err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate(%q) error = %v", lineText, err)
|
|
}
|
|
|
|
allAsm = append(allAsm, fmt.Sprintf("; %s", lineText))
|
|
allAsm = append(allAsm, asm...)
|
|
}
|
|
|
|
if !equalAsmElse(allAsm, tt.wantAsm) {
|
|
t.Errorf("Assembly mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(allAsm, "\n"),
|
|
strings.Join(tt.wantAsm, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestElseCommand_Errors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "ELSE without IF",
|
|
line: "ELSE",
|
|
wantErr: "stack underflow",
|
|
},
|
|
{
|
|
name: "wrong param count",
|
|
line: "ELSE extra",
|
|
wantErr: "wrong number of parameters",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
|
cmd := &ElseCommand{}
|
|
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Errorf("error = %q, want substring %q", err.Error(), tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEndIfCommand_Errors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "ENDIF without IF",
|
|
line: "ENDIF",
|
|
wantErr: "stack underflow",
|
|
},
|
|
{
|
|
name: "wrong param count",
|
|
line: "ENDIF extra",
|
|
wantErr: "wrong number of parameters",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
|
cmd := &EndIfCommand{}
|
|
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Errorf("error = %q, want substring %q", err.Error(), tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// equalAsmElse compares two assembly slices for equality
|
|
func equalAsmElse(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|