186 lines
7.4 KiB
Rust
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
|
|
}
|
|
}
|