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:
parent
1185f8d265
commit
d717002b27
9 changed files with 518 additions and 269 deletions
|
|
@ -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
|
||||
|
|
|
|||
20
examples/for_byte_max_test/cm.sh
Executable file
20
examples/for_byte_max_test/cm.sh
Executable 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
|
||||
266
examples/for_byte_max_test/for_byte_max_test.c65
Normal file
266
examples/for_byte_max_test/for_byte_max_test.c65
Normal 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
|
||||
1
examples/for_byte_max_test/start_in_vice.sh
Normal file
1
examples/for_byte_max_test/start_in_vice.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
x64 -autostartprgmode 1 main.prg
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue