192 lines
5.6 KiB
Go
192 lines
5.6 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
"c65gm/internal/utils"
|
|
)
|
|
|
|
// NextCommand handles NEXT statements
|
|
// Syntax: NEXT
|
|
// Increments 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
|
|
|
|
// 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)...)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 {
|
|
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
|
|
}
|