package compiler import ( "fmt" "strings" "testing" "c65gm/internal/preproc" "c65gm/internal/utils" ) // TestBreakCommand is a simple command implementation for testing type TestBreakCommand struct { line preproc.Line } func (c *TestBreakCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } return strings.ToUpper(params[0]) == "BREAK" } func (c *TestBreakCommand) Interpret(line preproc.Line, ctx *CompilerContext) error { c.line = line params, err := utils.ParseParams(line.Text) if err != nil { return err } if len(params) != 1 { return fmt.Errorf("BREAK does not expect parameters") } return nil } func (c *TestBreakCommand) Generate(ctx *CompilerContext) ([]string, error) { // BREAK jumps to end of WHILE loop label, err := ctx.LoopEndStack.Peek() if err != nil { return nil, fmt.Errorf("BREAK outside of WHILE loop") } return []string{ fmt.Sprintf(" jmp %s_end", label), }, nil } func TestCompilerArchitecture(t *testing.T) { // Create pragma pragma := preproc.NewPragma() // Create compiler comp := NewCompiler(pragma) // Register BREAK command comp.Registry().Register(&TestBreakCommand{}) // Create test input - BREAK inside a simulated WHILE lines := []preproc.Line{ { Text: "BREAK", Filename: "test.c65", LineNo: 1, Kind: preproc.Source, PragmaSetIndex: 0, }, } // Manually push a WHILE label so BREAK has something to reference comp.Context().LoopEndStack.Push() // Compile output, err := comp.Compile(lines) // Should fail because BREAK needs proper WHILE context // But this tests the basic flow: WillHandle -> Interpret -> Generate if err != nil { t.Logf("Expected controlled error: %v", err) } // Check we got some output structure if len(output) == 0 { t.Logf("Got output lines: %d", len(output)) } t.Logf("Output:\n%s", strings.Join(output, "\n")) } func TestCommandRegistry(t *testing.T) { registry := NewCommandRegistry() breakCmd := &TestBreakCommand{} registry.Register(breakCmd) line := preproc.Line{ Text: "BREAK", Filename: "test.c65", LineNo: 1, Kind: preproc.Source, } cmd, found := registry.FindHandler(line) if !found { t.Fatal("Expected to find BREAK handler") } if cmd != breakCmd { t.Fatal("Expected to get same command instance") } } func TestCompilerContext(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) // Test that all resources are initialized if ctx.SymbolTable == nil { t.Error("SymbolTable not initialized") } if ctx.FunctionHandler == nil { t.Error("FunctionHandler not initialized") } if ctx.ConstStrHandler == nil { t.Error("ConstStrHandler not initialized") } if ctx.LoopEndStack == nil { t.Error("LoopEndStack not initialized") } if ctx.IfStack == nil { t.Error("IfStack not initialized") } if ctx.GeneralStack == nil { t.Error("GeneralStack not initialized") } if ctx.Pragma == nil { t.Error("Pragma not initialized") } if ctx.ScriptLibraryGlobals == nil { t.Error("ScriptLibraryGlobals not initialized") } // Test CurrentScope scope := ctx.CurrentScope() if scope != nil { t.Errorf("Expected nil scope in global context, got %v", scope) } } func TestExecuteScript_BasicPrint(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) scriptLines := []string{ "for i in range(3):", " print(' nop')", } output, err := executeScript(scriptLines, ctx, false) if err != nil { t.Fatalf("executeScript failed: %v", err) } if len(output) != 3 { t.Fatalf("expected 3 output lines, got %d: %v", len(output), output) } for i, line := range output { if line != " nop" { t.Errorf("line %d: expected ' nop', got %q", i, line) } } } func TestExecuteScript_EmptyOutput(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) scriptLines := []string{ "x = 1 + 1", } output, err := executeScript(scriptLines, ctx, false) if err != nil { t.Fatalf("executeScript failed: %v", err) } if len(output) != 0 { t.Errorf("expected 0 output lines, got %d: %v", len(output), output) } } func TestExecuteScript_Library_DefinesFunction(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) // Define a function in library mode libraryLines := []string{ "def emit_nops(count):", " for i in range(count):", " print(' nop')", } _, err := executeScript(libraryLines, ctx, true) if err != nil { t.Fatalf("library executeScript failed: %v", err) } // Verify function is in globals if _, ok := ctx.ScriptLibraryGlobals["emit_nops"]; !ok { t.Fatal("expected emit_nops to be defined in ScriptLibraryGlobals") } } func TestExecuteScript_Library_FunctionCallableFromScript(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) // First: define function in library libraryLines := []string{ "def emit_nops(count):", " for i in range(count):", " print(' nop')", } _, err := executeScript(libraryLines, ctx, true) if err != nil { t.Fatalf("library executeScript failed: %v", err) } // Second: call function from regular script scriptLines := []string{ "emit_nops(2)", } output, err := executeScript(scriptLines, ctx, false) if err != nil { t.Fatalf("script executeScript failed: %v", err) } if len(output) != 2 { t.Fatalf("expected 2 output lines, got %d: %v", len(output), output) } for i, line := range output { if line != " nop" { t.Errorf("line %d: expected ' nop', got %q", i, line) } } } func TestExecuteScript_MultipleLibraries_Accumulate(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) // First library: define func_a lib1 := []string{ "def func_a():", " print(' ; from a')", } _, err := executeScript(lib1, ctx, true) if err != nil { t.Fatalf("lib1 failed: %v", err) } // Second library: define func_b (should still have func_a) lib2 := []string{ "def func_b():", " print(' ; from b')", } _, err = executeScript(lib2, ctx, true) if err != nil { t.Fatalf("lib2 failed: %v", err) } // Both functions should be available if _, ok := ctx.ScriptLibraryGlobals["func_a"]; !ok { t.Error("func_a missing after second library") } if _, ok := ctx.ScriptLibraryGlobals["func_b"]; !ok { t.Error("func_b missing after second library") } // Call both from a script scriptLines := []string{ "func_a()", "func_b()", } output, err := executeScript(scriptLines, ctx, false) if err != nil { t.Fatalf("script failed: %v", err) } if len(output) != 2 { t.Fatalf("expected 2 lines, got %d: %v", len(output), output) } if output[0] != " ; from a" { t.Errorf("expected ' ; from a', got %q", output[0]) } if output[1] != " ; from b" { t.Errorf("expected ' ; from b', got %q", output[1]) } } func TestExecuteScript_RegularScript_DoesNotPersist(t *testing.T) { pragma := preproc.NewPragma() ctx := NewCompilerContext(pragma) // Define function in regular script (not library) scriptLines := []string{ "def local_func():", " print('hello')", "local_func()", } output, err := executeScript(scriptLines, ctx, false) if err != nil { t.Fatalf("script failed: %v", err) } if len(output) != 1 || output[0] != "hello" { t.Errorf("unexpected output: %v", output) } // Function should NOT be in globals (it was in regular script) if _, ok := ctx.ScriptLibraryGlobals["local_func"]; ok { t.Error("local_func should not persist from regular script") } }