Split private Pratt steps to reduce numeric parser stack overlap
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user