if, a while and a for Parses the Body’s Language Minus the Forms a { Begins, and Those Take ParenthesesStatus: Accepted
Date: October 3, 2026
History: the records this one replaces are archived in docs/history/adr; read them only for how the decision was reached
Answers: #206’s
entry on the connectives missing from a head (if a && b { … } was a parse
error), and what else the head refused that the body takes: ??, as, null
and a tuple
Related:
ADR-276 (loops and jumps, whose benchmark found this; D19 measures an alternation’s order as D7 does),
ADR-296 D17,
ADR-278 D15 (?? is right-associative) and ADR-279 D1 (??’s fallback is one value, in a head as in a body),
ADR-285 (as names a type), ADR-278 (null),
ADR-132 (else if, which inherits this rule),
ADR-135 (a list literal, a primary the head also takes),
Part I 3.2 and 3.3
What may stand in the head of an if, a while and a for?
The head has a grammar of its own for one reason: in for d in whole { … } the
{ opens the body, so a head may not contain a form that begins with a brace —
a struct literal above all. That chain was built by copying the ordinary one
and stopping early, and more stopped than the brace-led forms:
if a && b { … } expected `{`; found unexpected token `&`
if a ?? b { … } expected `{`; found unexpected token `?`
if a as i64 > 3 { … } expected `{`; found unexpected token `as`
if a == null { … } expected one of: `!`, `-`, `false`, `true`; found `null`
if (a, b) == pair { … } expected `{`; found unexpected token `,`
Part I 3.2 wrote a condition as a bool with no restriction on what may
produce one. A head that takes false and refuses null is not a restriction
anybody could deduce.
Nothing outside this language is compared: the question is one of consistency with its own body grammar, and the comparison that settles it is the one in §1: the same expression in a body and in a head.
| Option | Lost because | See |
|---|---|---|
Add only what a program has run into (&&, ||), leave ?? for a reproduction |
a reproduction is evidence for adding a construct, not for an inconsistency in where an existing one may stand; waiting only means the next person meets it, and the gap turned out to be four forms, not one | D6 |
Keep a list of what the head takes (&&, || and !) |
a list of the same kind produced the defect; the page now states the rule | D3 |
| Make brace-led forms unavailable in a condition | a language restriction needing a justification; the actual constraint is only on the token that opens a body | D5 |
Order head_primary as primary_expr orders it |
a PEG alternation is ordered, so a rare alternative in front costs every common one behind it (D7) | D7 |
&& and || are in the head, mirroring the ordinary chain exactly.|| binds looser than &&, which binds looser than a comparison, and a range
looser than all of them, as everywhere else: if a || b && c is
a || (b && c).
{.An operator that cannot begin a block and does not end an expression — after it
comes another operand, which descends to the same brace-free primary — leaves
the token before the body’s { drawn from the same set as before. That is the
argument for every level the head takes.
{ begins.A head that differs from the ordinary grammar in any other way is a trap rather
than a restriction: the program reads as valid Nikaia and is refused for a
reason no rule states, or accepted with another meaning. So the head chain is a
copy with one difference, in head_primary, and a level missing for any
other reason is a defect.
Changed by ADR-279 D1: a ?? fallback is one value, so a ?? 0 > 3 is refused in a head exactly as in a body; the level itself stays where it is.
Changed by ADR-296 D24: dsl … from … is not a form of the language; its alternative stays in head_primary only to refuse it with the call to write instead.
Top to bottom: ??, range, ||, &&, comparison, +, *, as, unary,
postfix, primary — the ordinary chain, level for level, with the same
associativity and precedence. head_primary is primary_expr minus a list that
can be written out: struct_lit, block_expr, if_expr, match_expr,
overlap_expr, a dsl … { … } eod, and spawn, whose lambda is a brace.
null, a tuple and dsl … from … are in it.
if p == (P { x: 1 }) { … }
if (match n { 1 => 10, else => 20 }) > 15 { … }
paren_expr takes a whole expr, so a head may hold anything the language has,
and a form that starts with a brace is parenthesised so that the body’s {
is still the body’s. The restriction is the token that opens a body is the
body’s, with a one-character way round it.
A reproduction is the right evidence for adding a construct to the language and the wrong evidence for an inconsistency in where an existing construct may stand. The question does this already exist everywhere it should is answered by the rule.
head_primary is measured.dsl … from … goes last: at the front it would be a failed keyword match
before every primary in every head, and if { } would answer with dsl first
in its expectation list. null goes after the literals, not beside false:
third in the list it costs 0.45 % of a compile on a head-dense file. Neither word
can be taken by another alternative, since both are reserved. A PEG alternation
is an ordered list, so where a rare alternative sits is a cost paid by every
common one behind it.
Every level is descended for every head, whether used or not: 5,016
instructions per head, +0.5 % to +1.2 % of a compile on examples/; as
and the primaries are paid per operand, not per head.
What is bought is not a few constructs but a position that can be described in one sentence.
while i < n && running writable, a flag and a break cost the
same: the cost a while with a flag seemed to have was the nested if the
head forced.?? reads in a head exactly as in a body: a ?? 0 > 3 is refused in
both, and (a ?? 0) > 3 or a ?? (0 > 3) is written
(ADR-279 D1).if statement’s head is parsed twice, because assign_stmt is tried
before expr_stmt and the ordering is load-bearing (x = 1); a per-head
number for if is double a while’s for that reason.a_head_parses_what_a_body_parses lowers the same expression in both
positions and compares, so a missing level and a badly mirrored one both
fail it.if statement’s head. Known and measured, and not
changed, because the statement order needs it.