skip to content

In precedence climbing, why is the sub-expression inside brackets parsed with a minimum binding power of zero?

level: seniorimportance: nice to knowfreq 24%

answer

  1. brackets cancel the surrounding grip
  2. the floor is per call, not global
  3. what should the inner parse inherit?
  4. the failure appears at the closing token
  5. a spurious error on valid input

basics

~20 s

Brackets cancel the surrounding grip, so the inner parse starts from the loosest possible floor and can absorb every operator up to the closing bracket. Passing the ambient floor instead stops the inner parse early and reports a spurious error at the bracket.

solid answer

~40 s

The minimum binding power is a promise a call makes to its caller about which operators it will leave alone. A bracket voids that promise: whatever is inside is a complete expression in its own right, so the operand step calls `parse_expr(0)` and then expects the closing bracket. Pass the ambient floor down instead and the inner parse refuses operators that are looser than it — in `2 * (3 + 4)` the recursion runs at 21, the addition's left power of 10 is below that, so the inner call returns `3` and the closing-bracket check fails on a `+`. The user sees a syntax error on a perfectly valid formula. The same reset applies to any bracketed region: an argument in a call list, an index expression, the middle of a three-part conditional.

go deeper

for a junior

Know that brackets override precedence, and that in this style of parser they do so by starting a fresh sub-expression rather than by having a precedence of their own.

for a middle

Explain the floor as a per-call context value and say which of the four sources it comes from in each position, including the reset inside a bracketed region.

for a senior

Recognise the symptom in the field: valid input rejected at an operator inside brackets, but only after a tightly binding operator, which points straight at the argument passed into the sub-parse.

for a principal

Note that a parser rejecting formulas that used to work is a user-visible incident, not just a bug, so the bracket path deserves explicit tests over stored formulas rather than only unit coverage.

## What the minimum actually promises Every call into the expression parser carries a floor, and the floor means one thing: *this call will not consume an operator that binds more loosely than this, because such an operator belongs to whoever called me.* That promise is what makes the recursion produce correctly nested trees without any per-level functions. It is also why the floor is never a fixed property of the parser — it is recomputed at every recursive call from the operator that triggered it. ## A bracket cancels the promise A bracketed region is a complete expression in its own right. Nothing inside it can belong to the operator that sits outside it, because the closing bracket stands between them. So when the operand step encounters an opening bracket it does not pass its context down; it calls the expression parser with a floor of **zero**, the loosest value in the table, then requires a closing bracket and returns the inner node as if it were an ordinary operand. That single argument is the entire implementation of grouping. There is no bracket level in the precedence table, no rule layer above the operator levels, and no post-parse rebalancing. ## Tracing both versions Parsing `2 * (3 + 4)`, with `*` at left 20 / right 21 and `+` at left 10 / right 11: | step | with a floor of 0 inside | with the ambient floor of 21 inside | |---|---|---| | parse `2`, see `*` | consume, recurse at 21 | consume, recurse at 21 | | operand step sees `(` | recurse at **0** | recurse at **21** | | inner call parses `3`, sees `+` at 10 | 10 clears 0, absorb | 10 is below 21, stop | | inner call returns | `3 + 4` | `3` alone | | bracket check | next token is `)`, matches | next token is `+`, error | The failure signature is distinctive: a syntax error reported at or near an operator **inside** brackets, on input that is unambiguously valid, and only when the brackets follow a tightly binding operator. Put the same sub-expression at the start of the formula, where the ambient floor is 0, and it parses fine — which is what makes the defect confusing to report from the outside. ## Where else the reset appears Any construct with a token that closes it behaves the same way, because the closing token, not the floor, is what bounds the sub-parse: - **An argument in a call list** is parsed from zero and ends at a comma or the closing bracket, neither of which is in the operator table, so the loop stops on its own. - **An index expression** between square brackets is parsed from zero and ends at the closing bracket. - **The middle part of a three-part conditional** is parsed from zero and ends at the separator token, after which the parser resumes with the conditional's own power for the final part. In each case the rule is the same: when a token guarantees where the sub-expression ends, the floor has no work to do and should be at its minimum. ## Do brackets survive into the tree? Usually not. Grouping has no operator and no value of its own — its whole effect is the nesting it forced, and that nesting is already recorded in the shape of the tree. The inner node is returned directly, so `(3 + 4)` and `3 + 4` produce identical subtrees. Parsers that need to reproduce the original text — a formatter, or a tool that shows a stored formula back to the user exactly as typed — keep a marker node purely for that purpose, and evaluation ignores it. ## The general lesson The floor is a **context** parameter, not a configuration value, and the rule for setting it is mechanical: 1. Starting a whole expression: floor of zero. 2. Parsing an operator's right operand: the operator's right binding power. 3. Parsing a prefix operator's argument: that operator's power. 4. Parsing anything that a closing token will bound: floor of zero again. Every bug in this area is a case of using the wrong one of those four, and all four produce accepted-but-wrong trees except the fourth, which produces a spurious parse error instead. That makes it the easiest of the four to find and the most alarming to a user, since it rejects a formula that has always worked elsewhere.

  • What does the parser do once the inner parse returns?
    It requires the closing bracket, then returns the inner node as the operand it was asked for. That node becomes the current left operand in the caller's loop, so the caller's own floor resumes unchanged — the reset is scoped strictly to the bracketed region and never leaks outward.
  • Do brackets need a node in the tree?
    Not for evaluation. The grouping they force is already captured by the nesting, so returning the inner node directly is correct and `(3 + 4)` yields the same subtree as `3 + 4`. A parser that must reproduce the user's original text keeps a marker node for formatting, which evaluation then ignores.

saying these in an interview costs you the question

  • Thinks the inner parse should inherit the caller's minimum binding power
  • Believes grouping needs a rule layer of its own above every operator level
  • Assumes the parser rebalances the tree after seeing a closing bracket
  • Says a grouping node must survive for evaluation to be correct
  • Treats an unmatched closing bracket as something the loop silently skips