c65gm/internal/commands/shiftr_test.go

681 lines
No EOL
21 KiB
Go

package commands
import (
"strings"
"testing"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
)
func TestShiftRCommand_WillHandle(t *testing.T) {
tests := []struct {
name string
text string
want bool
}{
// Old syntax
{"old syntax BY/GIVING", "SHIFTR a BY b GIVING c", true},
{"old syntax >>/->", "SHIFTR a >> b -> c", true},
{"old syntax mixed case", "shiftr x by y giving z", true},
// New syntax
{"new syntax basic", "result = a >> b", true},
{"new syntax with literals", "x = 10 >> 3", true},
// Should not handle
{"not shiftr - shiftl", "SHIFTL a BY b GIVING c", false},
{"not shiftr - add", "result = a + b", false},
{"not shiftr - wrong params", "SHIFTR a b c", false},
{"empty", "", false},
{"just SHIFTR", "SHIFTR", false},
{"assignment without shift", "x = y", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := &ShiftRCommand{}
line := preproc.Line{Text: tt.text}
if got := cmd.WillHandle(line); got != tt.want {
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
}
})
}
}
func TestShiftRCommand_Interpret_OldSyntax(t *testing.T) {
tests := []struct {
name string
setup func(*compiler.CompilerContext)
text string
wantErr bool
check func(*testing.T, *ShiftRCommand)
}{
{
name: "byte >> byte -> byte",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 3, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a BY b GIVING c",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if !cmd.sourceIsVar || cmd.sourceVarName != "a" {
t.Errorf("source should be var 'a'")
}
if !cmd.amountIsVar || cmd.amountVarName != "b" {
t.Errorf("amount should be var 'b'")
}
if cmd.destVarName != "c" || cmd.destVarKind != compiler.KindByte {
t.Errorf("dest should be byte 'c'")
}
},
},
{
name: "word >> byte -> word",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("n", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR x BY n GIVING result",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.sourceVarKind != compiler.KindWord {
t.Errorf("source should be word")
}
if cmd.destVarKind != compiler.KindWord {
t.Errorf("dest should be word")
}
},
},
{
name: "literal >> var -> byte",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR $FF BY shift GIVING result",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.sourceIsVar {
t.Errorf("source should be literal")
}
if cmd.sourceValue != 0xFF {
t.Errorf("source value = %d, want 255", cmd.sourceValue)
}
},
},
{
name: "var >> literal -> byte",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 8, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR value BY 3 GIVING result",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.amountIsVar {
t.Errorf("amount should be literal")
}
if cmd.amountValue != 3 {
t.Errorf("amount value = %d, want 3", cmd.amountValue)
}
},
},
{
name: "arrow syntax",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a >> b -> c",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.destVarName != "c" {
t.Errorf("dest should be c")
}
},
},
{
name: "unknown variable",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a BY b GIVING c",
wantErr: true,
},
{
name: "assign to constant",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 255, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a BY b GIVING MAX",
wantErr: true,
},
{
name: "word amount variable (error)",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindWord, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a BY b GIVING c",
wantErr: true,
},
{
name: "amount constant > 255 (error)",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a BY 300 GIVING c",
wantErr: true,
},
{
name: "wrong separator #3",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a WITH b GIVING c",
wantErr: true,
},
{
name: "wrong separator #5",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("c", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "SHIFTR a BY b INTO c",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := compiler.NewCompilerContext(preproc.NewPragma())
if tt.setup != nil {
tt.setup(ctx)
}
cmd := &ShiftRCommand{}
line := preproc.Line{Text: tt.text}
err := cmd.Interpret(line, ctx)
if (err != nil) != tt.wantErr {
t.Errorf("Interpret() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.check != nil {
tt.check(t, cmd)
}
})
}
}
func TestShiftRCommand_Interpret_NewSyntax(t *testing.T) {
tests := []struct {
name string
setup func(*compiler.CompilerContext)
text string
wantErr bool
check func(*testing.T, *ShiftRCommand)
}{
{
name: "dest = var >> var",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "result = a >> b",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.destVarName != "result" {
t.Errorf("dest = %q, want 'result'", cmd.destVarName)
}
if !cmd.sourceIsVar || cmd.sourceVarName != "a" {
t.Errorf("source should be var 'a'")
}
if !cmd.amountIsVar || cmd.amountVarName != "b" {
t.Errorf("amount should be var 'b'")
}
},
},
{
name: "dest = literal >> literal",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "result = $08 >> 3",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.sourceIsVar || cmd.amountIsVar {
t.Errorf("both params should be literals")
}
if cmd.sourceValue != 8 || cmd.amountValue != 3 {
t.Errorf("source=%d, amount=%d, want 8,3", cmd.sourceValue, cmd.amountValue)
}
},
},
{
name: "word destination",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 1, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "result = value >> shift",
wantErr: false,
check: func(t *testing.T, cmd *ShiftRCommand) {
if cmd.destVarKind != compiler.KindWord {
t.Errorf("dest should be word")
}
},
},
{
name: "unknown dest variable",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "result = a >> b",
wantErr: true,
},
{
name: "wrong operator (not >>)",
setup: func(ctx *compiler.CompilerContext) {
ctx.SymbolTable.AddVar("a", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("b", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
},
text: "result = a << b",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := compiler.NewCompilerContext(preproc.NewPragma())
if tt.setup != nil {
tt.setup(ctx)
}
cmd := &ShiftRCommand{}
line := preproc.Line{Text: tt.text}
err := cmd.Interpret(line, ctx)
if (err != nil) != tt.wantErr {
t.Errorf("Interpret() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.check != nil {
tt.check(t, cmd)
}
})
}
}
func TestShiftRCommand_Generate(t *testing.T) {
tests := []struct {
name string
setup func(*compiler.CompilerContext) *ShiftRCommand
wantLines []string
}{
{
name: "constant folding - byte >> 0",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0x55,
amountIsVar: false,
amountValue: 0,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda #$55",
"\tsta result",
},
},
{
name: "constant folding - byte >> 3",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0x08,
amountIsVar: false,
amountValue: 3,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda #$08",
"\tsta result",
"\tlsr result",
"\tlsr result",
"\tlsr result",
},
},
{
name: "constant folding - byte >> 8 (zero)",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0xFF,
amountIsVar: false,
amountValue: 8,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda #0",
"\tsta result",
},
},
{
name: "constant folding - word >> 1",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0x1234,
amountIsVar: false,
amountValue: 1,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$34",
"\tsta result",
"\tlda #$12",
"\tsta result+1",
"\tlsr result+1",
"\tror result",
},
},
{
name: "constant folding - word >> 16 (zero)",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0xFFFF,
amountIsVar: false,
amountValue: 16,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #0",
"\tsta result",
"\tsta result+1",
},
},
{
name: "byte variable >> byte variable",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "value",
sourceVarKind: compiler.KindByte,
amountIsVar: true,
amountVarName: "shift",
amountVarKind: compiler.KindByte,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda value",
"\tsta result",
"\tldx shift",
"\tbeq _L2",
"_L1",
"\tlsr result",
"\tdex",
"\tbne _L1",
"_L2",
},
},
{
name: "word variable >> byte variable",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 1000, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 3, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "value",
sourceVarKind: compiler.KindWord,
amountIsVar: true,
amountVarName: "shift",
amountVarKind: compiler.KindByte,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda value",
"\tsta result",
"\tlda value+1",
"\tsta result+1",
"\tldx shift",
"\tbeq _L2",
"_L1",
"\tlsr result+1",
"\tror result",
"\tdex",
"\tbne _L1",
"_L2",
},
},
{
name: "same source and dest",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("value", "", compiler.KindByte, 10, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "value",
sourceVarKind: compiler.KindByte,
amountIsVar: true,
amountVarName: "shift",
amountVarKind: compiler.KindByte,
destVarName: "value",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tldx shift",
"\tbeq _L2",
"_L1",
"\tlsr value",
"\tdex",
"\tbne _L1",
"_L2",
},
},
{
name: "WORD optimization - shift by 8",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0x1234,
amountIsVar: false,
amountValue: 8,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$12",
"\tsta result",
"\tlda #0",
"\tsta result+1",
},
},
{
name: "WORD optimization - shift by 12 (8 + 4)",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: false,
sourceValue: 0xAB00,
amountIsVar: false,
amountValue: 12,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda #$ab",
"\tsta result",
"\tlda #0",
"\tsta result+1",
"\tlsr result",
"\tlsr result",
"\tlsr result",
"\tlsr result",
},
},
{
name: "BYTE to WORD conversion with shift",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("byteval", "", compiler.KindByte, 0x55, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "byteval",
sourceVarKind: compiler.KindByte,
amountIsVar: false,
amountValue: 4,
destVarName: "result",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tlda byteval",
"\tsta result",
"\tlda #0",
"\tsta result+1",
"\tlsr result+1",
"\tror result",
"\tlsr result+1",
"\tror result",
"\tlsr result+1",
"\tror result",
"\tlsr result+1",
"\tror result",
},
},
{
name: "WORD to BYTE conversion with shift (truncation)",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("wordval", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "wordval",
sourceVarKind: compiler.KindWord,
amountIsVar: false,
amountValue: 4,
destVarName: "result",
destVarKind: compiler.KindByte,
}
},
wantLines: []string{
"\tlda wordval",
"\tsta result",
"\tlda wordval+1",
"\tlsr",
"\tror result",
"\tlsr",
"\tror result",
"\tlsr",
"\tror result",
"\tlsr",
"\tror result",
},
},
{
name: "WORD same source and destination",
setup: func(ctx *compiler.CompilerContext) *ShiftRCommand {
ctx.SymbolTable.AddVar("value", "", compiler.KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("shift", "", compiler.KindByte, 2, preproc.Line{Filename: "test.c65", LineNo: 1})
return &ShiftRCommand{
sourceIsVar: true,
sourceVarName: "value",
sourceVarKind: compiler.KindWord,
amountIsVar: true,
amountVarName: "shift",
amountVarKind: compiler.KindByte,
destVarName: "value",
destVarKind: compiler.KindWord,
}
},
wantLines: []string{
"\tldx shift",
"\tbeq _L2",
"_L1",
"\tlsr value+1",
"\tror value",
"\tdex",
"\tbne _L1",
"_L2",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := compiler.NewCompilerContext(preproc.NewPragma())
cmd := tt.setup(ctx)
got, err := cmd.Generate(ctx)
if err != nil {
t.Fatalf("Generate() error = %v", err)
}
if len(got) != len(tt.wantLines) {
t.Errorf("Generate() got %d lines, want %d lines\nGot:\n%s\nWant:\n%s",
len(got), len(tt.wantLines),
strings.Join(got, "\n"), strings.Join(tt.wantLines, "\n"))
return
}
for i := range got {
// Skip exact label comparison (they're generated dynamically)
if strings.HasPrefix(got[i], "_L") && strings.HasPrefix(tt.wantLines[i], "_L") {
continue
}
if got[i] != tt.wantLines[i] {
t.Errorf("Line %d:\ngot: %q\nwant: %q", i, got[i], tt.wantLines[i])
}
}
})
}
}