// Conditional-compilation groups, `#[if(c)] { ... } #[else] { ... }`.
//
// THE ONE THING THE GRAMMAR CANNOT DO ITSELF. Pairing an `#[if]` with a
// following `#[else]` is the dangling-else problem, and since `#[else]` is not
// a single token it is not LALR(1). So each brace group parses into a MARKER,
// and a post-pass pairs adjacent markers, leaving everything else untouched.
//
// This mirrors the reference's `raw_stmt`/`process_stmts` and
// `raw_field`/`lower_fields` exactly, including the wrinkle about `;`.
///|
/// A statement as parsed, before conditional groups are paired.
priv enum RawStmt {
Plain(Instr[Location])
/// An empty `;` statement.
///
/// Kept in the raw list rather than dropped at the `;` production, because
/// the statement-level hint production inspects the raw tail and must be able
/// to insist on being ADJACENT to its instruction: `#[likely] ; f()` is
/// rejected rather than the hint silently reaching `f()` across the `;`.
/// `process_stmts` strips these before pairing, so an `#[if]`/`#[else]` pair
/// separated by a stray `;` still pairs.
Semi
If((Position, Position), Cond, Body[Location])
Else((Position, Position), Body[Location])
}
///|
/// Pair adjacent `#[if]`/`#[else]` markers into `IfAnnotation` nodes.
fn pair_stmts(l : ArrayView[RawStmt]) -> Array[Instr[Location]] {
let out : Array[Instr[Location]] = []
let mut i = 0
while i < l.length() {
match l[i] {
Plain(instr) => {
out.push(instr)
i += 1
}
Semi => i += 1 // filtered by process_stmts; belt and braces
If(p, cond, then_body) =>
// Each branch keeps its OWN `#[if]`/`#[else] { ... }` span, marker
// included, on its located body -- not just the combined span on the
// node -- so a consumer such as the editor's dead-branch dimming can
// locate a single branch.
if i + 1 < l.length() && l[i + 1] is Else(ep, else_body) {
out.push(
with_loc(
(p.0, ep.1),
IfAnnotation(
cond~,
then_body=respan(then_body, p),
else_body=Some(respan(else_body, ep)),
),
),
)
i += 2
} else {
out.push(
with_loc(
p,
IfAnnotation(
cond~,
then_body=respan(then_body, p),
else_body=None,
),
),
)
i += 1
}
Else(p, _) => {
record_error(
loc_of(p),
"An '#[else]' must directly follow an '#[if(...)]' group.",
)
i += 1
}
}
}
out
}
///|
/// Strip empty statements, then pair.
fn process_stmts(l : Array[RawStmt]) -> Array[Instr[Location]] {
let kept : Array[RawStmt] = []
for s in l {
if !(s is Semi) {
kept.push(s)
}
}
pair_stmts(kept[:])
}
///|
/// A located list of module fields, the payload of a module-level conditional.
type LocatedFields = Annotated[
Array[Annotated[ModuleField[Location], Location]],
Location,
]
///|
/// A module field as parsed, before conditional groups are paired.
///
/// Braces are mandatory here, so nesting is expressed by the braces -- a nested
/// `#[if]` lives inside a branch's field list -- and pairing is plain
/// adjacency, with no dangling-else search.
priv enum RawField {
FPlain(Annotated[ModuleField[Location], Location])
FIf((Position, Position), Cond, LocatedFields)
FElse((Position, Position), LocatedFields)
}
///|
/// Pair adjacent `#[if]`/`#[else]` field markers into `Conditional` fields.
fn lower_fields(
l : Array[RawField],
) -> Array[Annotated[ModuleField[Location], Location]] {
let out : Array[Annotated[ModuleField[Location], Location]] = []
let mut i = 0
while i < l.length() {
match l[i] {
FPlain(f) => {
out.push(f)
i += 1
}
FIf(p, cond, then_fields) =>
if i + 1 < l.length() && l[i + 1] is FElse(ep, else_fields) {
out.push(
annot(
(p.0, ep.1),
ModuleField::Conditional(
cond~,
then_fields=respan(then_fields, p),
else_fields=Some(respan(else_fields, ep)),
),
),
)
i += 2
} else {
out.push(
annot(
p,
ModuleField::Conditional(
cond~,
then_fields=respan(then_fields, p),
else_fields=None,
),
),
)
i += 1
}
FElse(p, _) => {
record_error(
loc_of(p),
"An '#[else]' must directly follow an '#[if(...)]' field.",
)
i += 1
}
}
}
out
}
///|
/// Apply a module field's leading attributes.
///
/// When there are attributes the field's span is WIDENED to cover them, so
/// comments and blank lines written before the attributes attach to the field
/// rather than to an attribute's inner expression.
fn attributed(
p : (Position, Position),
attributes : Attributes,
build : (Attributes) -> Annotated[ModuleField[Location], Location],
) -> Annotated[ModuleField[Location], Location] {
let f = build(attributes)
if attributes.is_empty() {
f
} else {
annot(p, f.desc)
}
}