package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // OrCommand handles bitwise OR operations // Syntax: // // OR WITH GIVING # old syntax with WITH/GIVING // OR WITH -> # old syntax with WITH/-> // = | # new syntax type OrCommand struct { param1VarName string param1VarKind compiler.VarKind param1Value uint16 param1IsVar bool param2VarName string param2VarKind compiler.VarKind param2Value uint16 param2IsVar bool destVarName string destVarKind compiler.VarKind } func (c *OrCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } // Old syntax: OR ... (must have exactly 6 params) if strings.ToUpper(params[0]) == "OR" && len(params) == 6 { return true } // New syntax: = | if len(params) == 5 && params[1] == "=" && params[3] == "|" { return true } return false } func (c *OrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error { // Clear state c.param1VarName = "" c.param1IsVar = false c.param1Value = 0 c.param2VarName = "" c.param2IsVar = false c.param2Value = 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 := ctx.SymbolTable.ConstantLookupFunc(scope) // Determine syntax and parse accordingly if strings.ToUpper(params[0]) == "OR" { // Old syntax: OR WITH GIVING/-> if paramCount != 6 { return fmt.Errorf("OR: wrong number of parameters (%d), expected 6", paramCount) } separator1 := strings.ToUpper(params[2]) if separator1 != "WITH" { return fmt.Errorf("OR: parameter #3 must be 'WITH', got %q", params[2]) } separator2 := strings.ToUpper(params[4]) if separator2 != "GIVING" && separator2 != "->" { return fmt.Errorf("OR: 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("OR: unknown variable %q", destName) } if destSym.IsConst() { return fmt.Errorf("OR: cannot assign to constant %q", destName) } c.destVarName = destSym.FullName() c.destVarKind = destSym.GetVarKind() // Parse param1 var err error c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam( params[1], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("OR: param1: %w", err) } // Parse param2 c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam( params[3], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("OR: param2: %w", err) } } else { // New syntax: = | if paramCount != 5 { return fmt.Errorf("OR: wrong number of parameters (%d), expected 5", paramCount) } if params[1] != "=" { return fmt.Errorf("OR: expected '=' at position 2, got %q", params[1]) } if params[3] != "|" { return fmt.Errorf("OR: 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("OR: unknown variable %q", destName) } if destSym.IsConst() { return fmt.Errorf("OR: cannot assign to constant %q", destName) } c.destVarName = destSym.FullName() c.destVarKind = destSym.GetVarKind() // Parse param1 var err error c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam( params[2], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("OR: param1: %w", err) } // Parse param2 c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam( params[4], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("OR: param2: %w", err) } } // Normalize operands: leverage OR commutativity to swap if needed // This ensures word operands are in param1 when mixed with bytes // Makes code generation simpler and more consistent param1IsByteSized := false if c.param1IsVar { param1IsByteSized = (c.param1VarKind == compiler.KindByte) } else { param1IsByteSized = ((c.param1Value >> 8) & 0xFF) == 0 } param2IsWord := c.param2IsVar && c.param2VarKind == compiler.KindWord if param1IsByteSized && param2IsWord { // Swap param1 and param2 c.param1VarName, c.param2VarName = c.param2VarName, c.param1VarName c.param1VarKind, c.param2VarKind = c.param2VarKind, c.param1VarKind c.param1Value, c.param2Value = c.param2Value, c.param1Value c.param1IsVar, c.param2IsVar = c.param2IsVar, c.param1IsVar } return nil } func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) { var asm []string // If both params are literals, fold at compile time if !c.param1IsVar && !c.param2IsVar { result := c.param1Value | c.param2Value lo := uint8(result & 0xFF) hi := uint8((result >> 8) & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) if c.destVarKind == compiler.KindWord { asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) } return asm, nil } // At least one param is a variable - generate OR code // Same variable on both sides: a | a = a if c.param1IsVar && c.param2IsVar && c.param1VarName == c.param2VarName { if c.destVarName == c.param1VarName { return asm, nil } if c.param1VarKind == compiler.KindWord { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) } else { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) if c.destVarKind == compiler.KindWord { asm = append(asm, "\tlda #0") asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) } } return asm, nil } // If either param is literal $FF, low byte result is always $FF param1IsFF := !c.param1IsVar && uint8(c.param1Value&0xFF) == 0xFF param2IsFF := !c.param2IsVar && uint8(c.param2Value&0xFF) == 0xFF if param1IsFF || param2IsFF { asm = append(asm, "\tlda #$ff") asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } else if !c.param1IsVar && uint8(c.param1Value&0xFF) == 0 && c.param2IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param2VarName)) asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } else { // Load param1 if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) } // OR with param2 (skip if literal 0, as ora #0 is a no-op) if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tora %s", c.param2VarName)) } else if uint8(c.param2Value&0xFF) != 0 { asm = append(asm, fmt.Sprintf("\tora #$%02x", uint8(c.param2Value&0xFF))) } // Store low byte asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName)) } // If destination is word, handle high byte if c.destVarKind == compiler.KindWord { // Optimization: skip high byte for self-assignment when param2 is byte-sized // e.g., word_var = word_var | byte_var would just copy high byte to itself if c.destVarName == c.param1VarName { param2IsByteSized := false if c.param2IsVar { param2IsByteSized = (c.param2VarKind == compiler.KindByte) } else { param2IsByteSized = ((c.param2Value >> 8) & 0xFF) == 0 } if param2IsByteSized { return asm, nil } } // Load high byte of param1 if c.param1IsVar { if c.param1VarKind == compiler.KindWord { asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) } else { asm = append(asm, "\tlda #0") } } else { hi := uint8((c.param1Value >> 8) & 0xFF) asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi)) } // OR with high byte of param2 (skip if literal 0, as ora #0 is a no-op) if c.param2IsVar { if c.param2VarKind == compiler.KindWord { asm = append(asm, fmt.Sprintf("\tora %s+1", c.param2VarName)) } // Skip ora when param2 is byte var (high byte is 0) } else { hi := uint8((c.param2Value >> 8) & 0xFF) if hi != 0 { asm = append(asm, fmt.Sprintf("\tora #$%02x", hi)) } // Skip ora when param2 const high byte is 0 } // Store high byte asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) } return asm, nil } func (c *OrCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear } func (c *OrCommand) GetName() string { return "OR" }