package commands import ( "fmt" "strings" "testing" "c65gm/internal/compiler" "c65gm/internal/preproc" ) func TestElseCommand_WillHandle(t *testing.T) { cmd := &ElseCommand{} tests := []struct { name string line string want bool }{ {"ELSE", "ELSE", true}, {"not ELSE", "IF a = b", false}, {"empty", "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { line := preproc.Line{Text: tt.line, Kind: preproc.Source} got := cmd.WillHandle(line) if got != tt.want { t.Errorf("WillHandle() = %v, want %v", got, tt.want) } }) } } func TestEndIfCommand_WillHandle(t *testing.T) { cmd := &EndIfCommand{} tests := []struct { name string line string want bool }{ {"ENDIF", "ENDIF", true}, {"not ENDIF", "IF a = b", false}, {"empty", "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { line := preproc.Line{Text: tt.line, Kind: preproc.Source} got := cmd.WillHandle(line) if got != tt.want { t.Errorf("WillHandle() = %v, want %v", got, tt.want) } }) } } func TestIfElseEndif_Integration(t *testing.T) { tests := []struct { name string lines []string setupVars func(*compiler.SymbolTable) wantAsm []string }{ { name: "IF...ENDIF (no ELSE)", lines: []string{ "IF a = b", "ENDIF", }, setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) }, wantAsm: []string{ "; IF a = b", "\tlda a", "\tcmp b", "\tbne _I1", "; ENDIF", "_I1", }, }, { name: "IF...ELSE...ENDIF", lines: []string{ "IF a = b", "ELSE", "ENDIF", }, setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) }, wantAsm: []string{ "; IF a = b", "\tlda a", "\tcmp b", "\tbne _I1", "; ELSE", "\tjmp _I2", "_I1", "; ENDIF", "_I2", }, }, { name: "nested IF statements", lines: []string{ "IF a = 10", "IF b = 20", "ENDIF", "ENDIF", }, setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) }, wantAsm: []string{ "; IF a = 10", "\tlda a", "\tcmp #$0a", "\tbne _I1", "; IF b = 20", "\tlda b", "\tcmp #$14", "\tbne _I2", "; ENDIF", "_I2", "; ENDIF", "_I1", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(preproc.NewPragma()) tt.setupVars(ctx.SymbolTable) var allAsm []string for _, lineText := range tt.lines { line := preproc.Line{Text: lineText, Kind: preproc.Source, PragmaSetIndex: 0} // Determine which command to use var cmd compiler.Command if strings.HasPrefix(strings.ToUpper(lineText), "IF") { cmd = &IfCommand{} } else if strings.ToUpper(lineText) == "ELSE" { cmd = &ElseCommand{} } else if strings.ToUpper(lineText) == "ENDIF" { cmd = &EndIfCommand{} } else { t.Fatalf("unknown command: %s", lineText) } err := cmd.Interpret(line, ctx) if err != nil { t.Fatalf("Interpret(%q) error = %v", lineText, err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate(%q) error = %v", lineText, err) } allAsm = append(allAsm, fmt.Sprintf("; %s", lineText)) allAsm = append(allAsm, asm...) } if !equalAsmElse(allAsm, tt.wantAsm) { t.Errorf("Assembly mismatch\ngot:\n%s\nwant:\n%s", strings.Join(allAsm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } func TestElseCommand_Errors(t *testing.T) { tests := []struct { name string line string wantErr string }{ { name: "ELSE without IF", line: "ELSE", wantErr: "stack underflow", }, { name: "wrong param count", line: "ELSE extra", wantErr: "wrong number of parameters", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(preproc.NewPragma()) cmd := &ElseCommand{} line := preproc.Line{Text: tt.line, Kind: preproc.Source} err := cmd.Interpret(line, ctx) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("error = %q, want substring %q", err.Error(), tt.wantErr) } }) } } func TestEndIfCommand_Errors(t *testing.T) { tests := []struct { name string line string wantErr string }{ { name: "ENDIF without IF", line: "ENDIF", wantErr: "stack underflow", }, { name: "wrong param count", line: "ENDIF extra", wantErr: "wrong number of parameters", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(preproc.NewPragma()) cmd := &EndIfCommand{} line := preproc.Line{Text: tt.line, Kind: preproc.Source} err := cmd.Interpret(line, ctx) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("error = %q, want substring %q", err.Error(), tt.wantErr) } }) } } // equalAsmElse compares two assembly slices for equality func equalAsmElse(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }