package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // LetCommand handles LET/assignment operations // Syntax: // // LET GET # old syntax with GET // LET = # old syntax with = // = # 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: = (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 GET/= 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: = 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 }