Errors & Diagnostics
Kinds of Errors
Every error belongs to a layer of Phase 1, and each layer catches different mistakes.
The Four Layers
- Lexical: a character sequence no token matches (
room @). - Syntax: tokens that do not fit the grammar (
room room). - Semantic: meaning violations — type errors, undefined names (Semantics & Scoping, Types & Values).
- Runtime: failures while running, from invalid input to missing resources.
Early vs. Late
Earlier layers are cheap and certain; runtime errors are expensive and context-dependent. A good design pushes as many mistakes upward (to compile/parse time) as the typing mode allows.
Writing Good Messages
The Three-Part Shape
Every diagnostic should say: where (file, line, column), what was expected versus found, and how to fix (an example or the accepted form). Then include the offending line with a caret under the span — text people can search the code against.
Tone and Severity
Levels matter: error (cannot proceed), warning (works but suspicious), note (context). Never blame the user; blame the program state, and always end with the most likely fix.
Test Your Messages
Keep a corpus of bad programs — one per error kind — and render their diagnostics. If a message cannot explain its own example, the message is a bug.
The Error Model
Exceptions vs. Results
Two philosophies: exceptions unwind the call stack to a handler; result values (like Rust’s Result or forced return codes) make failure an explicit value. DSLs lean on results for domain failures (a booking conflict) and exceptions for programmer bugs.
Recoverable by Design
Classify each error: user input (recoverable, tell the fix), environment (retryable), programmer bug (panic loudly). A DSL that cannot distinguish “bad room name” from “DRY RUN pipeline failed on room names” will frustrate both audiences.
REPL & Debugging
The REPL as Teacher
A read–eval–print loop lets users test one phrase against the semantics instantly. If the DSL can run interactively, build the REPL early; it doubles as a greeting and a test harness.
Debugging Foundations
Trace evaluation order, show the environment at each step, and expose the AST the parser produced. Those three windows explain almost every DSL surprise.
Example: Three Errors, One Diary
Keep a diary of bad programs; render their diagnostics and review them like tests.
A Bad Program for Each Layer
# bad.req — three errors from three layers of Phase 1
room @A11 # lexical: "@" is not a token in this DSL
room room # syntax: two keywords, no name payload
room 09:00 # semantic: a time is not a valid room name
The Good Diagnostic
error[E2] expected NAME after keyword "room"
--> bad.req:1:6
|
1 | room @A11
| ^--- "@" cannot start a name
help: write a room identifier, e.g. "room 3A"
Location, expectation, the offending span, and a fix in one breath. That message is a feature; the diary that produced it is a deliverable.
Next Steps
Continue Phase 1
Phase 1 is complete when the design document can answer Syntax, Grammar, Parsing, Semantics, Types, and Errors in one page each. Next, Phase 2 starts turning that specification into an implementation: Compiler Design.