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:**
|
**Syntax:**
|
||||||
```
|
```
|
||||||
FOR <iterator> = <start_value> TO <end_value> [STEP <increment>]
|
FOR <iterator> = <start_value> TO <end_value>
|
||||||
```
|
```
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
|
|
@ -210,10 +210,6 @@ FOR i = 0 TO 10
|
||||||
screen = i
|
screen = i
|
||||||
NEXT
|
NEXT
|
||||||
|
|
||||||
FOR x = 0 TO 255 STEP 2
|
|
||||||
result = result + x
|
|
||||||
NEXT
|
|
||||||
|
|
||||||
FOR counter = start TO finish
|
FOR counter = start TO finish
|
||||||
process(counter)
|
process(counter)
|
||||||
NEXT
|
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
|
// ForCommand handles FOR loop statements
|
||||||
// Syntax: FOR <var> = <start> TO <end> [STEP <step>]
|
// Syntax: FOR <var> = <start> TO <end>
|
||||||
type ForCommand struct {
|
type ForCommand struct {
|
||||||
varName string
|
varName string
|
||||||
varKind compiler.VarKind
|
varKind compiler.VarKind
|
||||||
startOp *compiler.OperandInfo
|
startOp *compiler.OperandInfo
|
||||||
endOp *compiler.OperandInfo
|
endOp *compiler.OperandInfo
|
||||||
stepOp *compiler.OperandInfo
|
|
||||||
useLongJump bool
|
useLongJump bool
|
||||||
loopLabel string
|
loopLabel string
|
||||||
skipLabel string
|
skipLabel string
|
||||||
|
|
@ -37,15 +36,10 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FOR <var> = <start> TO/DOWNTO <end> [STEP <step>]
|
// FOR <var> = <start> TO <end>
|
||||||
// Minimum: 6 params (FOR var = start TO end)
|
// Exactly 6 params (FOR var = start TO end)
|
||||||
// Maximum: 8 params (FOR var = start TO end STEP step)
|
if len(params) != 6 {
|
||||||
if len(params) < 6 { // FOR keyword goes towards count
|
return fmt.Errorf("FOR: expected 5 parameters (FOR var = start TO end), got %d", len(params)-1)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check '=' separator
|
// 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
|
// Check pragma
|
||||||
ps := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
ps := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
||||||
longJumpPragma := ps.GetPragma("_P_USE_LONG_JUMP")
|
longJumpPragma := ps.GetPragma("_P_USE_LONG_JUMP")
|
||||||
|
|
@ -177,9 +135,9 @@ func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
|
||||||
VarName: c.varName,
|
VarName: c.varName,
|
||||||
VarKind: c.varKind,
|
VarKind: c.varKind,
|
||||||
EndOperand: c.endOp,
|
EndOperand: c.endOp,
|
||||||
StepOperand: c.stepOp,
|
|
||||||
LoopLabel: c.loopLabel,
|
LoopLabel: c.loopLabel,
|
||||||
SkipLabel: c.skipLabel,
|
SkipLabel: c.skipLabel,
|
||||||
|
UseLongJump: c.useLongJump,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -192,17 +150,14 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
assignAsm := c.generateAssignment()
|
assignAsm := c.generateAssignment()
|
||||||
asm = append(asm, assignAsm...)
|
asm = append(asm, assignAsm...)
|
||||||
|
|
||||||
// Emit loop label
|
// Initial guard: skip loop if start > end (using start <= end, jump on FALSE)
|
||||||
asm = append(asm, c.loopLabel)
|
startOp := &operandInfo{
|
||||||
|
varName: c.startOp.VarName,
|
||||||
// Generate comparison for TO loop: continue if var <= end (skip if var > end)
|
varKind: c.startOp.VarKind,
|
||||||
varOp := &operandInfo{
|
value: c.startOp.Value,
|
||||||
varName: c.varName,
|
isVar: c.startOp.IsVar,
|
||||||
varKind: c.varKind,
|
|
||||||
isVar: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert compiler.OperandInfo to commands.operandInfo for comparison
|
|
||||||
endOp := &operandInfo{
|
endOp := &operandInfo{
|
||||||
varName: c.endOp.VarName,
|
varName: c.endOp.VarName,
|
||||||
varKind: c.endOp.VarKind,
|
varKind: c.endOp.VarKind,
|
||||||
|
|
@ -212,7 +167,7 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
|
||||||
gen, err := newComparisonGenerator(
|
gen, err := newComparisonGenerator(
|
||||||
opLessEqual,
|
opLessEqual,
|
||||||
varOp,
|
startOp,
|
||||||
endOp,
|
endOp,
|
||||||
c.useLongJump,
|
c.useLongJump,
|
||||||
ctx.LoopEndStack,
|
ctx.LoopEndStack,
|
||||||
|
|
@ -222,12 +177,16 @@ func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
return nil, fmt.Errorf("FOR: %w", err)
|
return nil, fmt.Errorf("FOR: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmpAsm, err := gen.generate()
|
guardAsm, err := gen.generate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("FOR: %w", err)
|
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
|
return asm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,18 @@ func TestForBasicTO(t *testing.T) {
|
||||||
setupVars: func(st *compiler.SymbolTable) {
|
setupVars: func(st *compiler.SymbolTable) {
|
||||||
st.AddVar("i", "", compiler.KindByte, 0)
|
st.AddVar("i", "", compiler.KindByte, 0)
|
||||||
},
|
},
|
||||||
|
// Do-while style: initial guard (constant folded - 0<=10 is true, no code)
|
||||||
|
// then loop label
|
||||||
wantFor: []string{
|
wantFor: []string{
|
||||||
"\tlda #$00",
|
"\tlda #$00",
|
||||||
"\tsta i",
|
"\tsta i",
|
||||||
"_LOOPSTART1",
|
"_LOOPSTART1",
|
||||||
"\tlda #$0a",
|
|
||||||
"\tcmp i",
|
|
||||||
"\tbcc _LOOPEND1",
|
|
||||||
},
|
},
|
||||||
|
// End check before increment
|
||||||
wantNext: []string{
|
wantNext: []string{
|
||||||
|
"\tlda i",
|
||||||
|
"\tcmp #$0a",
|
||||||
|
"\tbeq _LOOPEND1",
|
||||||
"\tinc i",
|
"\tinc i",
|
||||||
"\tjmp _LOOPSTART1",
|
"\tjmp _LOOPSTART1",
|
||||||
"_LOOPEND1",
|
"_LOOPEND1",
|
||||||
|
|
@ -42,25 +45,26 @@ func TestForBasicTO(t *testing.T) {
|
||||||
setupVars: func(st *compiler.SymbolTable) {
|
setupVars: func(st *compiler.SymbolTable) {
|
||||||
st.AddVar("counter", "", compiler.KindWord, 0)
|
st.AddVar("counter", "", compiler.KindWord, 0)
|
||||||
},
|
},
|
||||||
|
// Do-while style: initial guard (constant folded - 0<=1000 is true, no code)
|
||||||
wantFor: []string{
|
wantFor: []string{
|
||||||
"\tlda #$00",
|
"\tlda #$00",
|
||||||
"\tsta counter",
|
"\tsta counter",
|
||||||
"\tsta counter+1",
|
"\tsta counter+1",
|
||||||
"_LOOPSTART1",
|
"_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{
|
wantNext: []string{
|
||||||
|
"\tlda counter",
|
||||||
|
"\tcmp #$e8",
|
||||||
|
"\tbne +",
|
||||||
|
"\tlda counter+1",
|
||||||
|
"\tcmp #$03",
|
||||||
|
"\tbeq _LOOPEND1",
|
||||||
|
"+",
|
||||||
"\tinc counter",
|
"\tinc counter",
|
||||||
"\tbne _L2",
|
"\tbne _L1",
|
||||||
"\tinc counter+1",
|
"\tinc counter+1",
|
||||||
"_L2",
|
"_L1",
|
||||||
"\tjmp _LOOPSTART1",
|
"\tjmp _LOOPSTART1",
|
||||||
"_LOOPEND1",
|
"_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) {
|
func TestForBreak(t *testing.T) {
|
||||||
pragma := preproc.NewPragma()
|
pragma := preproc.NewPragma()
|
||||||
ctx := compiler.NewCompilerContext(pragma)
|
ctx := compiler.NewCompilerContext(pragma)
|
||||||
|
|
@ -411,8 +326,8 @@ func TestForWrongParamCount(t *testing.T) {
|
||||||
"FOR i",
|
"FOR i",
|
||||||
"FOR i = 0",
|
"FOR i = 0",
|
||||||
"FOR i = 0 TO",
|
"FOR i = 0 TO",
|
||||||
"FOR i = 0 TO 10 STEP",
|
"FOR i = 0 TO 10 STEP 2", // STEP not supported
|
||||||
"FOR i = 0 TO 10 STEP 2 EXTRA",
|
"FOR i = 0 TO 10 EXTRA",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, text := range tests {
|
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) {
|
func TestForConstVariable(t *testing.T) {
|
||||||
pragma := preproc.NewPragma()
|
pragma := preproc.NewPragma()
|
||||||
ctx := compiler.NewCompilerContext(pragma)
|
ctx := compiler.NewCompilerContext(pragma)
|
||||||
|
|
@ -558,13 +452,22 @@ func TestForConstantEnd(t *testing.T) {
|
||||||
t.Fatalf("FOR Interpret() error = %v", err)
|
t.Fatalf("FOR Interpret() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
asm, err := forCmd.Generate(ctx)
|
if _, err := forCmd.Generate(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("FOR Generate() error = %v", err)
|
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
|
found := false
|
||||||
for _, inst := range asm {
|
for _, inst := range nextAsm {
|
||||||
if strings.Contains(inst, "#$64") { // 100 in hex
|
if strings.Contains(inst, "#$64") { // 100 in hex
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
|
|
@ -573,22 +476,21 @@ func TestForConstantEnd(t *testing.T) {
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Constant should be folded to immediate value")
|
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()
|
pragma := preproc.NewPragma()
|
||||||
ctx := compiler.NewCompilerContext(pragma)
|
ctx := compiler.NewCompilerContext(pragma)
|
||||||
ctx.SymbolTable.AddVar("counter", "", compiler.KindWord, 0)
|
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 0)
|
||||||
|
|
||||||
forCmd := &ForCommand{}
|
forCmd := &ForCommand{}
|
||||||
nextCmd := &NextCommand{}
|
nextCmd := &NextCommand{}
|
||||||
|
|
||||||
forLine := preproc.Line{
|
forLine := preproc.Line{
|
||||||
Text: "FOR counter = 0 TO 1000 STEP 256",
|
Text: "FOR b = 0 TO 255",
|
||||||
Kind: preproc.Source,
|
Kind: preproc.Source,
|
||||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||||
}
|
}
|
||||||
|
|
@ -601,11 +503,9 @@ func TestForWordSTEP(t *testing.T) {
|
||||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||||
t.Fatalf("FOR Interpret() error = %v", err)
|
t.Fatalf("FOR Interpret() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := forCmd.Generate(ctx); err != nil {
|
if _, err := forCmd.Generate(ctx); err != nil {
|
||||||
t.Fatalf("FOR Generate() error = %v", err)
|
t.Fatalf("FOR Generate() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -615,18 +515,81 @@ func TestForWordSTEP(t *testing.T) {
|
||||||
t.Fatalf("NEXT Generate() error = %v", err)
|
t.Fatalf("NEXT Generate() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should handle both low and high bytes
|
// NEXT should check if b == 255 before incrementing
|
||||||
foundLowAdd := false
|
// Look for: lda b / cmp #$ff / beq _LOOPEND
|
||||||
foundHighAdd := false
|
hasLda := false
|
||||||
for _, inst := range nextAsm {
|
hasCmp255 := false
|
||||||
if strings.Contains(inst, "adc #$00") {
|
hasBeq := false
|
||||||
foundLowAdd = true
|
for _, line := range nextAsm {
|
||||||
|
if strings.Contains(line, "lda b") {
|
||||||
|
hasLda = true
|
||||||
}
|
}
|
||||||
if strings.Contains(inst, "adc #$01") {
|
if strings.Contains(line, "cmp #$ff") {
|
||||||
foundHighAdd = true
|
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) {
|
func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
var asm []string
|
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
|
// Generate increment
|
||||||
asm = append(asm, c.generateIncrement(ctx)...)
|
asm = append(asm, c.generateIncrement(ctx)...)
|
||||||
|
|
||||||
|
|
@ -70,17 +74,106 @@ func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
|
||||||
return asm, nil
|
return asm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NextCommand) generateIncrement(ctx *compiler.CompilerContext) []string {
|
// generateEndCheck generates code to exit if var == end.
|
||||||
// Check for step = 1 literal optimization
|
func (c *NextCommand) generateEndCheck(ctx *compiler.CompilerContext) []string {
|
||||||
if !c.info.StepOperand.IsVar && c.info.StepOperand.Value == 1 {
|
var asm []string
|
||||||
return c.generateIncrementByOne(ctx)
|
endOp := c.info.EndOperand
|
||||||
|
|
||||||
|
if c.info.VarKind == compiler.KindByte {
|
||||||
|
return c.generateByteEndCheck(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// General case: var = var + step
|
// WORD comparison
|
||||||
return c.generateAdd()
|
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
|
var asm []string
|
||||||
|
|
||||||
if c.info.VarKind == compiler.KindByte {
|
if c.info.VarKind == compiler.KindByte {
|
||||||
|
|
@ -97,48 +190,3 @@ func (c *NextCommand) generateIncrementByOne(ctx *compiler.CompilerContext) []st
|
||||||
|
|
||||||
return asm
|
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
|
VarName string
|
||||||
VarKind VarKind
|
VarKind VarKind
|
||||||
EndOperand *OperandInfo
|
EndOperand *OperandInfo
|
||||||
StepOperand *OperandInfo
|
|
||||||
LoopLabel string
|
LoopLabel string
|
||||||
SkipLabel string
|
SkipLabel string
|
||||||
|
UseLongJump bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperandInfo describes an operand (variable or literal)
|
// OperandInfo describes an operand (variable or literal)
|
||||||
|
|
|
||||||
|
|
@ -242,10 +242,6 @@ FOR i = 0 TO 10
|
||||||
screen = i
|
screen = i
|
||||||
NEXT
|
NEXT
|
||||||
|
|
||||||
FOR x = 0 TO 255 STEP 2
|
|
||||||
result = result + x
|
|
||||||
NEXT
|
|
||||||
|
|
||||||
FOR counter = start TO finish
|
FOR counter = start TO finish
|
||||||
process(counter)
|
process(counter)
|
||||||
NEXT
|
NEXT
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue