package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // PeekWCommand handles PEEKW statements (reads a word/2 bytes) // Syntax: // // PEEKW
[offset] GIVING|-> # old syntax // = PEEKW
[offset] # new syntax // // Where address is: // - Byte variable (zero-page indexed addressing) // - Word variable (self-modifying code or ZP pointer with offset) // - Expression (direct addressing) // // Destination must be a word variable type PeekWCommand struct { addrVarName string addrVarKind compiler.VarKind addrValue uint16 isAddrVar bool isZPPointer bool offsetVarName string offsetValue uint8 hasOffset bool isOffsetVar bool destVarName string pragmaSet preproc.PragmaSet } func (c *PeekWCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) != 4 { return false } // Old syntax: PEEKW ... (4 params) if strings.ToUpper(params[0]) == "PEEKW" { return true } // New syntax: = PEEKW
if params[1] == "=" && strings.ToUpper(params[2]) == "PEEKW" { return true } return false } func (c *PeekWCommand) 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.destVarName = "" // 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("PEEKW: expected 4 parameters, got %d", len(params)) } scope := ctx.CurrentScope() var addrParam string var destParam string // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "PEEKW" { // Old syntax: PEEKW
GIVING|-> // Validate separator (param 3) sep := strings.ToUpper(params[2]) if sep != "GIVING" && sep != "->" { return fmt.Errorf("PEEKW: parameter #3 must be 'GIVING' or '->', got %q", params[2]) } addrParam = params[1] destParam = params[3] } else { // New syntax: = PEEKW
if params[1] != "=" { return fmt.Errorf("PEEKW: expected '=' at position 2, got %q", params[1]) } if strings.ToUpper(params[2]) != "PEEKW" { return fmt.Errorf("PEEKW: expected 'PEEKW' at position 3, got %q", params[2]) } destParam = params[0] addrParam = params[3] } // Parse address - may have [offset] baseAddr, offsetParam := parseOffsetW(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("PEEKW: 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("PEEKW: 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("PEEKW: 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("PEEKW: 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("PEEKW: offset value %d out of byte range", offsetValue) } c.offsetValue = uint8(offsetValue) c.isOffsetVar = false } } // Parse destination variable destSym := ctx.SymbolTable.Lookup(destParam, scope) if destSym == nil { return fmt.Errorf("PEEKW: unknown destination variable %q", destParam) } if destSym.IsConst() { return fmt.Errorf("PEEKW: cannot PEEKW into constant %q", destParam) } if !destSym.IsWord() { return fmt.Errorf("PEEKW: destination %q must be word type", destParam) } c.destVarName = destSym.FullName() // Check for self-referential PEEKW (reading through a pointer into itself) if c.isZPPointer && c.addrVarName == c.destVarName { return fmt.Errorf("PEEKW: cannot read through pointer %q into itself (would corrupt pointer during read)", c.destVarName) } return nil } func (c *PeekWCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { var asm []string // 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") } // Indexed indirect load - low byte asm = append(asm, fmt.Sprintf("\tlda (%s),y", c.addrVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) // Increment Y and load high byte asm = append(asm, "\tiny") asm = append(asm, fmt.Sprintf("\tlda (%s),y", c.addrVarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) 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)) asm = append(asm, "\tlda $00,x") asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, "\tinx") asm = append(asm, "\tlda $00,x") asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) return asm, nil } // Case 3: Word variable (self-modifying code) if c.isAddrVar && c.addrVarKind == compiler.KindWord { // Check for immutable code pragma if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" { return nil, fmt.Errorf("PEEKW: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)") } 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)) // Read low byte asm = append(asm, label1) asm = append(asm, "\tlda $ffff") asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) // 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)) // Read high byte asm = append(asm, label2) asm = append(asm, "\tlda $ffff") asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) return asm, nil } // Case 4: Expression/constant address (direct addressing) asm = append(asm, fmt.Sprintf("\tlda %d", c.addrValue)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tlda %d", c.addrValue+1)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) return asm, nil } // parseOffsetW extracts offset from syntax like "var[offset]" // Returns (base, offset) where offset is empty if no brackets found func parseOffsetW(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 }