Updated FOR loop to do check at NEXT instead of at FOR handling edge cases 255 and 65536 better. Removed STEP

This commit is contained in:
Mattias Hansson 2026-01-11 14:45:15 +01:00
parent 1185f8d265
commit d717002b27
9 changed files with 518 additions and 269 deletions

View file

@ -201,7 +201,7 @@ Loop with automatic counter increment.
**Syntax:**
```
FOR <iterator> = <start_value> TO <end_value> [STEP <increment>]
FOR <iterator> = <start_value> TO <end_value>
```
**Examples:**
@ -210,10 +210,6 @@ FOR i = 0 TO 10
screen = i
NEXT
FOR x = 0 TO 255 STEP 2
result = result + x
NEXT
FOR counter = start TO finish
process(counter)
NEXT

View file

@ -0,0 +1,20 @@
#!/bin/sh
# Define filename as variable
PROGNAME="for_byte_max_test"
# Only set C65LIBPATH if not already defined
if [ -z "$C65LIBPATH" ]; then
export C65LIBPATH=$(readlink -f "../../lib")
fi
# Compile
c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s
if [ $? -ne 0 ]; then
echo "Compilation terminated"
exit 1
fi
echo assemble.
acme ${PROGNAME}.s
if [ -f ${PROGNAME}.prg ]; then
rm ${PROGNAME}.prg
fi
# main.bin ${PROGNAME}.prg
mv main.bin main.prg

View file

