13 KiB
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:
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: <message>
* --> <path>:<line>:<col>
* |
* 42 | <source_line>
* | <spaces>^
*/
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
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.buxlines 49-54
Context: Current lexer error loop (in Cli_Compile or similar):
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:
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
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.buxlines 239-253
Context: Current code in Cli_Check:
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:
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
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.buxlines 991-1005
Context: Current code in Cli_BuildProject:
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:
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
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.buxlines 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):
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
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
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.buxlines 1774-1777 (end ofParser_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:
/* 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(" --> <input>:");
PrintInt(d.line as int64);
Print(":");
PrintInt(d.column as int64);
PrintLine("");
Print(" |");
PrintLine("");
Print(" ");
PrintInt(d.line as int64);
Print(" | <source unavailable>");
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 <source unavailable> 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
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:
[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:
func Main() -> int {
let x: int = "hello";
return 0;
}
- Step 3: Build and verify output
Run:
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
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:
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.buxchanges 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
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.