package commands import ( "strings" "testing" "c65gm/internal/compiler" "c65gm/internal/preproc" ) func TestSubtractCommand_WillHandle(t *testing.T) { cmd := &SubtractCommand{} tests := []struct { name string line string want bool }{ {"old syntax SUBTRACT FROM/GIVING", "SUBTRACT a FROM b GIVING c", true}, {"old syntax SUBT FROM/arrow", "SUBT a FROM b -> c", true}, {"old syntax SUBT minus/arrow", "SUBT a - b -> c", true}, {"old syntax SUBTRACT minus/GIVING", "SUBTRACT a - b GIVING c", true}, {"new syntax", "result = x - y", true}, {"not SUBTRACT", "ADD a TO b GIVING c", false}, {"wrong param count", "SUBTRACT 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 TestSubtractCommand_OldSyntax_FROM(t *testing.T) { tests := []struct { name string line string setupVars func(*compiler.SymbolTable) wantAsm []string wantErr bool }{ { name: "SUBTRACT a FROM b (means b-a)", line: "SUBTRACT 5 FROM 10 GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda #$05", "\tsta result", }, }, { name: "SUBTRACT byte FROM byte -> byte (variables)", line: "SUBTRACT a FROM b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 10) st.AddVar("b", "", compiler.KindByte, 20) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda b", "\tsbc a", "\tsta result", }, }, { name: "SUBT a FROM b -> c with arrow", line: "SUBT a FROM b -> result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 10) st.AddVar("b", "", compiler.KindByte, 20) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda b", "\tsbc a", "\tsta result", }, }, { name: "SUBTRACT literal FROM variable", line: "SUBTRACT 10 FROM a GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc #$0a", "\tsta result", }, }, { name: "word FROM word -> word", line: "SUBTRACT x FROM y GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("x", "", compiler.KindWord, 0x1000) st.AddVar("y", "", compiler.KindWord, 0x2000) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tsec", "\tlda y", "\tsbc x", "\tsta result", "\tlda y+1", "\tsbc x+1", "\tsta result+1", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(&preproc.Pragma{}) tt.setupVars(ctx.SymbolTable) cmd := &SubtractCommand{} 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 !equalAsmSubtr(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } func TestSubtractCommand_OldSyntax_Minus(t *testing.T) { tests := []struct { name string line string setupVars func(*compiler.SymbolTable) wantAsm []string wantErr bool }{ { name: "SUBT a - b (means a-b, no swap)", line: "SUBT 10 - 5 GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda #$05", "\tsta result", }, }, { name: "SUBT byte - byte -> byte (variables)", line: "SUBT a - b -> result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("b", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc b", "\tsta result", }, }, { name: "SUBTRACT a - b GIVING c", line: "SUBTRACT a - b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("b", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc b", "\tsta result", }, }, { name: "SUBT variable - literal", line: "SUBT a - 10 -> result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc #$0a", "\tsta result", }, }, { name: "word - word -> word", line: "SUBT x - y -> result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("x", "", compiler.KindWord, 0x2000) st.AddVar("y", "", compiler.KindWord, 0x1000) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tsec", "\tlda x", "\tsbc y", "\tsta result", "\tlda x+1", "\tsbc y+1", "\tsta result+1", }, }, { name: "byte - byte -> word", line: "SUBT a - b GIVING result", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("b", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc b", "\tsta result", "\tlda #0", "\tsbc #0", "\tsta result+1", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := compiler.NewCompilerContext(&preproc.Pragma{}) tt.setupVars(ctx.SymbolTable) cmd := &SubtractCommand{} 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 !equalAsmSubtr(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } func TestSubtractCommand_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, 20) st.AddVar("b", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc b", "\tsta result", }, }, { name: "byte - byte -> word", line: "result = a - b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("b", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc b", "\tsta result", "\tlda #0", "\tsbc #0", "\tsta result+1", }, }, { name: "word - word -> word", line: "result = x - y", setupVars: func(st *compiler.SymbolTable) { st.AddVar("x", "", compiler.KindWord, 0x2000) st.AddVar("y", "", compiler.KindWord, 0x1000) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tsec", "\tlda x", "\tsbc y", "\tsta result", "\tlda x+1", "\tsbc y+1", "\tsta result+1", }, }, { name: "variable - literal", line: "result = a - 10", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 20) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc #$0a", "\tsta result", }, }, { name: "literal - variable", line: "result = 20 - b", setupVars: func(st *compiler.SymbolTable) { st.AddVar("b", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda #$14", "\tsbc b", "\tsta result", }, }, { name: "constant folding", line: "result = 100 - 25", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tlda #$4b", "\tsta result", }, }, { name: "constant folding word", line: "result = $2000 - $1000", setupVars: func(st *compiler.SymbolTable) { st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tlda #$00", "\tsta result", "\tlda #$10", "\tsta result+1", }, }, { name: "using constant in expression", line: "result = a - OFFSET", setupVars: func(st *compiler.SymbolTable) { st.AddVar("a", "", compiler.KindByte, 100) st.AddConst("OFFSET", "", compiler.KindByte, 10) st.AddVar("result", "", compiler.KindByte, 0) }, wantAsm: []string{ "\tsec", "\tlda a", "\tsbc #$0a", "\tsta result", }, }, { name: "word - byte -> word", line: "result = wval - bval", setupVars: func(st *compiler.SymbolTable) { st.AddVar("wval", "", compiler.KindWord, 0x1234) st.AddVar("bval", "", compiler.KindByte, 0x10) st.AddVar("result", "", compiler.KindWord, 0) }, wantAsm: []string{ "\tsec", "\tlda wval", "\tsbc bval", "\tsta result", "\tlda wval+1", "\tsbc #0", "\tsta result+1", }, }, { 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, }, { name: "error: cannot assign to constant", line: "MAXVAL = a - b", 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 := &SubtractCommand{} 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 !equalAsmSubtr(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // equalAsmSubtr compares two assembly slices for equality func equalAsmSubtr(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }