First non-working version of FOR/NEXT loops. (tests failing)
This commit is contained in:
parent
4b5f8b30b7
commit
37296cf627
6 changed files with 1299 additions and 1 deletions
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
// BreakCommand handles BREAK statements
|
||||
// Syntax: BREAK
|
||||
// Exits current WHILE loop
|
||||
// Exits current loop
|
||||
type BreakCommand struct {
|
||||
skipLabel string
|
||||
}
|
||||
|
|
|
|||
280
internal/commands/for.go
Normal file
280
internal/commands/for.go
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// ForCommand handles FOR loop statements
|
||||
// Syntax: FOR <var> = <start> TO <end> [STEP <step>]
|
||||
//
|
||||
// FOR <var> = <start> DOWNTO <end> [STEP <step>]
|
||||
type ForCommand struct {
|
||||
varName string
|
||||
varKind compiler.VarKind
|
||||
startOp *compiler.OperandInfo
|
||||
endOp *compiler.OperandInfo
|
||||
stepOp *compiler.OperandInfo
|
||||
isDownto bool
|
||||
useLongJump bool
|
||||
loopLabel string
|
||||
skipLabel string
|
||||
}
|
||||
|
||||
func (c *ForCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.ToUpper(params[0]) == "FOR"
|
||||
}
|
||||
|
||||
func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
// Check '=' separator
|
||||
if params[2] != "=" {
|
||||
return fmt.Errorf("FOR: expected '=' at position 3, got %q", params[2])
|
||||
}
|
||||
|
||||
scope := ctx.CurrentScope()
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
sym := ctx.SymbolTable.Lookup(name, scope)
|
||||
if sym != nil && sym.IsConst() {
|
||||
return int64(sym.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Parse variable
|
||||
varName := params[1]
|
||||
varSym := ctx.SymbolTable.Lookup(varName, scope)
|
||||
if varSym == nil {
|
||||
return fmt.Errorf("FOR: unknown variable %q", varName)
|
||||
}
|
||||
if varSym.IsConst() {
|
||||
return fmt.Errorf("FOR: cannot use constant %q as loop variable", varName)
|
||||
}
|
||||
c.varName = varSym.FullName()
|
||||
c.varKind = varSym.GetVarKind()
|
||||
|
||||
// Parse start value
|
||||
var parseErr error
|
||||
startVarName, startVarKind, startValue, startIsVar, parseErr := compiler.ParseOperandParam(
|
||||
params[3], ctx.SymbolTable, scope, constLookup)
|
||||
if parseErr != nil {
|
||||
return fmt.Errorf("FOR: start value: %w", parseErr)
|
||||
}
|
||||
c.startOp = &compiler.OperandInfo{
|
||||
VarName: startVarName,
|
||||
VarKind: startVarKind,
|
||||
Value: startValue,
|
||||
IsVar: startIsVar,
|
||||
}
|
||||
|
||||
// Parse direction (TO or DOWNTO)
|
||||
direction := strings.ToUpper(params[4])
|
||||
if direction != "TO" && direction != "DOWNTO" {
|
||||
return fmt.Errorf("FOR: expected 'TO' or 'DOWNTO' at position 5, got %q", params[4])
|
||||
}
|
||||
c.isDownto = (direction == "DOWNTO")
|
||||
|
||||
// Parse end value
|
||||
endVarName, endVarKind, endValue, endIsVar, parseErr := compiler.ParseOperandParam(
|
||||
params[5], ctx.SymbolTable, scope, constLookup)
|
||||
if parseErr != nil {
|
||||
return fmt.Errorf("FOR: end value: %w", parseErr)
|
||||
}
|
||||
c.endOp = &compiler.OperandInfo{
|
||||
VarName: endVarName,
|
||||
VarKind: endVarKind,
|
||||
Value: endValue,
|
||||
IsVar: endIsVar,
|
||||
}
|
||||
|
||||
// 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")
|
||||
c.useLongJump = longJumpPragma != "" && longJumpPragma != "0"
|
||||
|
||||
// Create labels
|
||||
c.loopLabel = ctx.LoopStartStack.Push()
|
||||
c.skipLabel = ctx.LoopEndStack.Push()
|
||||
|
||||
// Push FOR info to ForStack
|
||||
ctx.ForStack.Push(&compiler.ForLoopInfo{
|
||||
VarName: c.varName,
|
||||
VarKind: c.varKind,
|
||||
EndOperand: c.endOp,
|
||||
StepOperand: c.stepOp,
|
||||
IsDownto: c.isDownto,
|
||||
LoopLabel: c.loopLabel,
|
||||
SkipLabel: c.skipLabel,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ForCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
// Initial assignment: var = start
|
||||
assignAsm := c.generateAssignment()
|
||||
asm = append(asm, assignAsm...)
|
||||
|
||||
// Emit loop label
|
||||
asm = append(asm, c.loopLabel)
|
||||
|
||||
// Generate comparison
|
||||
// TO: continue if var <= end (skip if var > end)
|
||||
// DOWNTO: continue if var >= end (skip if var < end)
|
||||
var op comparisonOp
|
||||
if c.isDownto {
|
||||
op = opLess // skip if var < end
|
||||
} else {
|
||||
op = opGreater // skip if var > end
|
||||
}
|
||||
|
||||
varOp := &operandInfo{
|
||||
varName: c.varName,
|
||||
varKind: c.varKind,
|
||||
isVar: true,
|
||||
}
|
||||
|
||||
// Convert compiler.OperandInfo to commands.operandInfo for comparison
|
||||
endOp := &operandInfo{
|
||||
varName: c.endOp.VarName,
|
||||
varKind: c.endOp.VarKind,
|
||||
value: c.endOp.Value,
|
||||
isVar: c.endOp.IsVar,
|
||||
}
|
||||
|
||||
gen, err := newComparisonGenerator(
|
||||
op,
|
||||
varOp,
|
||||
endOp,
|
||||
c.useLongJump,
|
||||
ctx.LoopEndStack,
|
||||
ctx.GeneralStack,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FOR: %w", err)
|
||||
}
|
||||
|
||||
cmpAsm, err := gen.generate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FOR: %w", err)
|
||||
}
|
||||
|
||||
asm = append(asm, cmpAsm...)
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
func (c *ForCommand) generateAssignment() []string {
|
||||
var asm []string
|
||||
|
||||
// Variable assignment from startOp
|
||||
if c.startOp.IsVar {
|
||||
// Destination: byte
|
||||
if c.varKind == compiler.KindByte {
|
||||
// byte → byte or word → byte (take low byte)
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
|
||||
return asm
|
||||
}
|
||||
|
||||
// Destination: word
|
||||
// byte → word (zero-extend)
|
||||
if c.startOp.VarKind == compiler.KindByte {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
|
||||
asm = append(asm, "\tlda #0")
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.varName))
|
||||
return asm
|
||||
}
|
||||
|
||||
// word → word (copy both bytes)
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.startOp.VarName))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.varName))
|
||||
return asm
|
||||
}
|
||||
|
||||
// Literal assignment
|
||||
lo := uint8(c.startOp.Value & 0xFF)
|
||||
hi := uint8((c.startOp.Value >> 8) & 0xFF)
|
||||
|
||||
// Destination: byte
|
||||
if c.varKind == compiler.KindByte {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
|
||||
return asm
|
||||
}
|
||||
|
||||
// Destination: word
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
|
||||
|
||||
// Optimization: don't reload if lo == hi
|
||||
if lo != hi {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
}
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.varName))
|
||||
|
||||
return asm
|
||||
}
|
||||
727
internal/commands/for_test.go
Normal file
727
internal/commands/for_test.go
Normal file
|
|
@ -0,0 +1,727 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestForBasicTO(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forLine string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantFor []string
|
||||
wantNext []string
|
||||
}{
|
||||
{
|
||||
name: "byte var TO byte literal",
|
||||
forLine: "FOR i = 0 TO 10",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("i", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantFor: []string{
|
||||
"\tlda #$00",
|
||||
"\tsta i",
|
||||
"_LOOPSTART1",
|
||||
"\tlda i",
|
||||
"\tcmp #$0a",
|
||||
"\tbeq _L1",
|
||||
"\tbcc _L1",
|
||||
"\tjmp _LOOPEND1",
|
||||
"_L1",
|
||||
},
|
||||
wantNext: []string{
|
||||
"\tinc i",
|
||||
"\tjmp _LOOPSTART1",
|
||||
"_LOOPEND1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var TO word literal",
|
||||
forLine: "FOR counter = 0 TO 1000",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("counter", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantFor: []string{
|
||||
"\tlda #$00",
|
||||
"\tsta counter",
|
||||
"\tsta counter+1",
|
||||
"_LOOPSTART1",
|
||||
"\tlda counter+1",
|
||||
"\tcmp #$03",
|
||||
"\tbcc _L1",
|
||||
"\tbne _L2",
|
||||
"\tlda counter",
|
||||
"\tcmp #$e8",
|
||||
"\tbeq _L1",
|
||||
"\tbcc _L1",
|
||||
"_L2",
|
||||
"\tjmp _LOOPEND1",
|
||||
"_L1",
|
||||
},
|
||||
wantNext: []string{
|
||||
"\tinc counter",
|
||||
"\tbne _L3",
|
||||
"\tinc counter+1",
|
||||
"_L3",
|
||||
"\tjmp _LOOPSTART1",
|
||||
"_LOOPEND1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
forLine := preproc.Line{
|
||||
Text: tt.forLine,
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
nextLine := preproc.Line{
|
||||
Text: "NEXT",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||
t.Fatalf("FOR Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
forAsm, err := forCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("FOR Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
nextAsm, err := nextCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("NEXT Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsm(forAsm, tt.wantFor) {
|
||||
t.Errorf("FOR Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(forAsm, "\n"),
|
||||
strings.Join(tt.wantFor, "\n"))
|
||||
}
|
||||
if !equalAsm(nextAsm, tt.wantNext) {
|
||||
t.Errorf("NEXT Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(nextAsm, "\n"),
|
||||
strings.Join(tt.wantNext, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForBasicDOWNTO(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forLine string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantFor []string
|
||||
wantNext []string
|
||||
}{
|
||||
{
|
||||
name: "byte var DOWNTO byte literal",
|
||||
forLine: "FOR i = 10 DOWNTO 0",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("i", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantFor: []string{
|
||||
"\tlda #$0a",
|
||||
"\tsta i",
|
||||
"_LOOPSTART1",
|
||||
"\tlda i",
|
||||
"\tbne _L1",
|
||||
"\tjmp _LOOPEND1",
|
||||
"_L1",
|
||||
},
|
||||
wantNext: []string{
|
||||
"\tdec i",
|
||||
"\tjmp _LOOPSTART1",
|
||||
"_LOOPEND1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var DOWNTO word literal",
|
||||
forLine: "FOR counter = 1000 DOWNTO 0",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("counter", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantFor: []string{
|
||||
"\tlda #$e8",
|
||||
"\tsta counter",
|
||||
"\tlda #$03",
|
||||
"\tsta counter+1",
|
||||
"_LOOPSTART1",
|
||||
"\tlda counter",
|
||||
"\tbne _L1",
|
||||
"\tlda counter+1",
|
||||
"\tbne _L1",
|
||||
"\tjmp _LOOPEND1",
|
||||
"_L1",
|
||||
},
|
||||
wantNext: []string{
|
||||
"\tlda counter",
|
||||
"\tbne _L2",
|
||||
"\tdec counter+1",
|
||||
"_L2",
|
||||
"\tdec counter",
|
||||
"\tjmp _LOOPSTART1",
|
||||
"_LOOPEND1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
forLine := preproc.Line{
|
||||
Text: tt.forLine,
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
nextLine := preproc.Line{
|
||||
Text: "NEXT",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||
t.Fatalf("FOR Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
forAsm, err := forCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("FOR Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
nextAsm, err := nextCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("NEXT Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsm(forAsm, tt.wantFor) {
|
||||
t.Errorf("FOR Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(forAsm, "\n"),
|
||||
strings.Join(tt.wantFor, "\n"))
|
||||
}
|
||||
if !equalAsm(nextAsm, tt.wantNext) {
|
||||
t.Errorf("NEXT Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(nextAsm, "\n"),
|
||||
strings.Join(tt.wantNext, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForWithSTEP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forLine string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
checkNextAsm func([]string) bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "byte var TO with STEP 2",
|
||||
forLine: "FOR i = 0 TO 10 STEP 2",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("i", "", compiler.KindByte, 0)
|
||||
},
|
||||
checkNextAsm: func(asm []string) bool {
|
||||
// Should contain adc #$02
|
||||
for _, line := range asm {
|
||||
if strings.Contains(line, "adc #$02") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
description: "STEP 2 should use adc #$02",
|
||||
},
|
||||
{
|
||||
name: "byte var DOWNTO with STEP 3",
|
||||
forLine: "FOR i = 10 DOWNTO 0 STEP 3",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("i", "", compiler.KindByte, 0)
|
||||
},
|
||||
checkNextAsm: func(asm []string) bool {
|
||||
// Should contain sbc #$03
|
||||
for _, line := range asm {
|
||||
if strings.Contains(line, "sbc #$03") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
description: "STEP 3 should use sbc #$03",
|
||||
},
|
||||
{
|
||||
name: "byte var TO with variable STEP",
|
||||
forLine: "FOR i = 0 TO 10 STEP stepval",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("i", "", compiler.KindByte, 0)
|
||||
st.AddVar("stepval", "", compiler.KindByte, 0)
|
||||
},
|
||||
checkNextAsm: func(asm []string) bool {
|
||||
// Should contain adc stepval
|
||||
for _, line := range asm {
|
||||
if strings.Contains(line, "adc stepval") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
description: "variable STEP should use adc variable",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
forLine := preproc.Line{
|
||||
Text: tt.forLine,
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
nextLine := preproc.Line{
|
||||
Text: "NEXT",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||
t.Fatalf("FOR Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
if _, err := forCmd.Generate(ctx); err != nil {
|
||||
t.Fatalf("FOR Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
nextAsm, err := nextCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("NEXT Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !tt.checkNextAsm(nextAsm) {
|
||||
t.Errorf("%s\ngot:\n%s", tt.description, strings.Join(nextAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForBreak(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
breakCmd := &BreakCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
forLine := preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
||||
breakLine := preproc.Line{Text: "BREAK", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
||||
nextLine := preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}
|
||||
|
||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||
t.Fatalf("FOR Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
forAsm, _ := forCmd.Generate(ctx)
|
||||
_ = forAsm // body would go here
|
||||
|
||||
if err := breakCmd.Interpret(breakLine, ctx); err != nil {
|
||||
t.Fatalf("BREAK Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
breakAsm, err := breakCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("BREAK Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
if len(breakAsm) != 1 || !strings.Contains(breakAsm[0], "jmp _LOOPEND") {
|
||||
t.Errorf("BREAK should jump to loop end label, got: %v", breakAsm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForNested(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
ctx.SymbolTable.AddVar("j", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
for1 := &ForCommand{}
|
||||
for2 := &ForCommand{}
|
||||
next1 := &NextCommand{}
|
||||
next2 := &NextCommand{}
|
||||
|
||||
if err := for1.Interpret(preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("FOR 1 error = %v", err)
|
||||
}
|
||||
asm1, err := for1.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("FOR 1 Generate error = %v", err)
|
||||
}
|
||||
|
||||
if err := for2.Interpret(preproc.Line{Text: "FOR j = 0 TO 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("FOR 2 error = %v", err)
|
||||
}
|
||||
asm2, err := for2.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("FOR 2 Generate error = %v", err)
|
||||
}
|
||||
|
||||
if err := next2.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("NEXT 2 error = %v", err)
|
||||
}
|
||||
if err := next1.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("NEXT 1 error = %v", err)
|
||||
}
|
||||
|
||||
if asm1[0] == asm2[0] {
|
||||
t.Error("Nested loops should have different labels")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForMixedWithWhile(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
whileCmd := &WhileCommand{}
|
||||
wendCmd := &WendCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
// FOR i = 0 TO 10
|
||||
// WHILE x < 5
|
||||
// WEND
|
||||
// NEXT
|
||||
|
||||
if err := forCmd.Interpret(preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("FOR error = %v", err)
|
||||
}
|
||||
_, _ = forCmd.Generate(ctx)
|
||||
|
||||
if err := whileCmd.Interpret(preproc.Line{Text: "WHILE x < 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("WHILE error = %v", err)
|
||||
}
|
||||
_, _ = whileCmd.Generate(ctx)
|
||||
|
||||
if err := wendCmd.Interpret(preproc.Line{Text: "WEND", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("WEND error = %v", err)
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("NEXT error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForIllegalNesting(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
whileCmd := &WhileCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
// FOR i = 0 TO 10
|
||||
// WHILE x < 5
|
||||
// NEXT <- ERROR: crossing loop boundaries
|
||||
// WEND
|
||||
|
||||
if err := forCmd.Interpret(preproc.Line{Text: "FOR i = 0 TO 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("FOR error = %v", err)
|
||||
}
|
||||
_, _ = forCmd.Generate(ctx)
|
||||
|
||||
if err := whileCmd.Interpret(preproc.Line{Text: "WHILE x < 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("WHILE error = %v", err)
|
||||
}
|
||||
_, _ = whileCmd.Generate(ctx)
|
||||
|
||||
// NEXT should fail because of stack mismatch
|
||||
err := nextCmd.Interpret(preproc.Line{Text: "NEXT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("NEXT should fail when crossing loop boundaries")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "mismatch") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextWithoutFor(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
cmd := &NextCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "NEXT",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("NEXT outside FOR loop should fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not inside FOR") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForWrongParamCount(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
|
||||
tests := []string{
|
||||
"FOR i",
|
||||
"FOR i = 0",
|
||||
"FOR i = 0 TO",
|
||||
"FOR i = 0 TO 10 STEP",
|
||||
"FOR i = 0 TO 10 STEP 2 EXTRA",
|
||||
}
|
||||
|
||||
for _, text := range tests {
|
||||
cmd := &ForCommand{}
|
||||
line := preproc.Line{
|
||||
Text: text,
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Errorf("Should fail with wrong param count: %s", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForInvalidDirection(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
|
||||
cmd := &ForCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "FOR i = 0 UPTO 10",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with invalid direction keyword")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "TO") && !strings.Contains(err.Error(), "DOWNTO") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForZeroStep(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
|
||||
cmd := &ForCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "FOR i = 0 TO 10 STEP 0",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with STEP 0")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "STEP cannot be zero") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForConstVariable(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddConst("LIMIT", "", compiler.KindByte, 10)
|
||||
|
||||
cmd := &ForCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "FOR LIMIT = 0 TO 10",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail when using constant as loop variable")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "constant") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForUnknownVariable(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
cmd := &ForCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "FOR unknown = 0 TO 10",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with unknown variable")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForConstantEnd(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("i", "", compiler.KindByte, 0)
|
||||
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100)
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
forLine := preproc.Line{
|
||||
Text: "FOR i = 0 TO MAX",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
nextLine := preproc.Line{
|
||||
Text: "NEXT",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||
t.Fatalf("FOR Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := forCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("FOR Generate() error = %v", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, inst := range asm {
|
||||
if strings.Contains(inst, "#$64") { // 100 in hex
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Constant should be folded to immediate value")
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForWordSTEP(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("counter", "", compiler.KindWord, 0)
|
||||
|
||||
forCmd := &ForCommand{}
|
||||
nextCmd := &NextCommand{}
|
||||
|
||||
forLine := preproc.Line{
|
||||
Text: "FOR counter = 0 TO 1000 STEP 256",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
nextLine := preproc.Line{
|
||||
Text: "NEXT",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := forCmd.Interpret(forLine, ctx); err != nil {
|
||||
t.Fatalf("FOR Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
if _, err := forCmd.Generate(ctx); err != nil {
|
||||
t.Fatalf("FOR Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := nextCmd.Interpret(nextLine, ctx); err != nil {
|
||||
t.Fatalf("NEXT Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
nextAsm, err := nextCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("NEXT Generate() error = %v", err)
|
||||
}
|
||||
|
||||
// Should handle both low and high bytes
|
||||
foundLowAdd := false
|
||||
foundHighAdd := false
|
||||
for _, inst := range nextAsm {
|
||||
if strings.Contains(inst, "adc #$00") {
|
||||
foundLowAdd = true
|
||||
}
|
||||
if strings.Contains(inst, "adc #$01") {
|
||||
foundHighAdd = true
|
||||
}
|
||||
}
|
||||
if !foundLowAdd || !foundHighAdd {
|
||||
t.Errorf("Word STEP should handle both bytes\ngot:\n%s", strings.Join(nextAsm, "\n"))
|
||||
}
|
||||
}
|
||||
222
internal/commands/next.go
Normal file
222
internal/commands/next.go
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// NextCommand handles NEXT statements
|
||||
// Syntax: NEXT
|
||||
// Increments/decrements loop variable and jumps back to loop start
|
||||
type NextCommand struct {
|
||||
info *compiler.ForLoopInfo
|
||||
}
|
||||
|
||||
func (c *NextCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.ToUpper(params[0]) == "NEXT"
|
||||
}
|
||||
|
||||
func (c *NextCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(params) != 1 {
|
||||
return fmt.Errorf("NEXT: expected 1 parameter, got %d", len(params))
|
||||
}
|
||||
|
||||
// Pop FOR info
|
||||
info, err := ctx.ForStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("NEXT: not inside FOR loop")
|
||||
}
|
||||
c.info = info
|
||||
|
||||
// Pop and validate labels
|
||||
loopLabel, err := ctx.LoopStartStack.Pop()
|
||||
if err != nil || loopLabel != info.LoopLabel {
|
||||
return fmt.Errorf("NEXT: loop stack mismatch")
|
||||
}
|
||||
|
||||
skipLabel, err := ctx.LoopEndStack.Pop()
|
||||
if err != nil || skipLabel != info.SkipLabel {
|
||||
return fmt.Errorf("NEXT: loop stack mismatch")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NextCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
// Generate increment/decrement
|
||||
if c.info.IsDownto {
|
||||
asm = append(asm, c.generateDecrement(ctx)...)
|
||||
} else {
|
||||
asm = append(asm, c.generateIncrement(ctx)...)
|
||||
}
|
||||
|
||||
// Jump back to loop start
|
||||
asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.LoopLabel))
|
||||
|
||||
// Emit skip label
|
||||
asm = append(asm, c.info.SkipLabel)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// General case: var = var + step
|
||||
return c.generateAdd()
|
||||
}
|
||||
|
||||
func (c *NextCommand) generateDecrement(ctx *compiler.CompilerContext) []string {
|
||||
// Check for step = 1 literal optimization
|
||||
if !c.info.StepOperand.IsVar && c.info.StepOperand.Value == 1 {
|
||||
return c.generateDecrementByOne(ctx)
|
||||
}
|
||||
|
||||
// General case: var = var - step
|
||||
return c.generateSubtract()
|
||||
}
|
||||
|
||||
func (c *NextCommand) generateIncrementByOne(ctx *compiler.CompilerContext) []string {
|
||||
var asm []string
|
||||
|
||||
if c.info.VarKind == compiler.KindByte {
|
||||
asm = append(asm, fmt.Sprintf("\tinc %s", c.info.VarName))
|
||||
return asm
|
||||
}
|
||||
|
||||
// Word variable - handle carry to high byte
|
||||
label := ctx.GeneralStack.Push()
|
||||
asm = append(asm, fmt.Sprintf("\tinc %s", c.info.VarName))
|
||||
asm = append(asm, fmt.Sprintf("\tbne %s", label))
|
||||
asm = append(asm, fmt.Sprintf("\tinc %s+1", c.info.VarName))
|
||||
asm = append(asm, label)
|
||||
|
||||
return asm
|
||||
}
|
||||
|
||||
func (c *NextCommand) generateDecrementByOne(ctx *compiler.CompilerContext) []string {
|
||||
var asm []string
|
||||
|
||||
if c.info.VarKind == compiler.KindByte {
|
||||
asm = append(asm, fmt.Sprintf("\tdec %s", c.info.VarName))
|
||||
return asm
|
||||
}
|
||||
|
||||
// Word variable - handle borrow from high byte
|
||||
label := ctx.GeneralStack.Push()
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
|
||||
asm = append(asm, fmt.Sprintf("\tbne %s", label))
|
||||
asm = append(asm, fmt.Sprintf("\tdec %s+1", c.info.VarName))
|
||||
asm = append(asm, label)
|
||||
asm = append(asm, fmt.Sprintf("\tdec %s", c.info.VarName))
|
||||
|
||||
return asm
|
||||
}
|
||||
|
||||
func (c *NextCommand) generateAdd() []string {
|
||||
var asm []string
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (c *NextCommand) generateSubtract() []string {
|
||||
var asm []string
|
||||
|
||||
// var = var - step
|
||||
stepOp := c.info.StepOperand
|
||||
|
||||
asm = append(asm, "\tsec")
|
||||
|
||||
// Load var low byte
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
|
||||
|
||||
// Subtract step low byte
|
||||
if stepOp.IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tsbc %s", stepOp.VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tsbc #$%02x", uint8(stepOp.Value&0xFF)))
|
||||
}
|
||||
|
||||
// Store low byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.info.VarName))
|
||||
|
||||
// If variable is word, handle high byte
|
||||
if c.info.VarKind == compiler.KindWord {
|
||||
// Load var high byte
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
|
||||
|
||||
// Subtract step high byte (with borrow)
|
||||
if stepOp.IsVar {
|
||||
if stepOp.VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tsbc %s+1", stepOp.VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tsbc #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((stepOp.Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tsbc #$%02x", hi))
|
||||
}
|
||||
|
||||
// Store high byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.info.VarName))
|
||||
}
|
||||
|
||||
return asm
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ type CompilerContext struct {
|
|||
LoopEndStack *LabelStack // WHILE...WEND
|
||||
IfStack *LabelStack // IF...ENDIF
|
||||
GeneralStack *LabelStack // General purpose (GOSUB, etc)
|
||||
ForStack *ForStack // For loop stack
|
||||
|
||||
// Pragma access for per-line pragma lookup
|
||||
Pragma *preproc.Pragma
|
||||
|
|
@ -38,6 +39,7 @@ func NewCompilerContext(pragma *preproc.Pragma) *CompilerContext {
|
|||
LoopEndStack: NewLabelStack("_LOOPEND"),
|
||||
IfStack: NewLabelStack("_I"),
|
||||
GeneralStack: generalStack,
|
||||
ForStack: NewForStack(),
|
||||
Pragma: pragma,
|
||||
}
|
||||
|
||||
|
|
|
|||
67
internal/compiler/forstack.go
Normal file
67
internal/compiler/forstack.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package compiler
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ForLoopInfo stores information about a FOR loop
|
||||
type ForLoopInfo struct {
|
||||
VarName string
|
||||
VarKind VarKind
|
||||
EndOperand *OperandInfo
|
||||
StepOperand *OperandInfo
|
||||
IsDownto bool
|
||||
LoopLabel string
|
||||
SkipLabel string
|
||||
}
|
||||
|
||||
// OperandInfo describes an operand (variable or literal)
|
||||
type OperandInfo struct {
|
||||
VarName string
|
||||
VarKind VarKind
|
||||
Value uint16
|
||||
IsVar bool
|
||||
}
|
||||
|
||||
// ForStack manages the stack of FOR loop contexts
|
||||
type ForStack struct {
|
||||
stack []*ForLoopInfo
|
||||
}
|
||||
|
||||
// NewForStack creates a new ForStack
|
||||
func NewForStack() *ForStack {
|
||||
return &ForStack{
|
||||
stack: make([]*ForLoopInfo, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Push adds a new FOR loop context to the stack
|
||||
func (fs *ForStack) Push(info *ForLoopInfo) {
|
||||
fs.stack = append(fs.stack, info)
|
||||
}
|
||||
|
||||
// Peek returns the top FOR loop context without removing it
|
||||
func (fs *ForStack) Peek() (*ForLoopInfo, error) {
|
||||
if len(fs.stack) == 0 {
|
||||
return nil, fmt.Errorf("stack underflow: FOR stack is empty")
|
||||
}
|
||||
return fs.stack[len(fs.stack)-1], nil
|
||||
}
|
||||
|
||||
// Pop removes and returns the top FOR loop context
|
||||
func (fs *ForStack) Pop() (*ForLoopInfo, error) {
|
||||
if len(fs.stack) == 0 {
|
||||
return nil, fmt.Errorf("stack underflow: FOR stack is empty")
|
||||
}
|
||||
info := fs.stack[len(fs.stack)-1]
|
||||
fs.stack = fs.stack[:len(fs.stack)-1]
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the stack is empty
|
||||
func (fs *ForStack) IsEmpty() bool {
|
||||
return len(fs.stack) == 0
|
||||
}
|
||||
|
||||
// Size returns the number of items on the stack
|
||||
func (fs *ForStack) Size() int {
|
||||
return len(fs.stack)
|
||||
}
|
||||
Loading…
Reference in a new issue