package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // GotoCommand handles GOTO statements // Syntax: GOTO // Where target is: // - Label name (naked identifier) // - Word variable (indirect jump via jmp (var)) // - Expression/address (numeric constant or expression) // // Note: Byte variable jumps not supported - use inline asm for zero-page jumps type GotoCommand struct { targetName string targetKind compiler.VarKind address uint16 isVar bool isLabel bool // true if neither var nor expression } func (c *GotoCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } return strings.ToUpper(params[0]) == "GOTO" } func (c *GotoCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error { // Clear state c.targetName = "" c.address = 0 c.isVar = false c.isLabel = false params, err := utils.ParseParams(line.Text) if err != nil { return err } if len(params) != 2 { return fmt.Errorf("GOTO: expected 2 parameters, got %d", len(params)) } targetParam := params[1] 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 } // Try ParseOperandParam - handles variables, constants, expressions varName, varKind, value, isVar, err := compiler.ParseOperandParam( targetParam, ctx.SymbolTable, scope, constLookup) if err != nil { // Not a variable or expression -> treat as label c.targetName = targetParam c.isLabel = true return nil } // It's either a variable or expression/constant if isVar { // It's a variable if varKind == compiler.KindByte { return fmt.Errorf("GOTO: cannot jump to byte variable %q (use inline assembler for zero-page jumps)", targetParam) } c.targetName = varName c.targetKind = varKind c.isVar = true return nil } // It's an expression/constant c.address = value c.isVar = false c.isLabel = false return nil } func (c *GotoCommand) Generate(_ *compiler.CompilerContext) ([]string, error) { if c.isVar { // Indirect jump through word variable return []string{fmt.Sprintf("\tjmp (%s)", c.targetName)}, nil } if c.isLabel { // Jump to label return []string{fmt.Sprintf("\tjmp %s", c.targetName)}, nil } // Jump to computed address return []string{fmt.Sprintf("\tjmp $%04x", c.address)}, nil }