diff --git a/parser/src/diagnostics.rs b/parser/src/diagnostics.rs index bdc0547..3f5cdcd 100644 --- a/parser/src/diagnostics.rs +++ b/parser/src/diagnostics.rs @@ -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, } } diff --git a/parser/src/pratt.rs b/parser/src/pratt.rs index 22979e7..35a31fe 100644 --- a/parser/src/pratt.rs +++ b/parser/src/pratt.rs @@ -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,