419 lines
10 KiB
Go
419 lines
10 KiB
Go
package commands
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
func TestWhileBasicEqual(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
whileLine string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantWhile []string
|
|
wantWend []string
|
|
}{
|
|
{
|
|
name: "byte var == byte literal",
|
|
whileLine: "WHILE x = 10",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindByte, 0)
|
|
},
|
|
wantWhile: []string{
|
|
"_LOOPSTART1",
|
|
"\tlda x",
|
|
"\tcmp #$0a",
|
|
"\tbne _LOOPEND1",
|
|
},
|
|
wantWend: []string{
|
|
"\tjmp _LOOPSTART1",
|
|
"_LOOPEND1",
|
|
},
|
|
},
|
|
{
|
|
name: "word var == word literal",
|
|
whileLine: "WHILE x = 1000",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindWord, 0)
|
|
},
|
|
wantWhile: []string{
|
|
"_LOOPSTART1",
|
|
"\tlda x",
|
|
"\tcmp #$e8",
|
|
"\tbne _LOOPEND1",
|
|
"\tlda x+1",
|
|
"\tcmp #$03",
|
|
"\tbne _LOOPEND1",
|
|
},
|
|
wantWend: []string{
|
|
"\tjmp _LOOPSTART1",
|
|
"_LOOPEND1",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
whileCmd := &WhileCommand{}
|
|
wendCmd := &WendCommand{}
|
|
|
|
whileLine := preproc.Line{
|
|
Text: tt.whileLine,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
wendLine := preproc.Line{
|
|
Text: "WEND",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := whileCmd.Interpret(whileLine, ctx); err != nil {
|
|
t.Fatalf("WHILE Interpret() error = %v", err)
|
|
}
|
|
|
|
whileAsm, err := whileCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("WHILE Generate() error = %v", err)
|
|
}
|
|
|
|
if err := wendCmd.Interpret(wendLine, ctx); err != nil {
|
|
t.Fatalf("WEND Interpret() error = %v", err)
|
|
}
|
|
|
|
wendAsm, err := wendCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("WEND Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsm(whileAsm, tt.wantWhile) {
|
|
t.Errorf("WHILE Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(whileAsm, "\n"),
|
|
strings.Join(tt.wantWhile, "\n"))
|
|
}
|
|
if !equalAsm(wendAsm, tt.wantWend) {
|
|
t.Errorf("WEND Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(wendAsm, "\n"),
|
|
strings.Join(tt.wantWend, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWhileAllOperators(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
wantInst string
|
|
}{
|
|
{"equal", "WHILE x = 10", "bne"},
|
|
{"not equal", "WHILE x <> 10", "beq"},
|
|
{"greater", "WHILE x > 10", "bcs"},
|
|
{"less", "WHILE x < 10", "bcs"},
|
|
{"greater equal", "WHILE x >= 10", "bcc"},
|
|
{"less equal", "WHILE x <= 10", "bcc"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
cmd := &WhileCommand{}
|
|
line := preproc.Line{
|
|
Text: tt.line,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := cmd.Interpret(line, ctx); err != nil {
|
|
t.Fatalf("Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
found := false
|
|
for _, inst := range asm {
|
|
if strings.Contains(inst, tt.wantInst) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Expected %s instruction not found in: %v", tt.wantInst, asm)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWhileMixedTypes(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddVar("y", "", compiler.KindWord, 0)
|
|
|
|
cmd := &WhileCommand{}
|
|
line := preproc.Line{
|
|
Text: "WHILE x < y",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := cmd.Interpret(line, ctx); err != nil {
|
|
t.Fatalf("Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
foundHighByteCheck := false
|
|
for _, inst := range asm {
|
|
if strings.Contains(inst, "y+1") {
|
|
foundHighByteCheck = true
|
|
break
|
|
}
|
|
}
|
|
if !foundHighByteCheck {
|
|
t.Error("Expected high byte check for word in mixed comparison")
|
|
}
|
|
}
|
|
|
|
func TestWhileBreak(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
whileCmd := &WhileCommand{}
|
|
breakCmd := &BreakCommand{}
|
|
wendCmd := &WendCommand{}
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
whileLine := preproc.Line{Text: "WHILE x < 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
|
breakLine := preproc.Line{Text: "BREAK", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
|
wendLine := preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
|
|
|
if err := whileCmd.Interpret(whileLine, ctx); err != nil {
|
|
t.Fatalf("WHILE Interpret() error = %v", err)
|
|
}
|
|
|
|
whileAsm, _ := whileCmd.Generate(ctx)
|
|
_ = whileAsm // body would go here
|
|
|
|
if err := breakCmd.Interpret(breakLine, ctx); err != nil {
|
|
t.Fatalf("BREAK Interpret() error = %v", err)
|
|
}
|
|
|
|
breakAsm, err := breakCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("BREAK Generate() error = %v", err)
|
|
}
|
|
|
|
if err := wendCmd.Interpret(wendLine, ctx); err != nil {
|
|
t.Fatalf("WEND Interpret() error = %v", err)
|
|
}
|
|
|
|
if len(breakAsm) != 1 || !strings.Contains(breakAsm[0], "jmp _LOOPEND") {
|
|
t.Errorf("BREAK should jump to WEND label, got: %v", breakAsm)
|
|
}
|
|
}
|
|
|
|
func TestBreakOutsideLoop(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &BreakCommand{}
|
|
line := preproc.Line{
|
|
Text: "BREAK",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("BREAK outside loop should fail")
|
|
}
|
|
if !strings.Contains(err.Error(), "not inside WHILE") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWendWithoutWhile(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &WendCommand{}
|
|
line := preproc.Line{
|
|
Text: "WEND",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("WEND without WHILE should fail")
|
|
}
|
|
if !strings.Contains(err.Error(), "not inside WHILE") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWhileNested(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0)
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
while1 := &WhileCommand{}
|
|
while2 := &WhileCommand{}
|
|
wend1 := &WendCommand{}
|
|
wend2 := &WendCommand{}
|
|
|
|
if err := while1.Interpret(preproc.Line{Text: "WHILE i < 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WHILE 1 error = %v", err)
|
|
}
|
|
asm1, err := while1.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("WHILE 1 Generate error = %v", err)
|
|
}
|
|
|
|
if err := while2.Interpret(preproc.Line{Text: "WHILE j < 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WHILE 2 error = %v", err)
|
|
}
|
|
asm2, err := while2.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("WHILE 2 Generate error = %v", err)
|
|
}
|
|
|
|
if err := wend2.Interpret(preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WEND 2 error = %v", err)
|
|
}
|
|
if err := wend1.Interpret(preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WEND 1 error = %v", err)
|
|
}
|
|
|
|
if asm1[0] == asm2[0] {
|
|
t.Error("Nested loops should have different labels")
|
|
}
|
|
}
|
|
|
|
func TestWhileLongJump(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
pragma.AddPragma("_P_USE_LONG_JUMP", "1")
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
cmd := &WhileCommand{}
|
|
line := preproc.Line{
|
|
Text: "WHILE x = 10",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := cmd.Interpret(line, ctx); err != nil {
|
|
t.Fatalf("Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
foundJmp := false
|
|
for _, inst := range asm {
|
|
if strings.Contains(inst, "jmp") {
|
|
foundJmp = true
|
|
break
|
|
}
|
|
}
|
|
if !foundJmp {
|
|
t.Error("Long jump mode should contain JMP instruction")
|
|
}
|
|
}
|
|
|
|
func TestWhileConstant(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
cmd := &WhileCommand{}
|
|
line := preproc.Line{
|
|
Text: "WHILE x < MAX",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := cmd.Interpret(line, ctx); err != nil {
|
|
t.Fatalf("Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
found := false
|
|
for _, inst := range asm {
|
|
if strings.Contains(inst, "#$64") {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Constant should be folded to immediate value")
|
|
}
|
|
}
|
|
|
|
func TestWhileWrongParamCount(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
tests := []string{
|
|
"WHILE x",
|
|
"WHILE x =",
|
|
"WHILE x = 10 extra",
|
|
}
|
|
|
|
for _, text := range tests {
|
|
cmd := &WhileCommand{}
|
|
line := preproc.Line{
|
|
Text: text,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Errorf("Should fail with wrong param count: %s", text)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to compare assembly output
|
|
func equalAsm(got, want []string) bool {
|
|
if len(got) != len(want) {
|
|
return false
|
|
}
|
|
for i := range got {
|
|
if got[i] != want[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|