Master the fundamental concepts of syntax analysis through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksVaughan Pratt's top-down operator precedence parsing handles infix, prefix, and postfix operators with uniform binding-power logic. JavaScript engines, Rust's old parser, and many embedded DSL compilers use Pratt because it avoids left-recursion rewriting and handles mixed precedence cleanly.
Each token has left and right binding power. Parse a prefix expression, then while the next token's left power exceeds the current minimum, consume it as infix and parse the right side with appropriate right power.
cLoading…
-, ( expr )+, *, ==, function-call ()* before +Production compilers embed this step inside a longer pipeline. GCC flows through cpp, cc1, assembly, and ld; Clang uses the driver, Sema, LLVM IR passes, and a target backend. LLVM bitcode, JVM bytecode, and WASM are other familiar IRs at the same layer. The exercise isolates one pass so you can test it alone before chaining it to the next stage.
You will build a Pratt parser for expressions with correct operator precedence. This exercise requires assigning binding powers to tokens and implementing the parse loop that produces binary and unary AST nodes.
Write a Pratt parser (top-down operator precedence): instead of one function per precedence level, a single parse_expr(min_bp) loop driven by a table of binding powers. Print each parsed expression as an S-expression.
One expression per line; blank lines are ignored. Tokens: numbers, identifiers ([A-Za-z_][A-Za-z0-9_]*), + - * / ^ ( ) [ ] ,. Spaces and tabs are ignored.
| Token | As prefix (nud) | As infix / postfix (led) | Left binding power | Associativity |
|---|---|---|---|---|
| number, identifier | leaf | - | - | - |
( | grouping: ( expr ) | call: callee ( args ) | 50 | - |
[ | - | index: target [ expr ] | 50 | - |
- | negation, operand parsed with min bp 30 | subtraction | 10 | left |
+ | - | addition | 10 | left |
* / | - | multiply, divide | 20 | left |
^ | - | power | 40 | right |
The core loop: parse a prefix expression, then while the next token's left binding power is greater than min_bp, consume it and parse its right operand with min_bp = lbp for left-associative operators and lbp - 1 for ^. Call arguments and index expressions are parsed with min_bp = 0. Consequences worth checking: -2^2 is (neg (^ 2 2)), but -a*b is (* (neg a) b).
(OP LEFT RIGHT) for + - * / ^; (neg X) for prefix minus(call CALLEE ARG1 ARG2 ...): the callee may itself be any expression, e.g. f(1)(2) is (call (call f 1) 2)(index TARGET INDEX)On error print only the first one, error at column C: MESSAGE (C is where the offending token starts; line length + 1 at the end):
unexpected character 'X': a character that starts no tokenexpected expression: no valid prefix token where an operand is neededexpected ')' / expected ']': an unclosed group, call or indexunexpected token 'TEXT': tokens left over after a complete expressionInput:
cLoading…
Output:
cLoading…
parse_expr(int min_bp) function containing the prefix step and the infix loop, plus a binding-power lookup (lbp(token)): no separate function per precedence level.Hidden tests cover left vs right associativity, unary minus against every operator, calls with zero and several arguments, chained calls and indexes, and each error.