Expand description
Static recognition of TeX conditional control words — which \if…-named
command opens a \fi-terminated conditional, and which \else/\or/\fi
divides or closes one.
Shared by the grammar (which builds SyntaxKind::CONDITIONAL nodes behind
a shape gate) and the linter’s ConditionalIndex (which derives branch paths
for duplicate-label/duplicate-package), so the two read the same name
sets and the same state machine — the same arrangement as
super::lexer::expl_toggle.
What that buys is precise, and worth stating precisely: the two can never
disagree about what an opener is. They can still reach different verdicts
on a given token, because each feeds this scan a different stream on purpose.
The parser walks every token and suppresses openers inside an expl3 region
(in-region layout is the formatter’s); the linter walks COMMAND nodes and
skips definition-command spans wholesale (\def\stopit{\fi} carries a \fi,
it does not run one), which the parser has no need to do because its own brace
anchor already refuses to pair across the body’s group. Those are deliberate
per-consumer filters layered around a shared recognizer, not two recognizers.
Recognition is pair-and-trust: a lowercase-if-prefixed name opens a
conditional unless it is a known brace-argument macro. That leaves two
families to subtract, both measured over latex2e/latex3/pgf/latexindent:
- [
NOT_FI_TERMINATED] —\ifthenelse,\iftoggle, the etoolbox test family (102 occurrences). These take{true}{false}arguments and are never\fi-terminated. Subtracting them is load-bearing rather than cosmetic: shape alone does not merely fail on one, it mis-pairs. Inlatexindent’stest-cases/ifelsefi/issue-250.texan\ifnumgreaternests inside a real\ifluatex, so trusting it would steal the enclosing conditional’s\fi. - [
OPERAND_SKIPS] —\newif\if@foo(574 occurrences) and\let\ifpdf\iftrue, where the\ifXsits in an operand slot and is data being declared or aliased, not live control flow.
Both are curated compiled-in facts, on the same footing as the verbatim and
math environment tables: static lexical knowledge, never the mutable signature
database (AGENTS.md decision #8). A missing entry costs a desynced stack in
the linter and a demoted (never a mis-built) node in the parser, so the set
extends freely.
Structs§
- Opener
Scan - The running state that turns
is_conditional_openerinto a positional decision: the operand-slot countdown and the\ifcsnamebody.
Enums§
- Flow
Word - A control word that divides or closes a conditional.
- Word
- What a control word does to the conditional structure, once positional context is taken into account.
Functions§
- flow_
word - Classify a control word’s name (no leading backslash) as a conditional
divider or closer. Stateless:
\else/\or/\fiare never anything else. - is_
conditional_ opener - Pair-and-trust: a lowercase-
if-prefixed name opens a conditional unless it is a known brace-argument macro. Reads the name, no leading backslash. - operand_
skips - How many following control words
nameclaims as operands, if any.