cp-03 — MiniLang Frontend (Statements, Variables, Control Flow, Functions)

Status: ✅ Built — 34/34 tests pass.

Replaces the arithmetic grammar from cp-02 with a real language: let/var bindings, if/while, functions, blocks, closures. Switches the parser from hand-rolled recursive descent to a Pratt parser for expressions.

What You'll Build

  • A full Pratt expression parser driven by a binding-power table.
  • A recursive-descent statement parser: let/var, blocks, if/else, while, return, print, fn.
  • Two-tier AST (Stmt + templated-return Expr<R> visitors).
  • Scope-chain Environment (parent-linked maps) and lexical closures.
  • A tree-walking interpreter with first-class functions, recursion, higher-order calls.
  • A mli REPL and file runner.

Reading Order

  1. CONCEPTS.md — Pratt parsing, binding powers, statement/expression split, closures.
  2. src/cpp/steps/ — seven guided steps (tokens → lexer → AST → Pratt → environment/interpreter → functions → REPL+tests).
  3. src/cpp/src/ — the full source.

Prereqs

  • cp-02 complete (recursive descent + AST + Visitor internalized).

Outcomes

  • Hand-write a Pratt parser for any precedence-rich expression language.
  • Understand the statement/expression distinction and where each fits in the AST.
  • Implement lexical scope via a parent-linked environment chain.
  • Build first-class functions and closures using shared environments.

Build & Run

cd src/cpp
cmake -S . -B build && cmake --build build -j
ctest --test-dir build --output-on-failure
./build/mli                  # REPL
./build/mli script.ml        # run a file

Sample

fn fact(n) { if (n <= 1) return 1; return n * fact(n - 1); }
fn fib(n)  { if (n < 2) return n; return fib(n-1) + fib(n-2); }
print fact(10);   // 3628800
print fib(15);    // 610

fn makeAdder(a) { fn add(b) { return a + b; } return add; }
let plus5 = makeAdder(5);
print plus5(10);  // 15