c65gm/internal/compiler/compiler.go

251 lines
6.9 KiB
Go

package compiler
import (
"fmt"
"os"
"strings"
"c65gm/internal/preproc"
)
// Compiler orchestrates the compilation process
type Compiler struct {
ctx *CompilerContext
registry *CommandRegistry
}
// NewCompiler creates a new compiler with initialized context and registry
func NewCompiler(pragma *preproc.Pragma) *Compiler {
return &Compiler{
ctx: NewCompilerContext(pragma),
registry: NewCommandRegistry(),
}
}
// Context returns the compiler context (for registering commands that need it)
func (c *Compiler) Context() *CompilerContext {
return c.ctx
}
// Registry returns the command registry (for registering commands)
func (c *Compiler) Registry() *CommandRegistry {
return c.registry
}
// Compile processes preprocessed lines and generates assembly output
func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
var codeOutput []string
var lastKind = preproc.Source
var scriptBuffer []string
for i, line := range lines {
// Detect kind transitions and emit markers
if line.Kind != lastKind {
// Execute and close previous Script block
if lastKind == preproc.Script {
scriptOutput, err := executeScript(scriptBuffer, c.ctx)
if err != nil {
return nil, fmt.Errorf("script execution failed: %w", err)
}
codeOutput = append(codeOutput, scriptOutput...)
scriptBuffer = nil
codeOutput = append(codeOutput, "; ENDSCRIPT")
}
// Close previous Assembler block
if lastKind == preproc.Assembler {
codeOutput = append(codeOutput, "; ENDASM")
}
// Open new block
if line.Kind == preproc.Assembler {
codeOutput = append(codeOutput, "; ASM")
} else if line.Kind == preproc.Script {
codeOutput = append(codeOutput, "; SCRIPT")
}
lastKind = line.Kind
}
// Handle non-source lines
if line.Kind != preproc.Source {
if line.Kind == preproc.Assembler {
// Expand |varname| -> scoped_varname for local variables in ASM blocks
text := line.Text
for {
start := strings.IndexByte(text, '|')
if start == -1 {
break
}
end := strings.IndexByte(text[start+1:], '|')
if end == -1 {
c.printErrorWithContext(lines, i, fmt.Errorf("unclosed | in assembler line"))
return nil, fmt.Errorf("compilation failed")
}
end += start + 1
varName := text[start+1 : end]
expandedName := c.ctx.SymbolTable.ExpandName(varName, c.ctx.CurrentScope())
text = text[:start] + expandedName + text[end+1:]
}
codeOutput = append(codeOutput, text)
} else if line.Kind == preproc.Script {
// Collect script lines for execution
scriptBuffer = append(scriptBuffer, line.Text)
}
continue
}
// Skip empty/whitespace-only lines
if strings.TrimSpace(line.Text) == "" {
continue
}
// Find handler for this line
cmd, found := c.registry.FindHandler(line)
if !found {
c.printErrorWithContext(lines, i, fmt.Errorf("unhandled line (no matching command)"))
return nil, fmt.Errorf("compilation failed")
}
// Interpret the line
if err := cmd.Interpret(line, c.ctx); err != nil {
c.printErrorWithContext(lines, i, err)
return nil, fmt.Errorf("compilation failed")
}
// Generate assembly
asmLines, err := cmd.Generate(c.ctx)
if err != nil {
c.printErrorWithContext(lines, i, err)
return nil, fmt.Errorf("compilation failed")
}
codeOutput = append(codeOutput, fmt.Sprintf("; %s", line.Text))
codeOutput = append(codeOutput, asmLines...)
}
// Close any open block
if lastKind == preproc.Assembler {
//codeOutput = append(codeOutput, "; ENDASM")
return nil, fmt.Errorf("Unclosed ASM block.")
} else if lastKind == preproc.Script {
//codeOutput = append(codeOutput, "; ENDSCRIPT")
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
}
// printErrorWithContext prints an error with source code context to stderr
func (c *Compiler) printErrorWithContext(lines []preproc.Line, lineIndex int, err error) {
if lineIndex < 0 || lineIndex >= len(lines) {
// Shouldn't happen, but be safe
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
line := lines[lineIndex]
const contextLines = 3
// Print error header
_, _ = fmt.Fprintf(os.Stderr, "\nError: %v\n", err)
_, _ = fmt.Fprintf(os.Stderr, " --> %s:%d\n\n", line.Filename, line.LineNo)
// Find all lines from the same file for context
// Group consecutive lines from the same file
fileLines := make(map[string][]int)
for i, l := range lines {
if l.Filename == line.Filename {
fileLines[l.Filename] = append(fileLines[l.Filename], i)
}
}
// Get context range (lineIndex within our lines array)
startIdx := lineIndex - contextLines
if startIdx < 0 {
startIdx = 0
}
endIdx := lineIndex + contextLines
if endIdx >= len(lines) {
endIdx = len(lines) - 1
}
// Calculate width for line numbers based on actual source line numbers
maxLineNo := 0
for i := startIdx; i <= endIdx; i++ {
if lines[i].LineNo > maxLineNo {
maxLineNo = lines[i].LineNo
}
}
maxLineNumWidth := len(fmt.Sprintf("%d", maxLineNo))
// Print context
for i := startIdx; i <= endIdx; i++ {
l := lines[i]
// Skip lines from different files
if l.Filename != line.Filename {
continue
}
if i == lineIndex {
// Error line - highlight it
_, _ = fmt.Fprintf(os.Stderr, ">> %*d | %s\n", maxLineNumWidth, l.LineNo, l.Text)
} else {
// Context line
_, _ = fmt.Fprintf(os.Stderr, " %*d | %s\n", maxLineNumWidth, l.LineNo, l.Text)
}
}
_, _ = fmt.Fprintf(os.Stderr, "\n")
}
// 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
// Header comment
output = append(output, ";Generated by c65gm")
output = append(output, "")
// Constants section
if constLines := GenerateConstants(c.ctx.SymbolTable); len(constLines) > 0 {
output = append(output, constLines...)
}
// Absolute addresses section
if absLines := GenerateAbsolutes(c.ctx.SymbolTable); len(absLines) > 0 {
output = append(output, absLines...)
}
// Main code section
output = append(output, ";Main code")
output = append(output, "")
output = append(output, codeLines...)
output = append(output, "")
// Variables section
if varLines := GenerateVariables(c.ctx.SymbolTable); len(varLines) > 0 {
output = append(output, varLines...)
}
// Constant strings section
if strLines := c.ctx.ConstStrHandler.GenerateConstStrDecls(); len(strLines) > 0 {
output = append(output, ";Constant strings (from c65gm)")
output = append(output, "")
output = append(output, strLines...)
output = append(output, "")
}
return output
}