//! Block-body parsing for Fermented `UnrealScript`. //! //! Provides shared routines for parsing `{ ... }`-delimited bodies used in //! function, loop, state, and similar constructs after the opening `{` //! has been consumed. use crate::arena::ArenaVec; use crate::ast::{BlockBody, Expression, ExpressionRef, Statement, StatementRef}; use crate::lexer::{Token, TokenPosition, TokenSpan}; use crate::parser::{ParseErrorKind, Parser}; impl<'src, 'arena> Parser<'src, 'arena> { /// Parses a `{ ... }` block after the opening `{` has been consumed. /// /// Consumes tokens until the matching `}` and returns an /// [`Expression::Block`] whose span covers the entire block, from /// `opening_brace_position` to the closing `}`. /// /// On premature end-of-file, returns a best-effort block. #[must_use] pub(crate) fn parse_block_tail( &mut self, opening_brace_position: TokenPosition, ) -> ExpressionRef<'src, 'arena> { let BlockBody { statements, span } = self.parse_braced_block_statements_tail(opening_brace_position); self.arena.alloc_node(Expression::Block(statements), span) } /// Parses a `{ ... }` block after the opening `{` has been consumed. /// /// Consumes tokens until the matching `}` and returns the contained /// statements together with a span that covers the entire block, from /// `opening_brace_position` to the closing `}`. /// /// On premature end-of-file, returns a best-effort statement list and span. #[must_use] pub(crate) fn parse_braced_block_statements_tail( &mut self, opening_brace_position: TokenPosition, ) -> BlockBody<'src, 'arena> { let mut statements = self.arena.vec(); while let Some((token, token_position)) = self.peek_token_and_position() { if token == Token::RightBrace { self.advance(); // '}' let span = TokenSpan::range(opening_brace_position, token_position); return BlockBody { statements, span }; } self.parse_next_block_item_into(&mut statements); self.ensure_forward_progress(token_position); } // Reached EOF without a closing `}` self.report_error_here(ParseErrorKind::BlockMissingClosingBrace); let span = TokenSpan::range( opening_brace_position, self.last_consumed_position_or_start(), ); BlockBody { statements, span } } /// Parses one statement inside a `{ ... }` block and appends it to /// `statements`. /// /// This method never consumes the closing `}` and is only meant to be /// called while parsing inside a block. It always appends at least one /// statement, even in the presence of syntax errors. pub(crate) fn parse_next_block_item_into( &mut self, statements: &mut ArenaVec<'arena, StatementRef<'src, 'arena>>, ) { let mut next_statement = self.parse_statement().unwrap_or_else(|| { let next_expression = self.parse_expression(); let next_expression_span = *next_expression.span(); self.arena .alloc_node(Statement::Expression(next_expression), next_expression_span) }); if statement_needs_semicolon(&next_statement) && let Some((Token::Semicolon, semicolon_position)) = self.peek_token_and_position() { next_statement.span_mut().extend_to(semicolon_position); self.advance(); // ';' } statements.push(next_statement); } } fn statement_needs_semicolon(statement: &Statement) -> bool { use Statement::{Empty, Error, Expression, Function, Label, LocalVariableDeclaration}; match statement { Empty | Label(_) | Error | Function(_) => false, Expression(expression) => expression_needs_semicolon(expression), LocalVariableDeclaration { .. } => true, } } const fn expression_needs_semicolon(expression: &Expression) -> bool { use Expression::{Block, DoUntil, Error, For, ForEach, If, Switch, While}; matches!( expression, Block { .. } | If { .. } | While { .. } | DoUntil { .. } | ForEach { .. } | For { .. } | Switch { .. } | Error ) }