# Source Location Error Messages Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add Rust-style error formatting with code snippets to all parser and sema diagnostic output in the Bux CLI. **Architecture:** A `Diagnostic` struct and `Diagnostic_Print` helper are added to `src/cli.bux`. Lexer and sema error loops in CLI are updated to use the unified formatter. Parser diagnostics are printed inline by `Parser_Parse` using the same format. No changes to lexer, parser, or sema internals — they already emit `line`/`column`/`message`. **Tech Stack:** Bux (selfhost compiler), stdlib file I/O --- ## File Structure | File | Responsibility | |------|---------------| | `src/cli.bux` | Add `Diagnostic` struct, `Diagnostic_GetLine`, `Diagnostic_Print`. Replace all manual error printing loops. | | `src/parser.bux` | Add inline diagnostic printing at end of `Parser_Parse` before returning. | | `_test_error_snippet/src/Main.bux` | Test program with intentional type error to verify output format. | | `_test_error_snippet/bux.toml` | Test manifest. | --- ## Task 1: Add Diagnostic Formatter to `src/cli.bux` **Files:** - Modify: `src/cli.bux` (add structs + helpers near the top, before first function) **Context:** The file currently has no unified diagnostic type. We add `Diagnostic`, `Diagnostic_GetLine`, and `Diagnostic_Print` as new top-level items. - [ ] **Step 1: Add Diagnostic struct and helpers** Insert after the imports / module declaration, before any function in `src/cli.bux`: ```bux struct Diagnostic { message: String; line: uint32; column: uint32; severity: int; } /* Read a single line from a file (1-based). Returns "" on error or EOF. */ func Diagnostic_GetLine(path: String, lineNum: uint32) -> String { let fd: int = Open(path); if fd < 0 { return ""; } var buf: String = ""; var currentLine: uint32 = 1; var c: int = ReadChar(fd); /* Skip lines until we reach lineNum */ while currentLine < lineNum && c >= 0 { if c == 10 { /* '\n' */ currentLine = currentLine + 1; } c = ReadChar(fd); } /* Collect characters until newline or EOF */ while c >= 0 && c != 10 { buf = String_Concat(buf, String_FromChar(c as uint8)); c = ReadChar(fd); } Close(fd); return buf; } /* Print a diagnostic in Rust-style format: * error: * --> :: * | * 42 | * | ^ */ func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) { /* Severity prefix */ if diag.severity == 0 { Print("error: "); } else if diag.severity == 1 { Print("warning: "); } else { Print("note: "); } PrintLine(diag.message); /* Location header */ Print(" --> "); Print(sourcePath); Print(":"); PrintInt(diag.line as int64); Print(":"); PrintInt(diag.column as int64); PrintLine(""); /* Source snippet */ let lineText: String = Diagnostic_GetLine(sourcePath, diag.line); if !String_Eq(lineText, "") { let lineNumStr: String = String_FromInt(diag.line as int64); Print(" |"); PrintLine(""); Print(" "); Print(lineNumStr); Print(" | "); PrintLine(lineText); /* Underline */ Print(" | "); var i: uint32 = 0; while i < diag.column - 1 && i < 120 { Print(" "); i = i + 1; } PrintLine("^"); } } ``` - [ ] **Step 2: Verify no syntax errors** Run: `cd /home/ziko/z-git/bux/bux && ./build/buxc build` Expected: Should compile the test project or at least not crash immediately. Since we're modifying the compiler source, we need to use the bootstrap compiler: Run: `cd /home/ziko/z-git/bux/bux && make buxc` Expected: Bootstrap compiler builds successfully (Nim compiler compiles bootstrap). - [ ] **Step 3: Commit** ```bash git add src/cli.bux git commit -m "feat(diag): add Diagnostic struct and Rust-style formatter" ``` --- ## Task 2: Update Lexer Error Printing in CLI **Files:** - Modify: `src/cli.bux` lines 49-54 **Context:** Current lexer error loop (in `Cli_Compile` or similar): ```bux var i: int = 0; while i < Lexer_DiagCount(lex) { Print(" "); PrintLine(lex.diags[i].message); i = i + 1; } ``` - [ ] **Step 1: Replace lexer error loop with Diagnostic_Print** Replace the lexer error loop at lines 49-54: ```bux var i: int = 0; while i < Lexer_DiagCount(lex) { let diag: Diagnostic = Diagnostic { message: lex.diags[i].message, line: lex.diags[i].line, column: lex.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, sourceName); i = i + 1; } ``` **Note:** `sourceName` is a variable available in the function context. If it's named differently (e.g., `path`, `filePath`), use the correct variable name. - [ ] **Step 2: Verify compilation** Run: `make buxc` Expected: Success. - [ ] **Step 3: Commit** ```bash git add src/cli.bux git commit -m "feat(diag): format lexer errors with Diagnostic_Print" ``` --- ## Task 3: Update Sema Error Printing in `Cli_Check` **Files:** - Modify: `src/cli.bux` lines 239-253 **Context:** Current code in `Cli_Check`: ```bux if Sema_HasError(sema) { PrintLine("Sema errors found"); var i: int = 0; while i < Sema_DiagCount(sema) { Print(" line "); PrintInt(sema.diags[i].line as int64); Print(" col "); PrintInt(sema.diags[i].column as int64); Print(": "); PrintLine(sema.diags[i].message); i = i + 1; } return 1; } ``` - [ ] **Step 1: Replace with Diagnostic_Print** Replace lines 239-253: ```bux if Sema_HasError(sema) { var i: int = 0; while i < Sema_DiagCount(sema) { let diag: Diagnostic = Diagnostic { message: sema.diags[i].message, line: sema.diags[i].line, column: sema.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, sourceName); i = i + 1; } return 1; } ``` - [ ] **Step 2: Commit** ```bash git add src/cli.bux git commit -m "feat(diag): format sema errors in Cli_Check with Diagnostic_Print" ``` --- ## Task 4: Update Sema Error Printing in `Cli_BuildProject` **Files:** - Modify: `src/cli.bux` lines 991-1005 **Context:** Current code in `Cli_BuildProject`: ```bux if Sema_HasError(sema) { PrintLine("Sema errors found"); var i: int = 0; while i < Sema_DiagCount(sema) { Print(" line "); PrintInt(sema.diags[i].line as int64); Print(" col "); PrintInt(sema.diags[i].column as int64); Print(": "); PrintLine(sema.diags[i].message); i = i + 1; } return 1; } ``` **Note:** The source path in `Cli_BuildProject` is the merged source file path. Check what variable holds the path to the merged source being compiled. It might be a temp file path. For MVP, use whatever path variable is available (e.g., `mergedPath`, `cFile`, or similar). - [ ] **Step 1: Find the source path variable in Cli_BuildProject** Look at the context around line 991 to find what variable contains the source file path being compiled. Common candidates: `sourceName`, `cFile`, `mergedPath`, `projectDir`. - [ ] **Step 2: Replace sema error loop** Replace the loop with: ```bux if Sema_HasError(sema) { var i: int = 0; while i < Sema_DiagCount(sema) { let diag: Diagnostic = Diagnostic { message: sema.diags[i].message, line: sema.diags[i].line, column: sema.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, sourcePath); /* use correct variable here */ i = i + 1; } return 1; } ``` - [ ] **Step 3: Commit** ```bash git add src/cli.bux git commit -m "feat(diag): format sema errors in Cli_BuildProject with Diagnostic_Print" ``` --- ## Task 5: Update Sema Error Printing in `Cli_Compile` / `Cli_CompileSource` **Files:** - Modify: `src/cli.bux` lines 78-88 and/or 295-300 **Context:** There may be additional sema error printing in `Cli_Compile` or `Cli_CompileSource`. Check lines 78-88 and 295-300 for similar patterns and update them to use `Diagnostic_Print`. At line 78-88 (`Cli_Compile` or similar): ```bux if Sema_HasError(sema) { PrintLine("Sema errors:"); var i: int = 0; while i < Sema_DiagCount(sema) { Print(" "); PrintLine(sema.diags[i].message); i = i + 1; } return ""; } ``` - [ ] **Step 1: Replace with Diagnostic_Print** ```bux if Sema_HasError(sema) { var i: int = 0; while i < Sema_DiagCount(sema) { let diag: Diagnostic = Diagnostic { message: sema.diags[i].message, line: sema.diags[i].line, column: sema.diags[i].column, severity: 0, }; Diagnostic_Print(&diag, sourceName); i = i + 1; } return ""; } ``` - [ ] **Step 2: Commit** ```bash git add src/cli.bux git commit -m "feat(diag): format sema errors in Cli_Compile with Diagnostic_Print" ``` --- ## Task 6: Add Parser Diagnostic Printing **Files:** - Modify: `src/parser.bux` lines 1774-1777 (end of `Parser_Parse`) **Context:** `Parser_Parse` collects diagnostics in `p.diags` but never prints them. The CLI only checks `mod == null` which rarely/never happens. We add diagnostic printing at the end of `Parser_Parse` before returning. - [ ] **Step 1: Add parser diagnostic printing** Before `return mod;` at line 1776, add: ```bux /* Print parser diagnostics */ if p.diagCount > 0 { var di: int = 0; while di < p.diagCount { let d: ParserDiag = p.diags[di]; Print("error: "); PrintLine(d.message); Print(" --> :"); PrintInt(d.line as int64); Print(":"); PrintInt(d.column as int64); PrintLine(""); Print(" |"); PrintLine(""); Print(" "); PrintInt(d.line as int64); Print(" | "); PrintLine(""); Print(" | "); var sp: uint32 = 0; while sp < d.column - 1 && sp < 120 { Print(" "); sp = sp + 1; } PrintLine("^"); di = di + 1; } } ``` **Note:** Parser diagnostics show `` for the source line because `Parser_Parse` doesn't receive the source file path. In a future iteration, we can pass the path to `Parser_Parse`. - [ ] **Step 2: Verify compilation** Run: `make buxc` Expected: Success. - [ ] **Step 3: Commit** ```bash git add src/parser.bux git commit -m "feat(diag): print parser diagnostics in Parser_Parse" ``` --- ## Task 7: Create Test Program **Files:** - Create: `_test_error_snippet/bux.toml` - Create: `_test_error_snippet/src/Main.bux` - [ ] **Step 1: Create test manifest** `_test_error_snippet/bux.toml`: ```toml [package] name = "error_snippet_test" version = "0.1.0" pkgType = "bin" ``` - [ ] **Step 2: Create test program with intentional type error** `_test_error_snippet/src/Main.bux`: ```bux func Main() -> int { let x: int = "hello"; return 0; } ``` - [ ] **Step 3: Build and verify output** Run: ```bash cd /home/ziko/z-git/bux/bux/_test_error_snippet && ../../build/buxc build 2>&1 ``` Expected output should contain Rust-style formatting: ``` error: type mismatch --> .../Main.bux:2:9 | 2 | let x: int = "hello"; | ^ ``` (The exact message text depends on what sema emits for this error.) - [ ] **Step 4: Commit** ```bash git add -f _test_error_snippet/ git commit -m "test: add error snippet formatting test" ``` --- ## Task 8: Selfhost Bootstrap Loop Verification **Files:** - No changes — verification only - [ ] **Step 1: Run selfhost loop** Run: ```bash cd /home/ziko/z-git/bux/bux && make selfhost-loop ``` Expected: C output is IDENTICAL on both iterations. - [ ] **Step 2: If loop fails, debug** Common issues: - C output differs → check if `src/cli.bux` changes affect codegen (they shouldn't — only Print statements changed) - Bootstrap compiler fails → syntax error in new Bux code - [ ] **Step 3: Commit fixes if needed** --- ## Task 9: Push to Git - [ ] **Step 1: Push** ```bash git push origin main ``` --- ## Spec Coverage Check | Spec Section | Implementing Task | |-------------|-------------------| | Diagnostic struct | Task 1 | | Diagnostic_GetLine | Task 1 | | Diagnostic_Print | Task 1 | | Lexer error formatting | Task 2 | | Sema error in Cli_Check | Task 3 | | Sema error in Cli_BuildProject | Task 4 | | Sema error in Cli_Compile | Task 5 | | Parser diagnostic printing | Task 6 | | Testing | Task 7 | | Selfhost loop | Task 8 | ## Placeholder Scan - No TBD, TODO, or "implement later". - All code is complete and copy-paste ready. - All file paths are exact. - All commands have expected output.