c65gm/internal/commands/pokew.go

320 lines
9.1 KiB
Go

package commands
import (
"fmt"
"strings"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
"c65gm/internal/utils"
)
// PokeWCommand handles POKEW statements (writes a word/2 bytes to memory)
// Syntax: POKEW <address>[offset] WITH|, <value>
// Where address is:
// - Byte variable (zero-page indexed addressing)
// - Word variable (self-modifying code or ZP pointer with offset)
// - Expression (direct addressing)
//
// Value must be a word variable or word literal (0-65535)
type PokeWCommand struct {
addrVarName string
addrVarKind compiler.VarKind
addrValue uint16
isAddrVar bool
isZPPointer bool
offsetVarName string
offsetValue uint8
hasOffset bool
isOffsetVar bool
valueVarName string
valueLiteral uint16
isValueVar bool
pragmaSet preproc.PragmaSet
}
func (c *PokeWCommand) WillHandle(line preproc.Line) bool {
params, err := utils.ParseParams(line.Text)
if err != nil || len(params) != 4 {
return false
}
return strings.ToUpper(params[0]) == "POKEW"
}
func (c *PokeWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
// Clear state
c.addrVarName = ""
c.addrValue = 0
c.isAddrVar = false
c.isZPPointer = false
c.offsetVarName = ""
c.offsetValue = 0
c.hasOffset = false
c.isOffsetVar = false
c.valueVarName = ""
c.valueLiteral = 0
c.isValueVar = false
// Store pragma set for Generate phase
c.pragmaSet = ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
params, err := utils.ParseParams(line.Text)
if err != nil {
return err
}
if len(params) != 4 {
return fmt.Errorf("POKEW: expected 4 parameters, got %d", len(params))
}
scope := ctx.CurrentScope()
// Validate separator (param 3)
sep := strings.ToUpper(params[2])
if sep != "WITH" && sep != "," {
return fmt.Errorf("POKEW: parameter #3 must be 'WITH' or ',', got %q", params[2])
}
// Parse address (param 2) - may have [offset]
addrParam := params[1]
baseAddr, offsetParam := parsePOKEWOffset(addrParam)
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 to parse base address
varName, varKind, value, isVar, err := compiler.ParseOperandParam(
baseAddr, ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("POKEW: invalid address: %w", err)
}
if isVar {
// It's a variable
c.addrVarName = varName
c.addrVarKind = varKind
c.isAddrVar = true
// Check if it's a ZP pointer
addrSym := ctx.SymbolTable.Lookup(baseAddr, scope)
if addrSym != nil {
c.isZPPointer = addrSym.IsZeroPagePointer()
}
} else {
// It's an expression/constant
c.addrValue = value
c.isAddrVar = false
}
// Parse offset if present
if offsetParam != "" {
// Offset only allowed with ZP pointers
if !c.isZPPointer {
return fmt.Errorf("POKEW: offset [%s] only allowed with zero-page word pointer", offsetParam)
}
c.hasOffset = true
// Try to parse offset as variable or expression
offsetVarName, _, offsetValue, isOffsetVar, err := compiler.ParseOperandParam(
offsetParam, ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("POKEW: invalid offset: %w", err)
}
if isOffsetVar {
// Check it's a byte variable
offsetSym := ctx.SymbolTable.Lookup(offsetParam, scope)
if offsetSym != nil && !offsetSym.IsByte() {
return fmt.Errorf("POKEW: offset variable %q must be byte type", offsetParam)
}
c.offsetVarName = offsetVarName
c.isOffsetVar = true
} else {
// Numeric offset - must fit in byte
if offsetValue > 255 {
return fmt.Errorf("POKEW: offset value %d out of byte range", offsetValue)
}
c.offsetValue = uint8(offsetValue)
c.isOffsetVar = false
}
}
// Parse value (param 4)
valueParam := params[3]
// Try to parse as variable or expression
valVarName, valVarKind, valLiteral, isValVar, err := compiler.ParseOperandParam(
valueParam, ctx.SymbolTable, scope, constLookup)
if err != nil {
return fmt.Errorf("POKEW: invalid value: %w", err)
}
if isValVar {
// It's a variable - must be word
if valVarKind == compiler.KindByte {
return fmt.Errorf("POKEW: cannot use byte variable %q (use word variable or literal)", valueParam)
}
c.valueVarName = valVarName
c.isValueVar = true
} else {
// It's a literal word value
c.valueLiteral = valLiteral
c.isValueVar = false
}
// Check for self-referential POKEW (writing pointer through itself)
if c.isZPPointer && c.isValueVar && c.addrVarName == c.valueVarName {
return fmt.Errorf("POKEW: writing pointer %q through itself - pointer is both address and value", c.valueVarName)
}
return nil
}
func (c *PokeWCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
var asm []string
// Check for immutable code pragma when using self-modifying code
if c.isAddrVar && c.addrVarKind == compiler.KindWord && !c.isZPPointer {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
return nil, fmt.Errorf("POKEW: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)")
}
}
// Case 1: ZP pointer with offset
if c.isZPPointer {
// Load offset into Y
if c.hasOffset {
if c.isOffsetVar {
asm = append(asm, fmt.Sprintf("\tldy %s", c.offsetVarName))
} else {
asm = append(asm, fmt.Sprintf("\tldy #%d", c.offsetValue))
}
} else {
asm = append(asm, "\tldy #0")
}
// Store low byte
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
}
asm = append(asm, fmt.Sprintf("\tsta (%s),y", c.addrVarName))
// Increment Y and store high byte
asm = append(asm, "\tiny")
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
}
asm = append(asm, fmt.Sprintf("\tsta (%s),y", c.addrVarName))
return asm, nil
}
// Case 2: Byte variable (zero-page indexed addressing)
if c.isAddrVar && c.addrVarKind == compiler.KindByte {
asm = append(asm, fmt.Sprintf("\tldx %s", c.addrVarName))
// Store low byte
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
}
asm = append(asm, "\tsta $00,x")
// Increment X and store high byte
asm = append(asm, "\tinx")
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
}
asm = append(asm, "\tsta $00,x")
return asm, nil
}
// Case 3: Word variable (self-modifying code)
if c.isAddrVar && c.addrVarKind == compiler.KindWord {
label1 := ctx.GeneralStack.Push()
label2 := ctx.GeneralStack.Push()
// Setup both labels with base address
asm = append(asm, fmt.Sprintf("\tlda %s", c.addrVarName))
asm = append(asm, fmt.Sprintf("\tsta %s+1", label1))
asm = append(asm, fmt.Sprintf("\tsta %s+1", label2))
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.addrVarName))
asm = append(asm, fmt.Sprintf("\tsta %s+2", label1))
asm = append(asm, fmt.Sprintf("\tsta %s+2", label2))
// Store low byte
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
}
asm = append(asm, label1)
asm = append(asm, "\tsta $ffff")
// Increment address in label2
asm = append(asm, fmt.Sprintf("\tinc %s+1", label2))
asm = append(asm, fmt.Sprintf("\tbne %s", label2))
asm = append(asm, fmt.Sprintf("\tinc %s+2", label2))
// Store high byte
asm = append(asm, label2)
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
}
asm = append(asm, "\tsta $ffff")
return asm, nil
}
// Case 4: Expression/constant address (direct addressing)
if c.isValueVar {
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue))
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue+1))
} else {
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue))
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue+1))
}
return asm, nil
}
// parsePOKEWOffset extracts offset from syntax like "var[offset]"
// Returns (base, offset) where offset is empty if no brackets found
func parsePOKEWOffset(param string) (base string, offset string) {
openBracket := strings.IndexByte(param, '[')
closeBracket := strings.IndexByte(param, ']')
if openBracket == -1 || closeBracket == -1 {
return param, ""
}
if closeBracket <= openBracket || closeBracket != len(param)-1 {
return param, ""
}
base = param[:openBracket]
offset = param[openBracket+1 : closeBracket]
return base, offset
}