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