diff --git a/parser/src/ast.rs b/parser/src/ast.rs index f94b3cf..4d54c5f 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -151,6 +151,8 @@ pub enum BinaryPlaceOperator { InArray, } +/// Essentially lvalues. To the interpreter, these do not produce a value, but +/// get theirs modified. A place is a subset of all expressions. pub enum Place<'a> { Record(Expr<'a>), Variable(Variable<'a>), @@ -396,7 +398,8 @@ impl<'a> BinaryPlaceOperator { } impl<'a> Place<'a> { - pub fn promote_from(expr: Expr<'a>, span: Span) -> Result, ParsingError)> { + /// Attempts to lower an expression into a place; on error returns it back. + pub fn lower_from(expr: Expr<'a>, span: Span) -> Result, ParsingError)> { match expr { Expr::Leaf(Atom::Variable(var)) => Ok(Self::Variable(var)), Expr::Node(node) diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 2576ad1..5d1490f 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -87,7 +87,7 @@ impl<'a> Parser<'a> { // * Pattern (Expression) // * Expects brackets afterwards (body) or a newline (default). // * Action (Statement) - // * Expects a newline afterwards; inserts default pattern. + // * Expects a newline afterwards. while let Some(tok) = lex.peek() { if tok.as_ref().is_ok_and(Token::is_pattern_start) { match self.parse_pattern(lex)? { @@ -219,6 +219,8 @@ impl<'a> Parser<'a> { } } } + + /// These are a subset of statements usable in places like for-loop defs. #[tracing::instrument] fn parse_simple_statement( &mut self, diff --git a/parser/src/pratt.rs b/parser/src/pratt.rs index 10c3f31..432671e 100644 --- a/parser/src/pratt.rs +++ b/parser/src/pratt.rs @@ -63,6 +63,8 @@ impl<'a, 'b> Pratt<'a, 'b> { ) -> Result> { while let Some((next, span)) = lex.peek_with_span() { let next = next?; + // Short circuits if requested. Useful for returning early when a + // token may also match a known operator. if delimiter(next) { break; } @@ -73,13 +75,13 @@ impl<'a, 'b> Pratt<'a, 'b> { break; } lex.next(); - let place = Place::promote_from(lhs.take(), lex.span())?; + let place = Place::lower_from(lhs.take(), lex.span())?; Expr::node(op.expr(place), self.parser.arena) } else if let Ok(op) = BinaryPlaceOperator::parse(next, &span) { if op.binding_power().0 < min_bp { break; } - let place = Place::promote_from(lhs.take(), lex.span())?; + let place = Place::lower_from(lhs.take(), lex.span())?; self.parse_place_op(lex, op, place)? } else if let Ok(op) = BinaryOperator::parse(next, &span) && !matches!(next, Token::Increment | Token::Decrement) @@ -123,7 +125,7 @@ impl<'a, 'b> Pratt<'a, 'b> { if let Ok(op) = UnaryPlaceOperator::parse_prefix(&next, &lex.span()) { let rhs = self.parse_expression(lex, op.binding_power())?; Ok(Expr::node( - op.expr(Place::promote_from(rhs, lex.span())?), + op.expr(Place::lower_from(rhs, lex.span())?), self.parser.arena, )) } else if let Ok(op) = UnaryOperator::parse(&next, &lex.peeked_span()?) { @@ -142,17 +144,15 @@ impl<'a, 'b> Pratt<'a, 'b> { // redirection reading from file. Does not accept typed regexes. self.typed_regex = false; let place = if lex.peek_with(Token::is_place) { - Some(Place::promote_from( - self.parse_redirection(lex)?, - lex.span(), - )) + Some(Place::lower_from(self.parse_redirection(lex)?, lex.span())) } else { None } - .transpose(); + .transpose(); // trick to simplify checks. let getline = |gl| Expr::node(ExprNode::Getline(gl), self.parser.arena); match place { + // Nonsensical expression; gawk just assumes concatenation. Err((expr, _)) => Ok(Expr::node( BinaryOperator::Concat.expr(getline(Getline::FromInput(None)), expr), self.parser.arena, @@ -170,6 +170,9 @@ impl<'a, 'b> Pratt<'a, 'b> { fn parse_atom_or_call(&mut self, lex: &mut Lexer<'a>) -> Result> { let next = lex.expect_next()?; + // Only accepts calls if the function name is next to the parenthesis. + // If there is a space, we interpret it as a concatenation and let the + // interpreter error if necessary; elsewhere we can't concat with vars. if let Token::Identifier(name) = next && lex.peek_is(&Token::OpenParent) && lex.is_yuxtaposed() @@ -179,6 +182,7 @@ impl<'a, 'b> Pratt<'a, 'b> { } else { match self.parser.parse_atom(lex, next, self.typed_regex) { Ok(atom) => Ok(Expr::leaf(atom)), + // Add detail to this error. Err(ParsingError::UnexpectedToken(_, str)) => { Err(ParsingError::InvalidExpression(lex.span(), str)) } @@ -193,7 +197,9 @@ impl<'a, 'b> Pratt<'a, 'b> { op: BinaryOperator, lhs: Expr<'a>, ) -> Result> { + // Ensures it's not a typed regex; rejects cases like `x = @/a/ + 1`. 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); self.typed_regex = matches!(op, BinaryOperator::Matches | BinaryOperator::MatchesNot); @@ -228,14 +234,18 @@ impl<'a, 'b> Pratt<'a, 'b> { self.parse_expression(lex, op.binding_power().1)? }; if op == BinaryPlaceOperator::ArrayAccess { + // We can only index on variables. if !matches!(place, Place::Variable(_)) { return Err(ParsingError::OperatorExpectsVariable(lex.span())); } + // Concatenates each dimension with `SUBSEP`. + // FIXME: undo when pretty-printing or defer to the interpreter. rhs = self.parse_array_index(lex, rhs)?; } Ok(Expr::node(op.expr(place, rhs), self.parser.arena)) } + /// Continuously pub fn parse_array_index(&mut self, lex: &mut Lexer<'a>, lhs: Expr<'a>) -> Result> { let mut rhs = lhs; while lex.consume(&Token::Comma) { @@ -286,7 +296,7 @@ impl<'a, 'b> Pratt<'a, 'b> { let pipe = |place| Expr::node(op.expr_getline(place, lhs), self.parser.arena); if lex.peek_with(Token::is_place) { let expr = self.parse_redirection(lex)?; - match Place::promote_from(expr, lex.span()) { + match Place::lower_from(expr, lex.span()) { Ok(place) => Ok(pipe(Some(place))), Err((expr, _)) => Ok(Expr::node( BinaryOperator::Concat.expr(pipe(None), expr), @@ -302,6 +312,7 @@ impl<'a, 'b> Pratt<'a, 'b> { self.parse_expression(lex, BinaryOperator::Concat.binding_power().1 - 1) } + /// Errors if `expr` is a typed regex. fn typecheck(&self, lex: &mut Lexer<'a>, expr: &Expr<'a>) -> Result<()> { if matches!(expr, Expr::Leaf(Atom::TypedRegex(_))) { Err(ParsingError::UnexpectedTypedRegex(lex.span()))