Added call graph functionality and analysis/warning of absolute address variable reuse overlap
This commit is contained in:
parent
35e4b77ee3
commit
ebad3c2e16
5 changed files with 608 additions and 3 deletions
|
|
@ -129,10 +129,18 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
|||
return nil, fmt.Errorf("Unclosed SCRIPT block.")
|
||||
}
|
||||
|
||||
// Analyze for overlapping absolute addresses in function call chains
|
||||
c.checkAbsoluteOverlaps()
|
||||
|
||||
// Assemble final output with headers and footers
|
||||
return c.assembleOutput(codeOutput), nil
|
||||
}
|
||||
|
||||
// checkAbsoluteOverlaps analyzes and warns about overlapping absolute addresses
|
||||
func (c *Compiler) checkAbsoluteOverlaps() {
|
||||
c.ctx.FunctionHandler.ReportAbsoluteOverlaps()
|
||||
}
|
||||
|
||||
// assembleOutput combines all generated sections into final assembly
|
||||
func (c *Compiler) assembleOutput(codeLines []string) []string {
|
||||
var output []string
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ func NewCompilerContext(pragma *preproc.Pragma) *CompilerContext {
|
|||
// FunctionHandler needs references to other components
|
||||
ctx.FunctionHandler = NewFunctionHandler(symTable, generalStack, constStrHandler, pragma)
|
||||
|
||||
// Connect SymbolTable to FunctionHandler for absolute variable notifications
|
||||
symTable.SetFunctionHandler(ctx.FunctionHandler)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package compiler
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/preproc"
|
||||
|
|
@ -40,6 +42,10 @@ type FunctionHandler struct {
|
|||
labelStack *LabelStack
|
||||
constStrHandler *ConstantStringHandler
|
||||
pragma *preproc.Pragma
|
||||
|
||||
// Absolute address tracking for overlap detection
|
||||
absoluteAddrs map[string]map[uint16]bool // funcName -> set of absolute addresses used
|
||||
callGraph map[string][]string // funcName -> list of functions it calls
|
||||
}
|
||||
|
||||
// NewFunctionHandler creates a new function handler
|
||||
|
|
@ -51,6 +57,8 @@ func NewFunctionHandler(st *SymbolTable, ls *LabelStack, csh *ConstantStringHand
|
|||
labelStack: ls,
|
||||
constStrHandler: csh,
|
||||
pragma: pragma,
|
||||
absoluteAddrs: make(map[string]map[uint16]bool),
|
||||
callGraph: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,10 +158,51 @@ func (fh *FunctionHandler) HandleFuncDecl(line preproc.Line) ([]string, error) {
|
|||
Params: funcParams,
|
||||
})
|
||||
|
||||
// Record absolute addresses used by this function
|
||||
fh.recordAbsoluteAddresses(funcName, funcParams)
|
||||
|
||||
// Generate assembler label
|
||||
return []string{funcName}, nil
|
||||
}
|
||||
|
||||
// recordAbsoluteAddresses stores which absolute addresses a function uses
|
||||
func (fh *FunctionHandler) recordAbsoluteAddresses(funcName string, params []*FuncParam) {
|
||||
addrSet := make(map[uint16]bool)
|
||||
|
||||
for _, param := range params {
|
||||
if param.Symbol.IsAbsolute() {
|
||||
addr := param.Symbol.AbsAddr
|
||||
// Mark address as used
|
||||
addrSet[addr] = true
|
||||
// If it's a word, also mark the next byte
|
||||
if param.Symbol.IsWord() {
|
||||
addrSet[addr+1] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only store if function uses absolute addresses
|
||||
if len(addrSet) > 0 {
|
||||
fh.absoluteAddrs[funcName] = addrSet
|
||||
}
|
||||
}
|
||||
|
||||
// RecordAbsoluteVar is called by SymbolTable when an absolute variable is added to a function scope
|
||||
func (fh *FunctionHandler) RecordAbsoluteVar(funcScope string, addr uint16, isWord bool) {
|
||||
if funcScope == "" {
|
||||
return // global variable, skip
|
||||
}
|
||||
|
||||
if fh.absoluteAddrs[funcScope] == nil {
|
||||
fh.absoluteAddrs[funcScope] = make(map[uint16]bool)
|
||||
}
|
||||
|
||||
fh.absoluteAddrs[funcScope][addr] = true
|
||||
if isWord {
|
||||
fh.absoluteAddrs[funcScope][addr+1] = true
|
||||
}
|
||||
}
|
||||
|
||||
// buildComplexParams handles parameter lists that may contain {BYTE x} style declarations
|
||||
// Tokens like {BYTE x} are spread across multiple tokens and need to be reassembled
|
||||
func buildComplexParams(tokens []string) ([]string, error) {
|
||||
|
|
@ -332,6 +381,9 @@ func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Record the call in the call graph
|
||||
fh.recordCall(funcName)
|
||||
|
||||
// Generate final assembly
|
||||
asmLines = append(asmLines, inAssigns...)
|
||||
asmLines = append(asmLines, fmt.Sprintf(" jsr %s", funcName))
|
||||
|
|
@ -340,6 +392,31 @@ func (fh *FunctionHandler) HandleFuncCall(line preproc.Line) ([]string, error) {
|
|||
return asmLines, nil
|
||||
}
|
||||
|
||||
// recordCall tracks which function calls which
|
||||
func (fh *FunctionHandler) recordCall(calledFunc string) {
|
||||
// Get current function (caller)
|
||||
if len(fh.currentFuncs) == 0 {
|
||||
// Call from global scope - not inside a function
|
||||
return
|
||||
}
|
||||
|
||||
caller := fh.currentFuncs[len(fh.currentFuncs)-1]
|
||||
|
||||
// Add to call graph
|
||||
if fh.callGraph[caller] == nil {
|
||||
fh.callGraph[caller] = []string{}
|
||||
}
|
||||
|
||||
// Avoid duplicates (though they're harmless for our analysis)
|
||||
for _, existing := range fh.callGraph[caller] {
|
||||
if existing == calledFunc {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fh.callGraph[caller] = append(fh.callGraph[caller], calledFunc)
|
||||
}
|
||||
|
||||
// processLabelArg handles @label arguments
|
||||
func (fh *FunctionHandler) processLabelArg(arg string, param *FuncParam, funcName string, line preproc.Line, inAssigns *[]string) error {
|
||||
labelName := arg[1:] // strip @
|
||||
|
|
@ -768,3 +845,165 @@ func parseParamSpec(spec string) (ParamDirection, string, bool, string, error) {
|
|||
|
||||
return direction, varName, isImplicit, implicitDecl, nil
|
||||
}
|
||||
|
||||
// AbsoluteOverlap represents a detected overlap in absolute addresses
|
||||
type AbsoluteOverlap struct {
|
||||
Func1 string // First function using the address
|
||||
Func2 string // Second function using the address
|
||||
Address uint16 // Overlapping address
|
||||
CallChain []string // Call chain from Func1 to Func2
|
||||
}
|
||||
|
||||
// AnalyzeAbsoluteOverlaps checks for overlapping absolute addresses in call chains
|
||||
func (fh *FunctionHandler) AnalyzeAbsoluteOverlaps() []AbsoluteOverlap {
|
||||
// Use map to deduplicate: key is "func1:func2:addr", value is overlap with shortest chain
|
||||
seen := make(map[string]*AbsoluteOverlap)
|
||||
|
||||
// For each function that uses absolute addresses
|
||||
for funcName, addrSet := range fh.absoluteAddrs {
|
||||
// Get all functions transitively called by this function
|
||||
transitiveAddrs := fh.getTransitiveAddresses(funcName, make(map[string]bool))
|
||||
|
||||
// Check for overlaps
|
||||
for addr := range addrSet {
|
||||
for otherFunc, otherAddrs := range transitiveAddrs {
|
||||
if otherAddrs[addr] {
|
||||
// Found overlap!
|
||||
callChain := fh.findCallChain(funcName, otherFunc)
|
||||
|
||||
// Create unique key for this conflict
|
||||
key := fmt.Sprintf("%s:%s:%04x", funcName, otherFunc, addr)
|
||||
|
||||
// Keep only if this is the first occurrence or has shorter chain
|
||||
if existing, exists := seen[key]; !exists || len(callChain) < len(existing.CallChain) {
|
||||
seen[key] = &AbsoluteOverlap{
|
||||
Func1: funcName,
|
||||
Func2: otherFunc,
|
||||
Address: addr,
|
||||
CallChain: callChain,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to slice
|
||||
var overlaps []AbsoluteOverlap
|
||||
for _, overlap := range seen {
|
||||
overlaps = append(overlaps, *overlap)
|
||||
}
|
||||
|
||||
return overlaps
|
||||
}
|
||||
|
||||
// ReportAbsoluteOverlaps analyzes and reports overlapping absolute addresses to stderr
|
||||
func (fh *FunctionHandler) ReportAbsoluteOverlaps() {
|
||||
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||
|
||||
if len(overlaps) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Group overlaps by address
|
||||
byAddress := make(map[uint16]map[string]bool)
|
||||
for _, overlap := range overlaps {
|
||||
if byAddress[overlap.Address] == nil {
|
||||
byAddress[overlap.Address] = make(map[string]bool)
|
||||
}
|
||||
byAddress[overlap.Address][overlap.Func1] = true
|
||||
byAddress[overlap.Address][overlap.Func2] = true
|
||||
}
|
||||
|
||||
// Extract and sort addresses
|
||||
addresses := make([]uint16, 0, len(byAddress))
|
||||
for addr := range byAddress {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
sort.Slice(addresses, func(i, j int) bool {
|
||||
return addresses[i] < addresses[j]
|
||||
})
|
||||
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\nWarning: Detected overlapping absolute addresses in function call chains:\n\n")
|
||||
|
||||
// Report each address with its conflicting functions (in sorted order)
|
||||
for _, addr := range addresses {
|
||||
funcs := byAddress[addr]
|
||||
funcList := make([]string, 0, len(funcs))
|
||||
for f := range funcs {
|
||||
funcList = append(funcList, f)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(os.Stderr, " Address $%04X used by: %s\n", addr, strings.Join(funcList, ", "))
|
||||
_, _ = fmt.Fprintf(os.Stderr, " %d function(s) share this address in overlapping call chains\n\n", len(funcList))
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(os.Stderr, " These conflicts may cause data corruption when functions are active simultaneously.\n\n")
|
||||
}
|
||||
|
||||
// getTransitiveAddresses returns all addresses used by transitively called functions
|
||||
// Returns map: funcName -> set of addresses used by that function
|
||||
func (fh *FunctionHandler) getTransitiveAddresses(funcName string, visited map[string]bool) map[string]map[uint16]bool {
|
||||
result := make(map[string]map[uint16]bool)
|
||||
|
||||
// Avoid infinite recursion
|
||||
if visited[funcName] {
|
||||
return result
|
||||
}
|
||||
visited[funcName] = true
|
||||
|
||||
// Get functions directly called by this function
|
||||
for _, calledFunc := range fh.callGraph[funcName] {
|
||||
// Add addresses used by the called function
|
||||
if addrs, exists := fh.absoluteAddrs[calledFunc]; exists {
|
||||
result[calledFunc] = addrs
|
||||
}
|
||||
|
||||
// Recursively get addresses from transitively called functions
|
||||
transitiveAddrs := fh.getTransitiveAddresses(calledFunc, visited)
|
||||
for f, addrs := range transitiveAddrs {
|
||||
result[f] = addrs
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// findCallChain finds the call path from funcA to funcB
|
||||
func (fh *FunctionHandler) findCallChain(from, to string) []string {
|
||||
// BFS to find shortest path
|
||||
type node struct {
|
||||
funcName string
|
||||
path []string
|
||||
}
|
||||
|
||||
queue := []node{{funcName: from, path: []string{from}}}
|
||||
visited := make(map[string]bool)
|
||||
|
||||
for len(queue) > 0 {
|
||||
current := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
if visited[current.funcName] {
|
||||
continue
|
||||
}
|
||||
visited[current.funcName] = true
|
||||
|
||||
if current.funcName == to {
|
||||
return current.path
|
||||
}
|
||||
|
||||
// Explore called functions
|
||||
for _, calledFunc := range fh.callGraph[current.funcName] {
|
||||
if !visited[calledFunc] {
|
||||
newPath := make([]string, len(current.path)+1)
|
||||
copy(newPath, current.path)
|
||||
newPath[len(current.path)] = calledFunc
|
||||
queue = append(queue, node{funcName: calledFunc, path: newPath})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No path found (shouldn't happen if overlap detected correctly)
|
||||
return []string{from, to}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -1074,3 +1075,340 @@ func TestParseImplicitDecl_Absolute(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsoluteOverlapDetection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
wantOverlapAddr uint16
|
||||
wantFunc1 string
|
||||
wantFunc2 string
|
||||
}{
|
||||
{
|
||||
name: "direct call with overlap",
|
||||
code: `
|
||||
BYTE dummy
|
||||
FUNC funcB ( {BYTE data @ $fa} )
|
||||
FEND
|
||||
|
||||
FUNC funcA ( {BYTE temp @ $fa} )
|
||||
CALL funcB(dummy)
|
||||
FEND
|
||||
`,
|
||||
wantOverlapAddr: 0xFA,
|
||||
wantFunc1: "funcA",
|
||||
wantFunc2: "funcB",
|
||||
},
|
||||
{
|
||||
name: "transitive call with overlap",
|
||||
code: `
|
||||
BYTE dummy
|
||||
FUNC funcC ( {BYTE data @ $fa} )
|
||||
FEND
|
||||
|
||||
FUNC funcB
|
||||
CALL funcC(dummy)
|
||||
FEND
|
||||
|
||||
FUNC funcA ( {BYTE temp @ $fa} )
|
||||
CALL funcB()
|
||||
FEND
|
||||
`,
|
||||
wantOverlapAddr: 0xFA,
|
||||
wantFunc1: "funcA",
|
||||
wantFunc2: "funcC",
|
||||
},
|
||||
{
|
||||
name: "word overlap with byte",
|
||||
code: `
|
||||
BYTE dummy
|
||||
FUNC funcB ( {BYTE data @ $fb} )
|
||||
FEND
|
||||
|
||||
FUNC funcA ( {WORD value @ $fa} )
|
||||
CALL funcB(dummy)
|
||||
FEND
|
||||
`,
|
||||
wantOverlapAddr: 0xFB,
|
||||
wantFunc1: "funcA",
|
||||
wantFunc2: "funcB",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st := NewSymbolTable()
|
||||
ls := NewLabelStack("L")
|
||||
csh := NewConstantStringHandler()
|
||||
pragma := preproc.NewPragma()
|
||||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Parse code into lines
|
||||
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
||||
|
||||
// Process each line
|
||||
for i, lineText := range lines {
|
||||
lineText = strings.TrimSpace(lineText)
|
||||
if lineText == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pline := preproc.Line{
|
||||
Text: lineText,
|
||||
LineNo: i + 1,
|
||||
Filename: "test.c65",
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
||||
// Variable declaration - add to symbol table
|
||||
parts := strings.Fields(lineText)
|
||||
if len(parts) >= 2 {
|
||||
varKind := KindByte
|
||||
if strings.ToUpper(parts[0]) == "WORD" {
|
||||
varKind = KindWord
|
||||
}
|
||||
st.AddVar(parts[1], "", varKind, 0)
|
||||
}
|
||||
} else if strings.HasPrefix(lineText, "FUNC ") {
|
||||
_, err := fh.HandleFuncDecl(pline)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
} else if strings.HasPrefix(lineText, "CALL ") {
|
||||
_, err := fh.HandleFuncCall(pline)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncCall failed: %v", err)
|
||||
}
|
||||
} else if lineText == "FEND" {
|
||||
fh.EndFunction()
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze overlaps
|
||||
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||
|
||||
if len(overlaps) == 0 {
|
||||
t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
|
||||
}
|
||||
|
||||
// Find the expected overlap
|
||||
found := false
|
||||
for _, overlap := range overlaps {
|
||||
if overlap.Address == tt.wantOverlapAddr &&
|
||||
overlap.Func1 == tt.wantFunc1 &&
|
||||
overlap.Func2 == tt.wantFunc2 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
|
||||
tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsoluteLocalVariables(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
wantOverlapAddr uint16
|
||||
wantFunc1 string
|
||||
wantFunc2 string
|
||||
}{
|
||||
{
|
||||
name: "local absolute variables overlap",
|
||||
code: `
|
||||
FUNC funcB
|
||||
BYTE localB @ $fa
|
||||
FEND
|
||||
|
||||
FUNC funcA
|
||||
BYTE localA @ $fa
|
||||
CALL funcB()
|
||||
FEND
|
||||
`,
|
||||
wantOverlapAddr: 0xFA,
|
||||
wantFunc1: "funcA",
|
||||
wantFunc2: "funcB",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st := NewSymbolTable()
|
||||
ls := NewLabelStack("L")
|
||||
csh := NewConstantStringHandler()
|
||||
pragma := preproc.NewPragma()
|
||||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Connect symbol table to function handler
|
||||
st.SetFunctionHandler(fh)
|
||||
|
||||
// Parse code into lines
|
||||
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
||||
currentFunc := ""
|
||||
|
||||
// Process each line
|
||||
for i, lineText := range lines {
|
||||
lineText = strings.TrimSpace(lineText)
|
||||
if lineText == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pline := preproc.Line{
|
||||
Text: lineText,
|
||||
LineNo: i + 1,
|
||||
Filename: "test.c65",
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
||||
// Parse variable declaration
|
||||
parts := strings.Fields(lineText)
|
||||
if len(parts) >= 4 && parts[2] == "@" {
|
||||
// Absolute variable: BYTE name @ $addr
|
||||
varKind := KindByte
|
||||
if strings.ToUpper(parts[0]) == "WORD" {
|
||||
varKind = KindWord
|
||||
}
|
||||
addrStr := strings.TrimPrefix(parts[3], "$")
|
||||
var addr uint16
|
||||
fmt.Sscanf(addrStr, "%x", &addr)
|
||||
st.AddAbsolute(parts[1], currentFunc, varKind, addr)
|
||||
}
|
||||
} else if strings.HasPrefix(lineText, "FUNC ") {
|
||||
_, err := fh.HandleFuncDecl(pline)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
// Extract function name
|
||||
funcParts := strings.Fields(lineText)
|
||||
if len(funcParts) >= 2 {
|
||||
currentFunc = funcParts[1]
|
||||
}
|
||||
} else if strings.HasPrefix(lineText, "CALL ") {
|
||||
_, err := fh.HandleFuncCall(pline)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncCall failed: %v", err)
|
||||
}
|
||||
} else if lineText == "FEND" {
|
||||
fh.EndFunction()
|
||||
currentFunc = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze overlaps
|
||||
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||
|
||||
if len(overlaps) == 0 {
|
||||
t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
|
||||
}
|
||||
|
||||
// Find the expected overlap
|
||||
found := false
|
||||
for _, overlap := range overlaps {
|
||||
if overlap.Address == tt.wantOverlapAddr &&
|
||||
overlap.Func1 == tt.wantFunc1 &&
|
||||
overlap.Func2 == tt.wantFunc2 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
|
||||
tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsoluteNoOverlap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
}{
|
||||
{
|
||||
name: "different addresses",
|
||||
code: `
|
||||
BYTE dummy
|
||||
FUNC funcB ( {BYTE data @ $fb} )
|
||||
FEND
|
||||
|
||||
FUNC funcA ( {BYTE temp @ $fa} )
|
||||
CALL funcB(dummy)
|
||||
FEND
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no call relationship",
|
||||
code: `
|
||||
FUNC funcA ( {BYTE temp @ $fa} )
|
||||
FEND
|
||||
|
||||
FUNC funcB ( {BYTE data @ $fa} )
|
||||
FEND
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st := NewSymbolTable()
|
||||
ls := NewLabelStack("L")
|
||||
csh := NewConstantStringHandler()
|
||||
pragma := preproc.NewPragma()
|
||||
fh := NewFunctionHandler(st, ls, csh, pragma)
|
||||
|
||||
// Parse code into lines
|
||||
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
||||
|
||||
// Process each line
|
||||
for i, lineText := range lines {
|
||||
lineText = strings.TrimSpace(lineText)
|
||||
if lineText == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pline := preproc.Line{
|
||||
Text: lineText,
|
||||
LineNo: i + 1,
|
||||
Filename: "test.c65",
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
||||
// Variable declaration - add to symbol table
|
||||
parts := strings.Fields(lineText)
|
||||
if len(parts) >= 2 {
|
||||
varKind := KindByte
|
||||
if strings.ToUpper(parts[0]) == "WORD" {
|
||||
varKind = KindWord
|
||||
}
|
||||
st.AddVar(parts[1], "", varKind, 0)
|
||||
}
|
||||
} else if strings.HasPrefix(lineText, "FUNC ") {
|
||||
_, err := fh.HandleFuncDecl(pline)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncDecl failed: %v", err)
|
||||
}
|
||||
} else if strings.HasPrefix(lineText, "CALL ") {
|
||||
_, err := fh.HandleFuncCall(pline)
|
||||
if err != nil {
|
||||
t.Fatalf("HandleFuncCall failed: %v", err)
|
||||
}
|
||||
} else if lineText == "FEND" {
|
||||
fh.EndFunction()
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze overlaps
|
||||
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
||||
|
||||
if len(overlaps) > 0 {
|
||||
t.Errorf("Expected no overlaps, but got: %+v", overlaps)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,16 @@ func (s *Symbol) FullName() string {
|
|||
|
||||
// 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
|
||||
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
|
||||
|
|
@ -76,6 +83,11 @@ func NewSymbolTable() *SymbolTable {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -149,6 +161,11 @@ func (st *SymbolTable) AddAbsolute(name, scope string, kind VarKind, addr uint16
|
|||
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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue