rott/rottlib/src/parser/grammar/statements.rs

186 lines
7.4 KiB
Rust

//! Statement parsing for the language front-end.
//!
//! Implements a simple recursive-descent parser for
//! *Fermented UnrealScript statements*.
use crate::ast::{AstSpan, Statement, StatementRef};
use crate::lexer::Token;
use crate::parser::{ParseErrorKind, ResultRecoveryExt, SyncLevel};
impl<'src, 'arena> crate::parser::Parser<'src, 'arena> {
/// Parses a single statement.
///
/// Does not consume a trailing `;` except for [`Statement::Empty`].
/// The caller handles semicolons. Returns [`Some`] if a statement is
/// recognized; otherwise [`None`].
#[must_use]
pub(crate) fn parse_statement(&mut self) -> Option<StatementRef<'src, 'arena>> {
let Some((token, lexeme, location)) = self.peek_token_lexeme_and_location() else {
self.report_error_here(ParseErrorKind::UnexpectedEndOfFile);
return None;
};
match token {
// Empty statement
Token::Semicolon => {
self.advance(); // `;`
Some(self.arena.alloc(Statement::Empty, AstSpan::new(location)))
}
// UnrealScript's standard `local` variable declaration
Token::Local => {
self.advance(); // `local`
Some(
self.parse_local_variable_declaration_cont()
.widen_error_span_from(location)
.sync_error_until(self, SyncLevel::Statement)
.unwrap_or_fallback(self),
)
}
// Label definition
Token::Identifier if matches!(self.peek_token_at(1), Some(Token::Colon)) => {
self.advance(); // `Token::Identifier`
self.advance(); // `:`
Some(self.arena.alloc(
Statement::Label(self.arena.string(lexeme)),
AstSpan::range(location, self.last_visited_location()),
))
}
// C-like variable declaration
token
if token.is_valid_type_name_token()
&& Some(Token::Identifier) == self.peek_token_at(1) =>
{
self.advance(); // `TYPE_NAME`
// Next token is guaranteed to exist by the arm condition
Some(self.parse_variable_declaration_cont(lexeme))
}
// Not a statement
_ => None,
}
}
/// Parses a local variable declaration after `local` has been consumed.
///
/// Requires the next token to be a type name. Initializers are not allowed.
/// Reports and recovers from errors; the identifier list may be empty if
/// recovery fails.
fn parse_local_variable_declaration_cont(
&mut self,
) -> crate::parser::ParseResult<'src, 'arena, StatementRef<'src, 'arena>> {
let Some((type_token, type_name)) = self.peek_token_and_lexeme() else {
return Err(self.make_error_here(ParseErrorKind::UnexpectedEndOfFile));
};
if !type_token.is_valid_type_name_token() {
return Err(self.make_error_here(ParseErrorKind::LocalInvalidTypeName));
}
let declaration_start_location = self.last_visited_location();
self.advance(); // `TYPE_NAME`
let type_name = self.arena.string(type_name);
let identifiers = self.parse_local_identifier_list();
if identifiers.is_empty() {
self.make_error_here(ParseErrorKind::LocalMissingIdentifier)
.widen_error_span_from(declaration_start_location)
.report_error(self);
}
Ok(self.arena.alloc(
Statement::LocalVariableDeclaration {
type_name,
identifiers,
},
AstSpan::range(declaration_start_location, self.last_visited_location()),
))
}
/// Parses a comma-separated list of identifiers for a local declaration.
///
/// Best-effort recovery from errors. Returns an empty list if no valid
/// identifiers are found.
fn parse_local_identifier_list(
&mut self,
) -> crate::arena::ArenaVec<'arena, crate::arena::ArenaString<'arena>> {
let mut identifiers = self.arena.vec();
while let Some((token, next_variable_name)) = self.peek_token_and_lexeme() {
if token == Token::Identifier {
identifiers.push(self.arena.string(next_variable_name));
self.advance(); // `Token::Identifier`
} else {
self.report_error_here(ParseErrorKind::LocalBadVariableIdentifier);
// Try to recover to the next variable name
self.recover_until(SyncLevel::ListSeparator);
}
// Disallow initializers in `local`.
if let Some(Token::Assign) = self.peek_token() {
self.report_error_here(ParseErrorKind::LocalInitializerNotAllowed);
self.recover_until(SyncLevel::ListSeparator);
}
// Can the list continue?
// Loop cannot stall: each iteration consumes a token or breaks
if !self.eat(Token::Comma) {
break;
}
}
// End-of-file branch
identifiers
}
/// Parses a non-local variable declaration after the type name token
/// has been consumed.
///
/// The caller must guarantee that at least one declarator follows.
/// Optional initializers are allowed.
fn parse_variable_declaration_cont(
&mut self,
type_name: &'src str,
) -> StatementRef<'src, 'arena> {
let declaration_start_location = self.last_visited_location();
let type_name = self.arena.string(type_name);
let declarations = self.parse_variable_declaration_list();
// An identifier required by method's condition
debug_assert!(!declarations.is_empty());
self.arena.alloc(
Statement::VariableDeclaration {
type_name,
declarations,
},
AstSpan::range(declaration_start_location, self.last_visited_location()),
)
}
/// Parses a comma-separated list of declarators with optional `=`
/// initializers.
///
/// Best-effort recovery on errors.
/// The caller should invoke this when the next token starts a declarator.
fn parse_variable_declaration_list(
&mut self,
) -> crate::arena::ArenaVec<'arena, crate::ast::VariableDeclarator<'src, 'arena>> {
let mut variables = self.arena.vec();
while let Some((token, next_variable_name)) = self.peek_token_and_lexeme() {
if token == Token::Identifier {
self.advance(); // `Token::Identifier`
let name = self.arena.string(next_variable_name);
let initializer = if self.eat(Token::Assign) {
Some(self.parse_expression())
} else {
None
};
variables.push(crate::ast::VariableDeclarator { name, initializer });
} else {
self.report_error_here(ParseErrorKind::DeclBadVariableIdentifier);
// Try to recover to the next variable name
self.recover_until(SyncLevel::ListSeparator);
}
// Can the list continue?
// Loop cannot stall: each iteration consumes a token or breaks
if !self.eat(Token::Comma) {
break;
}
}
// End-of-file branch
variables
}
}