c65gm/internal/commands/word_test.go

462 lines
11 KiB
Go

package commands
import (
"strings"
"testing"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
)
func TestWordCommand_WillHandle(t *testing.T) {
tests := []struct {
name string
text string
want bool
}{
{
name: "handles WORD",
text: "WORD x",
want: true,
},
{
name: "handles word lowercase",
text: "word x",
want: true,
},
{
name: "handles WORD with init",
text: "WORD x = 1000",
want: true,
},
{
name: "handles WORD with string",
text: `WORD ptr = "hello"`,
want: true,
},
{
name: "handles WORD at absolute",
text: "WORD x @ $C000",
want: true,
},
{
name: "handles WORD CONST",
text: "WORD CONST x = 1000",
want: true,
},
{
name: "does not handle BYTE",
text: "BYTE x",
want: false,
},
{
name: "does not handle empty",
text: "",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := &WordCommand{}
line := preproc.Line{Text: tt.text}
got := cmd.WillHandle(line)
if got != tt.want {
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
}
})
}
}
func TestWordCommand_Interpret(t *testing.T) {
tests := []struct {
name string
text string
wantErr bool
errContains string
checkVar func(*testing.T, *compiler.CompilerContext)
}{
{
name: "simple word",
text: "WORD x",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("x", nil)
if sym == nil {
t.Fatal("Expected variable x to be declared")
}
if !sym.IsWord() {
t.Error("Expected word variable")
}
if sym.IsConst() {
t.Error("Expected regular variable, not const")
}
if sym.Value != 0 {
t.Errorf("Expected init value 0, got %d", sym.Value)
}
},
},
{
name: "word with decimal init",
text: "WORD counter = 1000",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("counter", nil)
if sym == nil {
t.Fatal("Expected variable counter to be declared")
}
if sym.Value != 1000 {
t.Errorf("Expected init value 1000, got %d", sym.Value)
}
},
},
{
name: "word with hex init",
text: "WORD status = $FFFF",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("status", nil)
if sym == nil {
t.Fatal("Expected variable status to be declared")
}
if sym.Value != 65535 {
t.Errorf("Expected init value 65535, got %d", sym.Value)
}
},
},
{
name: "word with string pointer",
text: `WORD msg = "hello world"`,
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("msg", nil)
if sym == nil {
t.Fatal("Expected variable msg to be declared")
}
if !sym.Has(compiler.FlagLabelRef) {
t.Error("Expected label reference")
}
if sym.LabelRef == "" {
t.Error("Expected non-empty label reference")
}
// Check that string was added to handler
strs := ctx.ConstStrHandler.GenerateConstStrDecls()
if len(strs) == 0 {
t.Error("Expected string to be added to ConstStrHandler")
}
},
},
{
name: "word at absolute address",
text: "WORD ptr @ $C000",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("ptr", nil)
if sym == nil {
t.Fatal("Expected variable ptr to be declared")
}
if !sym.IsAbsolute() {
t.Error("Expected absolute variable")
}
if sym.AbsAddr != 0xC000 {
t.Errorf("Expected address $C000, got $%04X", sym.AbsAddr)
}
},
},
{
name: "word at zero page",
text: "WORD zpvar @ $80",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("zpvar", nil)
if sym == nil {
t.Fatal("Expected variable zpvar to be declared")
}
if !sym.IsZeroPage() {
t.Error("Expected zero page variable")
}
},
},
{
name: "const word",
text: "WORD CONST maxval = 65535",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("maxval", nil)
if sym == nil {
t.Fatal("Expected constant maxval to be declared")
}
if !sym.IsConst() {
t.Error("Expected constant")
}
if sym.Value != 65535 {
t.Errorf("Expected value 65535, got %d", sym.Value)
}
},
},
{
name: "const word with hex",
text: "WORD CONST flag = $FFFF",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("flag", nil)
if sym == nil {
t.Fatal("Expected constant flag to be declared")
}
if !sym.IsConst() {
t.Error("Expected constant")
}
if sym.Value != 65535 {
t.Errorf("Expected value 65535, got %d", sym.Value)
}
},
},
{
name: "word with expression",
text: "WORD x = 100+200",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("x", nil)
if sym == nil {
t.Fatal("Expected variable x to be declared")
}
if sym.Value != 300 {
t.Errorf("Expected value 300, got %d", sym.Value)
}
},
},
{
name: "word with binary",
text: "WORD x = %1111111111111111",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("x", nil)
if sym == nil {
t.Fatal("Expected variable x to be declared")
}
if sym.Value != 65535 {
t.Errorf("Expected value 65535, got %d", sym.Value)
}
},
},
{
name: "word with bitwise OR",
text: "WORD x = $FF00|$00FF",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("x", nil)
if sym == nil {
t.Fatal("Expected variable x to be declared")
}
if sym.Value != 65535 {
t.Errorf("Expected value 65535, got %d", sym.Value)
}
},
},
{
name: "word with bitwise AND",
text: "WORD x = $FFFF&$00FF",
wantErr: false,
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
sym := ctx.SymbolTable.Lookup("x", nil)
if sym == nil {
t.Fatal("Expected variable x to be declared")
}
if sym.Value != 255 {
t.Errorf("Expected value 255, got %d", sym.Value)
}
},
},
{
name: "word with out of range value",
text: "WORD x = 65536",
wantErr: true,
errContains: "out of range",
},
{
name: "const word out of range",
text: "WORD CONST x = 65536",
wantErr: true,
errContains: "out of range",
},
{
name: "word without name",
text: "WORD",
wantErr: true,
errContains: "wrong number of parameters",
},
{
name: "word with invalid identifier",
text: "WORD 123invalid",
wantErr: true,
errContains: "invalid identifier",
},
{
name: "word with wrong operator",
text: "WORD x + 10",
wantErr: true,
errContains: "expected '=' or '@'",
},
{
name: "word string with wrong operator",
text: `WORD x @ "hello"`,
wantErr: true,
errContains: "expected '=' when assigning string pointer",
},
{
name: "const without equals",
text: "WORD CONST x 10",
wantErr: true,
errContains: "expected '='",
},
{
name: "wrong keyword instead of CONST",
text: "WORD VAR x = 10",
wantErr: true,
errContains: "expected CONST keyword",
},
{
name: "duplicate declaration",
text: "WORD x",
wantErr: true,
errContains: "already declared",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
// For duplicate test, pre-declare the variable
if tt.name == "duplicate declaration" {
ctx.SymbolTable.AddVar("x", "", compiler.KindWord, 0)
}
cmd := &WordCommand{}
line := preproc.Line{
Text: tt.text,
Filename: "test.c65",
LineNo: 1,
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
err := cmd.Interpret(line, ctx)
if tt.wantErr {
if err == nil {
t.Fatal("Expected error, got nil")
}
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("Error %q does not contain %q", err.Error(), tt.errContains)
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if tt.checkVar != nil {
tt.checkVar(t, ctx)
}
}
})
}
}
func TestWordCommand_Generate(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
cmd := &WordCommand{}
line := preproc.Line{
Text: "WORD x = 1000",
Filename: "test.c65",
LineNo: 1,
Kind: preproc.Source,
PragmaSetIndex: 0,
}
// Interpret first
if err := cmd.Interpret(line, ctx); err != nil {
t.Fatalf("Interpret failed: %v", err)
}
// Generate should return nil (variables handled by assembleOutput)
output, err := cmd.Generate(ctx)
if err != nil {
t.Errorf("Generate returned error: %v", err)
}
if output != nil {
t.Errorf("Generate should return nil, got %v", output)
}
}
func TestWordCommand_WithConstantExpression(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
// First, declare a constant
ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindWord, 1000)
ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindWord, 500)
// Now declare a word using the constant in an expression
cmd := &WordCommand{}
line := preproc.Line{
Text: "WORD x = MAXVAL+OFFSET",
Filename: "test.c65",
LineNo: 1,
Kind: preproc.Source,
PragmaSetIndex: 0,
}
if err := cmd.Interpret(line, ctx); err != nil {
t.Fatalf("Interpret failed: %v", err)
}
// Check value
sym := ctx.SymbolTable.Lookup("x", nil)
if sym == nil {
t.Fatal("Expected variable x to be declared")
}
if sym.Value != 1500 {
t.Errorf("Expected value 1500 (1000+500), got %d", sym.Value)
}
}
func TestWordCommand_MultipleStrings(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
// Create multiple string pointers
stringsd := []string{
`WORD msg1 = "hello"`,
`WORD msg2 = "world"`,
`WORD msg3 = "test"`,
}
for i, text := range stringsd {
cmd := &WordCommand{}
line := preproc.Line{
Text: text,
Filename: "test.c65",
LineNo: i + 1,
Kind: preproc.Source,
PragmaSetIndex: 0,
}
if err := cmd.Interpret(line, ctx); err != nil {
t.Fatalf("Failed to interpret line %d: %v", i+1, err)
}
}
// Check all were added
if ctx.SymbolTable.Count() != 3 {
t.Errorf("Expected 3 variables, got %d", ctx.SymbolTable.Count())
}
// Check string declarations were generated
strDecls := ctx.ConstStrHandler.GenerateConstStrDecls()
if len(strDecls) < 9 { // Each string needs at least 3 lines (label, data, terminator)
t.Errorf("Expected at least 9 lines of string declarations, got %d", len(strDecls))
}
}