parser: start enforcing non-associativity

This commit is contained in:
Guillem L. Jara
2026-05-15 02:59:54 +02:00
parent 84e007577c
commit e1badd2cc7
2 changed files with 38 additions and 0 deletions
+6
View File
@@ -73,6 +73,8 @@ pub enum ParsingError {
SpecialVariableCall(Span, String),
#[error("Can't use special variable `{}` for indirect function call.", .1)]
SpecialVariableIndirectCall(Span, String),
#[error("Can't chain non-associative operators.")]
NonAssociativeOperator(Span),
}
impl ParsingError {
@@ -118,6 +120,7 @@ impl ParsingError {
Self::UnexpectedTypedRegex(span) => Some(span.clone()),
Self::SpecialVariableCall(span, _) => Some(span.clone()),
Self::SpecialVariableIndirectCall(span, _) => Some(span.clone()),
Self::NonAssociativeOperator(span) => Some(span.clone()),
}
}
fn hint(&self) -> Option<&'static str> {
@@ -151,6 +154,9 @@ impl ParsingError {
Self::UnexpectedTypedRegex(_) => Some(
"This is only valid in some contexts, like a right-hand assignment or a function argument.",
),
Self::NonAssociativeOperator(_) => Some(
"Some operators can't be chained to avoid logical errors, such as comparison ones.\nExample: write `a == b && b == c` instead of `a == b == c`.",
),
_ => None,
}
}
+32
View File
@@ -231,6 +231,12 @@ impl<'a, 'b> Pratt<'a, 'b> {
self.typecheck(lex, &lhs)?;
// This is just a parsing construct; we only skip if it's a real token.
lex.consume_with(|_| op != BinaryOperator::Concat);
// Checks invalids like `a == b == c`. The docs are ambiguous about the
// associativity of redirection operators, but I couldn't get awk to
// error out when chaining them.
if op.is_non_associative() && lhs.is_non_associative() {
return Err(ParsingError::NonAssociativeOperator(lex.span()));
}
self.typed_regex = matches!(op, BinaryOperator::Matches | BinaryOperator::MatchesNot);
let rhs = self.parse_expression(lex, op.binding_power().1)?;
@@ -352,6 +358,32 @@ impl<'a, 'b> Pratt<'a, 'b> {
}
}
trait NonAssociativity {
fn is_non_associative(&self) -> bool;
}
impl NonAssociativity for Expr<'_> {
fn is_non_associative(&self) -> bool {
matches!(
self,
Expr::Node(x) if matches!(x.as_ref(), ExprNode::BinaryOperation(
op,
_,
_
) if op.is_non_associative())
)
}
}
impl NonAssociativity for BinaryOperator {
fn is_non_associative(&self) -> bool {
matches!(
self,
Self::Eq | Self::NEq | Self::Gt | Self::Lt | Self::LtE | Self::GtE,
)
}
}
fn is_special_var(name: &str) -> bool {
matches!(
name,