package commands import ( "strings" "testing" "c65gm/internal/compiler" "c65gm/internal/preproc" ) func TestIncrCommand_WillHandle(t *testing.T) { tests := []struct { name string line string expected bool }{ {"INC keyword", "INC myvar", true}, {"INCREMENT keyword", "INCREMENT myvar", true}, {"inc lowercase", "inc myvar", true}, {"New syntax ++ literal", "myvar++", true}, {"Invalid - space before ++", "myvar ++", false}, {"Invalid - no params", "INC", false}, {"Invalid - too many params old", "INC a b c", false}, {"Invalid - wrong suffix", "myvar+-", false}, {"Invalid - ADD command", "ADD x TO y", false}, } cmd := &IncrCommand{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"} result := cmd.WillHandle(line) if result != tt.expected { t.Errorf("WillHandle(%q) = %v, want %v", tt.line, result, tt.expected) } }) } } func TestDecrCommand_WillHandle(t *testing.T) { tests := []struct { name string line string expected bool }{ {"DEC keyword", "DEC myvar", true}, {"DECREMENT keyword", "DECREMENT myvar", true}, {"dec lowercase", "dec myvar", true}, {"New syntax -- literal", "myvar--", true}, {"Invalid - space before --", "myvar --", false}, {"Invalid - no params", "DEC", false}, {"Invalid - too many params old", "DEC a b c", false}, {"Invalid - wrong suffix", "myvar-+", false}, {"Invalid - SUB command", "SUB x FROM y", false}, } cmd := &DecrCommand{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"} result := cmd.WillHandle(line) if result != tt.expected { t.Errorf("WillHandle(%q) = %v, want %v", tt.line, result, tt.expected) } }) } } func TestIncrCommand_InterpretAndGenerate(t *testing.T) { tests := []struct { name string setup func(*compiler.CompilerContext) line string expectError bool checkAsm func(*testing.T, []string) }{ { name: "INC byte variable old syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) }, line: "INC counter", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 1 { t.Errorf("Expected 1 asm line, got %d", len(asm)) return } if !strings.Contains(asm[0], "inc counter") { t.Errorf("Expected 'inc counter', got %q", asm[0]) } }, }, { name: "INC byte variable new syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) }, line: "counter++", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 1 { t.Errorf("Expected 1 asm line, got %d", len(asm)) return } if !strings.Contains(asm[0], "inc counter") { t.Errorf("Expected 'inc counter', got %q", asm[0]) } }, }, { name: "INC word variable old syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) }, line: "INCREMENT pointer", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 4 { t.Errorf("Expected 4 asm lines for word inc, got %d", len(asm)) return } if !strings.Contains(asm[0], "inc pointer") { t.Errorf("Expected 'inc pointer' in line 0, got %q", asm[0]) } if !strings.Contains(asm[1], "bne") { t.Errorf("Expected 'bne' in line 1, got %q", asm[1]) } if !strings.Contains(asm[2], "inc pointer+1") { t.Errorf("Expected 'inc pointer+1' in line 2, got %q", asm[2]) } // Line 3 should be the label }, }, { name: "INC word variable new syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) }, line: "pointer++", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 4 { t.Errorf("Expected 4 asm lines for word inc, got %d", len(asm)) } }, }, { name: "INC absolute address", setup: func(ctx *compiler.CompilerContext) { // No variables needed }, line: "INC $D020", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 1 { t.Errorf("Expected 1 asm line, got %d", len(asm)) return } if !strings.Contains(strings.ToLower(asm[0]), "inc $d020") { t.Errorf("Expected 'inc $d020', got %q", asm[0]) } }, }, { name: "Error: INC unknown variable", setup: func(ctx *compiler.CompilerContext) { // No setup }, line: "INC unknown", expectError: true, }, { name: "Error: INC constant variable", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) }, line: "INC MAX", expectError: true, }, { name: "Error: new syntax on unknown variable", setup: func(ctx *compiler.CompilerContext) { // No setup }, line: "unknown++", expectError: 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 := &IncrCommand{} line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"} err := cmd.Interpret(line, ctx) if tt.expectError { if err == nil { t.Errorf("Expected error, got nil") } return } if err != nil { t.Fatalf("Unexpected error: %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate error: %v", err) } if tt.checkAsm != nil { tt.checkAsm(t, asm) } }) } } func TestDecrCommand_InterpretAndGenerate(t *testing.T) { tests := []struct { name string setup func(*compiler.CompilerContext) line string expectError bool checkAsm func(*testing.T, []string) }{ { name: "DEC byte variable old syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) }, line: "DEC counter", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 1 { t.Errorf("Expected 1 asm line, got %d", len(asm)) return } if !strings.Contains(asm[0], "dec counter") { t.Errorf("Expected 'dec counter', got %q", asm[0]) } }, }, { name: "DEC byte variable new syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0) }, line: "counter--", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 1 { t.Errorf("Expected 1 asm line, got %d", len(asm)) return } if !strings.Contains(asm[0], "dec counter") { t.Errorf("Expected 'dec counter', got %q", asm[0]) } }, }, { name: "DEC word variable old syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) }, line: "DECREMENT pointer", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 5 { t.Errorf("Expected 5 asm lines for word dec, got %d", len(asm)) return } if !strings.Contains(asm[0], "lda pointer") { t.Errorf("Expected 'lda pointer' in line 0, got %q", asm[0]) } if !strings.Contains(asm[1], "bne") { t.Errorf("Expected 'bne' in line 1, got %q", asm[1]) } if !strings.Contains(asm[2], "dec pointer+1") { t.Errorf("Expected 'dec pointer+1' in line 2, got %q", asm[2]) } // Line 3 is the label if !strings.Contains(asm[4], "dec pointer") { t.Errorf("Expected 'dec pointer' in line 4, got %q", asm[4]) } }, }, { name: "DEC word variable new syntax", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0) }, line: "pointer--", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 5 { t.Errorf("Expected 5 asm lines for word dec, got %d", len(asm)) } }, }, { name: "DEC absolute address", setup: func(ctx *compiler.CompilerContext) { // No variables needed }, line: "DEC $D020", expectError: false, checkAsm: func(t *testing.T, asm []string) { if len(asm) != 1 { t.Errorf("Expected 1 asm line, got %d", len(asm)) return } if !strings.Contains(strings.ToLower(asm[0]), "dec $d020") { t.Errorf("Expected 'dec $d020', got %q", asm[0]) } }, }, { name: "Error: DEC unknown variable", setup: func(ctx *compiler.CompilerContext) { // No setup }, line: "DEC unknown", expectError: true, }, { name: "Error: DEC constant variable", setup: func(ctx *compiler.CompilerContext) { ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100) }, line: "DEC MAX", expectError: true, }, { name: "Error: new syntax on unknown variable", setup: func(ctx *compiler.CompilerContext) { // No setup }, line: "unknown--", expectError: 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 := &DecrCommand{} line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"} err := cmd.Interpret(line, ctx) if tt.expectError { if err == nil { t.Errorf("Expected error, got nil") } return } if err != nil { t.Fatalf("Unexpected error: %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate error: %v", err) } if tt.checkAsm != nil { tt.checkAsm(t, asm) } }) } } func TestIncrDecrCommand_FullNameResolution(t *testing.T) { // Test that variable name resolution works with full names ctx := compiler.NewCompilerContext(preproc.NewPragma()) // Add a variable with scoped name directly ctx.SymbolTable.AddVar("counter", "myfunc", compiler.KindWord, 0) // Note: CurrentScope will return nil (global) since we're not in a function context // The symbol table lookup will try scoped search and fall back to global // Test that using the base name in global scope won't find the scoped var incrCmd := &IncrCommand{} line := preproc.Line{Text: "INC counter", LineNo: 1, Filename: "test.c65"} err := incrCmd.Interpret(line, ctx) // Should fail - counter exists only in myfunc scope, not global if err == nil { t.Errorf("Expected error when accessing scoped variable from global scope") } }