631 lines
15 KiB
Go
631 lines
15 KiB
Go
package commands
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
func TestIfBasicEqual(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ifLine string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantIf []string
|
|
wantEndif []string
|
|
}{
|
|
{
|
|
name: "byte var == byte literal",
|
|
ifLine: "IF x = 10",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindByte, 0)
|
|
},
|
|
wantIf: []string{
|
|
"\tlda x",
|
|
"\tcmp #$0a",
|
|
"\tbne _I1",
|
|
},
|
|
wantEndif: []string{
|
|
"_I1",
|
|
},
|
|
},
|
|
{
|
|
name: "word var == word literal",
|
|
ifLine: "IF x = 1000",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindWord, 0)
|
|
},
|
|
wantIf: []string{
|
|
"\tlda x",
|
|
"\tcmp #$e8",
|
|
"\tbne _I1",
|
|
"\tlda x+1",
|
|
"\tcmp #$03",
|
|
"\tbne _I1",
|
|
},
|
|
wantEndif: []string{
|
|
"_I1",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
ifCmd := &IfCommand{}
|
|
endifCmd := &EndIfCommand{}
|
|
|
|
ifLine := preproc.Line{
|
|
Text: tt.ifLine,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
endifLine := preproc.Line{
|
|
Text: "ENDIF",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := ifCmd.Interpret(ifLine, ctx); err != nil {
|
|
t.Fatalf("IF Interpret() error = %v", err)
|
|
}
|
|
|
|
ifAsm, err := ifCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("IF Generate() error = %v", err)
|
|
}
|
|
|
|
if err := endifCmd.Interpret(endifLine, ctx); err != nil {
|
|
t.Fatalf("ENDIF Interpret() error = %v", err)
|
|
}
|
|
|
|
endifAsm, err := endifCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ENDIF Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsm(ifAsm, tt.wantIf) {
|
|
t.Errorf("IF Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(ifAsm, "\n"),
|
|
strings.Join(tt.wantIf, "\n"))
|
|
}
|
|
if !equalAsm(endifAsm, tt.wantEndif) {
|
|
t.Errorf("ENDIF Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(endifAsm, "\n"),
|
|
strings.Join(tt.wantEndif, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIfElseEndif(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ifLine string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantIf []string
|
|
wantElse []string
|
|
wantEndif []string
|
|
}{
|
|
{
|
|
name: "byte var with else",
|
|
ifLine: "IF x = 10",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("x", "", compiler.KindByte, 0)
|
|
},
|
|
wantIf: []string{
|
|
"\tlda x",
|
|
"\tcmp #$0a",
|
|
"\tbne _I1",
|
|
},
|
|
wantElse: []string{
|
|
"\tjmp _I2",
|
|
"_I1",
|
|
},
|
|
wantEndif: []string{
|
|
"_I2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
ifCmd := &IfCommand{}
|
|
elseCmd := &ElseCommand{}
|
|
endifCmd := &EndIfCommand{}
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
if err := ifCmd.Interpret(preproc.Line{Text: tt.ifLine, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("IF Interpret() error = %v", err)
|
|
}
|
|
|
|
ifAsm, err := ifCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("IF Generate() error = %v", err)
|
|
}
|
|
|
|
if err := elseCmd.Interpret(preproc.Line{Text: "ELSE", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ELSE Interpret() error = %v", err)
|
|
}
|
|
|
|
elseAsm, err := elseCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ELSE Generate() error = %v", err)
|
|
}
|
|
|
|
if err := endifCmd.Interpret(preproc.Line{Text: "ENDIF", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ENDIF Interpret() error = %v", err)
|
|
}
|
|
|
|
endifAsm, err := endifCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ENDIF Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsm(ifAsm, tt.wantIf) {
|
|
t.Errorf("IF Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(ifAsm, "\n"),
|
|
strings.Join(tt.wantIf, "\n"))
|
|
}
|
|
if !equalAsm(elseAsm, tt.wantElse) {
|
|
t.Errorf("ELSE Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(elseAsm, "\n"),
|
|
strings.Join(tt.wantElse, "\n"))
|
|
}
|
|
if !equalAsm(endifAsm, tt.wantEndif) {
|
|
t.Errorf("ENDIF Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(endifAsm, "\n"),
|
|
strings.Join(tt.wantEndif, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIfAllOperators(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
wantInst string
|
|
}{
|
|
{"equal", "IF x = 10", "bne"},
|
|
{"not equal", "IF x <> 10", "beq"},
|
|
{"greater", "IF x > 10", "bcs"},
|
|
{"less", "IF x < 10", "bcs"},
|
|
{"greater equal", "IF x >= 10", "bcc"},
|
|
{"less equal", "IF 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 := &IfCommand{}
|
|
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 TestIfMixedTypes(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 := &IfCommand{}
|
|
line := preproc.Line{
|
|
Text: "IF 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 TestEndifWithoutIf(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &EndIfCommand{}
|
|
line := preproc.Line{
|
|
Text: "ENDIF",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("ENDIF without IF should fail")
|
|
}
|
|
if !strings.Contains(err.Error(), "not inside IF") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestElseWithoutIf(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &ElseCommand{}
|
|
line := preproc.Line{
|
|
Text: "ELSE",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("ELSE without IF should fail")
|
|
}
|
|
if !strings.Contains(err.Error(), "not inside IF") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIfNested(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddVar("y", "", compiler.KindByte, 0)
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
if1 := &IfCommand{}
|
|
if2 := &IfCommand{}
|
|
endif1 := &EndIfCommand{}
|
|
endif2 := &EndIfCommand{}
|
|
|
|
if err := if1.Interpret(preproc.Line{Text: "IF x = 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("IF 1 error = %v", err)
|
|
}
|
|
asm1, err := if1.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("IF 1 Generate error = %v", err)
|
|
}
|
|
|
|
if err := if2.Interpret(preproc.Line{Text: "IF y = 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("IF 2 error = %v", err)
|
|
}
|
|
asm2, err := if2.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("IF 2 Generate error = %v", err)
|
|
}
|
|
|
|
if err := endif2.Interpret(preproc.Line{Text: "ENDIF", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ENDIF 2 error = %v", err)
|
|
}
|
|
if err := endif1.Interpret(preproc.Line{Text: "ENDIF", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ENDIF 1 error = %v", err)
|
|
}
|
|
|
|
// Find labels in asm output
|
|
label1 := findLabel(asm1)
|
|
label2 := findLabel(asm2)
|
|
|
|
if label1 == label2 {
|
|
t.Error("Nested IFs should have different labels")
|
|
}
|
|
if label1 == "" || label2 == "" {
|
|
t.Error("Should generate labels for both IFs")
|
|
}
|
|
}
|
|
|
|
func TestIfNestedWithElse(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddVar("y", "", compiler.KindByte, 0)
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
if1 := &IfCommand{}
|
|
else1 := &ElseCommand{}
|
|
if2 := &IfCommand{}
|
|
endif2 := &EndIfCommand{}
|
|
endif1 := &EndIfCommand{}
|
|
|
|
if err := if1.Interpret(preproc.Line{Text: "IF x = 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("IF 1 error = %v", err)
|
|
}
|
|
if1.Generate(ctx)
|
|
|
|
if err := else1.Interpret(preproc.Line{Text: "ELSE", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ELSE 1 error = %v", err)
|
|
}
|
|
else1.Generate(ctx)
|
|
|
|
if err := if2.Interpret(preproc.Line{Text: "IF y = 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("IF 2 error = %v", err)
|
|
}
|
|
if2.Generate(ctx)
|
|
|
|
if err := endif2.Interpret(preproc.Line{Text: "ENDIF", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ENDIF 2 error = %v", err)
|
|
}
|
|
|
|
if err := endif1.Interpret(preproc.Line{Text: "ENDIF", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("ENDIF 1 error = %v", err)
|
|
}
|
|
|
|
// If this doesn't crash, nesting with ELSE works
|
|
}
|
|
|
|
func TestIfLongJump(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 := &IfCommand{}
|
|
line := preproc.Line{
|
|
Text: "IF 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 TestIfConstant(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 := &IfCommand{}
|
|
line := preproc.Line{
|
|
Text: "IF 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 TestIfWrongParamCount(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
tests := []string{
|
|
"IF x",
|
|
"IF x =",
|
|
"IF x = 10 extra",
|
|
}
|
|
|
|
for _, text := range tests {
|
|
cmd := &IfCommand{}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestElseWrongParamCount(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
// Setup IF first
|
|
ifCmd := &IfCommand{}
|
|
ifCmd.Interpret(preproc.Line{
|
|
Text: "IF x = 10",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}, ctx)
|
|
|
|
cmd := &ElseCommand{}
|
|
line := preproc.Line{
|
|
Text: "ELSE extra",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Error("ELSE with extra params should fail")
|
|
}
|
|
}
|
|
|
|
func TestEndifWrongParamCount(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
// Setup IF first
|
|
ifCmd := &IfCommand{}
|
|
ifCmd.Interpret(preproc.Line{
|
|
Text: "IF x = 10",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}, ctx)
|
|
|
|
cmd := &EndIfCommand{}
|
|
line := preproc.Line{
|
|
Text: "ENDIF extra",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Error("ENDIF with extra params should fail")
|
|
}
|
|
}
|
|
|
|
func TestIfConstantFolding(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ifLine string
|
|
shouldSkip bool
|
|
}{
|
|
{"true condition", "IF 10 = 10", false},
|
|
{"false condition", "IF 10 = 5", true},
|
|
{"true not equal", "IF 10 <> 5", false},
|
|
{"false not equal", "IF 10 <> 10", true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &IfCommand{}
|
|
line := preproc.Line{
|
|
Text: tt.ifLine,
|
|
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)
|
|
}
|
|
|
|
hasJump := false
|
|
for _, inst := range asm {
|
|
if strings.Contains(inst, "jmp") {
|
|
hasJump = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if tt.shouldSkip && !hasJump {
|
|
t.Error("False constant should generate JMP to skip block")
|
|
}
|
|
if !tt.shouldSkip && len(asm) > 0 && hasJump {
|
|
t.Error("True constant should not generate JMP")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Helper to find label in assembly
|
|
func findLabel(asm []string) string {
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "_I") && !strings.HasPrefix(strings.TrimSpace(line), "\t") {
|
|
return strings.TrimSpace(line)
|
|
}
|
|
if strings.Contains(line, "bne") || strings.Contains(line, "beq") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 2 {
|
|
return parts[1]
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
/*
|
|
// 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
|
|
}
|
|
*/
|