@ -0,0 +1,266 @@
//-----------------------------------------------------------
// FOR Loop BYTE Max Value Test
// Verifies that FOR b = 0 TO 255 correctly iterates 256 times
// without causing an infinite loop.
//-----------------------------------------------------------
#INCLUDE <c64start.c65>
#INCLUDE <c64defs.c65>
#INCLUDE <cbmiolib.c65>
#PRAGMA _P_USE_CBM_STRINGS 1
GOTO start
WORD count
BYTE iter
//-----------------------------------------------------------
// Test 1: FOR BYTE = 0 TO 255
// Should iterate exactly 256 times (0, 1, 2, ..., 255)
//-----------------------------------------------------------
FUNC test_byte_to_255
LET count = 0
FOR iter = 0 TO 255
count++
NEXT
lib_cbmio_print("byte 0-255: ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0100)")
FEND
//-----------------------------------------------------------
// Test 2: FOR BYTE = 250 TO 255
// Should iterate exactly 6 times (250, 251, 252, 253, 254, 255)
//-----------------------------------------------------------
FUNC test_byte_250_to_255
LET count = 0
FOR iter = 250 TO 255
count++
NEXT
lib_cbmio_print("byte 250-255: ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0006)")
FEND
//-----------------------------------------------------------
// Test 3: Verify last iteration value
// iter should be 255 after the loop completes
//-----------------------------------------------------------
FUNC test_final_value
LET iter = 0
FOR iter = 0 TO 255
// just iterate
NEXT
lib_cbmio_print("final iter: ")
lib_cbmio_hexoutb(iter)
lib_cbmio_printlf(" (exp ff)")
FEND
//-----------------------------------------------------------
// Test 4: FOR WORD = 65530 TO 65535
// Should iterate exactly 6 times (65530, 65531, ..., 65535)
// Tests WORD overflow protection without taking forever
//-----------------------------------------------------------
WORD witer
FUNC test_word_to_65535
LET count = 0
FOR witer = 65530 TO 65535
count++
NEXT
lib_cbmio_print("word 65530-65535: ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0006)")
FEND
//-----------------------------------------------------------
// Test 5: Verify WORD final value
// witer should be 65535 ($FFFF) after the loop completes
//-----------------------------------------------------------
FUNC test_word_final_value
LET witer = 0
FOR witer = 65530 TO 65535
// just iterate
NEXT
lib_cbmio_print("final witer: ")
lib_cbmio_hexoutw(witer)
lib_cbmio_printlf(" (exp ffff)")
FEND
//-----------------------------------------------------------
// Test 6: FOR BYTE with variable end value
// Tests that variable end values work correctly
//-----------------------------------------------------------
BYTE endval
FUNC test_byte_var_end
LET count = 0
LET endval = 10
FOR iter = 0 TO endval
count++
NEXT
lib_cbmio_print("byte 0-var(10): ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 000b)")
FEND
//-----------------------------------------------------------
// Test 7: FOR BYTE with variable end at max (255)
//-----------------------------------------------------------
FUNC test_byte_var_max
LET count = 0
LET endval = 255
FOR iter = 250 TO endval
count++
NEXT
lib_cbmio_print("byte 250-var(255): ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0006)")
FEND
//-----------------------------------------------------------
// Test 8: FOR WORD with variable end value
//-----------------------------------------------------------
WORD wendval
FUNC test_word_var_end
LET count = 0
LET wendval = 100
FOR witer = 0 TO wendval
count++
NEXT
lib_cbmio_print("word 0-var(100): ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0065)")
FEND
//-----------------------------------------------------------
// Test 9: FOR WORD with variable end at max (65535)
//-----------------------------------------------------------
FUNC test_word_var_max
LET count = 0
LET wendval = 65535
FOR witer = 65530 TO wendval
count++
NEXT
lib_cbmio_print("word 65530-var(max): ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0006)")
FEND
//-----------------------------------------------------------
// Test 10: FOR with start == end (single iteration)
//-----------------------------------------------------------
FUNC test_same_start_end
LET count = 0
FOR iter = 5 TO 5
count++
NEXT
lib_cbmio_print("byte 5-5: ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0001)")
FEND
//-----------------------------------------------------------
// Test 11: FOR with start var == loop var (skip assignment)
// When doing FOR i = i TO 10, assignment should be skipped
//-----------------------------------------------------------
FUNC test_self_start
LET count = 0
LET iter = 3
FOR iter = iter TO 5
count++
NEXT
lib_cbmio_print("byte i=i to 5: ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0003)")
FEND
//-----------------------------------------------------------
// Test 12: Long jump FOR loop
// Uses _P_USE_LONG_JUMP pragma with a 200-byte buffer in
// the loop body. Without long jump, branches would fail
// as they're limited to ±127 bytes.
//-----------------------------------------------------------
FUNC test_long_jump
LET count = 0
#PRAGMA _P_USE_LONG_JUMP 1
FOR iter = 0 TO 5
#PRAGMA _P_USE_LONG_JUMP 0
count++
GOTO _test_lj_after_buffer
ASM
!fill 200, 0
ENDASM
LABEL _test_lj_after_buffer
NEXT
lib_cbmio_print("long jump 0-5: ")
lib_cbmio_hexoutw(count)
lib_cbmio_printlf(" (exp 0006)")
FEND
//-----------------------------------------------------------
// Main program
//-----------------------------------------------------------
FUNC main
lib_cbmio_cls()
lib_cbmio_printlf("for max value test")
lib_cbmio_lf()
test_byte_to_255()
test_byte_250_to_255()
test_final_value()
test_word_to_65535()
test_word_final_value()
lib_cbmio_lf()
lib_cbmio_printlf("variable end tests:")
test_byte_var_end()
test_byte_var_max()
test_word_var_end()
test_word_var_max()
lib_cbmio_lf()
lib_cbmio_printlf("edge case tests:")
test_same_start_end()
test_self_start()
lib_cbmio_lf()
lib_cbmio_printlf("long jump test:")
test_long_jump()
lib_cbmio_lf()
lib_cbmio_printlf("tests complete!")
FEND
LABEL start
main()
SUBEND

View file

@ -0,0 +1 @@
x64 -autostartprgmode 1 main.prg

View file

