Defined in header <cparse/Parser.hh>
protected:
void enterScope() noexcept;
Increments the internal open-scope depth counter. Grammars with nesting call this after consuming an opener — {, [, begin, whatever the syntax uses — so that parse() can distinguish "the input ended cleanly" from "the input ended in the middle of an open construct".
Paired with leaveScope() on the matching-closer consumption path.
Parameters
None.
Return value
(none)
Exceptions
noexcept.
Why this exists
parse() cannot distinguish "the input ended because the grammar finished" from "the input ended in the middle of a construct". Both arrive as the same std::ios_base::failure from the cursor, and the driver treated both as clean EOF: a file cut short at a member boundary parsed with result = true and zero diagnostics. For a configuration format that means a truncated file — an interrupted copy, a full disk — loads as valid with the missing half silently defaulted.
The driver is grammar-agnostic by design and cannot know what is open, so the grammar has to say. When EOF fires and openScopeDepth() is non-zero, parse() records a Syntax error "unexpected end of input: N unterminated scope(s)" and returns false.
Grammars that never call enterScope/leaveScope keep a depth of zero and behave exactly as before — this is purely additive for existing cparse users.
Why this is not RAII
The natural instinct is to wrap enterScope() in a scope-guard:
// DON'T DO THIS
class ScopeGuard {
Parser& p_;
public:
ScopeGuard(Parser& p) : p_(p) { p_.enterScope(); }
~ScopeGuard() { p_.leaveScope(); }
};
This defeats the mechanism entirely. The depth must remain non-zero while the EOF exception unwinds, because the unwinding is the condition being detected. A guard that decrements during unwinding would erase the evidence: by the time parse() catches the exception in Phase 5, the depth would already be back to zero and the truncated file would look like clean EOF again.
leaveScope() must be called only on the path that actually consumed a closer — never from a destructor, never from an exception handler.
Example
bool parseBlock()
{
if (!extractCharacter('{'))
return false;
enterScope(); // depth++
while (getCursor().isValid() && getCursor().safePeek() != '}')
{
skipCommentsBlock();
if (!parseStatement())
return false; // depth stays non-zero — this is intentional
}
if (!extractCharacter('}'))
return false;
leaveScope(); // depth-- — reached only if the closer was consumed
return true;
}
Note the two "wrong" places where leaveScope() is deliberately absent — the early return false after a failed statement, and any exception unwinding. The point is precisely that these paths leave the depth non-zero.
See also
- Parser::leaveScope — the paired decrement.
- Parser::openScopeDepth — read the current depth.
- Parser::parse — reads the depth in Phase 5 to distinguish clean from truncated EOF.

