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
[offset] WITH|, // 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 }