c65gm/internal/commands/incr.go

146 lines
3.4 KiB
Go

package commands
import (
"fmt"
"strings"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
"c65gm/internal/utils"
)
// IncrCommand handles INCREMENT operations
// Syntax:
//
// INC <target> # old syntax
// INCREMENT <target> # old syntax
// <var>++ # new syntax (literal, no space - variables only)
//
// <target> can be a variable or absolute address (old syntax only)
type IncrCommand struct {
varName string
varKind compiler.VarKind
isAbsolute bool
absAddr uint16
}
func (c *IncrCommand) WillHandle(line preproc.Line) bool {
params, err := utils.ParseParams(line.Text)
if err != nil || len(params) == 0 {
return false
}
// Old syntax: INC/INCREMENT
keyword := strings.ToUpper(params[0])
if (keyword == "INC" || keyword == "INCREMENT") && len(params) == 2 {
return true
}
// New syntax: <var>++ (literal, no space)
if len(params) == 1 && strings.HasSuffix(params[0], "++") {
return true
}
return false
}
func (c *IncrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
// Clear state
c.varName = ""
c.isAbsolute = false
c.absAddr = 0
params, err := utils.ParseParams(line.Text)
if err != nil {
return err
}
scope := ctx.CurrentScope()
keyword := strings.ToUpper(params[0])
var targetParam string
var isNewSyntax bool
if keyword == "INC" || keyword == "INCREMENT" {
// Old syntax: INC/INCREMENT <target>
if len(params) != 2 {
return fmt.Errorf("INC: wrong number of parameters")
}
targetParam = params[1]
isNewSyntax = false
} else if strings.HasSuffix(params[0], "++") {
// New syntax: <var>++ (literal)
if len(params) != 1 {
return fmt.Errorf("INC: wrong number of parameters")
}
targetParam = strings.TrimSuffix(params[0], "++")
isNewSyntax = true
} else {
return fmt.Errorf("INC: unrecognized syntax")
}
// Try variable lookup
sym := ctx.SymbolTable.Lookup(targetParam, scope)
if sym != nil {
if sym.IsConst() {
return fmt.Errorf("INC: cannot increment constant %q", targetParam)
}
c.varName = sym.FullName()
c.varKind = sym.GetVarKind()
c.isAbsolute = false
return nil
}
// For new syntax (++), must be a variable
if isNewSyntax {
return fmt.Errorf("INC: unknown variable %q", targetParam)
}
// Old syntax allows absolute addresses
constLookup := func(name string) (int64, bool) {
s := ctx.SymbolTable.Lookup(name, scope)
if s != nil && s.IsConst() {
return int64(s.Value), true
}
return 0, false
}
val, evalErr := utils.EvaluateExpression(targetParam, constLookup)
if evalErr != nil {
return fmt.Errorf("INC: expected variable or absolute address, got %q: %w", targetParam, evalErr)
}
if val < 0 || val > 65535 {
return fmt.Errorf("INC: address %d out of range (0-65535)", val)
}
c.isAbsolute = true
c.absAddr = uint16(val)
return nil
}
func (c *IncrCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
if c.isAbsolute {
// Absolute address
asm = append(asm, fmt.Sprintf("\tinc $%04x", c.absAddr))
return asm, nil
}
// Variable
if c.varKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tinc %s", c.varName))
return asm, nil
}
// Word variable - handle carry to high byte
label := ctx.GeneralStack.Push()
asm = append(asm, fmt.Sprintf("\tinc %s", c.varName))
asm = append(asm, fmt.Sprintf("\tbne %s", label))
asm = append(asm, fmt.Sprintf("\tinc %s+1", c.varName))
asm = append(asm, label)
return asm, nil
}