190 lines
5 KiB
Go
190 lines
5 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
"c65gm/internal/utils"
|
|
)
|
|
|
|
// LetCommand handles LET/assignment operations
|
|
// Syntax:
|
|
//
|
|
// LET <dest> GET <source> # old syntax with GET
|
|
// LET <dest> = <source> # old syntax with =
|
|
// <dest> = <source> # new syntax
|
|
//
|
|
// Note: Differs from arithmetic ops by param count (3 vs 5)
|
|
type LetCommand struct {
|
|
sourceVarName string
|
|
sourceVarKind compiler.VarKind
|
|
sourceValue uint16
|
|
sourceIsVar bool
|
|
|
|
destVarName string
|
|
destVarKind compiler.VarKind
|
|
}
|
|
|
|
func (c *LetCommand) WillHandle(line preproc.Line) bool {
|
|
params, err := utils.ParseParams(line.Text)
|
|
if err != nil || len(params) == 0 {
|
|
return false
|
|
}
|
|
|
|
// Old syntax: LET ... (must have exactly 4 params)
|
|
if strings.ToUpper(params[0]) == "LET" && len(params) == 4 {
|
|
return true
|
|
}
|
|
|
|
// New syntax: <dest> = <source> (exactly 3 params)
|
|
if len(params) == 3 && params[1] == "=" {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *LetCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
|
// Clear state
|
|
c.sourceVarName = ""
|
|
c.sourceIsVar = false
|
|
c.sourceValue = 0
|
|
c.destVarName = ""
|
|
|
|
params, err := utils.ParseParams(line.Text)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
paramCount := len(params)
|
|
scope := ctx.CurrentScope()
|
|
|
|
// Create constant lookup function
|
|
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
|
|
}
|
|
|
|
// Determine syntax and parse accordingly
|
|
if strings.ToUpper(params[0]) == "LET" {
|
|
// Old syntax: LET <dest> GET/= <source>
|
|
if paramCount != 4 {
|
|
return fmt.Errorf("LET: wrong number of parameters (%d), expected 4", paramCount)
|
|
}
|
|
|
|
separator := strings.ToUpper(params[2])
|
|
if separator != "GET" && separator != "=" {
|
|
return fmt.Errorf("LET: parameter #3 must be 'GET' or '=', got %q", params[2])
|
|
}
|
|
|
|
// Parse destination
|
|
destName := params[1]
|
|
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
|
if destSym == nil {
|
|
return fmt.Errorf("LET: unknown variable %q", destName)
|
|
}
|
|
if destSym.IsConst() {
|
|
return fmt.Errorf("LET: cannot assign to constant %q", destName)
|
|
}
|
|
c.destVarName = destSym.FullName()
|
|
c.destVarKind = destSym.GetVarKind()
|
|
|
|
// Parse source
|
|
c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
|
|
params[3], ctx.SymbolTable, scope, constLookup)
|
|
if err != nil {
|
|
return fmt.Errorf("LET: source: %w", err)
|
|
}
|
|
|
|
} else {
|
|
// New syntax: <dest> = <source>
|
|
if paramCount != 3 {
|
|
return fmt.Errorf("LET: wrong number of parameters (%d), expected 3", paramCount)
|
|
}
|
|
|
|
if params[1] != "=" {
|
|
return fmt.Errorf("LET: expected '=' at position 2, got %q", params[1])
|
|
}
|
|
|
|
// Parse destination
|
|
destName := params[0]
|
|
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
|
if destSym == nil {
|
|
return fmt.Errorf("LET: unknown variable %q", destName)
|
|
}
|
|
if destSym.IsConst() {
|
|
return fmt.Errorf("LET: cannot assign to constant %q", destName)
|
|
}
|
|
c.destVarName = destSym.FullName()
|
|
c.destVarKind = destSym.GetVarKind()
|
|
|
|
// Parse source
|
|
c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
|
|
params[2], ctx.SymbolTable, scope, constLookup)
|
|
if err != nil {
|
|
return fmt.Errorf("LET: source: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *LetCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|
var asm []string
|
|
|
|
// Variable assignment
|
|
if c.sourceIsVar {
|
|
// Destination: byte
|
|
if c.destVarKind == compiler.KindByte {
|
|
// byte → byte or word → byte (take low byte)
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
|
return asm, nil
|
|
}
|
|
|
|
// Destination: word
|
|
// byte → word (zero-extend)
|
|
if c.sourceVarKind == compiler.KindByte {
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
|
asm = append(asm, "\tlda #0")
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
|
return asm, nil
|
|
}
|
|
|
|
// word → word (copy both bytes)
|
|
asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
|
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
|
return asm, nil
|
|
}
|
|
|
|
// Literal assignment
|
|
lo := uint8(c.sourceValue & 0xFF)
|
|
hi := uint8((c.sourceValue >> 8) & 0xFF)
|
|
|
|
// Destination: byte
|
|
if c.destVarKind == compiler.KindByte {
|
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
|
return asm, nil
|
|
}
|
|
|
|
// Destination: word
|
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
|
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
|
|
|
// Optimization: don't reload if lo == hi (common for $0000, $FFFF)
|
|
if lo != hi {
|
|
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
|
}
|
|
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
|
|
|
return asm, nil
|
|
}
|