//! Parser for `new` expressions in Fermented UnrealScript. use super::super::selectors::{CallArgumentListParseState, ParsedCallArgumentSlot}; use crate::ast::{Expression, ExpressionRef, OptionalExpression}; use crate::lexer::{Token, TokenPosition, TokenSpan}; use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel}; /// Determines how parsing of the class specifier proceeds after the optional /// `new(...)` argument list has been parsed or recovered. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] enum NewClassSpecifierParseAction { Parse, Skip, } /// Stores the parsed `new(...)` arguments and the action to take for /// class-specifier parsing. #[derive(Debug)] struct NewArgumentListParseResult<'src, 'arena> { outer_argument: OptionalExpression<'src, 'arena>, name_argument: OptionalExpression<'src, 'arena>, flags_argument: OptionalExpression<'src, 'arena>, class_specifier_parse_action: NewClassSpecifierParseAction, } impl<'src, 'arena> NewArgumentListParseResult<'src, 'arena> { /// Returns the parse result for `new` without an argument list. fn without_argument_list() -> Self { Self { outer_argument: None, name_argument: None, flags_argument: None, class_specifier_parse_action: NewClassSpecifierParseAction::Parse, } } } /// Holds shared state for parsing the optional `new(...)` argument list. #[derive(Debug)] struct NewArgumentListParseState<'src, 'arena> { call_argument_list_parse_state: CallArgumentListParseState, new_keyword_position: TokenPosition, left_parenthesis_position: TokenPosition, outer_argument: OptionalExpression<'src, 'arena>, name_argument: OptionalExpression<'src, 'arena>, flags_argument: OptionalExpression<'src, 'arena>, } impl<'src, 'arena> NewArgumentListParseState<'src, 'arena> { /// Stores an argument in the current `new` argument slot. fn store_argument_in_current_slot(&mut self, argument: OptionalExpression<'src, 'arena>) { match self.call_argument_list_parse_state.parsed_slot_count { 1 => self.outer_argument = argument, 2 => self.name_argument = argument, 3 => self.flags_argument = argument, _ => unreachable!("this method cannot be called after parsing three arguments"), } } /// Returns the span of the argument in the current parsed slot. /// /// Assumes the current slot has already been stored. #[must_use] fn current_argument_span(&self) -> Option { debug_assert!( (1..=3).contains(&self.call_argument_list_parse_state.parsed_slot_count), "parsed_slot_count out of range in new-argument parser" ); match self.call_argument_list_parse_state.parsed_slot_count { 1 => &self.outer_argument, 2 => &self.name_argument, 3 => &self.flags_argument, // Diagnostics can fall back to a missing span here. _ => return None, } .as_ref() .map(|argument| *argument.span()) } /// Returns the span of the last parsed non-empty `new` argument. #[must_use] fn last_parsed_allowed_argument_span(&self) -> Option { [ &self.flags_argument, &self.name_argument, &self.outer_argument, ] .into_iter() .find_map(|slot| slot.as_ref().map(|argument| *argument.span())) } /// Finishes argument-list parsing and returns the collected result. #[must_use] fn into_result( self, class_specifier_parse_action: NewClassSpecifierParseAction, ) -> NewArgumentListParseResult<'src, 'arena> { NewArgumentListParseResult { outer_argument: self.outer_argument, name_argument: self.name_argument, flags_argument: self.flags_argument, class_specifier_parse_action, } } } impl<'src, 'arena> Parser<'src, 'arena> { /// Parses a `new` expression. /// /// Assumes the `new` keyword has already been consumed. Parses an optional /// parenthesized argument list before the required class specifier. #[must_use] pub(super) fn parse_new_expression_tail( &mut self, new_keyword_position: TokenPosition, ) -> ExpressionRef<'src, 'arena> { let mut argument_list_end_position = None; let NewArgumentListParseResult { outer_argument, name_argument, flags_argument, class_specifier_parse_action, } = if let Some(left_parenthesis_position) = self.eat_with_position(Token::LeftParenthesis) { let parsed_argument_list = self.parse_new_argument_list_tail(new_keyword_position, left_parenthesis_position); argument_list_end_position = self.last_consumed_position(); parsed_argument_list } else { NewArgumentListParseResult::without_argument_list() }; let class_specifier = self.parse_new_class_specifier( new_keyword_position, argument_list_end_position, class_specifier_parse_action, ); let class_specifier_end_position = class_specifier.span().end; self.arena.alloc_node_between( Expression::New { outer_argument, name_argument, flags_argument, class_specifier, }, new_keyword_position, class_specifier_end_position, ) } /// Parses the parenthesized argument list of a `new` expression. /// /// Assumes the opening `(` has already been consumed. #[must_use] fn parse_new_argument_list_tail( &mut self, new_keyword_position: TokenPosition, left_parenthesis_position: TokenPosition, ) -> NewArgumentListParseResult<'src, 'arena> { let mut state = NewArgumentListParseState { new_keyword_position, left_parenthesis_position, outer_argument: None, name_argument: None, flags_argument: None, call_argument_list_parse_state: CallArgumentListParseState::new(), }; if let Some(class_specifier_parse_action) = self.parse_new_argument_slots(&mut state) { return state.into_result(class_specifier_parse_action); } self.diagnose_extra_new_arguments(&mut state); let class_specifier_parse_action = if self.eat(Token::RightParenthesis) { NewClassSpecifierParseAction::Parse } else { self.recover_from_missing_new_closing_parenthesis(&state) }; state.into_result(class_specifier_parse_action) } /// Parses up to three positional `new` arguments. /// /// Returns [`Some`] only when recovery determines whether /// the class specifier should be parsed or skipped. #[must_use] fn parse_new_argument_slots( &mut self, state: &mut NewArgumentListParseState<'src, 'arena>, ) -> Option { // Only successful slot parses continue the loop, // so each iteration makes progress. while state.call_argument_list_parse_state.parsed_slot_count < 3 && let ParsedCallArgumentSlot::Argument(argument) = self.parse_call_argument_slot(&mut state.call_argument_list_parse_state) { // On `ParsedCallArgumentSlot::Argument(_)`, // `parse_call_argument_slot` increases `parsed_slot_count` by 1, // so it is now in `1`, `2` or `3`, guaranteeing that this call // is valid and won't hit `unreachable!`. state.store_argument_in_current_slot(argument); if state .call_argument_list_parse_state .last_slot_missing_boundary { if let Some(class_specifier_parse_action) = self.recover_from_missing_new_argument_separator(state) { return Some(class_specifier_parse_action); } } } None } /// Recovers from a missing separator between `new` arguments. /// /// Returns [`Some`] when recovery instead treats the boundary as the end of /// the argument list. fn recover_from_missing_new_argument_separator( &mut self, state: &mut NewArgumentListParseState<'src, 'arena>, ) -> Option { let has_parsed_all_allowed_arguments = state.call_argument_list_parse_state.parsed_slot_count > 2; let likely_missing_comma = !self.next_token_definitely_cannot_start_expression() && !has_parsed_all_allowed_arguments; if likely_missing_comma { let next_argument_position = self.peek_position_or_eof(); let mut error = self .make_error_at_last_consumed(ParseErrorKind::NewArgumentMissingComma) .widen_error_span_from(state.left_parenthesis_position) .blame_token(next_argument_position) .related_token("new_keyword", state.new_keyword_position) .related_token("left_parenthesis", state.left_parenthesis_position); if let Some(argument_span) = state.current_argument_span() { error = error.related("previous_argument", argument_span); } error.report_error(self); None } else { // If this does not look like another argument, // treat the boundary as the missing `)` rather than inventing // an extra comma diagnostic. Some(self.recover_from_missing_new_closing_parenthesis(state)) } } /// Diagnoses and skips any arguments beyond the three accepted by `new`. fn diagnose_extra_new_arguments( &mut self, state: &mut NewArgumentListParseState<'src, 'arena>, ) { // Require an explicit comma before diagnosing extra arguments so this // does not mask a missing `)`. if let Some((Token::Comma, extra_argument_comma_position)) = self.peek_token_and_position() { // Preserve the first extra argument span for a more precise // diagnostic before we do any syncing. let first_extra_argument_span = match self.parse_call_argument_slot(&mut state.call_argument_list_parse_state) { ParsedCallArgumentSlot::Argument(Some(argument)) => Some(*argument.span()), ParsedCallArgumentSlot::Argument(None) => None, ParsedCallArgumentSlot::NoMoreArguments => None, }; let mut error = self .make_error_at_last_consumed(ParseErrorKind::NewTooManyArguments) .widen_error_span_from(state.left_parenthesis_position) .blame_token(extra_argument_comma_position) .related_token("new_keyword", state.new_keyword_position) .related_token("left_parenthesis", state.left_parenthesis_position); if let Some(span) = state.last_parsed_allowed_argument_span() { error = error.related("last_allowed_argument", span); } if let Some(span) = first_extra_argument_span { error = error.related("first_extra_argument", span); } error .sync_error_until_matching_delimiter(self, state.left_parenthesis_position) .report_error(self); } } /// Reports a missing closing `)` in a `new(...)` argument list and /// determines whether the class specifier should be parsed or skipped. #[must_use] fn recover_from_missing_new_closing_parenthesis( &mut self, state: &NewArgumentListParseState<'src, 'arena>, ) -> NewClassSpecifierParseAction { let mut error = self .make_error_at_last_consumed(ParseErrorKind::NewMissingClosingParenthesis) .widen_error_span_from(state.left_parenthesis_position) .blame_token(self.peek_position_or_eof()) .related_token("new_keyword", state.new_keyword_position) .related_token("left_parenthesis", state.left_parenthesis_position); let class_specifier_parse_action = if self.next_token_definitely_cannot_start_expression() { error = error.sync_error_at_matching_delimiter(self, state.left_parenthesis_position); // Skipping the class specifier avoids cascading errors when // the next token cannot start an expression anyway. NewClassSpecifierParseAction::Skip } else { NewClassSpecifierParseAction::Parse }; error.report_error(self); class_specifier_parse_action } /// Parses the class specifier of a `new` expression after argument-list /// parsing and recovery. #[must_use] fn parse_new_class_specifier( &mut self, new_keyword_position: TokenPosition, argument_list_end: Option, class_specifier_parse_action: NewClassSpecifierParseAction, ) -> ExpressionRef<'src, 'arena> { match class_specifier_parse_action { NewClassSpecifierParseAction::Parse if self.next_token_definitely_cannot_start_expression() => { let mut error = self .make_error_at_last_consumed(ParseErrorKind::NewMissingClassSpecifier) .widen_error_span_from(new_keyword_position) .sync_error_until(self, SyncLevel::ExpressionStart) .extend_blame_to_next_token(self) .related_token("new_keyword", new_keyword_position); if let Some(argument_list_end) = argument_list_end { error = error.related_token("argument_list_end", argument_list_end); } return self.report_error_with_fallback(error); } NewClassSpecifierParseAction::Parse => self.parse_expression(), NewClassSpecifierParseAction::Skip => { let error_position = self.peek_position_or_eof(); self.make_error_expression_at(error_position) } } } }