diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index 3a956e2..773af5a 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -223,8 +223,11 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { // Analyze for overlapping absolute addresses in function call chains c.checkAbsoluteOverlaps() - // Check for unused variables and print warnings - warnings := c.ctx.SymbolTable.CheckUnused() + // Get functions with _P_REMOVE_UNUSED pragma (for suppressing variable warnings) + funcsWithRemovePragma := c.ctx.FunctionHandler.GetFunctionsWithRemovePragma() + + // Check for unused variables and print warnings (skip variables in functions with remove pragma) + warnings := c.ctx.SymbolTable.CheckUnused(funcsWithRemovePragma) for _, warning := range warnings { _, _ = fmt.Fprintf(os.Stderr, "%s\n", warning) } @@ -236,10 +239,10 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { } // Remove unused functions with _P_REMOVE_UNUSED pragma - codeOutput = c.removeUnusedFunctions(codeOutput) + codeOutput, removedFuncs := c.removeUnusedFunctions(codeOutput) // Assemble final output with headers and footers - return c.assembleOutput(codeOutput), nil + return c.assembleOutput(codeOutput, removedFuncs), nil } // printErrorWithContext prints an error with source code context to stderr @@ -454,10 +457,11 @@ func findPipeOutsideStrings(line string, startFrom int) int { } // removeUnusedFunctions removes functions marked with _P_REMOVE_UNUSED pragma from assembly output -func (c *Compiler) removeUnusedFunctions(codeLines []string) []string { +// Returns the filtered code lines and a map of removed function names +func (c *Compiler) removeUnusedFunctions(codeLines []string) ([]string, map[string]bool) { toRemove := c.ctx.FunctionHandler.GetFunctionsToRemove() if len(toRemove) == 0 { - return codeLines + return codeLines, nil } var result []string @@ -515,11 +519,11 @@ func (c *Compiler) removeUnusedFunctions(codeLines []string) []string { result = append(result, line) i++ } - return result + return result, toRemove } // assembleOutput combines all generated sections into final assembly -func (c *Compiler) assembleOutput(codeLines []string) []string { +func (c *Compiler) assembleOutput(codeLines []string, removedFuncs map[string]bool) []string { var output []string // Header comment @@ -527,12 +531,12 @@ func (c *Compiler) assembleOutput(codeLines []string) []string { output = append(output, "") // Constants section - if constLines := GenerateConstants(c.ctx.SymbolTable); len(constLines) > 0 { + if constLines := GenerateConstants(c.ctx.SymbolTable, removedFuncs); len(constLines) > 0 { output = append(output, constLines...) } // Absolute addresses section - if absLines := GenerateAbsolutes(c.ctx.SymbolTable); len(absLines) > 0 { + if absLines := GenerateAbsolutes(c.ctx.SymbolTable, removedFuncs); len(absLines) > 0 { output = append(output, absLines...) } @@ -543,7 +547,7 @@ func (c *Compiler) assembleOutput(codeLines []string) []string { output = append(output, "") // Variables section - if varLines := GenerateVariables(c.ctx.SymbolTable); len(varLines) > 0 { + if varLines := GenerateVariables(c.ctx.SymbolTable, removedFuncs); len(varLines) > 0 { output = append(output, varLines...) } diff --git a/internal/compiler/funchandler.go b/internal/compiler/funchandler.go index 644540d..08230f6 100644 --- a/internal/compiler/funchandler.go +++ b/internal/compiler/funchandler.go @@ -1064,3 +1064,21 @@ func (fh *FunctionHandler) GetFunctionsToRemove() map[string]bool { return toRemove } + +// GetFunctionsWithRemovePragma returns a map of function names that have _P_REMOVE_UNUSED pragma enabled +// This is used to suppress variable warnings for functions marked for removal +func (fh *FunctionHandler) GetFunctionsWithRemovePragma() map[string]bool { + withPragma := make(map[string]bool) + + for _, funcDecl := range fh.functions { + if fh.pragma != nil { + pragmaSet := fh.pragma.GetPragmaSetByIndex(funcDecl.Line.PragmaSetIndex) + removeValue := pragmaSet.GetPragma("_P_REMOVE_UNUSED") + if removeValue != "" && removeValue != "0" { + withPragma[funcDecl.Name] = true + } + } + } + + return withPragma +} diff --git a/internal/compiler/symboltable.go b/internal/compiler/symboltable.go index ee0fe17..e4a7949 100644 --- a/internal/compiler/symboltable.go +++ b/internal/compiler/symboltable.go @@ -339,7 +339,8 @@ func (st *SymbolTable) ConstantLookupFunc(currentScopes []string) func(string) ( // CheckUnused returns warnings for unused variables // Returns slice of warning messages for regular variables (not constants, not absolutes) that were never used -func (st *SymbolTable) CheckUnused() []string { +// excludeFuncs is a map of function names that will be removed (e.g., have _P_REMOVE_UNUSED pragma) +func (st *SymbolTable) CheckUnused(excludeFuncs map[string]bool) []string { var warnings []string for _, sym := range st.symbols { // Skip constants and absolute variables (they shouldn't track usage) @@ -353,6 +354,11 @@ func (st *SymbolTable) CheckUnused() []string { continue } + // Skip variables in functions that will be removed + if sym.Scope != "" && excludeFuncs != nil && excludeFuncs[sym.Scope] { + continue + } + // Check if pragma indicates we should ignore unused warnings for this variable if st.pragma != nil { pragmaSet := st.pragma.GetPragmaSetByIndex(sym.Line.PragmaSetIndex) @@ -412,7 +418,7 @@ func (s *Symbol) String() string { // Code generation functions for ACME assembler syntax // GenerateConstants generates constant definitions (name = $value) -func GenerateConstants(st *SymbolTable) []string { +func GenerateConstants(st *SymbolTable, excludeScopes map[string]bool) []string { var lines []string hasConsts := false @@ -420,6 +426,10 @@ func GenerateConstants(st *SymbolTable) []string { if !sym.IsConst() { continue } + // Skip constants scoped to removed functions + if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] { + continue + } hasConsts = true var line string @@ -450,7 +460,7 @@ func GenerateConstants(st *SymbolTable) []string { } // GenerateAbsolutes generates absolute address assignments (name = $addr) -func GenerateAbsolutes(st *SymbolTable) []string { +func GenerateAbsolutes(st *SymbolTable, excludeScopes map[string]bool) []string { var lines []string hasAbsolutes := false @@ -458,6 +468,10 @@ func GenerateAbsolutes(st *SymbolTable) []string { if !sym.IsAbsolute() { continue } + // Skip absolutes scoped to removed functions + if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] { + continue + } hasAbsolutes = true var line string @@ -488,7 +502,7 @@ func GenerateAbsolutes(st *SymbolTable) []string { } // GenerateVariables generates variable declarations (name !8 $value) -func GenerateVariables(st *SymbolTable) []string { +func GenerateVariables(st *SymbolTable, excludeScopes map[string]bool) []string { var lines []string hasVars := false @@ -497,6 +511,10 @@ func GenerateVariables(st *SymbolTable) []string { if sym.IsConst() || sym.IsAbsolute() { continue } + // Skip variables scoped to removed functions + if excludeScopes != nil && sym.Scope != "" && excludeScopes[sym.Scope] { + continue + } hasVars = true var line string diff --git a/internal/compiler/symboltable_test.go b/internal/compiler/symboltable_test.go index 067e69a..16cdc7a 100644 --- a/internal/compiler/symboltable_test.go +++ b/internal/compiler/symboltable_test.go @@ -490,7 +490,7 @@ func TestGenerateConstants(t *testing.T) { st.AddConst("SIZE", "", KindWord, 0x1234, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("notconst", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) // should be skipped - lines := GenerateConstants(st) + lines := GenerateConstants(st, nil) if len(lines) == 0 { t.Fatal("expected output lines") @@ -531,7 +531,7 @@ func TestGenerateAbsolutes(t *testing.T) { // Regular var (should be skipped) st.AddVar("regular", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) - lines := GenerateAbsolutes(st) + lines := GenerateAbsolutes(st, nil) if len(lines) == 0 { t.Fatal("expected output lines") @@ -580,7 +580,7 @@ func TestGenerateVariables(t *testing.T) { // Absolute (should be skipped) st.AddAbsolute("SKIP2", "", KindByte, 0x80, preproc.Line{Filename: "test.c65", LineNo: 1}) - lines := GenerateVariables(st) + lines := GenerateVariables(st, nil) if len(lines) == 0 { t.Fatal("expected output lines") @@ -624,23 +624,23 @@ func TestGenerateEmpty(t *testing.T) { st := NewSymbolTable() // Empty table - if lines := GenerateConstants(st); lines != nil { + if lines := GenerateConstants(st, nil); lines != nil { t.Error("expected nil for empty constants") } - if lines := GenerateAbsolutes(st); lines != nil { + if lines := GenerateAbsolutes(st, nil); lines != nil { t.Error("expected nil for empty absolutes") } - if lines := GenerateVariables(st); lines != nil { + if lines := GenerateVariables(st, nil); lines != nil { t.Error("expected nil for empty variables") } // Only variables (no constants/absolutes) st.AddVar("test", "", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) - if lines := GenerateConstants(st); lines != nil { + if lines := GenerateConstants(st, nil); lines != nil { t.Error("expected nil when no constants exist") } - if lines := GenerateAbsolutes(st); lines != nil { + if lines := GenerateAbsolutes(st, nil); lines != nil { t.Error("expected nil when no absolutes exist") } } @@ -652,7 +652,7 @@ func TestGenerateScopedVariables(t *testing.T) { st.AddVar("local", "main", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("nested", "main_helper", KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) - lines := GenerateVariables(st) + lines := GenerateVariables(st, nil) output := strings.Join(lines, "\n") // Check full names are used @@ -674,9 +674,9 @@ func TestGenerateHexLowercase(t *testing.T) { st.AddAbsolute("ADDR", "", KindWord, 0xDEAD, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("VAR", "", KindWord, 0xBEEF, preproc.Line{Filename: "test.c65", LineNo: 1}) - constLines := GenerateConstants(st) - absLines := GenerateAbsolutes(st) - varLines := GenerateVariables(st) + constLines := GenerateConstants(st, nil) + absLines := GenerateAbsolutes(st, nil) + varLines := GenerateVariables(st, nil) output := strings.Join(append(append(constLines, absLines...), varLines...), "\n") @@ -716,7 +716,7 @@ func TestUsageTracking(t *testing.T) { } // Check that warning is generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 1 { t.Fatalf("CheckUnused() returned %d warnings, want 1", len(warnings)) } @@ -748,7 +748,7 @@ func TestUsageTracking(t *testing.T) { } // Check that no warning is generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 0 { t.Errorf("CheckUnused() returned %d warnings, want 0: %v", len(warnings), warnings) } @@ -764,7 +764,7 @@ func TestUsageTracking(t *testing.T) { } // Check that no warning is generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 0 { t.Errorf("CheckUnused() returned %d warnings for constant, want 0: %v", len(warnings), warnings) } @@ -786,7 +786,7 @@ func TestUsageTracking(t *testing.T) { } // Check that no warning is generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 0 { t.Errorf("CheckUnused() returned %d warnings for absolute variable, want 0: %v", len(warnings), warnings) } @@ -821,7 +821,7 @@ func TestUsageTracking(t *testing.T) { st.Lookup("local_used", []string{"myFunc"}) // Check warnings - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 2 { t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings) } @@ -873,7 +873,7 @@ func TestUsageTracking(t *testing.T) { } // No warnings should be generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 0 { t.Errorf("CheckUnused() returned %d warnings for used variable, want 0", len(warnings)) } @@ -900,7 +900,7 @@ func TestUsageTracking(t *testing.T) { } // Warning should be generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 1 { t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings)) } @@ -922,7 +922,7 @@ func TestUsageTracking(t *testing.T) { st.Lookup("used2", []string{"func1"}) // Check warnings - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 2 { t.Fatalf("CheckUnused() returned %d warnings, want 2: %v", len(warnings), warnings) } @@ -970,7 +970,7 @@ func TestUsageTracking(t *testing.T) { } // Since we haven't used it, it should generate a warning - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 1 { t.Errorf("CheckUnused() returned %d warnings for label reference, want 1", len(warnings)) } @@ -979,7 +979,7 @@ func TestUsageTracking(t *testing.T) { st.Lookup("handler", []string{}) // Now no warning should be generated - warnings = st.CheckUnused() + warnings = st.CheckUnused(nil) if len(warnings) != 0 { t.Errorf("CheckUnused() returned %d warnings for used label reference, want 0", len(warnings)) } @@ -1022,7 +1022,7 @@ func TestUsageTracking(t *testing.T) { } // Warning should be generated for the variable - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 1 { t.Errorf("CheckUnused() returned %d warnings, want 1", len(warnings)) } @@ -1067,7 +1067,7 @@ func TestUsageTracking(t *testing.T) { } // No warnings should be generated - warnings := st.CheckUnused() + warnings := st.CheckUnused(nil) if len(warnings) != 0 { t.Errorf("CheckUnused() returned %d warnings for constants/absolutes, want 0", len(warnings)) }