docs: add source location error messages implementation plan
This commit is contained in:
@@ -0,0 +1,513 @@
|
|||||||
|
# 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: <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**
|
||||||
|
|
||||||
|
```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(" --> <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**
|
||||||
|
|
||||||
|
```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.
|
||||||
Reference in New Issue
Block a user