package compiler import ( "fmt" "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 _, 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 { return nil, fmt.Errorf("%s:%d: unclosed | in assembler line", line.Filename, line.LineNo) } 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 { return nil, &UnhandledLineError{Line: line} } // Interpret the line if err := cmd.Interpret(line, c.ctx); err != nil { return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err) } // Generate assembly asmLines, err := cmd.Generate(c.ctx) if err != nil { return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err) } 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.") } // Assemble final output with headers and footers return c.assembleOutput(codeOutput), nil } // 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 }