Step 06 · Building a REPL

A REPL ("read-eval-print loop") is the most useful tool a language ships. Our implementation in repl.cpp is ~50 lines because all the heavy lifting is reused from the CLI.

void runRepl(in, out, err, opts) {
    EvalState st;                           // persists across lines
    std::string buffer;
    while (getline(in, line)) {
        buffer += line + "\n";
        SourceFile src("<repl>", buffer);
        auto l = lex(src);
        auto p = parse(l.tokens);
        if (needsContinuation(...)) continue;   // accumulate
        for (auto& d : l.diagnostics) renderTo(err, d, src);
        for (auto& d : p.diagnostics) renderTo(err, d, src);
        if (no errors) eval(st, p.program, out);
        buffer.clear();
    }
}

Multi-line continuation

The REPL recognises unfinished input — unbalanced parens, dangling operators — and doesn't evaluate yet. It keeps reading lines into a buffer, showing a | continuation prompt, until the input type-checks as a complete program.

Heuristic in needsContinuation:

  • ( > ) count → expect more.
  • Last diagnostic mentions "got end of input" → expect more.

A more rigorous approach: have the parser return a distinguished "unexpected EOF" error type rather than scanning messages. We chose strings here for simplicity; it's the kind of decision easy to revisit once you feel the friction.

State preservation

EvalState st lives outside the loop, so:

> let x = 10;
> print x;
10

works as expected. The semantics is "each REPL line is appended to a notional program that you've been building all along".

Error recovery, REPL-style

When evaluation fails, we discard the buffer and re-prompt. The alternative — keeping the buffer so the user can edit — is what IPython / Jupyter offer, but requires terminal-line-editor integration (readline / replxx) outside the scope of this lab.

Things real REPLs add

  • Line editing & history (libedit, readline, replxx).
  • Tab completion (introspect st.env for variable names).
  • Special commands (:type, :reset, :doc).
  • Pretty-printing of last value (Python's _).
  • Persistent history file.

Each is a half-day; they're orthogonal to the core REPL loop.