diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index 6e13b66..c09f671 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -2,6 +2,7 @@ package compiler import ( "fmt" + "os" "strings" "c65gm/internal/preproc" @@ -37,7 +38,7 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { var lastKind = preproc.Source var scriptBuffer []string - for _, line := range lines { + for i, line := range lines { // Detect kind transitions and emit markers if line.Kind != lastKind { // Execute and close previous Script block @@ -78,7 +79,8 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { } end := strings.IndexByte(text[start+1:], '|') if end == -1 { - return nil, fmt.Errorf("%s:%d: unclosed | in assembler line", line.Filename, line.LineNo) + c.printErrorWithContext(lines, i, fmt.Errorf("unclosed | in assembler line")) + return nil, fmt.Errorf("compilation failed") } end += start + 1 @@ -102,18 +104,21 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { // Find handler for this line cmd, found := c.registry.FindHandler(line) if !found { - return nil, &UnhandledLineError{Line: line} + 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 { - return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err) + c.printErrorWithContext(lines, i, err) + return nil, fmt.Errorf("compilation failed") } // Generate assembly asmLines, err := cmd.Generate(c.ctx) if err != nil { - return nil, fmt.Errorf("%s:%d: %w", line.Filename, line.LineNo, err) + c.printErrorWithContext(lines, i, err) + return nil, fmt.Errorf("compilation failed") } codeOutput = append(codeOutput, fmt.Sprintf("; %s", line.Text)) @@ -136,6 +141,70 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) { 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()