239 lines
6.3 KiB
Go
239 lines
6.3 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
"c65gm/internal/utils"
|
|
)
|
|
|
|
// AddCommand handles ADD operations
|
|
// Syntax:
|
|
//
|
|
// ADD <param1> TO <param2> GIVING <dest> # old syntax with TO/GIVING
|
|
// ADD <param1> + <param2> -> <dest> # old syntax with +/->
|
|
// <dest> = <param1> + <param2> # new syntax
|
|
type AddCommand 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 *AddCommand) WillHandle(line preproc.Line) bool {
|
|
params, err := utils.ParseParams(line.Text)
|
|
if err != nil || len(params) == 0 {
|
|
return false
|
|
}
|
|
|
|
// Old syntax: ADD ... (must have exactly 6 params)
|
|
if strings.ToUpper(params[0]) == "ADD" && len(params) == 6 {
|
|
return true
|
|
}
|
|
|
|
// New syntax: <dest> = <param1> + <param2>
|
|
if len(params) == 5 && params[1] == "=" && params[3] == "+" {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *AddCommand) 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
|
|
if strings.ToUpper(params[0]) == "ADD" {
|
|
// Old syntax: ADD <param1> TO/+ <param2> GIVING/-> <dest>
|
|
if paramCount != 6 {
|
|
return fmt.Errorf("ADD: wrong number of parameters (%d), expected 6", paramCount)
|
|
}
|
|
|
|
separator1 := strings.ToUpper(params[2])
|
|
if separator1 != "TO" && separator1 != "+" {
|
|
return fmt.Errorf("ADD: parameter #3 must be 'TO' or '+', got %q", params[2])
|
|
}
|
|
|
|
separator2 := strings.ToUpper(params[4])
|
|
if separator2 != "GIVING" && separator2 != "->" {
|
|
return fmt.Errorf("ADD: 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("ADD: unknown variable %q", destName)
|
|
}
|
|
if destSym.IsConst() {
|
|
return fmt.Errorf("ADD: cannot assign to constant %q", destName)
|
|
}
|
|
c.destVarName = destSym.FullName()
|
|
c.destVarKind = destSym.GetVarKind()
|
|
|
|
// Parse param1
|
|
var err error
|
|
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
|
params[1], ctx.SymbolTable, scope, constLookup)
|
|
if err != nil {
|
|
return fmt.Errorf("ADD: param1: %w", err)
|
|
}
|
|
|
|
// Parse param2
|
|
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
|
params[3], ctx.SymbolTable, scope, constLookup)
|
|
if err != nil {
|
|
return fmt.Errorf("ADD: param2: %w", err)
|
|
}
|
|
|
|
} else {
|
|
// New syntax: <dest> = <param1> + <param2>
|
|
if paramCount != 5 {
|
|
return fmt.Errorf("ADD: wrong number of parameters (%d), expected 5", paramCount)
|
|
}
|
|
|
|
if params[1] != "=" {
|
|
return fmt.Errorf("ADD: expected '=' at position 2, got %q", params[1])
|
|
}
|
|
|
|
if params[3] != "+" {
|
|
return fmt.Errorf("ADD: 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("ADD: unknown variable %q", destName)
|
|
}
|
|
if destSym.IsConst() {
|
|
return fmt.Errorf("ADD: cannot assign to constant %q", destName)
|
|
}
|
|
c.destVarName = destSym.FullName()
|
|
c.destVarKind = destSym.GetVarKind()
|
|
|
|
// Parse param1
|
|
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("ADD: param1: %w", err)
|
|
}
|
|
|
|
// Parse param2
|
|
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
|
params[4], ctx.SymbolTable, scope, constLookup)
|
|
if err != nil {
|
|
return fmt.Errorf("ADD: param2: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *AddCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|
var asm []string
|
|
|
|
// If both params are literals, fold at compile time
|
|
if !c.param1IsVar && !c.param2IsVar {
|
|
sum := uint32(c.param1Value) + uint32(c.param2Value)
|
|
lo := uint8(sum & 0xFF)
|
|
hi := uint8((sum >> 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 add code
|
|
asm = append(asm, "\tclc")
|
|
|
|
// Load 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)))
|
|
}
|
|
|
|
// Add param2
|
|
if c.param2IsVar {
|
|
asm = append(asm, fmt.Sprintf("\tadc %s", c.param2VarName))
|
|
} else {
|
|
asm = append(asm, fmt.Sprintf("\tadc #$%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 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))
|
|
}
|
|
|
|
// Add high byte of param2
|
|
if c.param2IsVar {
|
|
if c.param2VarKind == compiler.KindWord {
|
|
asm = append(asm, fmt.Sprintf("\tadc %s+1", c.param2VarName))
|
|
} else {
|
|
asm = append(asm, "\tadc #0")
|
|
}
|
|
} else {
|
|
hi := uint8((c.param2Value >> 8) & 0xFF)
|
|
asm = append(asm, fmt.Sprintf("\tadc #$%02x", hi))
|
|
}
|
|
|
|
// Store high byte
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
|
}
|
|
|
|
return asm, nil
|
|
}
|