package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // IfCommand handles IF conditional statements // Syntax: // // IF # basic syntax // IF THEN # optional THEN keyword // // Supported operators (for now): =, ==, <>, != // More operators (>, <, >=, <=) can be added later // // Uses short jumps by default (inverted branch condition) // Uses long jumps if pragma _P_USE_LONG_JUMP is set type IfCommand struct { operator string // =, <>, etc. param1VarName string param1VarKind compiler.VarKind param1Value uint16 param1IsVar bool param2VarName string param2VarKind compiler.VarKind param2Value uint16 param2IsVar bool useLongJump bool skipLabel string } func (c *IfCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } return strings.ToUpper(params[0]) == "IF" } func (c *IfCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error { params, err := utils.ParseParams(line.Text) if err != nil { return err } paramCount := len(params) // IF [THEN] if paramCount != 4 && paramCount != 5 { return fmt.Errorf("IF: wrong number of parameters (%d), expected 4 or 5", paramCount) } // Check optional THEN keyword if paramCount == 5 { if strings.ToUpper(params[4]) != "THEN" { return fmt.Errorf("IF: parameter #5 must be 'THEN', got %q", params[4]) } } // Parse operator c.operator = params[2] switch c.operator { case "=", "==": c.operator = "=" // normalize case "<>", "!=": c.operator = "<>" // normalize default: return fmt.Errorf("IF: unsupported operator %q (only =, ==, <>, != supported for now)", c.operator) } 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 } // Parse param1 c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam( params[1], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("IF: 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("IF: param2: %w", err) } // Check pragma for long jump ps := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex) longJumpPragma := ps.GetPragma("_P_USE_LONG_JUMP") c.useLongJump = longJumpPragma != "" && longJumpPragma != "0" // Push skip label onto IF stack c.skipLabel = ctx.IfStack.Push() return nil } func (c *IfCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { switch c.operator { case "=": return c.generateEqual(ctx) case "<>": return c.generateNotEqual(ctx) default: return nil, fmt.Errorf("IF: internal error - unsupported operator %q", c.operator) } } // generateEqual generates code for == comparison func (c *IfCommand) generateEqual(ctx *compiler.CompilerContext) ([]string, error) { var asm []string // Constant folding: both literals if !c.param1IsVar && !c.param2IsVar { if c.param1Value != c.param2Value { // Always false - skip entire IF block asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) } // If equal, do nothing (condition always true) return asm, nil } // Generate comparison based on types if c.useLongJump { return c.generateEqualLongJump(ctx) } return c.generateEqualShortJump(ctx) } // generateEqualShortJump generates optimized short jumps (inverted condition) func (c *IfCommand) generateEqualShortJump(_ *compiler.CompilerContext) ([]string, error) { var asm []string // Determine effective types for comparison kind1, kind2 := c.param1VarKind, c.param2VarKind if !c.param1IsVar { kind1 = inferKindFromValue(c.param1Value) } if !c.param2IsVar { kind2 = inferKindFromValue(c.param2Value) } // byte == byte if kind1 == compiler.KindByte && kind2 == compiler.KindByte { if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value))) } // Inverted: if NOT equal, skip asm = append(asm, fmt.Sprintf("\tbne %s", c.skipLabel)) return asm, nil } // word == word if kind1 == compiler.KindWord && kind2 == compiler.KindWord { // Compare low bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value&0xFF))) } // If low bytes differ, skip asm = append(asm, fmt.Sprintf("\tbne %s", c.skipLabel)) // Compare high bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value>>8))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s+1", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value>>8))) } // If high bytes differ, skip asm = append(asm, fmt.Sprintf("\tbne %s", c.skipLabel)) return asm, nil } // Mixed byte/word comparisons - extend byte to word // byte == word or word == byte var byteVal uint16 var byteIsVar bool var byteName string var wordVal uint16 var wordIsVar bool var wordName string if kind1 == compiler.KindByte { byteVal, byteIsVar, byteName = c.param1Value, c.param1IsVar, c.param1VarName wordVal, wordIsVar, wordName = c.param2Value, c.param2IsVar, c.param2VarName } else { byteVal, byteIsVar, byteName = c.param2Value, c.param2IsVar, c.param2VarName wordVal, wordIsVar, wordName = c.param1Value, c.param1IsVar, c.param1VarName } // Check word high byte must be 0 if wordIsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", wordName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(wordVal>>8))) } asm = append(asm, "\tcmp #0") asm = append(asm, fmt.Sprintf("\tbne %s", c.skipLabel)) // Compare low bytes if byteIsVar { asm = append(asm, fmt.Sprintf("\tlda %s", byteName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(byteVal))) } if wordIsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", wordName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(wordVal&0xFF))) } asm = append(asm, fmt.Sprintf("\tbne %s", c.skipLabel)) return asm, nil } // generateEqualLongJump generates traditional long jumps (old style) func (c *IfCommand) generateEqualLongJump(ctx *compiler.CompilerContext) ([]string, error) { var asm []string successLabel := ctx.GeneralStack.Push() // temporary label // Similar logic but with inverted branches kind1, kind2 := c.param1VarKind, c.param2VarKind if !c.param1IsVar { kind1 = inferKindFromValue(c.param1Value) } if !c.param2IsVar { kind2 = inferKindFromValue(c.param2Value) } // byte == byte if kind1 == compiler.KindByte && kind2 == compiler.KindByte { if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value))) } asm = append(asm, fmt.Sprintf("\tbeq %s", successLabel)) asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // word == word if kind1 == compiler.KindWord && kind2 == compiler.KindWord { // Compare low bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value&0xFF))) } failLabel := ctx.GeneralStack.Push() asm = append(asm, fmt.Sprintf("\tbne %s", failLabel)) // Compare high bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value>>8))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s+1", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value>>8))) } asm = append(asm, fmt.Sprintf("\tbeq %s", successLabel)) asm = append(asm, failLabel) asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // Mixed comparisons similar to short jump var byteVal uint16 var byteIsVar bool var byteName string var wordVal uint16 var wordIsVar bool var wordName string if kind1 == compiler.KindByte { byteVal, byteIsVar, byteName = c.param1Value, c.param1IsVar, c.param1VarName wordVal, wordIsVar, wordName = c.param2Value, c.param2IsVar, c.param2VarName } else { byteVal, byteIsVar, byteName = c.param2Value, c.param2IsVar, c.param2VarName wordVal, wordIsVar, wordName = c.param1Value, c.param1IsVar, c.param1VarName } failLabel := ctx.GeneralStack.Push() // Check word high byte must be 0 if wordIsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", wordName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(wordVal>>8))) } asm = append(asm, "\tcmp #0") asm = append(asm, fmt.Sprintf("\tbne %s", failLabel)) // Compare low bytes if byteIsVar { asm = append(asm, fmt.Sprintf("\tlda %s", byteName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(byteVal))) } if wordIsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", wordName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(wordVal&0xFF))) } asm = append(asm, fmt.Sprintf("\tbeq %s", successLabel)) asm = append(asm, failLabel) asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // generateNotEqual generates code for != comparison func (c *IfCommand) generateNotEqual(ctx *compiler.CompilerContext) ([]string, error) { var asm []string // Constant folding: both literals if !c.param1IsVar && !c.param2IsVar { if c.param1Value == c.param2Value { // Always false - skip entire IF block asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) } // If not equal, do nothing (condition always true) return asm, nil } // Generate comparison based on types if c.useLongJump { return c.generateNotEqualLongJump(ctx) } return c.generateNotEqualShortJump(ctx) } // generateNotEqualShortJump generates optimized short jumps for != func (c *IfCommand) generateNotEqualShortJump(ctx *compiler.CompilerContext) ([]string, error) { var asm []string kind1, kind2 := c.param1VarKind, c.param2VarKind if !c.param1IsVar { kind1 = inferKindFromValue(c.param1Value) } if !c.param2IsVar { kind2 = inferKindFromValue(c.param2Value) } // byte != byte if kind1 == compiler.KindByte && kind2 == compiler.KindByte { if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value))) } // Inverted: if EQUAL, skip asm = append(asm, fmt.Sprintf("\tbeq %s", c.skipLabel)) return asm, nil } // word != word - need to check if ANY byte differs if kind1 == compiler.KindWord && kind2 == compiler.KindWord { successLabel := ctx.GeneralStack.Push() // Compare low bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value&0xFF))) } // If low bytes differ, condition is true - continue asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) // Compare high bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value>>8))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s+1", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value>>8))) } // If high bytes differ, condition is true - continue asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) // Both bytes equal - skip asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // Mixed byte/word - similar logic var byteVal uint16 var byteIsVar bool var byteName string var wordVal uint16 var wordIsVar bool var wordName string if kind1 == compiler.KindByte { byteVal, byteIsVar, byteName = c.param1Value, c.param1IsVar, c.param1VarName wordVal, wordIsVar, wordName = c.param2Value, c.param2IsVar, c.param2VarName } else { byteVal, byteIsVar, byteName = c.param2Value, c.param2IsVar, c.param2VarName wordVal, wordIsVar, wordName = c.param1Value, c.param1IsVar, c.param1VarName } successLabel := ctx.GeneralStack.Push() // Check word high byte != 0 means not equal if wordIsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", wordName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(wordVal>>8))) } asm = append(asm, "\tcmp #0") asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) // Compare low bytes if byteIsVar { asm = append(asm, fmt.Sprintf("\tlda %s", byteName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(byteVal))) } if wordIsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", wordName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(wordVal&0xFF))) } asm = append(asm, fmt.Sprintf("\tbeq %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // generateNotEqualLongJump generates traditional long jumps for != func (c *IfCommand) generateNotEqualLongJump(ctx *compiler.CompilerContext) ([]string, error) { var asm []string successLabel := ctx.GeneralStack.Push() kind1, kind2 := c.param1VarKind, c.param2VarKind if !c.param1IsVar { kind1 = inferKindFromValue(c.param1Value) } if !c.param2IsVar { kind2 = inferKindFromValue(c.param2Value) } // byte != byte if kind1 == compiler.KindByte && kind2 == compiler.KindByte { if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value))) } asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // word != word if kind1 == compiler.KindWord && kind2 == compiler.KindWord { // Compare low bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value&0xFF))) } asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) // Compare high bytes if c.param1IsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value>>8))) } if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tcmp %s+1", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(c.param2Value>>8))) } asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil } // Mixed byte/word var byteVal uint16 var byteIsVar bool var byteName string var wordVal uint16 var wordIsVar bool var wordName string if kind1 == compiler.KindByte { byteVal, byteIsVar, byteName = c.param1Value, c.param1IsVar, c.param1VarName wordVal, wordIsVar, wordName = c.param2Value, c.param2IsVar, c.param2VarName } else { byteVal, byteIsVar, byteName = c.param2Value, c.param2IsVar, c.param2VarName wordVal, wordIsVar, wordName = c.param1Value, c.param1IsVar, c.param1VarName } // Check word high byte != 0 if wordIsVar { asm = append(asm, fmt.Sprintf("\tlda %s+1", wordName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(wordVal>>8))) } asm = append(asm, "\tcmp #0") asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) // Compare low bytes if byteIsVar { asm = append(asm, fmt.Sprintf("\tlda %s", byteName)) } else { asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(byteVal))) } if wordIsVar { asm = append(asm, fmt.Sprintf("\tcmp %s", wordName)) } else { asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(wordVal&0xFF))) } asm = append(asm, fmt.Sprintf("\tbne %s", successLabel)) asm = append(asm, fmt.Sprintf("\tjmp %s", c.skipLabel)) asm = append(asm, successLabel) return asm, nil }