727 lines
18 KiB
Go
727 lines
18 KiB
Go
package commands
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
func TestForBasicTO(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
forLine string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantFor []string
|
|
wantNext []string
|
|
}{
|
|
{
|
|
name: "byte var TO byte literal",
|
|
forLine: "FOR i = 0 TO 10",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("i", "", compiler.KindByte, 0)
|
|
},
|
|
wantFor: []string{
|
|
"\tlda #$00",
|
|
"\tsta i",
|
|
"_LOOPSTART1",
|
|
"\tlda i",
|
|
"\tcmp #$0a",
|
|
"\tbeq _L1",
|
|
"\tbcc _L1",
|
|
"\tjmp _LOOPEND1",
|
|
"_L1",
|
|
},
|
|
wantNext: []string{
|
|
"\tinc i",
|
|
"\tjmp _LOOPSTART1",
|
|
"_LOOPEND1",
|
|
},
|
|
},
|
|
{
|
|
name: "word var TO word literal",
|
|
forLine: "FOR counter = 0 TO 1000",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("counter", "", compiler.KindWord, 0)
|
|
},
|
|
wantFor: []string{
|
|
"\tlda #$00",
|
|
"\tsta counter",
|
|
"\tsta counter+1",
|
|
"_LOOPSTART1",
|
|
"\tlda counter+1",
|
|
"\tcmp #$03",
|
|
"\tbcc _L1",
|
|
"\tbne _L2",
|
|
"\tlda counter",
|
|
"\tcmp #$e8",
|
|
"\tbeq _L1",
|
|
"\tbcc _L1",
|
|
"_L2",
|
|
"\tjmp _LOOPEND1",
|
|
"_L1",
|
|
},
|
|
wantNext: []string{
|
|
"\tinc counter",
|
|
"\tbne _L3",
|
|
"\tinc counter+1",
|
|
"_L3",
|
|
"\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)
|
|
|
|
forCmd := &ForCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
forLine := preproc.Line{
|
|
Text: tt.forLine,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
nextLine := preproc.Line{
|
|
Text: "NEXT",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
|
t.Fatalf("FOR Interpret() error = %v", err)
|
|
}
|
|
|
|
forAsm, err := forCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FOR Generate() error = %v", err)
|
|
}
|
|
|
|
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
|
}
|
|
|
|
nextAsm, err := nextCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("NEXT Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsm(forAsm, tt.wantFor) {
|
|
t.Errorf("FOR Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(forAsm, "\n"),
|
|
strings.Join(tt.wantFor, "\n"))
|
|
}
|
|
if !equalAsm(nextAsm, tt.wantNext) {
|
|
t.Errorf("NEXT Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(nextAsm, "\n"),
|
|
strings.Join(tt.wantNext, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForBasicDOWNTO(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
forLine string
|
|
setupVars func(*compiler.SymbolTable)
|
|
wantFor []string
|
|
wantNext []string
|
|
}{
|
|
{
|
|
name: "byte var DOWNTO byte literal",
|
|
forLine: "FOR i = 10 DOWNTO 0",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("i", "", compiler.KindByte, 0)
|
|
},
|
|
wantFor: []string{
|
|
"\tlda #$0a",
|
|
"\tsta i",
|
|
"_LOOPSTART1",
|
|
"\tlda i",
|
|
"\tbne _L1",
|
|
"\tjmp _LOOPEND1",
|
|
"_L1",
|
|
},
|
|
wantNext: []string{
|
|
"\tdec i",
|
|
"\tjmp _LOOPSTART1",
|
|
"_LOOPEND1",
|
|
},
|
|
},
|
|
{
|
|
name: "word var DOWNTO word literal",
|
|
forLine: "FOR counter = 1000 DOWNTO 0",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("counter", "", compiler.KindWord, 0)
|
|
},
|
|
wantFor: []string{
|
|
"\tlda #$e8",
|
|
"\tsta counter",
|
|
"\tlda #$03",
|
|
"\tsta counter+1",
|
|
"_LOOPSTART1",
|
|
"\tlda counter",
|
|
"\tbne _L1",
|
|
"\tlda counter+1",
|
|
"\tbne _L1",
|
|
"\tjmp _LOOPEND1",
|
|
"_L1",
|
|
},
|
|
wantNext: []string{
|
|
"\tlda counter",
|
|
"\tbne _L2",
|
|
"\tdec counter+1",
|
|
"_L2",
|
|
"\tdec counter",
|
|
"\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)
|
|
|
|
forCmd := &ForCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
forLine := preproc.Line{
|
|
Text: tt.forLine,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
nextLine := preproc.Line{
|
|
Text: "NEXT",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
|
t.Fatalf("FOR Interpret() error = %v", err)
|
|
}
|
|
|
|
forAsm, err := forCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FOR Generate() error = %v", err)
|
|
}
|
|
|
|
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
|
}
|
|
|
|
nextAsm, err := nextCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("NEXT Generate() error = %v", err)
|
|
}
|
|
|
|
if !equalAsm(forAsm, tt.wantFor) {
|
|
t.Errorf("FOR Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(forAsm, "\n"),
|
|
strings.Join(tt.wantFor, "\n"))
|
|
}
|
|
if !equalAsm(nextAsm, tt.wantNext) {
|
|
t.Errorf("NEXT Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
|
strings.Join(nextAsm, "\n"),
|
|
strings.Join(tt.wantNext, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForWithSTEP(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
forLine string
|
|
setupVars func(*compiler.SymbolTable)
|
|
checkNextAsm func([]string) bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "byte var TO with STEP 2",
|
|
forLine: "FOR i = 0 TO 10 STEP 2",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("i", "", compiler.KindByte, 0)
|
|
},
|
|
checkNextAsm: func(asm []string) bool {
|
|
// Should contain adc #$02
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "adc #$02") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
description: "STEP 2 should use adc #$02",
|
|
},
|
|
{
|
|
name: "byte var DOWNTO with STEP 3",
|
|
forLine: "FOR i = 10 DOWNTO 0 STEP 3",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("i", "", compiler.KindByte, 0)
|
|
},
|
|
checkNextAsm: func(asm []string) bool {
|
|
// Should contain sbc #$03
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "sbc #$03") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
description: "STEP 3 should use sbc #$03",
|
|
},
|
|
{
|
|
name: "byte var TO with variable STEP",
|
|
forLine: "FOR i = 0 TO 10 STEP stepval",
|
|
setupVars: func(st *compiler.SymbolTable) {
|
|
st.AddVar("i", "", compiler.KindByte, 0)
|
|
st.AddVar("stepval", "", compiler.KindByte, 0)
|
|
},
|
|
checkNextAsm: func(asm []string) bool {
|
|
// Should contain adc stepval
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "adc stepval") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
description: "variable STEP should use adc variable",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
tt.setupVars(ctx.SymbolTable)
|
|
|
|
forCmd := &ForCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
forLine := preproc.Line{
|
|
Text: tt.forLine,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
nextLine := preproc.Line{
|
|
Text: "NEXT",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
|
t.Fatalf("FOR Interpret() error = %v", err)
|
|
}
|
|
|
|
if _, err := forCmd.Generate(ctx); err != nil {
|
|
t.Fatalf("FOR Generate() error = %v", err)
|
|
}
|
|
|
|
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
|
}
|
|
|
|
nextAsm, err := nextCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("NEXT Generate() error = %v", err)
|
|
}
|
|
|
|
if !tt.checkNextAsm(nextAsm) {
|
|
t.Errorf("%s\ngot:\n%s", tt.description, strings.Join(nextAsm, "\n"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForBreak(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
|
|
forCmd := &ForCommand{}
|
|
breakCmd := &BreakCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
forLine := preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
|
breakLine := preproc.Line{Text: "BREAK", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
|
nextLine := preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
|
|
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
|
t.Fatalf("FOR Interpret() error = %v", err)
|
|
}
|
|
|
|
forAsm, _ := forCmd.Generate(ctx)
|
|
_ = forAsm // 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 := nextCmd.Interpret(nextLine, ctx); err != nil {
|
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
|
}
|
|
|
|
if len(breakAsm) != 1 || !strings.Contains(breakAsm[0], "jmp _LOOPEND") {
|
|
t.Errorf("BREAK should jump to loop end label, got: %v", breakAsm)
|
|
}
|
|
}
|
|
|
|
func TestForNested(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()
|
|
|
|
for1 := &ForCommand{}
|
|
for2 := &ForCommand{}
|
|
next1 := &NextCommand{}
|
|
next2 := &NextCommand{}
|
|
|
|
if err := for1.Interpret(preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("FOR 1 error = %v", err)
|
|
}
|
|
asm1, err := for1.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FOR 1 Generate error = %v", err)
|
|
}
|
|
|
|
if err := for2.Interpret(preproc.Line{Text: "FOR j = 0 TO 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("FOR 2 error = %v", err)
|
|
}
|
|
asm2, err := for2.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FOR 2 Generate error = %v", err)
|
|
}
|
|
|
|
if err := next2.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("NEXT 2 error = %v", err)
|
|
}
|
|
if err := next1.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("NEXT 1 error = %v", err)
|
|
}
|
|
|
|
if asm1[0] == asm2[0] {
|
|
t.Error("Nested loops should have different labels")
|
|
}
|
|
}
|
|
|
|
func TestForMixedWithWhile(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
forCmd := &ForCommand{}
|
|
whileCmd := &WhileCommand{}
|
|
wendCmd := &WendCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
// FOR i = 0 TO 10
|
|
// WHILE x < 5
|
|
// WEND
|
|
// NEXT
|
|
|
|
if err := forCmd.Interpret(preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("FOR error = %v", err)
|
|
}
|
|
_, _ = forCmd.Generate(ctx)
|
|
|
|
if err := whileCmd.Interpret(preproc.Line{Text: "WHILE x < 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WHILE error = %v", err)
|
|
}
|
|
_, _ = whileCmd.Generate(ctx)
|
|
|
|
if err := wendCmd.Interpret(preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WEND error = %v", err)
|
|
}
|
|
|
|
if err := nextCmd.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("NEXT error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForIllegalNesting(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
|
|
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
|
|
|
forCmd := &ForCommand{}
|
|
whileCmd := &WhileCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
// FOR i = 0 TO 10
|
|
// WHILE x < 5
|
|
// NEXT <- ERROR: crossing loop boundaries
|
|
// WEND
|
|
|
|
if err := forCmd.Interpret(preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("FOR error = %v", err)
|
|
}
|
|
_, _ = forCmd.Generate(ctx)
|
|
|
|
if err := whileCmd.Interpret(preproc.Line{Text: "WHILE x < 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
|
t.Fatalf("WHILE error = %v", err)
|
|
}
|
|
_, _ = whileCmd.Generate(ctx)
|
|
|
|
// NEXT should fail because of stack mismatch
|
|
err := nextCmd.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
|
if err == nil {
|
|
t.Fatal("NEXT should fail when crossing loop boundaries")
|
|
}
|
|
if !strings.Contains(err.Error(), "mismatch") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNextWithoutFor(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &NextCommand{}
|
|
line := preproc.Line{
|
|
Text: "NEXT",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("NEXT outside FOR loop should fail")
|
|
}
|
|
if !strings.Contains(err.Error(), "not inside FOR") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForWrongParamCount(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
|
|
tests := []string{
|
|
"FOR i",
|
|
"FOR i = 0",
|
|
"FOR i = 0 TO",
|
|
"FOR i = 0 TO 10 STEP",
|
|
"FOR i = 0 TO 10 STEP 2 EXTRA",
|
|
}
|
|
|
|
for _, text := range tests {
|
|
cmd := &ForCommand{}
|
|
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 TestForInvalidDirection(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
|
|
cmd := &ForCommand{}
|
|
line := preproc.Line{
|
|
Text: "FOR i = 0 UPTO 10",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("Should fail with invalid direction keyword")
|
|
}
|
|
if !strings.Contains(err.Error(), "TO") && !strings.Contains(err.Error(), "DOWNTO") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForZeroStep(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
|
|
cmd := &ForCommand{}
|
|
line := preproc.Line{
|
|
Text: "FOR i = 0 TO 10 STEP 0",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("Should fail with STEP 0")
|
|
}
|
|
if !strings.Contains(err.Error(), "STEP cannot be zero") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForConstVariable(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddConst("LIMIT", "", compiler.KindByte, 10)
|
|
|
|
cmd := &ForCommand{}
|
|
line := preproc.Line{
|
|
Text: "FOR LIMIT = 0 TO 10",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("Should fail when using constant as loop variable")
|
|
}
|
|
if !strings.Contains(err.Error(), "constant") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForUnknownVariable(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &ForCommand{}
|
|
line := preproc.Line{
|
|
Text: "FOR unknown = 0 TO 10",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("Should fail with unknown variable")
|
|
}
|
|
if !strings.Contains(err.Error(), "unknown") {
|
|
t.Errorf("Wrong error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForConstantEnd(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
|
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100)
|
|
|
|
forCmd := &ForCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
forLine := preproc.Line{
|
|
Text: "FOR i = 0 TO MAX",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
nextLine := preproc.Line{
|
|
Text: "NEXT",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
|
t.Fatalf("FOR Interpret() error = %v", err)
|
|
}
|
|
|
|
asm, err := forCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FOR Generate() error = %v", err)
|
|
}
|
|
|
|
found := false
|
|
for _, inst := range asm {
|
|
if strings.Contains(inst, "#$64") { // 100 in hex
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Constant should be folded to immediate value")
|
|
}
|
|
|
|
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestForWordSTEP(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
ctx.SymbolTable.AddVar("counter", "", compiler.KindWord, 0)
|
|
|
|
forCmd := &ForCommand{}
|
|
nextCmd := &NextCommand{}
|
|
|
|
forLine := preproc.Line{
|
|
Text: "FOR counter = 0 TO 1000 STEP 256",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
nextLine := preproc.Line{
|
|
Text: "NEXT",
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
|
t.Fatalf("FOR Interpret() error = %v", err)
|
|
}
|
|
|
|
if _, err := forCmd.Generate(ctx); err != nil {
|
|
t.Fatalf("FOR Generate() error = %v", err)
|
|
}
|
|
|
|
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
|
}
|
|
|
|
nextAsm, err := nextCmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Fatalf("NEXT Generate() error = %v", err)
|
|
}
|
|
|
|
// Should handle both low and high bytes
|
|
foundLowAdd := false
|
|
foundHighAdd := false
|
|
for _, inst := range nextAsm {
|
|
if strings.Contains(inst, "adc #$00") {
|
|
foundLowAdd = true
|
|
}
|
|
if strings.Contains(inst, "adc #$01") {
|
|
foundHighAdd = true
|
|
}
|
|
}
|
|
if !foundLowAdd || !foundHighAdd {
|
|
t.Errorf("Word STEP should handle both bytes\ngot:\n%s", strings.Join(nextAsm, "\n"))
|
|
}
|
|
}
|