Errors & Diagnostics

Users judge a language by its errors long before they judge its semantics. Diagnosing well — saying precisely what was wrong, where, and what to do — is the highest-return investment a DSL can make after its grammar.

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.

Resources