Step 01 · Developer experience matters

A compiler that's correct but cryptic loses to one that's slightly less powerful but obviously helpful. Compare:

$ old-compiler
error: syntax error
$ rustc-style
error[E0202]: expected expression, got `;`
  --> example.rs:5:11
    |
  5 | print 1 + ;
    |           ^
    | help: try a number, a variable, or `(`

Same parser bug; vastly different debugging experience. The investment that pays off:

  • Source spans on every AST node. Tokens carry (start, length); AST nodes inherit and merge them.
  • Structured diagnostics: (severity, code, message, span, hint) rather than a string. This lets:
    • The compiler suggest fix-its (hint).
    • IDEs render squigglies precisely (span).
    • clippy-style tools filter by code.
  • Error recovery: parsers continue past errors so users see all problems in one pass, not "fix → recompile → fix → recompile".
  • Tooling ecosystem: fmt, ast, repl, lsp are first-class citizens that share the same parser & diagnostics.

This lab implements the foundation. cp-16's capstone wires it into the full compiler frontend.

Why the CLI is one binary with subcommands

minilang run|fmt|ast|repl instead of minilang-run, minilang-fmt, etc:

  • Single install footprint.
  • Shared option parsing & shared error format.
  • Easier to add minilang check, minilang test, minilang doc later.

This is the design cargo, go, git, dotnet, and dart all converged on for the same reasons.