package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // CaseCommand handles CASE statements within SWITCH // Syntax: CASE type CaseCommand struct { caseValue *operandInfo } func (c *CaseCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } return strings.ToUpper(params[0]) == "CASE" } func (c *CaseCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error { params, err := utils.ParseParams(line.Text) if err != nil { return err } if len(params) != 2 { return fmt.Errorf("CASE: expected 2 parameters (CASE ), got %d", len(params)) } // Check if we're inside a SWITCH if ctx.SwitchStack.IsEmpty() { return fmt.Errorf("CASE: not inside a SWITCH statement") } scope := ctx.CurrentScope() 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 the case value varName, varKind, value, isVar, err := compiler.ParseOperandParam( params[1], ctx.SymbolTable, scope, constLookup) if err != nil { return fmt.Errorf("CASE: %w", err) } c.caseValue = &operandInfo{ varName: varName, varKind: varKind, value: value, isVar: isVar, } // Get switch info to validate type compatibility switchInfo, err := ctx.SwitchStack.Peek() if err != nil { return fmt.Errorf("CASE: %w", err) } // Error if case value is out of range for switch variable type if !isVar && !switchInfo.SwitchOperand.IsVar { // Both are constants/literals if switchInfo.SwitchOperand.VarKind == compiler.KindByte && value > 255 { return fmt.Errorf("CASE: constant value %d exceeds BYTE range (0-255)", value) } if switchInfo.SwitchOperand.VarKind == compiler.KindByte && value < 0 { return fmt.Errorf("CASE: constant value %d below BYTE range (0-255)", value) } } else if !isVar { // Case is literal, switch is variable if switchInfo.SwitchOperand.VarKind == compiler.KindByte && value > 255 { return fmt.Errorf("CASE: literal value %d will never match BYTE variable '%s' (valid range: 0-255)", value, switchInfo.SwitchOperand.VarName) } if switchInfo.SwitchOperand.VarKind == compiler.KindByte && value < 0 { return fmt.Errorf("CASE: literal value %d will never match BYTE variable '%s' (valid range: 0-255)", value, switchInfo.SwitchOperand.VarName) } } return nil } func (c *CaseCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) { switchInfo, err := ctx.SwitchStack.Peek() if err != nil { return nil, fmt.Errorf("CASE: %w", err) } // Check if DEFAULT has already been seen if switchInfo.HasDefault { return nil, fmt.Errorf("CASE: cannot have CASE after DEFAULT") } var asm []string // If there was a previous CASE, emit implicit break and skip label if switchInfo.NeedsPendingCode { // Implicit break: jump to end of switch asm = append(asm, fmt.Sprintf("\tjmp %s", switchInfo.EndLabel)) // Emit the pending skip label asm = append(asm, switchInfo.PendingSkipLabel) } // Push a new skip label onto the CaseSkipStack (like IF does with ctx.IfStack) skipLabel := ctx.CaseSkipStack.Push() // Convert switch operand to operandInfo switchOp := &operandInfo{ varName: switchInfo.SwitchOperand.VarName, varKind: switchInfo.SwitchOperand.VarKind, value: switchInfo.SwitchOperand.Value, isVar: switchInfo.SwitchOperand.IsVar, } // Generate comparison: if switch_var == case_value, execute case (don't jump) // Otherwise jump to skipLabel // comparisonGenerator jumps on FALSE, so we use opEqual: // - When equal (TRUE): don't jump, execute case // - When not equal (FALSE): jump to skip label gen, err := newComparisonGenerator( opEqual, switchOp, c.caseValue, switchInfo.UseLongJump, ctx.CaseSkipStack, ctx.GeneralStack, ) if err != nil { return nil, fmt.Errorf("CASE: %w", err) } cmpAsm, err := gen.generate() if err != nil { return nil, fmt.Errorf("CASE: %w", err) } asm = append(asm, cmpAsm...) // Mark that we need to emit break + skip label next time switchInfo.NeedsPendingCode = true switchInfo.PendingSkipLabel = skipLabel return asm, nil }