rott/rottlib/src/parser/grammar/expression/block.rs

110 lines
4.3 KiB
Rust

//! 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
)
}