From 0bfbfcdde5458b3e51d6ba5af1f90faab617aa81 Mon Sep 17 00:00:00 2001 From: Mattias Hansson Date: Mon, 13 Apr 2026 12:57:27 +0200 Subject: [PATCH] Now unused vars seem to work --- internal/compiler/compiler.go | 6 +++ internal/compiler/funchandler.go | 4 +- internal/compiler/symboltable.go | 69 +++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index 6a61b1c..352e940 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -222,6 +222,12 @@ 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() + for _, warning := range warnings { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", warning) + } + // Assemble final output with headers and footers return c.assembleOutput(codeOutput), nil } diff --git a/internal/compiler/funchandler.go b/internal/compiler/funchandler.go index 0bfb449..ab63adc 100644 --- a/internal/compiler/funchandler.go +++ b/internal/compiler/funchandler.go @@ -130,8 +130,8 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) { } } - // Look up variable in symbol table - sym := fh.symTable.Lookup(varName, []string{funcName}) + // Look up variable in symbol table (validation only, not usage) + sym := fh.symTable.LookupWithoutUsage(varName, []string{funcName}) if sym == nil { fh.currentFuncs = fh.currentFuncs[:len(fh.currentFuncs)-1] return nil, fmt.Errorf("%s:%d: FUNC %s: parameter %q not declared", line.Filename, line.LineNo, funcName, varName) diff --git a/internal/compiler/symboltable.go b/internal/compiler/symboltable.go index 05f1850..b903257 100644 --- a/internal/compiler/symboltable.go +++ b/internal/compiler/symboltable.go @@ -26,6 +26,12 @@ const ( FlagLabelRef ) +// Usage tracking for variables +const ( + UsageNone uint8 = iota + UsageUsed +) + // Symbol represents a variable or constant declaration type Symbol struct { Name string @@ -34,6 +40,7 @@ type Symbol struct { Value uint16 // init value or const value AbsAddr uint16 // if FlagAbsolute set LabelRef string // if FlagLabelRef set + Usage uint8 // tracks variable usage (see Usage constants) } // Helper methods for Symbol @@ -52,6 +59,11 @@ func (s *Symbol) IsAbsolute() bool { return s.Has(FlagAbsolute) } func (s *Symbol) IsZeroPage() bool { return s.Has(FlagZeroPage) } func (s *Symbol) IsZeroPagePointer() bool { return s.HasAll(FlagAbsolute | FlagZeroPage | FlagWord) } +// MarkUsed marks the symbol as used (should not be called for constants or absolutes) +func (s *Symbol) MarkUsed() { + s.Usage = UsageUsed +} + // FullName returns the fully qualified name (scope.name or just name) func (s *Symbol) FullName() string { if s.Scope == "" { @@ -208,12 +220,17 @@ func (st *SymbolTable) add(sym *Symbol) error { // Lookup finds a symbol by name, resolving scope // Searches local scope first (if currentScopes provided), then global +// Marks non-constant, non-absolute variables as used func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol { // Try local scopes first (innermost to outermost) for i := len(currentScopes) - 1; i >= 0; i-- { scope := currentScopes[i] if scopeMap, ok := st.byScope[scope]; ok { if sym, ok := scopeMap[name]; ok { + // Mark as used if it's a regular variable (not constant, not absolute) + if !sym.IsConst() && !sym.IsAbsolute() { + sym.MarkUsed() + } return sym } } @@ -222,6 +239,10 @@ func (st *SymbolTable) Lookup(name string, currentScopes []string) *Symbol { // Try global scope if scopeMap, ok := st.byScope[""]; ok { if sym, ok := scopeMap[name]; ok { + // Mark as used if it's a regular variable (not constant, not absolute) + if !sym.IsConst() && !sym.IsAbsolute() { + sym.MarkUsed() + } return sym } } @@ -253,10 +274,33 @@ func (st *SymbolTable) ExpandName(name string, currentScopes []string) string { return name } +// LookupWithoutUsage finds a symbol by name without marking it as used +// Used for validation-only lookups (e.g., checking if variable exists) +func (st *SymbolTable) LookupWithoutUsage(name string, currentScopes []string) *Symbol { + // Try local scopes first (innermost to outermost) + for i := len(currentScopes) - 1; i >= 0; i-- { + scope := currentScopes[i] + if scopeMap, ok := st.byScope[scope]; ok { + if sym, ok := scopeMap[name]; ok { + return sym + } + } + } + + // Try global scope + if scopeMap, ok := st.byScope[""]; ok { + if sym, ok := scopeMap[name]; ok { + return sym + } + } + + return nil +} + // LookupConstant looks up a constant by name and returns its value if found // Returns (value, true) if symbol exists and is a constant, (0, false) otherwise func (st *SymbolTable) LookupConstant(name string, currentScopes []string) (int64, bool) { - sym := st.Lookup(name, currentScopes) + sym := st.LookupWithoutUsage(name, currentScopes) if sym != nil && sym.IsConst() { return int64(sym.Value), true } @@ -271,6 +315,29 @@ 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 { + var warnings []string + for _, sym := range st.symbols { + // Skip constants and absolute variables + if sym.IsConst() || sym.IsAbsolute() { + continue + } + // Check if variable was never used + if sym.Usage == UsageNone { + // Format warning message + var scopeInfo string + if sym.Scope != "" { + scopeInfo = fmt.Sprintf(" in function '%s'", sym.Scope) + } + warning := fmt.Sprintf("warning: variable '%s'%s declared but never used", sym.Name, scopeInfo) + warnings = append(warnings, warning) + } + } + return warnings +} + // String representation for debugging func (s *Symbol) String() string { var parts []string