package compiler import ( "fmt" "strings" ) // VarKind represents the data type/size of a variable type VarKind uint8 const ( KindByte VarKind = iota KindWord // Future: KindDWord, Kind24bit, etc ) // SymbolFlags represents properties of a symbol as a bitfield type SymbolFlags uint16 const ( FlagByte SymbolFlags = 1 << iota FlagWord FlagConst FlagAbsolute FlagZeroPage FlagLabelRef ) // Symbol represents a variable or constant declaration type Symbol struct { Name string Scope string // empty string = global, otherwise function name Flags SymbolFlags Value uint16 // init value or const value AbsAddr uint16 // if FlagAbsolute set LabelRef string // if FlagLabelRef set } // Helper methods for Symbol func (s *Symbol) Has(flag SymbolFlags) bool { return s.Flags&flag != 0 } func (s *Symbol) HasAll(flags SymbolFlags) bool { return s.Flags&flags == flags } func (s *Symbol) IsByte() bool { return s.Has(FlagByte) } func (s *Symbol) IsWord() bool { return s.Has(FlagWord) } func (s *Symbol) IsConst() bool { return s.Has(FlagConst) } 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) } // FullName returns the fully qualified name (scope.name or just name) func (s *Symbol) FullName() string { if s.Scope == "" { return s.Name } return s.Scope + "_" + s.Name } // SymbolTable manages variable and constant declarations type SymbolTable struct { symbols []*Symbol // insertion order byFullName map[string]*Symbol // fullname -> symbol byScope map[string]map[string]*Symbol // scope -> name -> symbol funcHandler FunctionHandlerInterface // for notifying about absolute variables in functions } // FunctionHandlerInterface allows SymbolTable to notify about absolute variables // without creating a circular dependency type FunctionHandlerInterface interface { RecordAbsoluteVar(funcScope string, addr uint16, isWord bool) } // NewSymbolTable creates a new symbol table func NewSymbolTable() *SymbolTable { return &SymbolTable{ symbols: make([]*Symbol, 0), byFullName: make(map[string]*Symbol), byScope: make(map[string]map[string]*Symbol), } } // SetFunctionHandler sets the function handler for absolute variable notifications func (st *SymbolTable) SetFunctionHandler(fh FunctionHandlerInterface) { st.funcHandler = fh } // AddVar adds a regular variable (byte or word) func (st *SymbolTable) AddVar(name, scope string, kind VarKind, initValue uint16) error { var flags SymbolFlags switch kind { case KindByte: flags = FlagByte if initValue > 255 { return fmt.Errorf("byte variable %q init value %d out of range", name, initValue) } case KindWord: flags = FlagWord default: return fmt.Errorf("unknown variable kind: %d", kind) } return st.add(&Symbol{ Name: name, Scope: scope, Flags: flags, Value: initValue, }) } // AddConst adds a constant (byte or word) func (st *SymbolTable) AddConst(name, scope string, kind VarKind, value uint16) error { var flags SymbolFlags switch kind { case KindByte: flags = FlagByte | FlagConst if value > 255 { return fmt.Errorf("byte constant %q value %d out of range", name, value) } case KindWord: flags = FlagWord | FlagConst default: return fmt.Errorf("unknown variable kind: %d", kind) } return st.add(&Symbol{ Name: name, Scope: scope, Flags: flags, Value: value, }) } // AddAbsolute adds a variable at a fixed memory address func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16) error { if addr > 0xFFFF { return fmt.Errorf("absolute address %d exceeds 16-bit range", addr) } var flags SymbolFlags switch kind { case KindByte: flags = FlagByte | FlagAbsolute // Zero page check for bytes if addr < 0x100 { flags |= FlagZeroPage } case KindWord: flags = FlagWord | FlagAbsolute // Zero page check for words (pointer must fit in ZP) if addr < 0xFF { flags |= FlagZeroPage } default: return fmt.Errorf("unknown variable kind: %d", kind) } // Notify function handler about absolute variable in function scope if st.funcHandler != nil && scope != "" { st.funcHandler.RecordAbsoluteVar(scope, addr, kind == KindWord) } return st.add(&Symbol{ Name: name, Scope: scope, Flags: flags, AbsAddr: addr, }) } // AddLabel adds a word variable that references a label func (st *SymbolTable) AddLabel(name, scope string, labelRef string) error { return st.add(&Symbol{ Name: name, Scope: scope, Flags: FlagWord | FlagLabelRef, LabelRef: labelRef, }) } // add is the internal method that actually adds a symbol func (st *SymbolTable) add(sym *Symbol) error { fullName := sym.FullName() // Check for redeclaration if _, exists := st.byFullName[fullName]; exists { return fmt.Errorf("symbol %q already declared", fullName) } // Add to all indexes st.symbols = append(st.symbols, sym) st.byFullName[fullName] = sym // Add to scope index if st.byScope[sym.Scope] == nil { st.byScope[sym.Scope] = make(map[string]*Symbol) } st.byScope[sym.Scope][sym.Name] = sym return nil } // Lookup finds a symbol by name, resolving scope // Searches local scope first (if currentScopes provided), then global 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 { return sym } } } // Try global scope if scopeMap, ok := st.byScope[""]; ok { if sym, ok := scopeMap[name]; ok { return sym } } return nil } // Get retrieves a symbol by its full name func (st *SymbolTable) Get(fullName string) *Symbol { return st.byFullName[fullName] } // Symbols returns all symbols in insertion order func (st *SymbolTable) Symbols() []*Symbol { return st.symbols } // Count returns the number of symbols func (st *SymbolTable) Count() int { return len(st.symbols) } // ExpandName resolves a local name to its full name using scope resolution func (st *SymbolTable) ExpandName(name string, currentScopes []string) string { sym := st.Lookup(name, currentScopes) if sym != nil { return sym.FullName() } return name } // String representation for debugging func (s *Symbol) String() string { var parts []string parts = append(parts, fmt.Sprintf("Name=%s", s.FullName())) if s.IsByte() { parts = append(parts, "BYTE") } else if s.IsWord() { parts = append(parts, "WORD") } if s.IsConst() { parts = append(parts, fmt.Sprintf("CONST=%d", s.Value)) } else if s.IsAbsolute() { parts = append(parts, fmt.Sprintf("@$%04X", s.AbsAddr)) if s.IsZeroPage() { parts = append(parts, "ZP") } } else if s.Has(FlagLabelRef) { parts = append(parts, fmt.Sprintf("->%s", s.LabelRef)) } else if s.Value != 0 { parts = append(parts, fmt.Sprintf("=%d", s.Value)) } return strings.Join(parts, " ") } // Code generation functions for ACME assembler syntax // GenerateConstants generates constant definitions (name = $value) func GenerateConstants(st *SymbolTable) []string { var lines []string hasConsts := false for _, sym := range st.Symbols() { if !sym.IsConst() { continue } hasConsts = true var line string if sym.IsByte() { // Byte constant with decimal comment line = fmt.Sprintf("%s = $%02x\t; %d", sym.FullName(), sym.Value, sym.Value) } else { // Word constant line = fmt.Sprintf("%s = $%04x", sym.FullName(), sym.Value) } lines = append(lines, line) } if hasConsts { // Prepend header result := []string{ ";Constant values (from c65gm)", "", } result = append(result, lines...) result = append(result, "") // blank line after return result } return nil } // GenerateAbsolutes generates absolute address assignments (name = $addr) func GenerateAbsolutes(st *SymbolTable) []string { var lines []string hasAbsolutes := false for _, sym := range st.Symbols() { if !sym.IsAbsolute() { continue } hasAbsolutes = true var line string if sym.IsZeroPage() { // Zero-page: 2 hex digits line = fmt.Sprintf("%s = $%02x", sym.FullName(), sym.AbsAddr) } else { // Non-zero-page: 4 hex digits line = fmt.Sprintf("%s = $%04x", sym.FullName(), sym.AbsAddr) } lines = append(lines, line) } if hasAbsolutes { // Prepend header result := []string{ ";Absolute variable definitions (from c65gm)", "", } result = append(result, lines...) result = append(result, "") // blank line after return result } return nil } // GenerateVariables generates variable declarations (name !8 $value) func GenerateVariables(st *SymbolTable) []string { var lines []string hasVars := false for _, sym := range st.Symbols() { // Skip constants and absolutes - they're handled separately if sym.IsConst() || sym.IsAbsolute() { continue } hasVars = true var line string if sym.IsByte() { // Byte variable with decimal comment line = fmt.Sprintf("%s\t!8 $%02x\t; %d", sym.FullName(), sym.Value&0xFF, sym.Value&0xFF) } else if sym.Has(FlagLabelRef) { // Word with label reference line = fmt.Sprintf("%s\t!8 <%s, >%s", sym.FullName(), sym.LabelRef, sym.LabelRef) } else { // Word variable (split into low byte, high byte) lo := sym.Value & 0xFF hi := (sym.Value >> 8) & 0xFF line = fmt.Sprintf("%s\t!8 $%02x, $%02x", sym.FullName(), lo, hi) } lines = append(lines, line) } if hasVars { // Prepend header result := []string{ ";Variables (from c65gm)", "", } result = append(result, lines...) result = append(result, "") // blank line after return result } return nil } // GetVarKind extracts VarKind from Symbol func (s *Symbol) GetVarKind() VarKind { if s.IsByte() { return KindByte } return KindWord }