@ -11,13 +11,12 @@ import (
)
// ForCommand handles FOR loop statements
// Syntax: FOR <var> = <start> TO <end> [STEP <step>]
// Syntax: FOR <var> = <start> TO <end>
type ForCommand struct {
varName string
varKind compiler.VarKind
startOp *compiler.OperandInfo
endOp *compiler.OperandInfo
stepOp *compiler.OperandInfo
useLongJump bool
loopLabel string
skipLabel string
@ -37,15 +36,10 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
return err
}
// FOR <var> = <start> TO/DOWNTO <end> [STEP <step>]
// Minimum: 6 params (FOR var = start TO end)
// Maximum: 8 params (FOR var = start TO end STEP step)
if len(params) < 6 { // FOR keyword goes towards count
return fmt.Errorf("FOR: expected at least 5 parameters, got %d", len(params))
}
if len(params) != 6 && len(params) != 8 {
return fmt.Errorf("FOR: expected 5 or 7 parameters, got %d", len(params))
// FOR <var> = <start> TO <end>
// Exactly 6 params (FOR var = start TO end)
if len(params) != 6 {
return fmt.Errorf("FOR: expected 5 parameters (FOR var = start TO end), got %d", len(params)-1)
}
// Check '=' separator
@ -127,42 +121,6 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
}
}
// Parse optional STEP
if len(params) == 8 {
if strings.ToUpper(params[6]) != "STEP" {
return fmt.Errorf("FOR: expected 'STEP' at position 7, got %q", params[6])
}
stepVarName, stepVarKind, stepValue, stepIsVar, parseErr := compiler.ParseOperandParam(
params[7], ctx.SymbolTable, scope, constLookup)
if parseErr != nil {
return fmt.Errorf("FOR: step value: %w", parseErr)
}
// Check for zero or negative step if literal
if !stepIsVar {
if stepValue == 0 {
return fmt.Errorf("FOR: STEP cannot be zero")
}
// Since BYTE and WORD are unsigned, values > 32767 are treated as large positive
// We don't allow negative literals since they'd be interpreted as large unsigned
// This is a reasonable restriction for step values
}
c.stepOp = &compiler.OperandInfo{
VarName: stepVarName,
VarKind: stepVarKind,
Value: stepValue,
IsVar: stepIsVar,
}
} else {
// Default STEP 1
c.stepOp = &compiler.OperandInfo{
Value: 1,
IsVar: false,
}
}
// Check pragma
ps := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
longJumpPragma := ps.GetPragma("_P_USE_LONG_JUMP")
@ -177,9 +135,9 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
VarName: c.varName,
VarKind: c.varKind,
EndOperand: c.endOp,
StepOperand: c.stepOp,
LoopLabel: c.loopLabel,
SkipLabel: c.skipLabel,
UseLongJump: c.useLongJump,
})
return nil
@ -192,17 +150,14 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
assignAsm := c.generateAssignment()
asm = append(asm, assignAsm...)
// Emit loop label
asm = append(asm, c.loopLabel)
// Generate comparison for TO loop: continue if var <= end (skip if var > end)
varOp := &operandInfo{
varName: c.varName,
varKind: c.varKind,
isVar: true,
// Initial guard: skip loop if start > end (using start <= end, jump on FALSE)
startOp := &operandInfo{
varName: c.startOp.VarName,
varKind: c.startOp.VarKind,
value: c.startOp.Value,
isVar: c.startOp.IsVar,
}
// Convert compiler.OperandInfo to commands.operandInfo for comparison
endOp := &operandInfo{
varName: c.endOp.VarName,
varKind: c.endOp.VarKind,
@ -212,7 +167,7 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
gen, err := newComparisonGenerator(
opLessEqual,
varOp,
startOp,
endOp,
c.useLongJump,
ctx.LoopEndStack,
@ -222,12 +177,16 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
return nil, fmt.Errorf("FOR: %w", err)
}
cmpAsm, err := gen.generate()
guardAsm, err := gen.generate()
if err != nil {
return nil, fmt.Errorf("FOR: %w", err)
}
asm = append(asm, cmpAsm...)
asm = append(asm, guardAsm...)
// Emit loop label (body starts here)
asm = append(asm, c.loopLabel)
return asm, nil
}

View file

@ -22,15 +22,18 @@ func TestForBasicTO(t *testing.T) {
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("i", "", compiler.KindByte, 0)
},
// Do-while style: initial guard (constant folded - 0<=10 is true, no code)
// then loop label
wantFor: []string{
"\tlda #$00",
"\tsta i",
"_LOOPSTART1",
"\tlda #$0a",
"\tcmp i",
"\tbcc _LOOPEND1",
},
// End check before increment
wantNext: []string{
"\tlda i",
"\tcmp #$0a",
"\tbeq _LOOPEND1",
"\tinc i",
"\tjmp _LOOPSTART1",
"_LOOPEND1",
@ -42,25 +45,26 @@ func TestForBasicTO(t *testing.T) {
setupVars: func(st *compiler.SymbolTable) {
st.AddVar("counter", "", compiler.KindWord, 0)
},
// Do-while style: initial guard (constant folded - 0<=1000 is true, no code)
wantFor: []string{
"\tlda #$00",
"\tsta counter",
"\tsta counter+1",
"_LOOPSTART1",
"\tlda #$03",
"\tcmp counter+1",
"\tbcc _LOOPEND1",
"\tbne _L1",
"\tlda #$e8",
"\tcmp counter",
"\tbcc _LOOPEND1",
"_L1",
},
// End check before increment (WORD comparison)
wantNext: []string{
"\tlda counter",
"\tcmp #$e8",
"\tbne +",
"\tlda counter+1",
"\tcmp #$03",
"\tbeq _LOOPEND1",
"+",
"\tinc counter",
"\tbne _L2",
"\tbne _L1",
"\tinc counter+1",
"_L2",
"_L1",
"\tjmp _LOOPSTART1",
"_LOOPEND1",
},
@ -119,95 +123,6 @@ func TestForBasicTO(t *testing.T) {
}
}
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 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)
@ -411,8 +326,8 @@ func TestForWrongParamCount(t *testing.T) {
"FOR i",
"FOR i = 0",
"FOR i = 0 TO",
"FOR i = 0 TO 10 STEP",
"FOR i = 0 TO 10 STEP 2 EXTRA",
"FOR i = 0 TO 10 STEP 2", // STEP not supported
"FOR i = 0 TO 10 EXTRA",
}
for _, text := range tests {
@ -472,27 +387,6 @@ func TestForDOWNTORejected(t *testing.T) {
}
}
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)
@ -558,13 +452,22 @@ func TestForConstantEnd(t *testing.T) {
t.Fatalf("FOR Interpret() error = %v", err)
}
asm, err := forCmd.Generate(ctx)
if err != nil {
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)
}
// With do-while pattern, the constant end value appears in NEXT's end check
nextAsm, err := nextCmd.Generate(ctx)
if err != nil {
t.Fatalf("NEXT Generate() error = %v", err)
}
found := false
for _, inst := range asm {
for _, inst := range nextAsm {
if strings.Contains(inst, "#$64") { // 100 in hex
found = true
break
@ -573,22 +476,21 @@ func TestForConstantEnd(t *testing.T) {
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) {
func TestForByteMaxEndValue(t *testing.T) {
// FOR b = 0 TO 255 with BYTE iterator uses do-while pattern:
// Before incrementing, check if b == end and exit if so.
// This naturally handles the max value case (255) without overflow.
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
ctx.SymbolTable.AddVar("counter", "", compiler.KindWord, 0)
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 0)
forCmd := &ForCommand{}
nextCmd := &NextCommand{}
forLine := preproc.Line{
Text: "FOR counter = 0 TO 1000 STEP 256",
Text: "FOR b = 0 TO 255",
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
@ -601,11 +503,9 @@ func TestForWordSTEP(t *testing.T) {
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)
}
@ -615,18 +515,81 @@ func TestForWordSTEP(t *testing.T) {
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
// NEXT should check if b == 255 before incrementing
// Look for: lda b / cmp #$ff / beq _LOOPEND
hasLda := false
hasCmp255 := false
hasBeq := false
for _, line := range nextAsm {
if strings.Contains(line, "lda b") {
hasLda = true
}
if strings.Contains(inst, "adc #$01") {
foundHighAdd = true
if strings.Contains(line, "cmp #$ff") {
hasCmp255 = true
}
if strings.Contains(line, "beq _LOOPEND") {
hasBeq = true
}
}
if !foundLowAdd || !foundHighAdd {
t.Errorf("Word STEP should handle both bytes\ngot:\n%s", strings.Join(nextAsm, "\n"))
if !hasLda || !hasCmp255 || !hasBeq {
t.Errorf("NEXT should generate overflow check for BYTE TO 255\ngot:\n%s",
strings.Join(nextAsm, "\n"))
}
}
func TestForWordMaxEndValue(t *testing.T) {
// FOR w = 0 TO 65535 with WORD iterator uses do-while pattern.
// Naturally handles the max value case (65535) without overflow.
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
ctx.SymbolTable.AddVar("w", "", compiler.KindWord, 0)
forCmd := &ForCommand{}
nextCmd := &NextCommand{}
forLine := preproc.Line{
Text: "FOR w = 0 TO 65535",
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)
}
// NEXT should check if w == 65535 ($FFFF) before incrementing
// Look for comparisons with $ff for both bytes
hasCmpFF := false
hasBeq := false
for _, line := range nextAsm {
if strings.Contains(line, "cmp #$ff") {
hasCmpFF = true
}
if strings.Contains(line, "beq _LOOPEND") {
hasBeq = true
}
}
if !hasCmpFF || !hasBeq {
t.Errorf("NEXT should generate overflow check for WORD TO 65535\ngot:\n%s",
strings.Join(nextAsm, "\n"))
}
}

View file

@ -58,6 +58,10 @@ func (c *NextCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
// Do-while style: check if var == end BEFORE incrementing.
// Exit loop if var == end, otherwise continue to increment.
asm = append(asm, c.generateEndCheck(ctx)...)
// Generate increment
asm = append(asm, c.generateIncrement(ctx)...)
@ -70,17 +74,106 @@ func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
return asm, nil
}
func (c *NextCommand) generateIncrement(ctx *compiler.CompilerContext) []string {
// Check for step = 1 literal optimization
if !c.info.StepOperand.IsVar && c.info.StepOperand.Value == 1 {
return c.generateIncrementByOne(ctx)
// generateEndCheck generates code to exit if var == end.
func (c *NextCommand) generateEndCheck(ctx *compiler.CompilerContext) []string {
var asm []string
endOp := c.info.EndOperand
if c.info.VarKind == compiler.KindByte {
return c.generateByteEndCheck(ctx)
}
// General case: var = var + step
return c.generateAdd()
// WORD comparison
if !endOp.IsVar {
// Literal end value
lo := uint8(endOp.Value & 0xFF)
hi := uint8((endOp.Value >> 8) & 0xFF)
if !c.info.UseLongJump {
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tbne +"))
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp #$%02x", hi))
asm = append(asm, fmt.Sprintf("\tbeq %s", c.info.SkipLabel))
asm = append(asm, "+")
} else {
continueLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp #$%02x", hi))
asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.SkipLabel))
asm = append(asm, continueLabel)
}
} else {
// Variable end value
if !c.info.UseLongJump {
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp %s", endOp.VarName))
asm = append(asm, fmt.Sprintf("\tbne +"))
if endOp.VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp %s+1", endOp.VarName))
} else {
// BYTE end var compared to WORD loop var
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
asm = append(asm, "\tcmp #$00")
}
asm = append(asm, fmt.Sprintf("\tbeq %s", c.info.SkipLabel))
asm = append(asm, "+")
} else {
continueLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp %s", endOp.VarName))
asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
if endOp.VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
asm = append(asm, fmt.Sprintf("\tcmp %s+1", endOp.VarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
asm = append(asm, "\tcmp #$00")
}
asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.SkipLabel))
asm = append(asm, continueLabel)
}
}
return asm
}
func (c *NextCommand) generateIncrementByOne(ctx *compiler.CompilerContext) []string {
// generateByteEndCheck generates end check for BYTE loop variable.
func (c *NextCommand) generateByteEndCheck(ctx *compiler.CompilerContext) []string {
var asm []string
endOp := c.info.EndOperand
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
if !endOp.IsVar {
asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(endOp.Value)))
} else {
asm = append(asm, fmt.Sprintf("\tcmp %s", endOp.VarName))
}
if !c.info.UseLongJump {
asm = append(asm, fmt.Sprintf("\tbeq %s", c.info.SkipLabel))
} else {
continueLabel := ctx.GeneralStack.Push()
ctx.GeneralStack.Pop()
asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.SkipLabel))
asm = append(asm, continueLabel)
}
return asm
}
func (c *NextCommand) generateIncrement(ctx *compiler.CompilerContext) []string {
var asm []string
if c.info.VarKind == compiler.KindByte {
@ -97,48 +190,3 @@ func (c *NextCommand) generateIncrementByOne(ctx *compiler.CompilerContext) []st
return asm
}
func (c *NextCommand) generateAdd() []string {
var asm []string
// var = var + step
stepOp := c.info.StepOperand
asm = append(asm, "\tclc")
// Load var low byte
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
// Add step low byte
if stepOp.IsVar {
asm = append(asm, fmt.Sprintf("\tadc %s", stepOp.VarName))
} else {
asm = append(asm, fmt.Sprintf("\tadc #$%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))
// Add step high byte (with carry)
if stepOp.IsVar {
if stepOp.VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tadc %s+1", stepOp.VarName))
} else {
asm = append(asm, "\tadc #0")
}
} else {
hi := uint8((stepOp.Value >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tadc #$%02x", hi))
}
// Store high byte
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.info.VarName))
}
return asm
}

View file

@ -7,9 +7,9 @@ type ForLoopInfo struct {
VarName string
VarKind VarKind
EndOperand *OperandInfo
StepOperand *OperandInfo
LoopLabel string
SkipLabel string
UseLongJump bool
}
// OperandInfo describes an operand (variable or literal)

View file

@ -242,10 +242,6 @@ FOR i = 0 TO 10
screen = i
NEXT
FOR x = 0 TO 255 STEP 2
result = result + x
NEXT
FOR counter = start TO finish
process(counter)
NEXT