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