fluffos/docs/lpc/preprocessor
Yucong Sun f3e5bfa799 compiler: eliminate C-stack recursion from macro expansion and lexer; raise nesting cap to 65535
The macro-expansion rescan path recursed one yylex() frame per nesting
level, the textual argument pre-expander (lpc_lex_expand_string) recursed
per level with per-level guard-vector copies, the #if/#elif evaluator
recursed per unary/paren/ternary token, and the lexer's no-token recovery
paths (malformed heredocs, over-long $N, template-interpolation close)
retried via recursive yylex() -- error REPORTING stops after 5 parse
errors but scanning does not, so runs of malformed constructs nested a
frame each (a 600KB file of '@' lines segfaulted the driver).

All of these are now iterative:
- lpc_lex_resolve_identifier returns LPC_TOKEN_RESCAN (no token); the
  identifier rule falls through and the SAME yylex() frame keeps
  scanning the pushed expansion buffer -- one Flex buffer per level,
  zero C-stack growth.
- lpc_lex_expand_string is an explicit work-stack machine (one shared
  guard stack + O(1) name-count lookups instead of per-level copies).
- the #if evaluator is an explicit-stack machine (ifexpr_eval), depth
  bounded by token count on the heap; no cap needed. Keeps the audit's
  int64_t retyping (LLP64 correctness).
- heredoc recovery / @@ splice, over-long $N, and template '}' close
  fall through instead of recursing (pinned by lexer_retry_chains.lpc,
  which segfaults the previous binary).

This supersedes the 2026-07-20 audit's stack-overflow mitigations for
the preprocessor (kMaxIfExprDepth, MAX_EXPANSION_NESTING lowered to 32
under the sanitizer build's measured crash boundary): the recursion
itself is gone, so MAX_EXPANSION_NESTING becomes kLpcMaxExpansionNesting
= 65535, shared by both expansion engines as a runaway/memory bound and
counted in LIVE frames only via a live-index stack (dead same-line
provenance frames no longer trip it, so 128+ sequential uses of a macro
on one line compile again). deep_nesting_caps.lpc's deep cases now
compile -- under ASan included -- instead of being rejected; its
comments and AGENTS.md's cap-sizing guidance are updated to the new
design. The self-reference guard is an O(1) hash lookup.
innermost_real_buffer_index -- behind every current_line read, per
matched token -- is O(1) via a maintained include-buffer index stack
instead of walking the whole buffer stack (the walk made deep-chain
compiles quadratic).

Diagnostics keep 16 innermost + 16 outermost expansion notes with an
elision marker instead of one note per level.

Tests: deep_macro_nesting.lpc (60000-deep chains through both engines,
same-line frame accounting, beyond-cap clean error, 262144-token #if
shapes), lexer_retry_chains.lpc, deep_ternary_nesting.lpc (parser-stack
bound pins). Validated post-rebase on Debug and clang ASan+UBSan full
suites (621/621 files each) plus RelWithDebInfo pre-rebase; gtests
320/320.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 01:39:28 -07:00
..
conditionals.md Add local search and documentation guide for docs site (#1221) 2026-07-09 22:09:55 -04:00
define.md compiler: eliminate C-stack recursion from macro expansion and lexer; raise nesting cap to 65535 2026-07-22 01:39:28 -07:00
include.md Add inherit_program / include_file master applies; auto hot-reload demo (#1230) 2026-07-10 10:27:27 -04:00
index.md Add local search and documentation guide for docs site (#1221) 2026-07-09 22:09:55 -04:00
pragma.md Add local search and documentation guide for docs site (#1221) 2026-07-09 22:09:55 -04:00