package commands import ( "fmt" "os" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // ShiftLCommand handles logical shift left operations // Syntax: // // SHIFTL BY GIVING # old syntax with BY/GIVING // SHIFTL << -> # old syntax with < // = << # new syntax type ShiftLCommand struct { sourceVarName string sourceVarKind compiler.VarKind sourceValue uint16 sourceIsVar bool amountVarName string amountVarKind compiler.VarKind amountValue uint16 amountIsVar bool destVarName string destVarKind compiler.VarKind line preproc.Line // Store line info for warnings } func (c *ShiftLCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } // Old syntax: SHIFTL ... (must have exactly 6 params) if strings.ToUpper(params[0]) == "SHIFTL" && len(params) == 6 { return true } // New syntax: = << if len(params) == 5 && params[1] == "=" && params[3] == "<<" { return true } return false } func (c *ShiftLCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error { // Clear state c.sourceVarName = "" c.sourceIsVar = false c.sourceValue = 0 c.amountVarName = "" c.amountIsVar = false c.amountValue = 0 c.destVarName = "" c.line = line // Store line for warnings params, err := utils.ParseParams(line.Text) if err != nil { return err } paramCount := len(params) scope := ctx.CurrentScope() // Create constant lookup function constLookup := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "SHIFTL" { // Old syntax: SHIFTL BY/<< GIVING/-> if paramCount != 6 { return fmt.Errorf("SHIFTL: wrong number of parameters (%d), expected 6", paramCount) } separator1 := strings.ToUpper(params[2]) if separator1 != "BY" && separator1 != "<<" { return fmt.Errorf("SHIFTL: parameter #3 must be 'BY' or '<<', got %q", params[2]) } separator2 := strings.ToUpper(params[4]) if separator2 != "GIVING" && separator2 != "->" { return fmt.Errorf("SHIFTL: parameter #5 must be 'GIVING' or '->', got %q", params[4]) } // Parse destination destName := params[5] destSym := ctx.SymbolTable.Lookup(destName, scope) if destSym == nil { return fmt.Errorf("SHIFTL: unknown variable %q", destName) } if destSym.IsConst() { return fmt.Errorf("SHIFTL: cannot assign to constant %q", destName) } c.destVarName = destSym.FullName() c.destVarKind = destSym.GetVarKind() // Parse source var err error c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam( params[1], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("SHIFTL: source: %w", err) } // Parse amount c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam( params[3], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("SHIFTL: amount: %w", err) } } else { // New syntax: = << if paramCount != 5 { return fmt.Errorf("SHIFTL: wrong number of parameters (%d), expected 5", paramCount) } if params[1] != "=" { return fmt.Errorf("SHIFTL: expected '=' at position 2, got %q", params[1]) } if params[3] != "<<" { return fmt.Errorf("SHIFTL: expected '<<' at position 4, got %q", params[3]) } // Parse destination destName := params[0] destSym := ctx.SymbolTable.Lookup(destName, scope) if destSym == nil { return fmt.Errorf("SHIFTL: unknown variable %q", destName) } if destSym.IsConst() { return fmt.Errorf("SHIFTL: cannot assign to constant %q", destName) } c.destVarName = destSym.FullName() c.destVarKind = destSym.GetVarKind() // Parse source var err error c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam( params[2], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("SHIFTL: source: %w", err) } // Parse amount c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam( params[4], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("SHIFTL: amount: %w", err) } } // Validate amount if c.amountIsVar { if c.amountVarKind == compiler.KindWord { return fmt.Errorf("SHIFTL: amount must be BYTE variable, got WORD %q", c.amountVarName) } } else { if c.amountValue > 255 { return fmt.Errorf("SHIFTL: amount constant %d out of BYTE range (0-255)", c.amountValue) } } return nil } func (c *ShiftLCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { // Case 1: BYTE destination if c.destVarKind == compiler.KindByte { return c.generateByteShift(ctx) } // Case 2: WORD destination return c.generateWordShift(ctx) } // generateByteShift handles BYTE << amount func (c *ShiftLCommand) generateByteShift(ctx *compiler.CompilerContext) ([]string, error) { var asm []string // Constant shift amount if !c.amountIsVar { amount := c.amountValue // Shift >= 8 bits -> zero if amount >= 8 { if amount > 0 { _, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 8 bits, value will be zero\n", c.line.Filename, c.line.LineNo, amount) } // Store zero if c.sourceIsVar && c.sourceVarName == c.destVarName { // Same variable: just zero it asm = append(asm, "\tlda #0") asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } else { // Different: load zero and store asm = append(asm, "\tlda #0") asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } return asm, nil } // Shift 0 -> copy source if amount == 0 { return c.generateByteCopy(), nil } // Constant shift 1-7 return c.generateByteShiftConst(amount), nil } // Variable shift amount return c.generateByteShiftVar(ctx) } // generateByteCopy copies source to destination (BYTE) func (c *ShiftLCommand) generateByteCopy() []string { var asm []string if c.sourceIsVar { if c.sourceVarName == c.destVarName { // Same variable, no copy needed return asm } // Different variable asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } else { // Literal val := uint8(c.sourceValue & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", val)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } return asm } // generateByteShiftConst generates BYTE << constant (1-7) func (c *ShiftLCommand) generateByteShiftConst(amount uint16) []string { var asm []string // Check if same variable if c.sourceIsVar && c.sourceVarName == c.destVarName { // Same variable: load to accumulator, shift, store back (faster) asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) // Shift in accumulator (faster: 2 cycles vs 5 for memory) for i := uint16(0); i < amount; i++ { asm = append(asm, "\tasl") } asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) return asm } // Different variables or literal source: shift in accumulator if c.sourceIsVar { // Variable source asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) } else { // Literal source val := uint8(c.sourceValue & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", val)) } // Shift in accumulator (faster: 2 cycles vs 5 for memory) for i := uint16(0); i < amount; i++ { asm = append(asm, "\tasl") } // Store result asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) return asm } // generateByteShiftVar generates BYTE << variable func (c *ShiftLCommand) generateByteShiftVar(ctx *compiler.CompilerContext) ([]string, error) { var asm []string // Check if same variable if c.sourceIsVar && c.sourceVarName == c.destVarName { // Same variable: load to accumulator, shift loop, store back (faster) // Generate labels loopLabel := ctx.GeneralStack.Push() ctx.GeneralStack.Pop() doneLabel := ctx.GeneralStack.Push() ctx.GeneralStack.Pop() // Load value to accumulator asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) // Variable shift loop (in accumulator, 2 cycles vs 5 for memory) asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName)) asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel)) asm = append(asm, loopLabel) asm = append(asm, "\tasl") asm = append(asm, "\tdex") asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel)) asm = append(asm, doneLabel) // Store result back asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) return asm, nil } // Different variables or literal: shift in accumulator (faster) // Generate labels loopLabel := ctx.GeneralStack.Push() ctx.GeneralStack.Pop() doneLabel := ctx.GeneralStack.Push() ctx.GeneralStack.Pop() // Load source to accumulator if c.sourceIsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) } else { val := uint8(c.sourceValue & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", val)) } // Load shift amount to X asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName)) asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel)) // Shift loop (in accumulator, 2 cycles vs 5 for memory) asm = append(asm, loopLabel) asm = append(asm, "\tasl") asm = append(asm, "\tdex") asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel)) // Store result asm = append(asm, doneLabel) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) return asm, nil } // generateWordShift handles WORD << amount func (c *ShiftLCommand) generateWordShift(ctx *compiler.CompilerContext) ([]string, error) { // Constant shift amount if !c.amountIsVar { amount := c.amountValue // Shift >= 16 bits -> zero if amount >= 16 { if amount > 0 { _, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 16 bits, value will be zero\n", c.line.Filename, c.line.LineNo, amount) } // Store zero if c.sourceIsVar && c.sourceVarName == c.destVarName { // Same variable: zero both bytes asm := []string{ "\tlda #0", fmt.Sprintf("\tsta %s", c.destVarName), fmt.Sprintf("\tsta %s+1", c.destVarName), } return asm, nil } else { // Different: load zero and store to both bytes asm := []string{ "\tlda #0", fmt.Sprintf("\tsta %s", c.destVarName), fmt.Sprintf("\tsta %s+1", c.destVarName), } return asm, nil } } // Shift 0 -> copy source if amount == 0 { return c.generateWordCopy(), nil } // Constant shift 1-15 return c.generateWordShiftConst(amount), nil } // Variable shift amount return c.generateWordShiftVar(ctx) } // generateWordCopy copies source to destination (WORD) func (c *ShiftLCommand) generateWordCopy() []string { var asm []string if c.sourceIsVar && c.sourceVarName == c.destVarName { // Same variable, no copy needed return asm } if c.sourceIsVar { // Variable source if c.sourceVarKind == compiler.KindByte { // Byte -> Word (zero-extend) 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)) } else { // Word -> Word 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)) } } else { // Literal source lo := uint8(c.sourceValue & 0xFF) hi := uint8((c.sourceValue >> 8) & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) if lo != hi { asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi)) } asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) } return asm } // generateWordShiftConst generates WORD << constant (1-15) func (c *ShiftLCommand) generateWordShiftConst(amount uint16) []string { var asm []string // Special case: shift >= 8 if amount >= 8 && amount < 16 { remaining := amount - 8 // Get source low byte if c.sourceIsVar { if c.sourceVarName == c.destVarName { // Same variable: read from destination (already has value) asm = append(asm, fmt.Sprintf("\tlda %s", c.destVarName)) } else { // Different: read from source asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName)) } } else { // Literal lo := uint8(c.sourceValue & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo)) } // Store to destination high byte asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) // Zero low byte asm = append(asm, "\tlda #0") asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) // Shift high byte remaining bits for i := uint16(0); i < remaining; i++ { asm = append(asm, fmt.Sprintf("\tasl %s+1", c.destVarName)) } return asm } // Shift 1-7: normal copy and shift copyAsm := c.generateWordCopy() asm = append(asm, copyAsm...) // Apply shift for i := uint16(0); i < amount; i++ { asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName)) } return asm } // generateWordShiftVar generates WORD << variable func (c *ShiftLCommand) generateWordShiftVar(ctx *compiler.CompilerContext) ([]string, error) { var asm []string // Copy source to destination if needed copyAsm := c.generateWordCopy() asm = append(asm, copyAsm...) // Generate labels loopLabel := ctx.GeneralStack.Push() ctx.GeneralStack.Pop() doneLabel := ctx.GeneralStack.Push() ctx.GeneralStack.Pop() // Variable shift loop asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName)) asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel)) asm = append(asm, loopLabel) asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName)) asm = append(asm, "\tdex") asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel)) asm = append(asm, doneLabel) return asm, nil }