package commands import ( "strings" "testing" "c65gm/internal/compiler" "c65gm/internal/preproc" ) func TestXorCommand_WillHandle(t *testing.T) { cmd := &XorCommand{} tests := []struct { name string line string want bool }{ {"old syntax WITH/GIVING", "XOR a WITH b GIVING c", true}, {"old syntax WITH/arrow", "XOR a WITH b -> c", true}, {"new syntax", "result = x ^ y", true}, {"not XOR", "ADD a TO b GIVING c", false}, {"wrong param count", "XOR a b c", false}, {"empty", "", false}, {"new syntax wrong op", "result = x + y", 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 TestXorCommand_OldSyntax(t *testing.T) { tests := []struct { name string line string setupVars func(*compiler.SymbolTable) wantAsm []string wantErr bool }{ { name: "byte XOR byte -> byte (variables)", line: "XOR a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddVar("b", "", compiler.KindByte, 0xAA) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda a", "\teor b", "\tsta result", }, }, { name: "byte XOR byte -> word", line: "XOR a WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddVar("b", "", compiler.KindByte, 0xAA) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda a", "\teor b", "\tsta result", "\tlda #0", "\tsta result+1", }, }, { name: "word XOR word -> word", line: "XOR x WITH y GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("x", "", compiler.KindWord, 0x1234) st.AddVar("y", "", compiler.KindWord, 0x5678) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda x", "\teor y", "\tsta result", "\tlda x+1", "\teor y+1", "\tsta result+1", }, }, { name: "byte XOR literal -> byte", line: "XOR a WITH $AA GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda a", "\teor #$aa", "\tsta result", }, }, { name: "literal XOR byte -> byte", line: "XOR 255 WITH b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("b", "", compiler.KindByte, 0xAA) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda #$ff", "\teor b", "\tsta result", }, }, { name: "constant folding: 255 XOR 170 -> byte", line: "XOR 255 WITH 170 GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda #$55", "\tsta result", }, }, { name: "constant folding: $FFFF XOR $AAAA -> word", line: "XOR $FFFF WITH $AAAA GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda #$55", "\tsta result", "\tlda #$55", "\tsta result+1", }, }, { name: "arrow syntax", line: "XOR a WITH b -> result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda a", "\teor b", "\tsta result", }, }, { name: "word XOR byte -> byte", line: "XOR wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("bval", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda wval", "\teor bval", "\tsta result", }, }, { name: "word XOR byte -> word", line: "XOR wval WITH bval GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("bval", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda wval", "\teor bval", "\tsta result", "\tlda wval+1", "\tsta result+1", }, }, { name: "error: unknown destination variable", line: "XOR a WITH b GIVING unknown", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) }, wantErr: true, }, { name: "error: wrong separator", line: "XOR a TO b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) st.AddVar("result", "", compiler.KindByte, 0) }, wantErr: true, }, { name: "error: cannot assign to constant", line: "XOR a WITH b GIVING MAXVAL", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) st.AddConst("MAXVAL", "", compiler.KindByte, 255) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(&preproc.Pragma{}) tt.setupVars(ctx.SymbolTable) cmd := &XorCommand{} line := preproc.Line{Text: tt.line, Kind: preproc.Source} err := cmd.Interpret(line, ctx) if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsmXor(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } func TestXorCommand_NewSyntax(t *testing.T) { tests := []struct { name string line string setupVars func(*compiler.SymbolTable) wantAsm []string wantErr bool }{ { name: "byte ^ byte -> byte", line: "result = a ^ b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddVar("b", "", compiler.KindByte, 0xAA) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda a", "\teor b", "\tsta result", }, }, { name: "byte ^ byte -> word", line: "result = a ^ b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddVar("b", "", compiler.KindByte, 0xAA) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda a", "\teor b", "\tsta result", "\tlda #0", "\tsta result+1", }, }, { name: "word ^ word -> word", line: "result = x ^ y", setupVars: func(st *compiler.SymbolTable) { st.AddVar("x", "", compiler.KindWord, 0x1234) st.AddVar("y", "", compiler.KindWord, 0x5678) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda x", "\teor y", "\tsta result", "\tlda x+1", "\teor y+1", "\tsta result+1", }, }, { name: "byte ^ byte_const -> word (optimization: skip eor #0)", line: "result = b ^ 5", setupVars: func(st *compiler.SymbolTable) { st.AddVar("b", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda b", "\teor #$05", "\tsta result", "\tlda #0", "\tsta result+1", }, }, { name: "byte ^ word -> word (swapped to word ^ byte)", line: "result = bval ^ wval", setupVars: func(st *compiler.SymbolTable) { st.AddVar("bval", "", compiler.KindByte, 0xFF) st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda wval", "\teor bval", "\tsta result", "\tlda wval+1", "\tsta result+1", }, }, { name: "byte_const ^ byte -> word (optimization: skip eor #0)", line: "result = 5 ^ b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("b", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda #$05", "\teor b", "\tsta result", "\tlda #$00", "\tsta result+1", }, }, { name: "byte ^ word_const -> word (no optimization)", line: "result = b ^ 300", setupVars: func(st *compiler.SymbolTable) { st.AddVar("b", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda b", "\teor #$2c", "\tsta result", "\tlda #0", "\teor #$01", "\tsta result+1", }, }, { name: "word_const ^ byte -> word (optimization: skip eor #0)", line: "result = 300 ^ b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("b", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda #$2c", "\teor b", "\tsta result", "\tlda #$01", "\tsta result+1", }, }, { name: "self-assignment: word ^= byte (optimization: skip high byte entirely)", line: "wval = wval ^ bval", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("bval", "", compiler.KindByte, 0xFF) }, wantAsm: []string{ "\tlda wval", "\teor bval", "\tsta wval", }, }, { name: "self-assignment reversed: word ^= byte (optimization via swap)", line: "wval = bval ^ wval", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("bval", "", compiler.KindByte, 0xFF) }, wantAsm: []string{ "\tlda wval", "\teor bval", "\tsta wval", }, }, { name: "self-assignment: word ^= byte_const (optimization: skip high byte entirely)", line: "wval = wval ^ 42", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) }, wantAsm: []string{ "\tlda wval", "\teor #$2a", "\tsta wval", }, }, { name: "self-assignment: word ^= word_const (no optimization: high byte needed)", line: "wval = wval ^ 300", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) }, wantAsm: []string{ "\tlda wval", "\teor #$2c", "\tsta wval", "\tlda wval+1", "\teor #$01", "\tsta wval+1", }, }, { name: "self-assignment: word ^= word (no optimization: both high bytes needed)", line: "wval = wval ^ wval2", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("wval2", "", compiler.KindWord, 0x5678) }, wantAsm: []string{ "\tlda wval", "\teor wval2", "\tsta wval", "\tlda wval+1", "\teor wval2+1", "\tsta wval+1", }, }, { name: "variable ^ literal", line: "result = a ^ $AA", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda a", "\teor #$aa", "\tsta result", }, }, { name: "constant folding", line: "result = 255 ^ 170", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda #$55", "\tsta result", }, }, { name: "constant folding word", line: "result = $FFFF ^ $AAAA", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda #$55", "\tsta result", "\tlda #$55", "\tsta result+1", }, }, { name: "using constant in expression", line: "result = a ^ MASK", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0xFF) st.AddConst("MASK", "", compiler.KindByte, 0xAA) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda a", "\teor #$aa", "\tsta result", }, }, { name: "error: unknown destination", line: "unknown = a ^ b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) }, wantErr: true, }, { name: "error: wrong operator", line: "result = a + b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 0) st.AddVar("b", "", compiler.KindByte, 0) st.AddVar("result", "", compiler.KindByte, 0) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(&preproc.Pragma{}) tt.setupVars(ctx.SymbolTable) cmd := &XorCommand{} line := preproc.Line{Text: tt.line, Kind: preproc.Source} err := cmd.Interpret(line, ctx) if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsmXor(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // equalAsmXor compares two assembly slices for equality func equalAsmXor(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }