Split private Pratt steps to reduce numeric parser stack overlap
This commit is contained in:
@@ -40,7 +40,22 @@ pub(super) fn binding(p: &mut Cursor<'_, '_>) -> Result<Binding, Error> {
|
|||||||
while let Some(task) = tasks.pop() {
|
while let Some(task) = tasks.pop() {
|
||||||
p.budget.active()?;
|
p.budget.active()?;
|
||||||
match task {
|
match task {
|
||||||
Task::Expression(minimum) => {
|
Task::Expression(minimum) => prefix(p, minimum, &mut tasks, &mut values)?,
|
||||||
|
task => continue_task(p, task, &mut tasks, &mut values)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Binding::Expression(
|
||||||
|
values.pop().expect("one complete expression"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
// Keep mutually exclusive construction temporaries out of the numeric prefix
|
||||||
|
// call chain. Both steps borrow the driver's original iterative stacks.
|
||||||
|
fn prefix(
|
||||||
|
p: &mut Cursor<'_, '_>,
|
||||||
|
minimum: u8,
|
||||||
|
tasks: &mut Vec<Task>,
|
||||||
|
values: &mut Vec<Expression>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let start = p.span().start;
|
let start = p.span().start;
|
||||||
if p.text() != "(" {
|
if p.text() != "(" {
|
||||||
p.budget.entry(p.span())?;
|
p.budget.entry(p.span())?;
|
||||||
@@ -82,18 +97,14 @@ pub(super) fn binding(p: &mut Cursor<'_, '_>) -> Result<Binding, Error> {
|
|||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
let end = reference.span.end;
|
let end = reference.span.end;
|
||||||
values.push(
|
values.push(p.node(ExpressionKind::Property(reference), Span { start, end })?);
|
||||||
p.node(ExpressionKind::Property(reference), Span { start, end })?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"$" => {
|
"$" => {
|
||||||
p.cursor += 1;
|
p.cursor += 1;
|
||||||
let reference = p.reference()?;
|
let reference = p.reference()?;
|
||||||
let end = reference.span.end;
|
let end = reference.span.end;
|
||||||
values.push(
|
values.push(p.node(ExpressionKind::Parameter(reference), Span { start, end })?);
|
||||||
p.node(ExpressionKind::Parameter(reference), Span { start, end })?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
"ratio" => {
|
"ratio" => {
|
||||||
let (value, literal) = p.exact_literal()?;
|
let (value, literal) = p.exact_literal()?;
|
||||||
@@ -138,10 +149,18 @@ pub(super) fn binding(p: &mut Cursor<'_, '_>) -> Result<Binding, Error> {
|
|||||||
}
|
}
|
||||||
_ => return Err(error(p.span(), "Expected an analysis expression.")),
|
_ => return Err(error(p.span(), "Expected an analysis expression.")),
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
}
|
||||||
|
fn continue_task(
|
||||||
|
p: &mut Cursor<'_, '_>,
|
||||||
|
task: Task,
|
||||||
|
tasks: &mut Vec<Task>,
|
||||||
|
values: &mut Vec<Expression>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match task {
|
||||||
|
Task::Expression(_) => unreachable!("prefix tasks are dispatched by the driver"),
|
||||||
Task::Continue(minimum) => {
|
Task::Continue(minimum) => {
|
||||||
if let Some((op, precedence)) =
|
if let Some((op, precedence)) = operator(p.text()).filter(|(_, prec)| *prec >= minimum)
|
||||||
operator(p.text()).filter(|(_, prec)| *prec >= minimum)
|
|
||||||
{
|
{
|
||||||
let left = values.pop().expect("completed Pratt operand");
|
let left = values.pop().expect("completed Pratt operand");
|
||||||
if comparison(op) && ungrouped_comparison(&left) {
|
if comparison(op) && ungrouped_comparison(&left) {
|
||||||
@@ -295,10 +314,7 @@ pub(super) fn binding(p: &mut Cursor<'_, '_>) -> Result<Binding, Error> {
|
|||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
Ok(Binding::Expression(
|
|
||||||
values.pop().expect("one complete expression"),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
impl Sequence {
|
impl Sequence {
|
||||||
fn new(kind: SequenceKind, start: usize) -> Self {
|
fn new(kind: SequenceKind, start: usize) -> Self {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Private Pratt parser stack-frame repair candidate
|
||||||
|
|
||||||
|
2026-09-25. Separate correction after emission source `19a4082117b88caf332198e4c685bde91005cb52`. Parent recorded eleven focused emission tests passing, then a full-suite STATUS_STACK_OVERFLOW (0xc00000fd) in the unchanged ordinary-index 128 KiB control. The prior `fa7a9804` full run completed. This note preserves the failed candidate rather than treating focused success as acceptance.
|
||||||
|
|
||||||
|
Parent's exact 19a4082 artifact is 42,402,816 bytes, SHA256 `777457cfefb8a3046e3f0809cabd936cfb742abe792befad2c50e05dfac1bf6b`. Its LLDB trace reproduces overflow in Windows allocation beneath formatting in Exact::parse_decimal, then exact_literal, syntax::binding, declarations::parse, State::parse_typed, Parser::unit and parse_impl. No emitter call is active. A debugger process killed with exit 0 is not a test pass. Raw parent records live in `.local/analysis-mixed-emission-gates-20260925/emission-full-19a4082-1.*` and `emission-debugger-19a4082-1.*`; integration owns publication.
|
||||||
|
|
||||||
|
Parent measured fixed active prologues: binding 27,272 B, declarations::parse 22,664 B, Parser::unit 11,864 B, State::parse_typed 6,952 B. The previously measured outer frames (parse_impl 11,048 B, profile wrapper 200 B, parse 280 B, accepted 1,432 B, indexed 1,496 B, worker 6,920 B) match fa7. These partial native reservations are not total stack use. No inherited owner field or parser source changed in emission, so duplicated inline policy is not the cause established here. Environment-sensitive headroom remains a limitation; the failed full run still blocks acceptance.
|
||||||
|
|
||||||
|
## Narrow correction
|
||||||
|
|
||||||
|
`syntax.rs` keeps the same iterative binding driver, task/value Vec allocations, initial active check/unbound branch, per-popped-task active check, and final value extraction. The current Task::Expression body moves mechanically to a private prefix step borrowing those Vecs and Cursor. Remaining task arms move mechanically to a continuation step, also borrowing the same containers. No recursive parser call, new collection, clone, Box/Arc, numeric parsing change, work debit, depth limit, logical reservation or producer-timing claim is introduced. All cursor mutations, pushes/pops, opening/closing, validation, spans and error order remain at the same points. Steps return unit or the same Error; the driver retains ownership and cleanup of its original stacks.
|
||||||
|
|
||||||
|
The intended native-stack improvement is that numeric prefix work no longer runs below all continuation construction temporaries in one large function. This is a source-level rationale, not an accepted measurement. Parent must measure the new active driver+prefix prologue sum against 27,272 B (and driver+continuation where relevant), then run unchanged stack controls and required focused/full gates. No test, fixture, allowance, RUST_MIN_STACK, Exact implementation or forwarding wrapper was changed. Independent source review checks all moved arms; targeted rustfmt and diff checks are the only implementer tools, with no compiler/runtime execution.
|
||||||
Reference in New Issue
Block a user