rott/rottlib/src/parser/grammar/expression/primary/new.rs

342 lines
14 KiB
Rust

//! 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<TokenSpan> {
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<TokenSpan> {
[
&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<NewClassSpecifierParseAction> {
// 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<NewClassSpecifierParseAction> {
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<TokenPosition>,
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)
}
}
}
}