From aedc605445a45cbbe5cc7e4d0c6f582412a83c62 Mon Sep 17 00:00:00 2001 From: Mattias Hansson Date: Wed, 5 Nov 2025 19:14:26 +0100 Subject: [PATCH] Added goto --- internal/commands/goto.go | 106 ++++++++++++++++++++++++++++++++++++++ main.go | 1 + 2 files changed, 107 insertions(+) create mode 100644 internal/commands/goto.go diff --git a/internal/commands/goto.go b/internal/commands/goto.go new file mode 100644 index 0000000..78d277f --- /dev/null +++ b/internal/commands/goto.go @@ -0,0 +1,106 @@ +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 +} diff --git a/main.go b/main.go index 2ba9f15..0280c6f 100644 --- a/main.go +++ b/main.go @@ -95,6 +95,7 @@ func registerCommands(comp *compiler.Compiler) { comp.Registry().Register(&commands.FendCommand{}) comp.Registry().Register(&commands.IncrCommand{}) comp.Registry().Register(&commands.DecrCommand{}) + comp.Registry().Register(&commands.GotoCommand{}) }