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)) } }