Added all peeks, pokes, pointer, gosub and subend
This commit is contained in:
parent
5b3e5737b8
commit
6085bccad3
8 changed files with 1601 additions and 0 deletions
276
internal/commands/gosub.go
Normal file
276
internal/commands/gosub.go
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
"c65gm/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GosubCommand handles GOSUB statements (subroutine call with return)
|
||||||
|
// Syntax:
|
||||||
|
//
|
||||||
|
// GOSUB <target>
|
||||||
|
// GOSUB <target> PASSING <var1> AS ACC|XREG|YREG [<var2> AS XREG|YREG [<var3> AS YREG]]
|
||||||
|
//
|
||||||
|
// Where target is:
|
||||||
|
// - Label name
|
||||||
|
// - Variable (byte or word) - uses self-modifying code
|
||||||
|
// - Expression/address
|
||||||
|
type GosubCommand struct {
|
||||||
|
targetName string
|
||||||
|
targetKind compiler.VarKind
|
||||||
|
targetAddress uint16
|
||||||
|
isVar bool
|
||||||
|
isLabel bool
|
||||||
|
|
||||||
|
// Register passing
|
||||||
|
accVarName string
|
||||||
|
accVarKind compiler.VarKind
|
||||||
|
xregVarName string
|
||||||
|
xregVarKind compiler.VarKind
|
||||||
|
yregVarName string
|
||||||
|
yregVarKind compiler.VarKind
|
||||||
|
|
||||||
|
pragmaSet preproc.PragmaSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GosubCommand) WillHandle(line preproc.Line) bool {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil || len(params) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.ToUpper(params[0]) == "GOSUB"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GosubCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||||
|
// Clear state
|
||||||
|
c.targetName = ""
|
||||||
|
c.targetAddress = 0
|
||||||
|
c.isVar = false
|
||||||
|
c.isLabel = false
|
||||||
|
c.accVarName = ""
|
||||||
|
c.xregVarName = ""
|
||||||
|
c.yregVarName = ""
|
||||||
|
|
||||||
|
// Store pragma set
|
||||||
|
c.pragmaSet = ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
||||||
|
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
paramCount := len(params)
|
||||||
|
if paramCount != 2 && paramCount != 6 && paramCount != 9 && paramCount != 12 {
|
||||||
|
return fmt.Errorf("GOSUB: expected 2, 6, 9, or 12 parameters, got %d", paramCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := ctx.CurrentScope()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse target (param 2)
|
||||||
|
targetParam := params[1]
|
||||||
|
|
||||||
|
// Try ParseOperandParam - handles variables, constants, expressions
|
||||||
|
varName, varKind, value, isVar, err := compiler.ParseOperandParam(
|
||||||
|
targetParam, ctx.SymbolTable, scope, constLookup)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Not a variable or expression -> treat as label
|
||||||
|
c.targetName = targetParam
|
||||||
|
c.isLabel = true
|
||||||
|
} else if isVar {
|
||||||
|
// It's a variable
|
||||||
|
c.targetName = varName
|
||||||
|
c.targetKind = varKind
|
||||||
|
c.isVar = true
|
||||||
|
} else {
|
||||||
|
// It's an expression/constant
|
||||||
|
c.targetAddress = value
|
||||||
|
c.isVar = false
|
||||||
|
c.isLabel = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse PASSING clause if present
|
||||||
|
if paramCount > 2 {
|
||||||
|
if strings.ToUpper(params[2]) != "PASSING" {
|
||||||
|
return fmt.Errorf("GOSUB: parameter #3 must be 'PASSING', got %q", params[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// First register (params 4-6)
|
||||||
|
if err := c.parseRegisterParam(params[3], params[4], params[5], ctx, scope); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second register (params 7-9)
|
||||||
|
if paramCount >= 9 {
|
||||||
|
if err := c.parseRegisterParam(params[6], params[7], params[8], ctx, scope); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third register (params 10-12)
|
||||||
|
if paramCount == 12 {
|
||||||
|
if err := c.parseRegisterParam(params[9], params[10], params[11], ctx, scope); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GosubCommand) parseRegisterParam(varName, asWord, regName string, ctx *compiler.CompilerContext, scope []string) error {
|
||||||
|
if strings.ToUpper(asWord) != "AS" {
|
||||||
|
return fmt.Errorf("GOSUB: expected 'AS', got %q", asWord)
|
||||||
|
}
|
||||||
|
|
||||||
|
reg := strings.ToUpper(regName)
|
||||||
|
if reg != "ACC" && reg != "XREG" && reg != "YREG" {
|
||||||
|
return fmt.Errorf("GOSUB: register must be ACC, XREG, or YREG, got %q", regName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up variable
|
||||||
|
sym := ctx.SymbolTable.Lookup(varName, scope)
|
||||||
|
if sym == nil {
|
||||||
|
return fmt.Errorf("GOSUB: unknown variable %q for register %s", varName, reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullName := sym.FullName()
|
||||||
|
kind := sym.GetVarKind()
|
||||||
|
|
||||||
|
// Assign to appropriate register
|
||||||
|
switch reg {
|
||||||
|
case "ACC":
|
||||||
|
if c.accVarName != "" {
|
||||||
|
return fmt.Errorf("GOSUB: ACC already assigned")
|
||||||
|
}
|
||||||
|
c.accVarName = fullName
|
||||||
|
c.accVarKind = kind
|
||||||
|
case "XREG":
|
||||||
|
if c.xregVarName != "" {
|
||||||
|
return fmt.Errorf("GOSUB: XREG already assigned")
|
||||||
|
}
|
||||||
|
c.xregVarName = fullName
|
||||||
|
c.xregVarKind = kind
|
||||||
|
case "YREG":
|
||||||
|
if c.yregVarName != "" {
|
||||||
|
return fmt.Errorf("GOSUB: YREG already assigned")
|
||||||
|
}
|
||||||
|
c.yregVarName = fullName
|
||||||
|
c.yregVarKind = kind
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GosubCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// Check for immutable code pragma when using variable target
|
||||||
|
if c.isVar {
|
||||||
|
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
|
||||||
|
return nil, fmt.Errorf("GOSUB: USE_IMMUTABLE_CODE pragma set but variable target requires self-modifying code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate based on target type
|
||||||
|
if c.isVar {
|
||||||
|
// Variable target - self-modifying code
|
||||||
|
label := ctx.GeneralStack.Push()
|
||||||
|
|
||||||
|
if c.targetKind == compiler.KindByte {
|
||||||
|
// Byte variable
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.targetName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", label))
|
||||||
|
asm = append(asm, label)
|
||||||
|
} else {
|
||||||
|
// Word variable
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.targetName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", label))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.targetName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+2", label))
|
||||||
|
asm = append(asm, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-passing: load variables into registers
|
||||||
|
asm = append(asm, c.generatePrePassing()...)
|
||||||
|
|
||||||
|
// JSR with placeholder address
|
||||||
|
asm = append(asm, "\tjsr $0000")
|
||||||
|
|
||||||
|
// Post-passing: store registers back to variables
|
||||||
|
asm = append(asm, c.generatePostPassing()...)
|
||||||
|
|
||||||
|
} else if c.isLabel {
|
||||||
|
// Label target
|
||||||
|
asm = append(asm, c.generatePrePassing()...)
|
||||||
|
asm = append(asm, fmt.Sprintf("\tjsr %s", c.targetName))
|
||||||
|
asm = append(asm, c.generatePostPassing()...)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Address target
|
||||||
|
asm = append(asm, c.generatePrePassing()...)
|
||||||
|
asm = append(asm, fmt.Sprintf("\tjsr %d", c.targetAddress))
|
||||||
|
asm = append(asm, c.generatePostPassing()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GosubCommand) generatePrePassing() []string {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
if c.accVarName != "" {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.accVarName))
|
||||||
|
}
|
||||||
|
if c.xregVarName != "" {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldx %s", c.xregVarName))
|
||||||
|
}
|
||||||
|
if c.yregVarName != "" {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldy %s", c.yregVarName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return asm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GosubCommand) generatePostPassing() []string {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
if c.accVarName != "" {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.accVarName))
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.accVarKind == compiler.KindWord {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.accVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.xregVarName != "" {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tstx %s", c.xregVarName))
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.xregVarKind == compiler.KindWord {
|
||||||
|
asm = append(asm, "\tldx #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tstx %s+1", c.xregVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.yregVarName != "" {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsty %s", c.yregVarName))
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.yregVarKind == compiler.KindWord {
|
||||||
|
asm = append(asm, "\tldy #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsty %s+1", c.yregVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return asm
|
||||||
|
}
|
||||||
269
internal/commands/peek.go
Normal file
269
internal/commands/peek.go
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
"c65gm/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PeekCommand handles PEEK statements
|
||||||
|
// Syntax: PEEK <address>[offset] GIVING|-> <dest>
|
||||||
|
// Where address is:
|
||||||
|
// - Byte variable (self-modifying code)
|
||||||
|
// - Word variable (self-modifying code or ZP pointer with offset)
|
||||||
|
// - Expression (direct addressing)
|
||||||
|
type PeekCommand struct {
|
||||||
|
addrVarName string
|
||||||
|
addrVarKind compiler.VarKind
|
||||||
|
addrValue uint16
|
||||||
|
isAddrVar bool
|
||||||
|
isZPPointer bool
|
||||||
|
|
||||||
|
offsetVarName string
|
||||||
|
offsetValue uint8
|
||||||
|
hasOffset bool
|
||||||
|
isOffsetVar bool
|
||||||
|
|
||||||
|
destVarName string
|
||||||
|
destVarKind compiler.VarKind
|
||||||
|
|
||||||
|
pragmaSet preproc.PragmaSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PeekCommand) WillHandle(line preproc.Line) bool {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil || len(params) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.ToUpper(params[0]) == "PEEK"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PeekCommand) 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("PEEK: expected 4 parameters, got %d", len(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := ctx.CurrentScope()
|
||||||
|
|
||||||
|
// Validate separator (param 3)
|
||||||
|
sep := strings.ToUpper(params[2])
|
||||||
|
if sep != "GIVING" && sep != "->" {
|
||||||
|
return fmt.Errorf("PEEK: parameter #3 must be 'GIVING' or '->', got %q", params[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse address (param 2) - may have [offset]
|
||||||
|
addrParam := params[1]
|
||||||
|
baseAddr, offsetParam := parseOffset(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("PEEK: 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("PEEK: 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("PEEK: 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("PEEK: 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("PEEK: offset value %d out of byte range", offsetValue)
|
||||||
|
}
|
||||||
|
c.offsetValue = uint8(offsetValue)
|
||||||
|
c.isOffsetVar = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination variable (param 4)
|
||||||
|
destParam := params[3]
|
||||||
|
destSym := ctx.SymbolTable.Lookup(destParam, scope)
|
||||||
|
if destSym == nil {
|
||||||
|
return fmt.Errorf("PEEK: unknown destination variable %q", destParam)
|
||||||
|
}
|
||||||
|
if destSym.IsConst() {
|
||||||
|
return fmt.Errorf("PEEK: cannot PEEK into constant %q", destParam)
|
||||||
|
}
|
||||||
|
c.destVarName = destSym.FullName()
|
||||||
|
c.destVarKind = destSym.GetVarKind()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PeekCommand) 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("PEEK: 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
// Optimization: if offset is 0 and not a variable, use Y register (which is 0)
|
||||||
|
if !c.hasOffset || (!c.isOffsetVar && c.offsetValue == 0) {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsty %s+1", c.destVarName))
|
||||||
|
} else {
|
||||||
|
asm = append(asm, "\tldx #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tstx %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexed indirect load
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda (%s),y", c.addrVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
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))
|
||||||
|
asm = append(asm, label)
|
||||||
|
asm = append(asm, "\tlda $ff")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
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 {
|
||||||
|
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))
|
||||||
|
asm = append(asm, label)
|
||||||
|
asm = append(asm, "\tlda $ffff")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||||
|
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
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))
|
||||||
|
|
||||||
|
// Clear high byte for word destination
|
||||||
|
if c.destVarKind == compiler.KindWord {
|
||||||
|
asm = append(asm, "\tlda #0")
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOffset extracts offset from syntax like "var[offset]"
|
||||||
|
// Returns (base, offset) where offset is empty if no brackets found
|
||||||
|
func parseOffset(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
|
||||||
|
}
|
||||||
265
internal/commands/peekw.go
Normal file
265
internal/commands/peekw.go
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
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 <address>[offset] GIVING|-> <dest>
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
return strings.ToUpper(params[0]) == "PEEKW"
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
// 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse address (param 2) - may have [offset]
|
||||||
|
addrParam := params[1]
|
||||||
|
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 (param 4)
|
||||||
|
destParam := params[3]
|
||||||
|
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()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
140
internal/commands/point.go
Normal file
140
internal/commands/point.go
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
"c65gm/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PointerCommand handles POINTER statements
|
||||||
|
// Syntax: POINTER <ptrvar> TO|-> <target>
|
||||||
|
// Where target is:
|
||||||
|
// - Variable name
|
||||||
|
// - Label name
|
||||||
|
// - Numeric address/expression
|
||||||
|
type PointerCommand struct {
|
||||||
|
pointerVarName string
|
||||||
|
|
||||||
|
targetVarName string
|
||||||
|
targetLabel string
|
||||||
|
targetAddress uint16
|
||||||
|
|
||||||
|
isVar bool
|
||||||
|
isLabel bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PointerCommand) WillHandle(line preproc.Line) bool {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil || len(params) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.ToUpper(params[0]) == "POINTER"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PointerCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||||
|
// Clear state
|
||||||
|
c.pointerVarName = ""
|
||||||
|
c.targetVarName = ""
|
||||||
|
c.targetLabel = ""
|
||||||
|
c.targetAddress = 0
|
||||||
|
c.isVar = false
|
||||||
|
c.isLabel = false
|
||||||
|
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params) != 4 {
|
||||||
|
return fmt.Errorf("POINTER: expected 4 parameters, got %d", len(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := ctx.CurrentScope()
|
||||||
|
|
||||||
|
// Validate pointer variable (param 2)
|
||||||
|
ptrVarName := params[1]
|
||||||
|
ptrSym := ctx.SymbolTable.Lookup(ptrVarName, scope)
|
||||||
|
if ptrSym == nil {
|
||||||
|
return fmt.Errorf("POINTER: unknown variable %q", ptrVarName)
|
||||||
|
}
|
||||||
|
if !ptrSym.IsWord() {
|
||||||
|
return fmt.Errorf("POINTER: variable %q is not a word", ptrVarName)
|
||||||
|
}
|
||||||
|
c.pointerVarName = ptrSym.FullName()
|
||||||
|
|
||||||
|
// Validate separator (param 3)
|
||||||
|
sep := strings.ToUpper(params[2])
|
||||||
|
if sep != "TO" && sep != "->" {
|
||||||
|
return fmt.Errorf("POINTER: parameter #3 must be 'TO' or '->', got %q", params[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse target (param 4)
|
||||||
|
targetParam := params[3]
|
||||||
|
|
||||||
|
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 ParseOperandParam - handles variables, constants, expressions
|
||||||
|
varName, _, value, isVar, err := compiler.ParseOperandParam(
|
||||||
|
targetParam, ctx.SymbolTable, scope, constLookup)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Not a variable or expression -> treat as label
|
||||||
|
c.targetLabel = targetParam
|
||||||
|
c.isLabel = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's either a variable or numeric address
|
||||||
|
if isVar {
|
||||||
|
c.targetVarName = varName
|
||||||
|
c.isVar = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric address
|
||||||
|
c.targetAddress = value
|
||||||
|
c.isVar = false
|
||||||
|
c.isLabel = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PointerCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||||
|
var asm []string
|
||||||
|
|
||||||
|
// Label reference
|
||||||
|
if c.isLabel {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldx #<%s", c.targetLabel))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #>%s", c.targetLabel))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.pointerVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tstx %s", c.pointerVarName))
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable reference
|
||||||
|
if c.isVar {
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldx #<%s", c.targetVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #>%s", c.targetVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.pointerVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tstx %s", c.pointerVarName))
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric address - create temp label
|
||||||
|
tempLabel := ctx.GeneralStack.Push()
|
||||||
|
asm = append(asm, fmt.Sprintf("%s = %d", tempLabel, c.targetAddress))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tldx #<%s", tempLabel))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tlda #>%s", tempLabel))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.pointerVarName))
|
||||||
|
asm = append(asm, fmt.Sprintf("\tstx %s", c.pointerVarName))
|
||||||
|
|
||||||
|
return asm, nil
|
||||||
|
}
|
||||||
284
internal/commands/poke.go
Normal file
284
internal/commands/poke.go
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
316
internal/commands/pokew.go
Normal file
316
internal/commands/pokew.go
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
44
internal/commands/subend.go
Normal file
44
internal/commands/subend.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"c65gm/internal/compiler"
|
||||||
|
"c65gm/internal/preproc"
|
||||||
|
"c65gm/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubEndCommand handles SUBEND/EXIT statements
|
||||||
|
// Syntax: SUBEND (no parameters)
|
||||||
|
//
|
||||||
|
// EXIT (no parameters)
|
||||||
|
//
|
||||||
|
// Generates RTS (return from subroutine)
|
||||||
|
type SubEndCommand struct{}
|
||||||
|
|
||||||
|
func (c *SubEndCommand) WillHandle(line preproc.Line) bool {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil || len(params) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
keyword := strings.ToUpper(params[0])
|
||||||
|
return keyword == "SUBEND" || keyword == "EXIT"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubEndCommand) Interpret(line preproc.Line, _ *compiler.CompilerContext) error {
|
||||||
|
params, err := utils.ParseParams(line.Text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params) != 1 {
|
||||||
|
return fmt.Errorf("SUBEND: no parameters allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubEndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||||
|
return []string{"\trts"}, nil
|
||||||
|
}
|
||||||
7
main.go
7
main.go
|
|
@ -98,6 +98,13 @@ func registerCommands(comp *compiler.Compiler) {
|
||||||
comp.Registry().Register(&commands.GotoCommand{})
|
comp.Registry().Register(&commands.GotoCommand{})
|
||||||
comp.Registry().Register(&commands.LabelCommand{})
|
comp.Registry().Register(&commands.LabelCommand{})
|
||||||
comp.Registry().Register(&commands.OriginCommand{})
|
comp.Registry().Register(&commands.OriginCommand{})
|
||||||
|
comp.Registry().Register(&commands.PointerCommand{})
|
||||||
|
comp.Registry().Register(&commands.PeekCommand{})
|
||||||
|
comp.Registry().Register(&commands.PeekWCommand{})
|
||||||
|
comp.Registry().Register(&commands.PokeCommand{})
|
||||||
|
comp.Registry().Register(&commands.PokeWCommand{})
|
||||||
|
comp.Registry().Register(&commands.SubEndCommand{})
|
||||||
|
comp.Registry().Register(&commands.GosubCommand{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeOutput(filename string, lines []string) error {
|
func writeOutput(filename string, lines []string) error {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue