Split private Pratt steps to reduce numeric parser stack overlap

This commit is contained in:
2026-09-24 18:19:08 -07:00
parent 19a4082117
commit 8d52dab97c
2 changed files with 283 additions and 254 deletions
@@ -40,266 +40,282 @@ pub(super) fn binding(p: &mut Cursor<'_, '_>) -> Result<Binding, Error> {
while let Some(task) = tasks.pop() {
p.budget.active()?;
match task {
Task::Expression(minimum) => {
let start = p.span().start;
if p.text() != "(" {
p.budget.entry(p.span())?;
}
tasks.push(Task::Continue(minimum));
match p.text() {
"true" | "false" => {
let value = p.text() == "true";
let span = p.span();
p.cursor += 1;
values.push(p.node(ExpressionKind::Boolean(value), span)?);
}
"-" => {
p.open()?;
p.cursor += 1;
tasks.push(Task::Unary(start, UnaryOp::Negate));
tasks.push(Task::Expression(7));
}
"(" => {
p.open()?;
p.cursor += 1;
tasks.push(Task::Group(start));
tasks.push(Task::Expression(0));
}
"[" => {
p.open()?;
p.cursor += 1;
tasks.push(Task::Next(Sequence::new(SequenceKind::List, start)));
}
"@" => {
p.cursor += 1;
let reference = p.reference()?;
if p.text() == "(" {
p.open()?;
p.cursor += 1;
tasks.push(Task::Next(Sequence::new(
SequenceKind::Call(reference),
start,
)));
} else {
let end = reference.span.end;
values.push(
p.node(ExpressionKind::Property(reference), Span { start, end })?,
);
}
}
"$" => {
p.cursor += 1;
let reference = p.reference()?;
let end = reference.span.end;
values.push(
p.node(ExpressionKind::Parameter(reference), Span { start, end })?,
);
}
"ratio" => {
let (value, literal) = p.exact_literal()?;
values.push(p.number(value, literal)?);
}
"abs" | "not" | "if" | "sum" => {
let kind = match p.text() {
"abs" => SequenceKind::Unary(UnaryOp::Absolute),
"not" => SequenceKind::Unary(UnaryOp::Not),
"if" => SequenceKind::If,
_ => SequenceKind::Sum,
};
p.cursor += 1;
p.open()?;
p.take("(")?;
tasks.push(Task::Next(Sequence::new(kind, start)));
}
"power" => {
p.cursor += 1;
p.open()?;
p.take("(")?;
tasks.push(Task::Power(start));
tasks.push(Task::Expression(0));
}
"as_kind" => {
p.cursor += 1;
p.open()?;
p.take("(")?;
p.take("@")?;
let kind = p.reference()?;
p.take(",")?;
tasks.push(Task::AsKind(start, kind));
tasks.push(Task::Expression(0));
}
_ if p
.tokens
.get(p.cursor)
.is_some_and(|token| token.class == Class::Number) =>
{
let (value, literal) = p.exact_literal()?;
values.push(p.number(value, literal)?);
}
_ => return Err(error(p.span(), "Expected an analysis expression.")),
}
}
Task::Continue(minimum) => {
if let Some((op, precedence)) =
operator(p.text()).filter(|(_, prec)| *prec >= minimum)
{
let left = values.pop().expect("completed Pratt operand");
if comparison(op) && ungrouped_comparison(&left) {
return Err(error(p.span(), "Comparison children require parentheses."));
}
p.budget.entry(p.span())?;
p.cursor += 1;
tasks.push(Task::Continue(minimum));
tasks.push(Task::Binary(left, op));
tasks.push(Task::Expression(precedence + 1));
}
}
Task::Binary(left, op) => {
let right = values.pop().expect("completed right operand");
if comparison(op) && ungrouped_comparison(&right) {
return Err(error(
right.span,
"Comparison children require parentheses.",
));
}
let span = Span {
start: left.span.start,
end: right.span.end,
};
values.push(p.node(
ExpressionKind::Binary {
op,
left: Box::new(left),
right: Box::new(right),
},
span,
)?);
}
Task::Unary(start, op) => {
let operand = values.pop().expect("completed unary operand");
let end = operand.span.end;
p.close();
values.push(p.node(
ExpressionKind::Unary {
op,
operand: Box::new(operand),
},
Span { start, end },
)?);
}
Task::Group(start) => {
let end = p.take(")")?.end;
p.close();
let value = values.last_mut().expect("completed grouped expression");
value.grouped = true;
value.span = Span { start, end };
}
Task::Next(mut sequence) => {
let closing = if matches!(sequence.kind, SequenceKind::List) {
"]"
} else {
")"
};
if p.text() == closing {
let end = p.take(closing)?.end;
p.close();
values.push(p.sequence(sequence, end)?);
} else {
let count = sequence.values.len();
let maximum = match sequence.kind {
SequenceKind::List => crate::analysis::MAX_LIST_LENGTH,
SequenceKind::Unary(_) => 1,
SequenceKind::If => 3,
SequenceKind::Sum => 2,
SequenceKind::Call(_) => usize::MAX,
};
if count >= maximum {
return Err(error(
p.span(),
"Too many list members or constructor arguments.",
));
}
let key = if matches!(sequence.kind, SequenceKind::Call(_)) {
p.budget.entry(p.span())?;
let key = p.reference()?;
if !sequence.keys.insert(key.name.clone()) {
return Err(error(key.span, "Duplicate call argument name."));
}
p.take(":")?;
Some(key)
} else {
None
};
tasks.push(Task::Collected(sequence, key));
tasks.push(Task::Expression(0));
}
}
Task::Collected(mut sequence, key) => {
sequence
.values
.push((key, values.pop().expect("completed sequence value")));
let closing = if matches!(sequence.kind, SequenceKind::List) {
"]"
} else {
")"
};
if p.text() != closing {
p.take(",")?;
if p.text() == closing {
return Err(error(p.span(), "Trailing comma is not an expression."));
}
}
tasks.push(Task::Next(sequence));
}
Task::Power(start) => {
let base = values.pop().expect("completed power base");
p.take(",")?;
let negative = if p.text() == "-" {
p.cursor += 1;
true
} else {
false
};
let span = p.integer()?;
let magnitude = p.source[span.start..span.end]
.parse::<u8>()
.map_err(|_| error(span, "Power exponent is outside -16..16."))?;
if magnitude > 16 {
return Err(error(span, "Power exponent is outside -16..16."));
}
let exponent = if negative {
-(magnitude as i8)
} else {
magnitude as i8
};
let end = p.take(")")?.end;
p.close();
values.push(p.node(
ExpressionKind::Power {
base: Box::new(base),
exponent,
},
Span { start, end },
)?);
}
Task::AsKind(start, kind) => {
let value = values.pop().expect("completed kind assertion value");
let end = p.take(")")?.end;
p.close();
values.push(p.node(
ExpressionKind::AsKind {
kind,
value: Box::new(value),
},
Span { start, end },
)?);
}
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;
if p.text() != "(" {
p.budget.entry(p.span())?;
}
tasks.push(Task::Continue(minimum));
match p.text() {
"true" | "false" => {
let value = p.text() == "true";
let span = p.span();
p.cursor += 1;
values.push(p.node(ExpressionKind::Boolean(value), span)?);
}
"-" => {
p.open()?;
p.cursor += 1;
tasks.push(Task::Unary(start, UnaryOp::Negate));
tasks.push(Task::Expression(7));
}
"(" => {
p.open()?;
p.cursor += 1;
tasks.push(Task::Group(start));
tasks.push(Task::Expression(0));
}
"[" => {
p.open()?;
p.cursor += 1;
tasks.push(Task::Next(Sequence::new(SequenceKind::List, start)));
}
"@" => {
p.cursor += 1;
let reference = p.reference()?;
if p.text() == "(" {
p.open()?;
p.cursor += 1;
tasks.push(Task::Next(Sequence::new(
SequenceKind::Call(reference),
start,
)));
} else {
let end = reference.span.end;
values.push(p.node(ExpressionKind::Property(reference), Span { start, end })?);
}
}
"$" => {
p.cursor += 1;
let reference = p.reference()?;
let end = reference.span.end;
values.push(p.node(ExpressionKind::Parameter(reference), Span { start, end })?);
}
"ratio" => {
let (value, literal) = p.exact_literal()?;
values.push(p.number(value, literal)?);
}
"abs" | "not" | "if" | "sum" => {
let kind = match p.text() {
"abs" => SequenceKind::Unary(UnaryOp::Absolute),
"not" => SequenceKind::Unary(UnaryOp::Not),
"if" => SequenceKind::If,
_ => SequenceKind::Sum,
};
p.cursor += 1;
p.open()?;
p.take("(")?;
tasks.push(Task::Next(Sequence::new(kind, start)));
}
"power" => {
p.cursor += 1;
p.open()?;
p.take("(")?;
tasks.push(Task::Power(start));
tasks.push(Task::Expression(0));
}
"as_kind" => {
p.cursor += 1;
p.open()?;
p.take("(")?;
p.take("@")?;
let kind = p.reference()?;
p.take(",")?;
tasks.push(Task::AsKind(start, kind));
tasks.push(Task::Expression(0));
}
_ if p
.tokens
.get(p.cursor)
.is_some_and(|token| token.class == Class::Number) =>
{
let (value, literal) = p.exact_literal()?;
values.push(p.number(value, literal)?);
}
_ => 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) => {
if let Some((op, precedence)) = operator(p.text()).filter(|(_, prec)| *prec >= minimum)
{
let left = values.pop().expect("completed Pratt operand");
if comparison(op) && ungrouped_comparison(&left) {
return Err(error(p.span(), "Comparison children require parentheses."));
}
p.budget.entry(p.span())?;
p.cursor += 1;
tasks.push(Task::Continue(minimum));
tasks.push(Task::Binary(left, op));
tasks.push(Task::Expression(precedence + 1));
}
}
Task::Binary(left, op) => {
let right = values.pop().expect("completed right operand");
if comparison(op) && ungrouped_comparison(&right) {
return Err(error(
right.span,
"Comparison children require parentheses.",
));
}
let span = Span {
start: left.span.start,
end: right.span.end,
};
values.push(p.node(
ExpressionKind::Binary {
op,
left: Box::new(left),
right: Box::new(right),
},
span,
)?);
}
Task::Unary(start, op) => {
let operand = values.pop().expect("completed unary operand");
let end = operand.span.end;
p.close();
values.push(p.node(
ExpressionKind::Unary {
op,
operand: Box::new(operand),
},
Span { start, end },
)?);
}
Task::Group(start) => {
let end = p.take(")")?.end;
p.close();
let value = values.last_mut().expect("completed grouped expression");
value.grouped = true;
value.span = Span { start, end };
}
Task::Next(mut sequence) => {
let closing = if matches!(sequence.kind, SequenceKind::List) {
"]"
} else {
")"
};
if p.text() == closing {
let end = p.take(closing)?.end;
p.close();
values.push(p.sequence(sequence, end)?);
} else {
let count = sequence.values.len();
let maximum = match sequence.kind {
SequenceKind::List => crate::analysis::MAX_LIST_LENGTH,
SequenceKind::Unary(_) => 1,
SequenceKind::If => 3,
SequenceKind::Sum => 2,
SequenceKind::Call(_) => usize::MAX,
};
if count >= maximum {
return Err(error(
p.span(),
"Too many list members or constructor arguments.",
));
}
let key = if matches!(sequence.kind, SequenceKind::Call(_)) {
p.budget.entry(p.span())?;
let key = p.reference()?;
if !sequence.keys.insert(key.name.clone()) {
return Err(error(key.span, "Duplicate call argument name."));
}
p.take(":")?;
Some(key)
} else {
None
};
tasks.push(Task::Collected(sequence, key));
tasks.push(Task::Expression(0));
}
}
Task::Collected(mut sequence, key) => {
sequence
.values
.push((key, values.pop().expect("completed sequence value")));
let closing = if matches!(sequence.kind, SequenceKind::List) {
"]"
} else {
")"
};
if p.text() != closing {
p.take(",")?;
if p.text() == closing {
return Err(error(p.span(), "Trailing comma is not an expression."));
}
}
tasks.push(Task::Next(sequence));
}
Task::Power(start) => {
let base = values.pop().expect("completed power base");
p.take(",")?;
let negative = if p.text() == "-" {
p.cursor += 1;
true
} else {
false
};
let span = p.integer()?;
let magnitude = p.source[span.start..span.end]
.parse::<u8>()
.map_err(|_| error(span, "Power exponent is outside -16..16."))?;
if magnitude > 16 {
return Err(error(span, "Power exponent is outside -16..16."));
}
let exponent = if negative {
-(magnitude as i8)
} else {
magnitude as i8
};
let end = p.take(")")?.end;
p.close();
values.push(p.node(
ExpressionKind::Power {
base: Box::new(base),
exponent,
},
Span { start, end },
)?);
}
Task::AsKind(start, kind) => {
let value = values.pop().expect("completed kind assertion value");
let end = p.take(")")?.end;
p.close();
values.push(p.node(
ExpressionKind::AsKind {
kind,
value: Box::new(value),
},
Span { start, end },
)?);
}
}
Ok(())
}
impl Sequence {
fn new(kind: SequenceKind, start: usize) -> Self {
Self {