Conditional Laws
Arata Furukawa · Last revised 2026-11-04
Status: Draft.
Some Grammar Laws hold only under a condition that the syntax of a rule does not show: that two alternatives never both match, that a rule is called only at a bounded depth, or that a variable takes few values. Such a condition becomes available in one of two ways. A transformation rewrites the grammar into an equivalent one in which the condition is visible, as left factoring makes two alternatives start differently. A declaration states the condition, and the compiler checks it, or a parser checks it when it binds a variable or calls a rule, or no one checks it. A declaration that no one checks is trust in the author, and a wrong declaration then changes the result of a parse silently; the conditions below prefer the other two.
This document lists the conditions, what each makes provable about the Conditions for Linear Cost, how it is checked and what it costs an author. It then starts from the reasons that the classification gives and names, for each kind of reason, the condition that would remove it. The notation and the relations \equiv_E, \equiv_V, \equiv_X and \equiv_O are those of Grammar Laws.
Conditions From Transformations
The transformations below preserve \equiv_O or the relation stated, so a compiler can apply them without a declaration.
Normal Forms
The level E rules of Normalization give a view of each rule in which unrolled whitespace is one repetition, a run of single characters is one class, and annotations that affect only values or messages are gone. On that view (P2) and (P3) decide whether a rule is idempotent or annihilates itself, and the first characters of a unit are those of its normal form.
Checked: statically. Author cost: none.
Left Factoring
a b / a c becomes a (b / c) (C6). The alternatives b and c then start
at one position after a shared prefix, where their first items can differ.
The rewrite needs a prefix and alternatives without loose cuts; it changes
the value when b or c is a sequence, so an optimizer applies it to a
copy that keeps the original value shape, as Core IR can by building the
tuple after the choice.
Checked: statically. Author cost: none.
Specialization per Call Site
An option, a repetition or a lookahead at the end of a rule is a unit whose continuation is what follows each call of the rule. The classifier must take every caller's continuation at once. Cloning the rule per caller, or inlining it where (P1) of Grammar Laws allows, gives each copy one continuation, which is often disjoint from the unit.
Checked: statically. Author cost: none. Code size grows with the number of clones; the memo of a cloned rule is split by clone.
Filtered First Characters
(F2) lets a unit &L e with a single character test L be compared with its
continuations by the intersection of the first characters of L and e.
Grammars that dispatch on the first character, as &[0-9] number, then have
disjoint units.
Checked: statically. Author cost: none.
Equal Environments and Free Variables
(D2) lets a shared call at the start of a unit read variables, and (D3) lets the key of a call keep only its free variables. A rule that binds a variable before it reads it then has a smaller key, which (D1) uses both for sharing and for the memo.
Checked: statically, by a definite-binding analysis. Author cost: none.
Words Without Case
A sequence of classes that each accept the upper and lower case of one letter,
as [sS][eE][lL][eE][cC][tT], matches what a case-insensitive literal
matches. Treating it as such a literal lets the words condition apply to
[sS][eE]... !identchar, as it applies to "select"i !identchar.
Checked: statically. Author cost: none.
Conditions From Declarations
Exclusive Alternatives
Condition. No state lets two alternatives of a choice both match.
Equivalent to a #cut after the part that decides the alternative (T1), which
the author can write, or an analysis can insert where it proves the
exclusion.
Checked: statically where first sets of length k or the exclusion of (K5) prove it, and at run time only by evaluating the other alternatives, which costs what the condition saves; otherwise not at all. A written cut needs no check: it defines the behavior rather than asserting it.
Author cost: a cut per choice, and the author must know that the language needs no later alternative after the cut, which is the knowledge that the condition asserts.
Bounded Nesting
Condition. The calls of a recursive rule nest at most d deep. A unit that is not committing and nests without bound costs, under the bound, at most a factor exponential in d more than linear.
Checked: at run time, by a nesting limit as in Limits, which ends the parse with a run-time error when the bound is exceeded. A bound per rule needs a counter per rule.
Author cost: a number per rule, which also limits the inputs that the parser accepts. The class that results is linear for a fixed d, not linear.
Variables With Few Values
Condition. A variable takes values from a set fixed by the grammar, as a level that stays between 0 and K, or only increases along a nesting so that its number of values is bounded by the depth. The key of a call that reads it then takes boundedly many values at a position, so the memo still bounds the evaluations of the rule at a position.
Checked: statically, without a declaration, where the condition is a
range: a test next to each binding that reads the earlier value, as
{v := v + 1} {v <= 3} or {0 <= e && e <= 3} {v := e}, is a guard that
bounds the values that the binding leaves bound, and the classifier counts the
values of the variable from the guards
(Conditions for Linear Cost,
Lemma 8). The guard is part of the
grammar: at run time a value outside the range fails the sequence of the
binding, and backtracking undoes it. A declaration is needed only for what a
guard cannot express: a set that is not a range of constants, or a value that
only increases along a nesting, which no constant bounds. Such a declaration is
checked at binding time, by comparing the value bound with the declared set or
with the earlier value; a violation is a run-time error of the action.
Author cost: a guard per binding, which a grammar that already limits the variable may have, or a declaration per variable for what guards cannot express. A level that grows with the input, as an indentation level, has no fixed set; bounding it limits the inputs, as bounded nesting does.
Never Failing
Condition. A rule always matches. (C3) and (R1) then hold for its calls.
Checked: statically by AlwaysSucceeds where the shape proves it, and at
run time by failing with a run-time error where a declared rule fails.
Author cost: none when the shape proves it.
Prefix-Free Matches and Token Boundaries
Condition. A rule ends only where its caller can start a new item: no
continuation of the caller can extend a match of the rule, as when the rule
ends with !identchar or each match ends with a closing delimiter. A unit
that ends with such a call then commits at the end of the call.
Checked: statically where the rule ends with a negative lookahead that covers the first characters of every continuation, which the words condition already uses; otherwise not at all.
Author cost: none where the lookahead is written.
Idempotent and Position-Only Rules
Condition. A call of the rule at the end of a match of it matches nothing
and depends only on its position. (P2) and (P3) prove this from the normal
form; for a settled rule (P4) such as one that reads an indentation n, a
declaration n > 0 turns R R into _|_.
Checked: statically for (P2) and (P3); at binding time for a declared range of the variable. Author cost: none, or one range per variable.
From Reasons to Conditions
Every reason that the classification gives names a unit that is not committing and a continuation that it is compared with. Grouped by what keeps the unit from committing, the reasons and the conditions that would remove them are as follows.
| Blocker | Shape | Condition |
|---|---|---|
| Nesting through recursion | The unit and its continuation both reach a recursive rule | Bounded nesting; or one of the conditions below, which make the unit commit |
| First items overlap through calls | Alternatives start with different calls whose first sets overlap, or with a call that reads variables | Left factoring; first sets of length k > 1; free variables (D3); exclusive alternatives |
| Follow of a rule | An option, repetition or lookahead at the end of a rule | Specialization per call site |
| Trivia first | A unit and its continuation start with a whitespace call | Normal forms with (P2), so that the shared whitespace call is removed as shared |
| Operator parts | An operator part against another part or an operand | Exclusive alternatives over the operator table; first sets of length k > 1 |
| Lookahead first | A unit that starts with &L |
Filtered first characters |
| Nullable continuation | A continuation that can match nothing | Comparison with what follows the nullable item, as for the follow of a rule |
| Variables in a shared call | A shared call reads a variable | Equal environments (D2) |
A unit that commits is linear without a depth bound. A depth bound removes the nesting from every reason at once, but the class it gives grows with the bound. The conditions from transformations remove fewer reasons each, and cost nothing.