Added SWITCH/CASE/DEFAULT/ENDSWITCH
This commit is contained in:
parent
4319373828
commit
e33460d84d
13 changed files with 1647 additions and 5 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -30,3 +30,6 @@ c65gm
|
|||
|
||||
.claude/
|
||||
.npm/
|
||||
*.sym
|
||||
*.prg
|
||||
*.s
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache bash
|
||||
ENV SHELL=/bin/bash
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
CMD ["claude"]
|
||||
|
|
|
|||
20
examples/switch_demo/cm.sh
Executable file
20
examples/switch_demo/cm.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
# Define filename as variable
|
||||
PROGNAME="switch_demo"
|
||||
# Only set C65LIBPATH if not already defined
|
||||
if [ -z "$C65LIBPATH" ]; then
|
||||
export C65LIBPATH=$(readlink -f "../../lib")
|
||||
fi
|
||||
# Compile
|
||||
c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Compilation terminated"
|
||||
exit 1
|
||||
fi
|
||||
echo assemble.
|
||||
acme ${PROGNAME}.s
|
||||
if [ -f ${PROGNAME}.prg ]; then
|
||||
rm ${PROGNAME}.prg
|
||||
fi
|
||||
# main.bin ${PROGNAME}.prg
|
||||
mv main.bin main.prg
|
||||
1
examples/switch_demo/start_in_vice.sh
Normal file
1
examples/switch_demo/start_in_vice.sh
Normal file
|
|
@ -0,0 +1 @@
|
|||
x64 -autostartprgmode 1 main.prg
|
||||
256
examples/switch_demo/switch_demo.c65
Normal file
256
examples/switch_demo/switch_demo.c65
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
//-----------------------------------------------------------
|
||||
// SWITCH/CASE Statement Demo
|
||||
// Demonstrates the SWITCH/CASE control flow statement
|
||||
// with implicit breaks and long jump pragma support
|
||||
//-----------------------------------------------------------
|
||||
|
||||
#INCLUDE <c64start.c65>
|
||||
#INCLUDE <c64defs.c65>
|
||||
#INCLUDE <cbmiolib.c65>
|
||||
|
||||
#PRAGMA _P_USE_CBM_STRINGS 1
|
||||
|
||||
GOTO start
|
||||
|
||||
WORD result
|
||||
BYTE test_var
|
||||
|
||||
BYTE CONST TEST_VAL1 = 10
|
||||
BYTE CONST TEST_VAL2 = 20
|
||||
BYTE CONST TEST_VAL3 = 30
|
||||
BYTE CONST OFFSET = 5
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 1: Basic SWITCH with DEFAULT
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_basic_switch
|
||||
LET test_var = 2
|
||||
|
||||
SWITCH test_var
|
||||
CASE 1
|
||||
LET result = $0A
|
||||
CASE 2
|
||||
LET result = $14
|
||||
CASE 3
|
||||
LET result = $1E
|
||||
DEFAULT
|
||||
LET result = $63
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("1.basic: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 0014)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 2: SWITCH without DEFAULT
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_no_default
|
||||
LET test_var = 5
|
||||
LET result = 0
|
||||
|
||||
SWITCH test_var
|
||||
CASE 1
|
||||
LET result = $64
|
||||
CASE 5
|
||||
LET result = $01F4
|
||||
CASE 10
|
||||
LET result = $03E8
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("2.no default: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 01f4)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 3: Nested SWITCH statements
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_nested_switch
|
||||
BYTE outer
|
||||
BYTE inner
|
||||
|
||||
LET outer = 2
|
||||
LET inner = 3
|
||||
LET result = 0
|
||||
|
||||
SWITCH outer
|
||||
CASE 1
|
||||
LET result = $01
|
||||
CASE 2
|
||||
SWITCH inner
|
||||
CASE 2
|
||||
LET result = $16
|
||||
CASE 3
|
||||
LET result = $17
|
||||
DEFAULT
|
||||
LET result = $14
|
||||
ENDSWITCH
|
||||
CASE 3
|
||||
LET result = $03
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("3.nested: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 0017)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 4: SWITCH with long jump pragma
|
||||
// (for cases where branches are far apart)
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_long_jump
|
||||
#PRAGMA _P_USE_LONG_JUMP 1
|
||||
|
||||
LET test_var = 3
|
||||
|
||||
SWITCH test_var
|
||||
CASE 1
|
||||
LET result = $0B
|
||||
CASE 2
|
||||
LET result = $16
|
||||
CASE 3
|
||||
LET result = $21
|
||||
CASE 4
|
||||
LET result = $2C
|
||||
DEFAULT
|
||||
LET result = $00
|
||||
ENDSWITCH
|
||||
|
||||
#PRAGMA _P_USE_LONG_JUMP 0
|
||||
|
||||
lib_cbmio_print("4.long jump: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 0021)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 5: SWITCH with WORD values
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_word_switch
|
||||
WORD big_value
|
||||
|
||||
LET big_value = 1000
|
||||
LET result = 0
|
||||
|
||||
SWITCH big_value
|
||||
CASE 100
|
||||
LET result = $0001
|
||||
CASE 1000
|
||||
LET result = $0002
|
||||
CASE 10000
|
||||
LET result = $0003
|
||||
DEFAULT
|
||||
LET result = $0063
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("5.word: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 0002)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 6: SWITCH with constants and compile-time evaluation
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_constants
|
||||
LET test_var = 25
|
||||
LET result = 0
|
||||
|
||||
SWITCH test_var
|
||||
CASE TEST_VAL1
|
||||
LET result = $01
|
||||
CASE TEST_VAL2
|
||||
LET result = $02
|
||||
CASE TEST_VAL2+OFFSET
|
||||
LET result = $03
|
||||
CASE TEST_VAL3
|
||||
LET result = $04
|
||||
DEFAULT
|
||||
LET result = $63
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("6.constants: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 0003)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 7: SWITCH with variable cases (not just literals)
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_variables
|
||||
BYTE match_val1
|
||||
BYTE match_val2
|
||||
WORD match_val3
|
||||
|
||||
LET match_val1 = 15
|
||||
LET match_val2 = 42
|
||||
LET match_val3 = 1000
|
||||
|
||||
LET test_var = 42
|
||||
LET result = 0
|
||||
|
||||
SWITCH test_var
|
||||
CASE match_val1
|
||||
LET result = $01
|
||||
CASE match_val2
|
||||
LET result = $02
|
||||
CASE match_val3
|
||||
LET result = $03
|
||||
DEFAULT
|
||||
LET result = $63
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("7.variables: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 0002)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Test 8: SWITCH that actually executes DEFAULT
|
||||
//-----------------------------------------------------------
|
||||
FUNC test_default_execution
|
||||
LET test_var = 99
|
||||
LET result = 0
|
||||
|
||||
SWITCH test_var
|
||||
CASE 1
|
||||
LET result = $0A
|
||||
CASE 2
|
||||
LET result = $14
|
||||
CASE 3
|
||||
LET result = $1E
|
||||
DEFAULT
|
||||
LET result = $03E7
|
||||
ENDSWITCH
|
||||
|
||||
lib_cbmio_print("8.default exec: ")
|
||||
lib_cbmio_hexoutw(result)
|
||||
lib_cbmio_printlf(" (exp 03e7)")
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Main program
|
||||
//-----------------------------------------------------------
|
||||
FUNC main
|
||||
lib_cbmio_cls()
|
||||
|
||||
lib_cbmio_printlf("switch/case demo")
|
||||
lib_cbmio_lf()
|
||||
|
||||
test_basic_switch()
|
||||
test_no_default()
|
||||
test_nested_switch()
|
||||
test_long_jump()
|
||||
test_word_switch()
|
||||
test_constants()
|
||||
test_variables()
|
||||
test_default_execution()
|
||||
|
||||
lib_cbmio_lf()
|
||||
lib_cbmio_printlf("all tests complete!")
|
||||
|
||||
FEND
|
||||
|
||||
LABEL start
|
||||
main()
|
||||
SUBEND
|
||||
156
internal/commands/case.go
Normal file
156
internal/commands/case.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// CaseCommand handles CASE statements within SWITCH
|
||||
// Syntax: CASE <value>
|
||||
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 <value>), 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
|
||||
}
|
||||
78
internal/commands/default.go
Normal file
78
internal/commands/default.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// DefaultCommand handles DEFAULT statements within SWITCH
|
||||
// Syntax: DEFAULT
|
||||
type DefaultCommand struct {
|
||||
}
|
||||
|
||||
func (c *DefaultCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.ToUpper(params[0]) == "DEFAULT"
|
||||
}
|
||||
|
||||
func (c *DefaultCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(params) != 1 {
|
||||
return fmt.Errorf("DEFAULT: expected 1 parameter (DEFAULT), got %d", len(params))
|
||||
}
|
||||
|
||||
// Check if we're inside a SWITCH
|
||||
if ctx.SwitchStack.IsEmpty() {
|
||||
return fmt.Errorf("DEFAULT: not inside a SWITCH statement")
|
||||
}
|
||||
|
||||
switchInfo, err := ctx.SwitchStack.Peek()
|
||||
if err != nil {
|
||||
return fmt.Errorf("DEFAULT: %w", err)
|
||||
}
|
||||
|
||||
// Check if DEFAULT has already been seen
|
||||
if switchInfo.HasDefault {
|
||||
return fmt.Errorf("DEFAULT: multiple DEFAULT statements in same SWITCH")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DefaultCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
switchInfo, err := ctx.SwitchStack.Peek()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DEFAULT: %w", err)
|
||||
}
|
||||
|
||||
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 (where previous case jumps if not matched)
|
||||
asm = append(asm, switchInfo.PendingSkipLabel)
|
||||
}
|
||||
|
||||
// Mark that we've seen DEFAULT
|
||||
switchInfo.HasDefault = true
|
||||
|
||||
// DEFAULT doesn't need a skip label, so clear pending code
|
||||
switchInfo.NeedsPendingCode = false
|
||||
switchInfo.PendingSkipLabel = ""
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
60
internal/commands/endswitch.go
Normal file
60
internal/commands/endswitch.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// EndSwitchCommand handles ENDSWITCH statements
|
||||
// Syntax: ENDSWITCH
|
||||
type EndSwitchCommand struct {
|
||||
}
|
||||
|
||||
func (c *EndSwitchCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.ToUpper(params[0]) == "ENDSWITCH"
|
||||
}
|
||||
|
||||
func (c *EndSwitchCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(params) != 1 {
|
||||
return fmt.Errorf("ENDSWITCH: expected 1 parameter (ENDSWITCH), got %d", len(params))
|
||||
}
|
||||
|
||||
// Check if we're inside a SWITCH
|
||||
if ctx.SwitchStack.IsEmpty() {
|
||||
return fmt.Errorf("ENDSWITCH: not inside a SWITCH statement")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *EndSwitchCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
switchInfo, err := ctx.SwitchStack.Pop()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ENDSWITCH: %w", err)
|
||||
}
|
||||
|
||||
var asm []string
|
||||
|
||||
// If there's pending code (last CASE without DEFAULT), emit skip label
|
||||
if switchInfo.NeedsPendingCode {
|
||||
asm = append(asm, switchInfo.PendingSkipLabel)
|
||||
}
|
||||
|
||||
// Emit the ENDSWITCH label (where all breaks jump to)
|
||||
asm = append(asm, switchInfo.EndLabel)
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
82
internal/commands/switch.go
Normal file
82
internal/commands/switch.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// SwitchCommand handles SWITCH statements
|
||||
// Syntax: SWITCH <variable>
|
||||
type SwitchCommand struct {
|
||||
}
|
||||
|
||||
func (c *SwitchCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.ToUpper(params[0]) == "SWITCH"
|
||||
}
|
||||
|
||||
func (c *SwitchCommand) 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("SWITCH: expected 2 parameters (SWITCH <variable>), got %d", len(params))
|
||||
}
|
||||
|
||||
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 switch variable/expression
|
||||
varName, varKind, value, isVar, err := compiler.ParseOperandParam(
|
||||
params[1], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SWITCH: %w", err)
|
||||
}
|
||||
|
||||
// Check pragma for long jumps
|
||||
ps := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
||||
longJumpPragma := ps.GetPragma("_P_USE_LONG_JUMP")
|
||||
useLongJump := longJumpPragma != "" && longJumpPragma != "0"
|
||||
|
||||
// Create end label
|
||||
endLabel := ctx.GeneralStack.Push()
|
||||
|
||||
// Create and push switch info
|
||||
switchInfo := &compiler.SwitchInfo{
|
||||
SwitchOperand: &compiler.OperandInfo{
|
||||
VarName: varName,
|
||||
VarKind: varKind,
|
||||
Value: value,
|
||||
IsVar: isVar,
|
||||
},
|
||||
EndLabel: endLabel,
|
||||
NeedsPendingCode: false,
|
||||
PendingSkipLabel: "",
|
||||
HasDefault: false,
|
||||
UseLongJump: useLongJump,
|
||||
}
|
||||
|
||||
ctx.SwitchStack.Push(switchInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SwitchCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
// SWITCH itself generates no assembly code
|
||||
// The variable will be loaded and compared by each CASE
|
||||
return []string{}, nil
|
||||
}
|
||||
918
internal/commands/switch_test.go
Normal file
918
internal/commands/switch_test.go
Normal file
|
|
@ -0,0 +1,918 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestSwitchBasicByte(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
caseValue string
|
||||
wantSwitch []string
|
||||
wantCase []string
|
||||
wantEndswitch []string
|
||||
}{
|
||||
{
|
||||
name: "byte var with byte literal case",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindByte, 0)
|
||||
},
|
||||
caseValue: "10",
|
||||
wantSwitch: []string{},
|
||||
wantCase: []string{
|
||||
"\tlda x",
|
||||
"\tcmp #$0a",
|
||||
"\tbne ",
|
||||
},
|
||||
wantEndswitch: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
caseCmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
switchAsm, err := switchCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("SWITCH Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := caseCmd.Interpret(preproc.Line{Text: "CASE " + tt.caseValue, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
caseAsm, err := caseCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("ENDSWITCH Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
endswitchAsm, err := endswitchCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ENDSWITCH Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmSwitch(switchAsm, tt.wantSwitch) {
|
||||
t.Errorf("SWITCH Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(switchAsm, "\n"),
|
||||
strings.Join(tt.wantSwitch, "\n"))
|
||||
}
|
||||
|
||||
// For CASE, check that expected instructions are present
|
||||
if !containsInstructions(caseAsm, tt.wantCase) {
|
||||
t.Errorf("CASE Generate() missing expected instructions\ngot:\n%s\nwant to contain:\n%s",
|
||||
strings.Join(caseAsm, "\n"),
|
||||
strings.Join(tt.wantCase, "\n"))
|
||||
}
|
||||
|
||||
// ENDSWITCH should emit at least one label
|
||||
if len(endswitchAsm) == 0 {
|
||||
t.Error("ENDSWITCH should generate at least end label")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchMultipleCases(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
case2Cmd := &CaseCommand{}
|
||||
case3Cmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
if err := case1Cmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 1 error = %v", err)
|
||||
}
|
||||
case1Asm, _ := case1Cmd.Generate(ctx)
|
||||
|
||||
if err := case2Cmd.Interpret(preproc.Line{Text: "CASE 2", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 2 error = %v", err)
|
||||
}
|
||||
case2Asm, _ := case2Cmd.Generate(ctx)
|
||||
|
||||
if err := case3Cmd.Interpret(preproc.Line{Text: "CASE 3", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 3 error = %v", err)
|
||||
}
|
||||
_, _ = case3Cmd.Generate(ctx)
|
||||
|
||||
if err := endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("ENDSWITCH error = %v", err)
|
||||
}
|
||||
endswitchAsm, _ := endswitchCmd.Generate(ctx)
|
||||
|
||||
// First CASE should not have JMP at the beginning
|
||||
if len(case1Asm) > 0 && strings.Contains(case1Asm[0], "jmp") {
|
||||
t.Error("First CASE should not start with JMP")
|
||||
}
|
||||
|
||||
// Second CASE should have implicit break (JMP) from previous case
|
||||
foundJmp := false
|
||||
for _, line := range case2Asm {
|
||||
if strings.Contains(line, "jmp") {
|
||||
foundJmp = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundJmp {
|
||||
t.Error("Second CASE should have implicit break JMP from previous case")
|
||||
}
|
||||
|
||||
// ENDSWITCH should have end label
|
||||
if len(endswitchAsm) == 0 {
|
||||
t.Error("ENDSWITCH should have end label")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchWithDefault(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
defaultCmd := &DefaultCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
if err := case1Cmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE error = %v", err)
|
||||
}
|
||||
case1Cmd.Generate(ctx)
|
||||
|
||||
if err := defaultCmd.Interpret(preproc.Line{Text: "DEFAULT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("DEFAULT error = %v", err)
|
||||
}
|
||||
defaultAsm, err := defaultCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("DEFAULT Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("ENDSWITCH error = %v", err)
|
||||
}
|
||||
endswitchCmd.Generate(ctx)
|
||||
|
||||
// DEFAULT should emit implicit break and skip label
|
||||
if len(defaultAsm) == 0 {
|
||||
t.Error("DEFAULT should emit code for implicit break")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchCaseAfterDefault(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
defaultCmd := &DefaultCommand{}
|
||||
case2Cmd := &CaseCommand{}
|
||||
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
case1Cmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
case1Cmd.Generate(ctx)
|
||||
|
||||
defaultCmd.Interpret(preproc.Line{Text: "DEFAULT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
defaultCmd.Generate(ctx)
|
||||
|
||||
// Try to add CASE after DEFAULT - should fail
|
||||
if err := case2Cmd.Interpret(preproc.Line{Text: "CASE 2", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
// This is expected to fail during Interpret
|
||||
return
|
||||
}
|
||||
|
||||
_, err := case2Cmd.Generate(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("CASE after DEFAULT should fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "after DEFAULT") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchMultipleDefaults(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
default1Cmd := &DefaultCommand{}
|
||||
default2Cmd := &DefaultCommand{}
|
||||
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
case1Cmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
case1Cmd.Generate(ctx)
|
||||
|
||||
default1Cmd.Interpret(preproc.Line{Text: "DEFAULT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
default1Cmd.Generate(ctx)
|
||||
|
||||
// Try to add second DEFAULT - should fail
|
||||
err := default2Cmd.Interpret(preproc.Line{Text: "DEFAULT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Multiple DEFAULT statements should fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "multiple DEFAULT") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchWithoutSwitch(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
caseCmd := &CaseCommand{}
|
||||
err := caseCmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("CASE without SWITCH should fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not inside") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultWithoutSwitch(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
defaultCmd := &DefaultCommand{}
|
||||
err := defaultCmd.Interpret(preproc.Line{Text: "DEFAULT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("DEFAULT without SWITCH should fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not inside") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndswitchWithoutSwitch(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
err := endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("ENDSWITCH without SWITCH should fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not inside") {
|
||||
t.Errorf("Wrong error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchWrongParamCount(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
tests := []string{
|
||||
"SWITCH",
|
||||
"SWITCH x y",
|
||||
}
|
||||
|
||||
for _, text := range tests {
|
||||
cmd := &SwitchCommand{}
|
||||
err := cmd.Interpret(preproc.Line{Text: text, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Errorf("Should fail with wrong param count: %s", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseWrongParamCount(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
tests := []string{
|
||||
"CASE",
|
||||
"CASE 1 2",
|
||||
}
|
||||
|
||||
for _, text := range tests {
|
||||
cmd := &CaseCommand{}
|
||||
err := cmd.Interpret(preproc.Line{Text: text, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Errorf("Should fail with wrong param count: %s", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultWrongParamCount(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
cmd := &DefaultCommand{}
|
||||
err := cmd.Interpret(preproc.Line{Text: "DEFAULT extra", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Error("DEFAULT with extra params should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndswitchWrongParamCount(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
cmd := &EndSwitchCommand{}
|
||||
err := cmd.Interpret(preproc.Line{Text: "ENDSWITCH extra", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if err == nil {
|
||||
t.Error("ENDSWITCH with extra params should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchWordType(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("big_val", "", compiler.KindWord, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
caseCmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH big_val", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
if err := caseCmd.Interpret(preproc.Line{Text: "CASE 1000", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE error = %v", err)
|
||||
}
|
||||
caseAsm, err := caseCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE Generate() error = %v", err)
|
||||
}
|
||||
|
||||
endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
endswitchCmd.Generate(ctx)
|
||||
|
||||
// Should have high byte check for word
|
||||
foundHighByteCheck := false
|
||||
for _, inst := range caseAsm {
|
||||
if strings.Contains(inst, "big_val+1") {
|
||||
foundHighByteCheck = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundHighByteCheck {
|
||||
t.Error("Expected high byte check for word comparison")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchWithConstant(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddConst("MAX_VAL", "", compiler.KindByte, 100)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
caseCmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
if err := caseCmd.Interpret(preproc.Line{Text: "CASE MAX_VAL", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE error = %v", err)
|
||||
}
|
||||
caseAsm, err := caseCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE Generate() error = %v", err)
|
||||
}
|
||||
|
||||
endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
endswitchCmd.Generate(ctx)
|
||||
|
||||
// Constant should be folded to immediate value
|
||||
found := false
|
||||
for _, inst := range caseAsm {
|
||||
if strings.Contains(inst, "#$64") { // 100 = 0x64
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Constant should be folded to immediate value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchNested(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("outer", "", compiler.KindByte, 0)
|
||||
ctx.SymbolTable.AddVar("inner", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switch1Cmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
switch2Cmd := &SwitchCommand{}
|
||||
case2Cmd := &CaseCommand{}
|
||||
endswitch2Cmd := &EndSwitchCommand{}
|
||||
endswitch1Cmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switch1Cmd.Interpret(preproc.Line{Text: "SWITCH outer", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH 1 error = %v", err)
|
||||
}
|
||||
switch1Asm, _ := switch1Cmd.Generate(ctx)
|
||||
|
||||
if err := case1Cmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 1 error = %v", err)
|
||||
}
|
||||
case1Cmd.Generate(ctx)
|
||||
|
||||
if err := switch2Cmd.Interpret(preproc.Line{Text: "SWITCH inner", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH 2 error = %v", err)
|
||||
}
|
||||
switch2Asm, _ := switch2Cmd.Generate(ctx)
|
||||
|
||||
if err := case2Cmd.Interpret(preproc.Line{Text: "CASE 2", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 2 error = %v", err)
|
||||
}
|
||||
case2Cmd.Generate(ctx)
|
||||
|
||||
if err := endswitch2Cmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("ENDSWITCH 2 error = %v", err)
|
||||
}
|
||||
endswitch2Asm, _ := endswitch2Cmd.Generate(ctx)
|
||||
|
||||
if err := endswitch1Cmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("ENDSWITCH 1 error = %v", err)
|
||||
}
|
||||
endswitch1Asm, _ := endswitch1Cmd.Generate(ctx)
|
||||
|
||||
// Both switches should generate assembly
|
||||
if len(switch1Asm) < 0 || len(switch2Asm) < 0 {
|
||||
// SWITCHes don't generate asm, just setup state
|
||||
}
|
||||
|
||||
// Both ENDSWITCHes should generate labels
|
||||
if len(endswitch1Asm) == 0 || len(endswitch2Asm) == 0 {
|
||||
t.Error("Nested switches should both generate end labels")
|
||||
}
|
||||
|
||||
// Labels should be different
|
||||
label1 := ""
|
||||
label2 := ""
|
||||
if len(endswitch1Asm) > 0 {
|
||||
label1 = endswitch1Asm[len(endswitch1Asm)-1]
|
||||
}
|
||||
if len(endswitch2Asm) > 0 {
|
||||
label2 = endswitch2Asm[len(endswitch2Asm)-1]
|
||||
}
|
||||
|
||||
if label1 == label2 && label1 != "" {
|
||||
t.Error("Nested switches should have different end labels")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchLongJump(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
pragma.AddPragma("_P_USE_LONG_JUMP", "1")
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
if err := case1Cmd.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 1 error = %v", err)
|
||||
}
|
||||
case1Asm, _ := case1Cmd.Generate(ctx)
|
||||
|
||||
endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
endswitchCmd.Generate(ctx)
|
||||
|
||||
// In long jump mode, comparison should have pattern: short branch + jmp + label
|
||||
// Look for both a short branch (beq/bne) and a jmp to _SKIPCASE
|
||||
foundShortBranch := false
|
||||
foundJmpToSkip := false
|
||||
foundLabel := false
|
||||
|
||||
for _, inst := range case1Asm {
|
||||
if strings.Contains(inst, "beq ") || strings.Contains(inst, "bne ") {
|
||||
foundShortBranch = true
|
||||
}
|
||||
if strings.Contains(inst, "jmp ") && strings.Contains(inst, "_SKIPCASE") {
|
||||
foundJmpToSkip = true
|
||||
}
|
||||
// Labels don't start with tab
|
||||
trimmed := strings.TrimSpace(inst)
|
||||
if !strings.HasPrefix(inst, "\t") && strings.Contains(inst, "_") && len(trimmed) > 0 {
|
||||
foundLabel = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundShortBranch {
|
||||
t.Error("Long jump mode should have short branch (beq/bne)")
|
||||
}
|
||||
if !foundJmpToSkip {
|
||||
t.Error("Long jump mode should have JMP to skip label in comparison")
|
||||
}
|
||||
if !foundLabel {
|
||||
t.Error("Long jump mode should have success label in comparison")
|
||||
}
|
||||
|
||||
// Verify the pattern is different from normal mode
|
||||
pragmaNormal := preproc.NewPragma()
|
||||
ctxNormal := compiler.NewCompilerContext(pragmaNormal)
|
||||
ctxNormal.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdxNormal := pragmaNormal.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmdNormal := &SwitchCommand{}
|
||||
caseCmdNormal := &CaseCommand{}
|
||||
|
||||
switchCmdNormal.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdxNormal}, ctxNormal)
|
||||
switchCmdNormal.Generate(ctxNormal)
|
||||
|
||||
caseCmdNormal.Interpret(preproc.Line{Text: "CASE 1", Kind: preproc.Source, PragmaSetIndex: pragmaIdxNormal}, ctxNormal)
|
||||
normalAsm, _ := caseCmdNormal.Generate(ctxNormal)
|
||||
|
||||
// Normal mode should NOT have jmp in comparison (only short branch)
|
||||
normalHasJmp := false
|
||||
for _, inst := range normalAsm {
|
||||
if strings.Contains(inst, "\tjmp") {
|
||||
normalHasJmp = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if normalHasJmp {
|
||||
t.Error("Normal mode should not have JMP in comparison code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchOnConstant(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddConst("VALUE", "", compiler.KindByte, 5)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
case1Cmd := &CaseCommand{}
|
||||
case2Cmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH VALUE", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH on constant error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
// CASE with matching constant - should be optimized away (constant folding)
|
||||
if err := case1Cmd.Interpret(preproc.Line{Text: "CASE 5", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 5 error = %v", err)
|
||||
}
|
||||
case1Asm, err := case1Cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE 5 Generate() error = %v", err)
|
||||
}
|
||||
|
||||
// With constant folding, matching constant case generates no comparison code (optimization)
|
||||
if len(case1Asm) != 0 {
|
||||
t.Errorf("CASE with matching constant should be optimized away, got: %v", case1Asm)
|
||||
}
|
||||
|
||||
// CASE with non-matching constant - should generate JMP to skip
|
||||
if err := case2Cmd.Interpret(preproc.Line{Text: "CASE 10", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("CASE 10 error = %v", err)
|
||||
}
|
||||
case2Asm, err := case2Cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE 10 Generate() error = %v", err)
|
||||
}
|
||||
|
||||
// Non-matching constant should generate JMP to skip this case
|
||||
if len(case2Asm) == 0 {
|
||||
t.Error("CASE with non-matching constant should generate skip code")
|
||||
}
|
||||
foundJmp := false
|
||||
for _, line := range case2Asm {
|
||||
if strings.Contains(line, "jmp") {
|
||||
foundJmp = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundJmp {
|
||||
t.Error("Non-matching constant case should have JMP to skip")
|
||||
}
|
||||
|
||||
endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
endswitchCmd.Generate(ctx)
|
||||
}
|
||||
|
||||
func TestSwitchEmptyWithOnlyDefault(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
defaultCmd := &DefaultCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
if err := defaultCmd.Interpret(preproc.Line{Text: "DEFAULT", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("DEFAULT error = %v", err)
|
||||
}
|
||||
defaultAsm, err := defaultCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("DEFAULT Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if err := endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("ENDSWITCH error = %v", err)
|
||||
}
|
||||
endswitchAsm, err := endswitchCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ENDSWITCH Generate() error = %v", err)
|
||||
}
|
||||
|
||||
// DEFAULT without previous CASE should not emit JMP
|
||||
hasJmp := false
|
||||
for _, line := range defaultAsm {
|
||||
if strings.Contains(line, "jmp") {
|
||||
hasJmp = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasJmp {
|
||||
t.Error("DEFAULT without previous CASE should not emit JMP")
|
||||
}
|
||||
|
||||
// ENDSWITCH should still emit end label
|
||||
if len(endswitchAsm) == 0 {
|
||||
t.Error("ENDSWITCH should emit end label")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchComparisonTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
varType compiler.VarKind
|
||||
caseValue string
|
||||
shouldWork bool
|
||||
}{
|
||||
{"byte var byte case", compiler.KindByte, "10", true},
|
||||
{"word var byte case", compiler.KindWord, "10", true},
|
||||
{"byte var word case", compiler.KindByte, "1000", false},
|
||||
{"word var word case", compiler.KindWord, "1000", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", tt.varType, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
caseCmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
err := caseCmd.Interpret(preproc.Line{Text: "CASE " + tt.caseValue, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if tt.shouldWork && err != nil {
|
||||
t.Fatalf("CASE Interpret() unexpected error = %v", err)
|
||||
}
|
||||
if !tt.shouldWork && err == nil {
|
||||
t.Fatalf("CASE Interpret() should have failed but didn't")
|
||||
}
|
||||
|
||||
if tt.shouldWork {
|
||||
_, err := caseCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE Generate() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
endswitchCmd.Generate(ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchWithVariableCase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
switchType compiler.VarKind
|
||||
caseType compiler.VarKind
|
||||
shouldWork bool
|
||||
}{
|
||||
{"byte switch byte case", compiler.KindByte, compiler.KindByte, true},
|
||||
{"byte switch word case", compiler.KindByte, compiler.KindWord, true},
|
||||
{"word switch byte case", compiler.KindWord, compiler.KindByte, true},
|
||||
{"word switch word case", compiler.KindWord, compiler.KindWord, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("switch_var", "", tt.switchType, 0)
|
||||
ctx.SymbolTable.AddVar("case_var", "", tt.caseType, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
caseCmd := &CaseCommand{}
|
||||
endswitchCmd := &EndSwitchCommand{}
|
||||
|
||||
if err := switchCmd.Interpret(preproc.Line{Text: "SWITCH switch_var", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx); err != nil {
|
||||
t.Fatalf("SWITCH error = %v", err)
|
||||
}
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
err := caseCmd.Interpret(preproc.Line{Text: "CASE case_var", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
if tt.shouldWork && err != nil {
|
||||
t.Fatalf("CASE with variable unexpected error = %v", err)
|
||||
}
|
||||
if !tt.shouldWork && err == nil {
|
||||
t.Fatal("CASE with variable should have failed but didn't")
|
||||
}
|
||||
|
||||
if tt.shouldWork {
|
||||
caseAsm, err := caseCmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CASE Generate() error = %v", err)
|
||||
}
|
||||
// Verify assembly was generated for comparison
|
||||
if len(caseAsm) == 0 {
|
||||
t.Error("CASE with variable should generate comparison code")
|
||||
}
|
||||
}
|
||||
|
||||
endswitchCmd.Interpret(preproc.Line{Text: "ENDSWITCH", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
endswitchCmd.Generate(ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchByteRangeValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
caseValue string
|
||||
expectError bool
|
||||
errorContains string
|
||||
}{
|
||||
{"valid byte 0", "0", false, ""},
|
||||
{"valid byte 255", "255", false, ""},
|
||||
{"valid byte 100", "100", false, ""},
|
||||
{"out of range 256", "256", true, "will never match BYTE variable"},
|
||||
{"out of range 1000", "1000", true, "will never match BYTE variable"},
|
||||
{"out of range 10000", "10000", true, "will never match BYTE variable"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
||||
|
||||
pragmaIdx := pragma.GetCurrentPragmaSetIndex()
|
||||
|
||||
switchCmd := &SwitchCommand{}
|
||||
caseCmd := &CaseCommand{}
|
||||
|
||||
switchCmd.Interpret(preproc.Line{Text: "SWITCH x", Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
switchCmd.Generate(ctx)
|
||||
|
||||
err := caseCmd.Interpret(preproc.Line{Text: "CASE " + tt.caseValue, Kind: preproc.Source, PragmaSetIndex: pragmaIdx}, ctx)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for CASE %s but got none", tt.caseValue)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("Error message '%s' should contain '%s'", err.Error(), tt.errorContains)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error for CASE %s: %v", tt.caseValue, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to compare assembly output
|
||||
func equalAsmSwitch(got, want []string) bool {
|
||||
if len(got) != len(want) {
|
||||
return false
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Helper to check if assembly contains expected instructions
|
||||
func containsInstructions(asm []string, expected []string) bool {
|
||||
for _, exp := range expected {
|
||||
found := false
|
||||
for _, line := range asm {
|
||||
if strings.Contains(line, strings.TrimSpace(exp)) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -16,11 +16,13 @@ type CompilerContext struct {
|
|||
ConstStrHandler *ConstantStringHandler
|
||||
|
||||
// Label stacks for control flow
|
||||
LoopStartStack *LabelStack // Start of loop (like WHILE)
|
||||
LoopEndStack *LabelStack // WHILE...WEND
|
||||
IfStack *LabelStack // IF...ENDIF
|
||||
GeneralStack *LabelStack // General purpose (GOSUB, etc)
|
||||
ForStack *ForStack // For loop stack
|
||||
LoopStartStack *LabelStack // Start of loop (like WHILE)
|
||||
LoopEndStack *LabelStack // WHILE...WEND
|
||||
IfStack *LabelStack // IF...ENDIF
|
||||
GeneralStack *LabelStack // General purpose (GOSUB, etc)
|
||||
ForStack *ForStack // For loop stack
|
||||
SwitchStack *SwitchStack // Switch/case stack
|
||||
CaseSkipStack *LabelStack // SWITCH/CASE skip labels
|
||||
|
||||
// Pragma access for per-line pragma lookup
|
||||
Pragma *preproc.Pragma
|
||||
|
|
@ -40,6 +42,8 @@ func NewCompilerContext(pragma *preproc.Pragma) *CompilerContext {
|
|||
IfStack: NewLabelStack("_I"),
|
||||
GeneralStack: generalStack,
|
||||
ForStack: NewForStack(),
|
||||
SwitchStack: NewSwitchStack(),
|
||||
CaseSkipStack: NewLabelStack("_SKIPCASE"),
|
||||
Pragma: pragma,
|
||||
}
|
||||
|
||||
|
|
|
|||
58
internal/compiler/switchstack.go
Normal file
58
internal/compiler/switchstack.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package compiler
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SwitchInfo stores information about a SWITCH statement
|
||||
type SwitchInfo struct {
|
||||
SwitchOperand *OperandInfo // The expression being switched on
|
||||
EndLabel string // Label for end of switch (_ENDSWITCH)
|
||||
NeedsPendingCode bool // True if we need to emit implicit break + skip label
|
||||
PendingSkipLabel string // The skip label that needs to be emitted
|
||||
HasDefault bool // True if DEFAULT has been encountered
|
||||
UseLongJump bool // Whether to use long jumps
|
||||
}
|
||||
|
||||
// SwitchStack manages the stack of SWITCH contexts
|
||||
type SwitchStack struct {
|
||||
stack []*SwitchInfo
|
||||
}
|
||||
|
||||
// NewSwitchStack creates a new SwitchStack
|
||||
func NewSwitchStack() *SwitchStack {
|
||||
return &SwitchStack{
|
||||
stack: make([]*SwitchInfo, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Push adds a new SWITCH context to the stack
|
||||
func (ss *SwitchStack) Push(info *SwitchInfo) {
|
||||
ss.stack = append(ss.stack, info)
|
||||
}
|
||||
|
||||
// Peek returns the top SWITCH context without removing it
|
||||
func (ss *SwitchStack) Peek() (*SwitchInfo, error) {
|
||||
if len(ss.stack) == 0 {
|
||||
return nil, fmt.Errorf("stack underflow: SWITCH stack is empty")
|
||||
}
|
||||
return ss.stack[len(ss.stack)-1], nil
|
||||
}
|
||||
|
||||
// Pop removes and returns the top SWITCH context
|
||||
func (ss *SwitchStack) Pop() (*SwitchInfo, error) {
|
||||
if len(ss.stack) == 0 {
|
||||
return nil, fmt.Errorf("stack underflow: SWITCH stack is empty")
|
||||
}
|
||||
info := ss.stack[len(ss.stack)-1]
|
||||
ss.stack = ss.stack[:len(ss.stack)-1]
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the stack is empty
|
||||
func (ss *SwitchStack) IsEmpty() bool {
|
||||
return len(ss.stack) == 0
|
||||
}
|
||||
|
||||
// Size returns the number of items on the stack
|
||||
func (ss *SwitchStack) Size() int {
|
||||
return len(ss.stack)
|
||||
}
|
||||
4
main.go
4
main.go
|
|
@ -107,6 +107,10 @@ func registerCommands(comp *compiler.Compiler) {
|
|||
comp.Registry().Register(&commands.GosubCommand{})
|
||||
comp.Registry().Register(&commands.ForCommand{})
|
||||
comp.Registry().Register(&commands.NextCommand{})
|
||||
comp.Registry().Register(&commands.SwitchCommand{})
|
||||
comp.Registry().Register(&commands.CaseCommand{})
|
||||
comp.Registry().Register(&commands.DefaultCommand{})
|
||||
comp.Registry().Register(&commands.EndSwitchCommand{})
|
||||
}
|
||||
|
||||
func writeOutput(filename string, lines []string) error {
|
||||
|
|
|
|||
Loading…
Reference in a new issue