c65gm/internal/commands/subtr.go

259 lines
7.2 KiB
Go

package commands
import (
"fmt"
"strings"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
"c65gm/internal/utils"
)
// SubtractCommand handles SUBTRACT operations
// Syntax:
//
// SUBTRACT <param2> FROM <param1> GIVING <dest> # old syntax: param1 - param2
// SUBT <param1> - <param2> -> <dest> # old syntax: param1 - param2
// <dest> = <param1> - <param2> # new syntax: param1 - param2
//
// Note: FROM syntax swaps parameter order (SUBTRACT a FROM b means b-a)
type SubtractCommand struct {
param1VarName string
param1VarKind compiler.VarKind
param1Value uint16
param1IsVar bool
param2VarName string
param2VarKind compiler.VarKind
param2Value uint16
param2IsVar bool
destVarName string
destVarKind compiler.VarKind
}
func (c *SubtractCommand) WillHandle(line preproc.Line) bool {
params, err := utils.ParseParams(line.Text)
if err != nil || len(params) == 0 {
return false
}
// Old syntax: SUBTRACT/SUBT ... (must have exactly 6 params)
kw := strings.ToUpper(params[0])
if (kw == "SUBTRACT" || kw == "SUBT") && len(params) == 6 {
return true
}
// New syntax: <dest> = <param1> - <param2>
if len(params) == 5 && params[1] == "=" && params[3] == "-" {
return true
}
return false
}
func (c *SubtractCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
// Clear state
c.param1VarName = ""
c.param1IsVar = false
c.param1Value = 0
c.param2VarName = ""
c.param2IsVar = false
c.param2Value = 0
c.destVarName = ""
params, err := utils.ParseParams(line.Text)
if err != nil {
return err
}
paramCount := len(params)
scope := ctx.CurrentScope()
// Create constant lookup function
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
}
// Determine syntax and parse accordingly
kw := strings.ToUpper(params[0])
if kw == "SUBTRACT" || kw == "SUBT" {
// Old syntax: SUBTRACT/SUBT <p1> FROM/- <p2> GIVING/-> <dest>
if paramCount != 6 {
return fmt.Errorf("SUBTRACT: wrong number of parameters (%d), expected 6", paramCount)
}
separator1 := strings.ToUpper(params[2])
if separator1 != "FROM" && separator1 != "-" {
return fmt.Errorf("SUBTRACT: parameter #3 must be 'FROM' or '-', got %q", params[2])
}
separator2 := strings.ToUpper(params[4])
if separator2 != "GIVING" && separator2 != "->" {
return fmt.Errorf("SUBTRACT: parameter #5 must be 'GIVING' or '->', got %q", params[4])
}
// Parse destination
destName := params[5]
destSym := ctx.SymbolTable.Lookup(destName, scope)
if destSym == nil {
return fmt.Errorf("SUBTRACT: unknown variable %q", destName)
}
if destSym.IsConst() {
return fmt.Errorf("SUBTRACT: cannot assign to constant %q", destName)
}
c.destVarName = destSym.FullName()
c.destVarKind = destSym.GetVarKind()
// Parse parameters based on separator
// FROM syntax: SUBTRACT a FROM b means b - a (swap needed)
// Minus syntax: SUBT a - b means a - b (no swap)
var minuendParam, subtrahendParam string
if separator1 == "FROM" {
// Swap: position 4 becomes minuend, position 2 becomes subtrahend
minuendParam = params[3]
subtrahendParam = params[1]
} else {
// No swap: position 2 is minuend, position 4 is subtrahend
minuendParam = params[1]
subtrahendParam = params[3]
}
// Parse minuend (param1)
var err error
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
minuendParam, ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("SUBTRACT: minuend: %w", err)
}
// Parse subtrahend (param2)
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
subtrahendParam, ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("SUBTRACT: subtrahend: %w", err)
}
} else {
// New syntax: <dest> = <param1> - <param2>
if paramCount != 5 {
return fmt.Errorf("SUBTRACT: wrong number of parameters (%d), expected 5", paramCount)
}
if params[1] != "=" {
return fmt.Errorf("SUBTRACT: expected '=' at position 2, got %q", params[1])
}
if params[3] != "-" {
return fmt.Errorf("SUBTRACT: expected '-' at position 4, got %q", params[3])
}
// Parse destination
destName := params[0]
destSym := ctx.SymbolTable.Lookup(destName, scope)
if destSym == nil {
return fmt.Errorf("SUBTRACT: unknown variable %q", destName)
}
if destSym.IsConst() {
return fmt.Errorf("SUBTRACT: cannot assign to constant %q", destName)
}
c.destVarName = destSym.FullName()
c.destVarKind = destSym.GetVarKind()
// Parse param1 (minuend)
var err error
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
params[2], ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("SUBTRACT: param1: %w", err)
}
// Parse param2 (subtrahend)
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
params[4], ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("SUBTRACT: param2: %w", err)
}
}
return nil
}
func (c *SubtractCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
var asm []string
// If both params are literals, fold at compile time
if !c.param1IsVar && !c.param2IsVar {
// param1 - param2 (minuend - subtrahend)
result := uint16(int32(c.param1Value) - int32(c.param2Value))
lo := uint8(result & 0xFF)
hi := uint8((result >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
if c.destVarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
return asm, nil
}
// At least one param is a variable - generate subtract code
// SEC sets carry for borrow
asm = append(asm, "\tsec")
// Load minuend (param1)
if c.param1IsVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
}
// Subtract subtrahend (param2)
if c.param2IsVar {
asm = append(asm, fmt.Sprintf("\tsbc %s", c.param2VarName))
} else {
asm = append(asm, fmt.Sprintf("\tsbc #$%02x", uint8(c.param2Value&0xFF)))
}
// Store low byte
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
// If destination is word, handle high byte
if c.destVarKind == compiler.KindWord {
// Load high byte of minuend (param1)
if c.param1IsVar {
if c.param1VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
} else {
asm = append(asm, "\tlda #0")
}
} else {
hi := uint8((c.param1Value >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
}
// Subtract high byte of subtrahend (param2)
if c.param2IsVar {
if c.param2VarKind == compiler.KindWord {
asm = append(asm, fmt.Sprintf("\tsbc %s+1", c.param2VarName))
} else {
asm = append(asm, "\tsbc #0")
}
} else {
hi := uint8((c.param2Value >> 8) & 0xFF)
asm = append(asm, fmt.Sprintf("\tsbc #$%02x", hi))
}
// Store high byte
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
}
return asm, nil
}