342 lines
14 KiB
Rust
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)
|
|
}
|
|
}
|
|
}
|
|
}
|