284 lines
7.4 KiB
Go
284 lines
7.4 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
"c65gm/internal/utils"
|
|
)
|
|
|
|
// PokeCommand handles POKE statements (writes a byte to memory)
|
|
// Syntax: POKE <address>[offset] WITH|, <value>
|
|
// Where address is:
|
|
// - Byte variable (self-modifying code)
|
|
// - Word variable (self-modifying code or ZP pointer with offset)
|
|
// - Expression (direct addressing)
|
|
//
|
|
// Value is a byte literal or byte/word variable (uses low byte)
|
|
type PokeCommand struct {
|
|
addrVarName string
|
|
addrVarKind compiler.VarKind
|
|
addrValue uint16
|
|
isAddrVar bool
|
|
isZPPointer bool
|
|
|
|
offsetVarName string
|
|
offsetValue uint8
|
|
hasOffset bool
|
|
isOffsetVar bool
|
|
|
|
valueVarName string
|
|
valueVarKind compiler.VarKind
|
|
valueLiteral uint8
|
|
isValueVar bool
|
|
|
|
pragmaSet preproc.PragmaSet
|
|
}
|
|
|
|
func (c *PokeCommand) WillHandle(line preproc.Line) bool {
|
|
params, err := utils.ParseParams(line.Text)
|
|
if err != nil || len(params) != 4 {
|
|
return false
|
|
}
|
|
return strings.ToUpper(params[0]) == "POKE"
|
|
}
|
|
|
|
func (c *PokeCommand) 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("POKE: 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("POKE: parameter #3 must be 'WITH' or ',', got %q", params[2])
|
|
}
|
|
|
|
// Parse address (param 2) - may have [offset]
|
|
addrParam := params[1]
|
|
baseAddr, offsetParam := parsePOKEOffset(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("POKE: 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("POKE: 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("POKE: 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("POKE: 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("POKE: 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("POKE: invalid value: %w", err)
|
|
}
|
|
|
|
if isValVar {
|
|
// It's a variable (byte or word - we use low byte)
|
|
c.valueVarName = valVarName
|
|
c.valueVarKind = valVarKind
|
|
c.isValueVar = true
|
|
} else {
|
|
// It's a literal - must fit in byte
|
|
if valLiteral > 255 {
|
|
return fmt.Errorf("POKE: value %d out of byte range (0-255)", valLiteral)
|
|
}
|
|
c.valueLiteral = uint8(valLiteral)
|
|
c.isValueVar = false
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *PokeCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
|
var asm []string
|
|
|
|
// Check for immutable code pragma when using self-modifying code
|
|
if c.isAddrVar && !c.isZPPointer {
|
|
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
|
|
return nil, fmt.Errorf("POKE: 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")
|
|
}
|
|
|
|
// Load value
|
|
if c.isValueVar {
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
|
|
} else {
|
|
asm = append(asm, fmt.Sprintf("\tlda #%d", c.valueLiteral))
|
|
}
|
|
|
|
// Indexed indirect store
|
|
asm = append(asm, fmt.Sprintf("\tsta (%s),y", c.addrVarName))
|
|
return asm, nil
|
|
}
|
|
|
|
// Case 2: Byte variable (self-modifying code)
|
|
if c.isAddrVar && c.addrVarKind == compiler.KindByte {
|
|
label := ctx.GeneralStack.Push()
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.addrVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", label))
|
|
|
|
// Load value
|
|
if c.isValueVar {
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
|
|
} else {
|
|
asm = append(asm, fmt.Sprintf("\tlda #%d", c.valueLiteral))
|
|
}
|
|
|
|
asm = append(asm, label)
|
|
asm = append(asm, "\tsta $ff")
|
|
return asm, nil
|
|
}
|
|
|
|
// Case 3: Word variable (self-modifying code)
|
|
if c.isAddrVar && c.addrVarKind == compiler.KindWord {
|
|
label := ctx.GeneralStack.Push()
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.addrVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", label))
|
|
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.addrVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+2", label))
|
|
|
|
// Load value
|
|
if c.isValueVar {
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
|
|
} else {
|
|
asm = append(asm, fmt.Sprintf("\tlda #%d", c.valueLiteral))
|
|
}
|
|
|
|
asm = append(asm, label)
|
|
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))
|
|
} else {
|
|
asm = append(asm, fmt.Sprintf("\tlda #%d", c.valueLiteral))
|
|
asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue))
|
|
}
|
|
|
|
return asm, nil
|
|
}
|
|
|
|
// parsePOKEOffset extracts offset from syntax like "var[offset]"
|
|
// Returns (base, offset) where offset is empty if no brackets found
|
|
func parsePOKEOffset(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
|
|
}
|