diff --git a/internal/commands/for.go b/internal/commands/for.go index f450176..5bb681c 100644 --- a/internal/commands/for.go +++ b/internal/commands/for.go @@ -11,15 +11,12 @@ import ( // ForCommand handles FOR loop statements // Syntax: FOR = TO [STEP ] -// -// FOR = DOWNTO [STEP ] type ForCommand struct { varName string varKind compiler.VarKind startOp *compiler.OperandInfo endOp *compiler.OperandInfo stepOp *compiler.OperandInfo - isDownto bool useLongJump bool loopLabel string skipLabel string @@ -90,12 +87,11 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) IsVar: startIsVar, } - // Parse direction (TO or DOWNTO) + // Parse direction (TO only) direction := strings.ToUpper(params[4]) - if direction != "TO" && direction != "DOWNTO" { - return fmt.Errorf("FOR: expected 'TO' or 'DOWNTO' at position 5, got %q", params[4]) + if direction != "TO" { + return fmt.Errorf("FOR: expected 'TO' at position 5, got %q (DOWNTO is not supported)", params[4]) } - c.isDownto = (direction == "DOWNTO") // Parse end value endVarName, endVarKind, endValue, endIsVar, parseErr := compiler.ParseOperandParam( @@ -161,7 +157,6 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) VarKind: c.varKind, EndOperand: c.endOp, StepOperand: c.stepOp, - IsDownto: c.isDownto, LoopLabel: c.loopLabel, SkipLabel: c.skipLabel, }) @@ -179,16 +174,7 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { // Emit loop label asm = append(asm, c.loopLabel) - // Generate comparison - // TO: continue if var <= end (skip if var > end) - // DOWNTO: continue if var >= end (skip if var < end) - var op comparisonOp - if c.isDownto { - op = opGreaterEqual // skip if var < end - } else { - op = opLessEqual // skip if var > end - } - + // Generate comparison for TO loop: continue if var <= end (skip if var > end) varOp := &operandInfo{ varName: c.varName, varKind: c.varKind, @@ -204,7 +190,7 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { } gen, err := newComparisonGenerator( - op, + opLessEqual, varOp, endOp, c.useLongJump, @@ -231,14 +217,14 @@ func (c *ForCommand) generateAssignment() []string { if c.startOp.IsVar { // Destination: byte if c.varKind == compiler.KindByte { - // byte → byte or word → byte (take low byte) + // byte → byte or word → byte (take low byte) asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.varName)) return asm } // Destination: word - // byte → word (zero-extend) + // byte → word (zero-extend) if c.startOp.VarKind == compiler.KindByte { asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.varName)) @@ -247,7 +233,7 @@ func (c *ForCommand) generateAssignment() []string { return asm } - // word → word (copy both bytes) + // word → word (copy both bytes) asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.varName)) asm = append(asm, fmt.Sprintf("\tlda %s+1", c.startOp.VarName)) diff --git a/internal/commands/for_test.go b/internal/commands/for_test.go index ed03c41..cff679a 100644 --- a/internal/commands/for_test.go +++ b/internal/commands/for_test.go @@ -26,12 +26,9 @@ func TestForBasicTO(t *testing.T) { "\tlda #$00", "\tsta i", "_LOOPSTART1", - "\tlda i", - "\tcmp #$0a", - "\tbeq _L1", - "\tbcc _L1", - "\tjmp _LOOPEND1", - "_L1", + "\tlda #$0a", + "\tcmp i", + "\tbcc _LOOPEND1", }, wantNext: []string{ "\tinc i", @@ -50,135 +47,20 @@ func TestForBasicTO(t *testing.T) { "\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", + "\tlda #$03", + "\tcmp counter+1", + "\tbcc _LOOPEND1", + "\tbne _L1", + "\tlda #$e8", + "\tcmp counter", + "\tbcc _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", + "\tinc counter+1", "_L2", - "\tdec counter", "\tjmp _LOOPSTART1", "_LOOPEND1", }, @@ -262,23 +144,6 @@ func TestForWithSTEP(t *testing.T) { }, 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", @@ -419,7 +284,26 @@ func TestForNested(t *testing.T) { t.Fatalf("NEXT 1 error = %v", err) } - if asm1[0] == asm2[0] { + // Find loop start labels in the generated assembly + loopLabel1 := "" + loopLabel2 := "" + for _, line := range asm1 { + if strings.HasPrefix(line, "_LOOPSTART") { + loopLabel1 = line + break + } + } + for _, line := range asm2 { + if strings.HasPrefix(line, "_LOOPSTART") { + loopLabel2 = line + break + } + } + + if loopLabel1 == "" || loopLabel2 == "" { + t.Fatal("Could not find loop labels") + } + if loopLabel1 == loopLabel2 { t.Error("Nested loops should have different labels") } } @@ -562,7 +446,28 @@ func TestForInvalidDirection(t *testing.T) { if err == nil { t.Fatal("Should fail with invalid direction keyword") } - if !strings.Contains(err.Error(), "TO") && !strings.Contains(err.Error(), "DOWNTO") { + if !strings.Contains(err.Error(), "TO") { + t.Errorf("Wrong error message: %v", err) + } +} + +func TestForDOWNTORejected(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 = 10 DOWNTO 0", + Kind: preproc.Source, + PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(), + } + + err := cmd.Interpret(line, ctx) + if err == nil { + t.Fatal("Should fail with DOWNTO") + } + if !strings.Contains(err.Error(), "not supported") { t.Errorf("Wrong error message: %v", err) } } diff --git a/internal/commands/next.go b/internal/commands/next.go index f7e7217..f37d2d9 100644 --- a/internal/commands/next.go +++ b/internal/commands/next.go @@ -11,7 +11,7 @@ import ( // NextCommand handles NEXT statements // Syntax: NEXT -// Increments/decrements loop variable and jumps back to loop start +// Increments loop variable and jumps back to loop start type NextCommand struct { info *compiler.ForLoopInfo } @@ -58,12 +58,8 @@ func (c *NextCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { var asm []string - // Generate increment/decrement - if c.info.IsDownto { - asm = append(asm, c.generateDecrement(ctx)...) - } else { - asm = append(asm, c.generateIncrement(ctx)...) - } + // Generate increment + asm = append(asm, c.generateIncrement(ctx)...) // Jump back to loop start asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.LoopLabel)) @@ -84,16 +80,6 @@ func (c *NextCommand) generateIncrement(ctx *compiler.CompilerContext) []string return c.generateAdd() } -func (c *NextCommand) generateDecrement(ctx *compiler.CompilerContext) []string { - // Check for step = 1 literal optimization - if !c.info.StepOperand.IsVar && c.info.StepOperand.Value == 1 { - return c.generateDecrementByOne(ctx) - } - - // General case: var = var - step - return c.generateSubtract() -} - func (c *NextCommand) generateIncrementByOne(ctx *compiler.CompilerContext) []string { var asm []string @@ -112,25 +98,6 @@ func (c *NextCommand) generateIncrementByOne(ctx *compiler.CompilerContext) []st return asm } -func (c *NextCommand) generateDecrementByOne(ctx *compiler.CompilerContext) []string { - var asm []string - - if c.info.VarKind == compiler.KindByte { - asm = append(asm, fmt.Sprintf("\tdec %s", c.info.VarName)) - return asm - } - - // Word variable - handle borrow from high byte - label := ctx.GeneralStack.Push() - asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName)) - asm = append(asm, fmt.Sprintf("\tbne %s", label)) - asm = append(asm, fmt.Sprintf("\tdec %s+1", c.info.VarName)) - asm = append(asm, label) - asm = append(asm, fmt.Sprintf("\tdec %s", c.info.VarName)) - - return asm -} - func (c *NextCommand) generateAdd() []string { var asm []string @@ -175,48 +142,3 @@ func (c *NextCommand) generateAdd() []string { return asm } - -func (c *NextCommand) generateSubtract() []string { - var asm []string - - // var = var - step - stepOp := c.info.StepOperand - - asm = append(asm, "\tsec") - - // Load var low byte - asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName)) - - // Subtract step low byte - if stepOp.IsVar { - asm = append(asm, fmt.Sprintf("\tsbc %s", stepOp.VarName)) - } else { - asm = append(asm, fmt.Sprintf("\tsbc #$%02x", uint8(stepOp.Value&0xFF))) - } - - // Store low byte - asm = append(asm, fmt.Sprintf("\tsta %s", c.info.VarName)) - - // If variable is word, handle high byte - if c.info.VarKind == compiler.KindWord { - // Load var high byte - asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName)) - - // Subtract step high byte (with borrow) - if stepOp.IsVar { - if stepOp.VarKind == compiler.KindWord { - asm = append(asm, fmt.Sprintf("\tsbc %s+1", stepOp.VarName)) - } else { - asm = append(asm, "\tsbc #0") - } - } else { - hi := uint8((stepOp.Value >> 8) & 0xFF) - asm = append(asm, fmt.Sprintf("\tsbc #$%02x", hi)) - } - - // Store high byte - asm = append(asm, fmt.Sprintf("\tsta %s+1", c.info.VarName)) - } - - return asm -} diff --git a/internal/compiler/forstack.go b/internal/compiler/forstack.go index 1dba656..9e259d3 100644 --- a/internal/compiler/forstack.go +++ b/internal/compiler/forstack.go @@ -8,7 +8,6 @@ type ForLoopInfo struct { VarKind VarKind EndOperand *OperandInfo StepOperand *OperandInfo - IsDownto bool LoopLabel string SkipLabel string }