Step 03 · Rustc-style diagnostics
The Diagnostic struct is small and intentional:
struct Diagnostic {
Severity severity; // Error / Warning / Note
std::string code; // "E0202"
std::string message;
Span span;
std::string hint; // "help: try ..."
};
Renderer output:
error[E0202]: expected expression, got `;`
--> ex.ml:1:10
|
1 | print 1 +;
| ^
| help: try a number, a variable, or `(`
The four lines after the header are:
- Gutter blank line matching the line-number width.
- Source line with the offending text.
- Caret line — spaces to the column, then
^for the span. - Hint (optional) — what to try next.
Why no ANSI colours
Easier to test (string comparison), easier to pipe to less, easier
to integrate with editors that re-style errors. A real CLI adds a
--color=auto flag that wraps error and the caret in red — trivial
to layer on top.
Codes
Rust assigns each error an E#### code, Swift uses descriptive IDs,
TypeScript uses TS####. Benefits:
- Documentation hooks (
rustc --explain E0382). - Stable references in tutorials/bug reports.
- Tooling can ignore-list specific codes.
We pre-assign blocks: E01xx lex errors, E02xx parse, E03xx semantic.
Cheap up-front; pays for itself the first time someone googles
minilang E0301.
Spans across multiple lines
The renderer currently assumes the span fits on one line. Multi-line
spans (if {\n bad\n}) need the line bar repeated with ~~~ for
trailing lines and ^^^ on the start. We left this as a 30-minute
extension exercise — pattern-match what rustc does.
Suggestions / fix-its
hint is plain text. A richer system attaches a replacement span +
replacement text that an IDE can apply automatically:
struct FixIt { Span span; std::string replacement; };
Useful but easy to get wrong — apply two fix-its that overlap and you corrupt the file. Production compilers (rustc, clang-tidy) keep them gated behind explicit user invocation.