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 }