Part 1 · 1 chapters · ~8 min

Parsing: Recursive Descent, Pratt, LR

Recursive descent for statements, Pratt parsing (top-down operator precedence) for expressions, binding powers and associativity, prefix operators, error messages and recovery, LL and LR parsing, parser generators (yacc, Bison, ANTLR, tree-sitter), and why most production compilers hand-write parsers.

2

The BYO Pratt loop

code
// from src/parser.ts
const BP = { '||': 1, '&&': 2, '==': 3, '!=': 3, '<': 4, '<=': 4, '>': 4, '>=': 4, '+': 5, '-': 5, '*': 6, '/': 6, '%': 6 };

function expr(minBp = 0): Expr {
  let left = prefix();                                  // number, bool, var, call, ( expr ), -e or !e (bp 7)
  for (;;) {
    const op = peek(); const bp = BP[op.text];
    if (op.kind !== 'op' || bp === undefined || bp <= minBp) break;   // left-associative: <=
    next();
    left = { k: 'binary', op: op.text, l: left, r: expr(bp), line: op.line, col: op.col };
  }
  return left;
}
techniquestrengthused by
recursive descent (hand-written)simple, great errors, easy to extendGCC (C++ since 3.4 in 2004, C since 4.1 in 2006), Clang, Go, V8, TypeScript, Rust
Pratt / precedence climbingexpressions with many operators in one loopmany hand-written parsers for their expression part
LR / LALR (generated)handles large grammars, finds ambiguityyacc, Bison; PostgreSQL's SQL grammar
GLR / incrementalerror-tolerant, re-parses on each keystroketree-sitter (editors, GitHub code navigation)
PRATT PARSING 1 - 2 - 3 * 4
binding powers decide where each operator attaches
expr(0)expr(5)expr(6)left = 1; see "-" (bp 5 > 0)parse right side with minBp 5
swipe the figure sideways, or tap expand for full screen
1/4
binding power
Each infix operator has a binding power: || 1, && 2, comparison 3-4, + and - 5, * / % 6. The loop keeps consuming operators stronger than the current minimum.
one table of powersBP in parser.ts