360 lines
15 KiB
Rust
360 lines
15 KiB
Rust
//! Parser for primary expressions in Fermented UnrealScript.
|
|
//!
|
|
//! This module implements parsing of primary expressions via
|
|
//! [`Parser::parse_primary_from_current_token`] and its helper
|
|
//! [`Parser::try_parse_keyword_primary`].
|
|
//!
|
|
//! ## What is a "primary expression" here?
|
|
//!
|
|
//! In this module, "primary" is used somewhat more broadly than in a
|
|
//! textbook grammar, but it still has one essential property:
|
|
//!
|
|
//! A primary expression is an expression form that can be parsed
|
|
//! directly from the current token, without requiring an already
|
|
//! parsed left-hand side.
|
|
//!
|
|
//! This includes ordinary primaries such as literals, identifiers, and
|
|
//! parenthesized expressions, as well as keyword-led forms such as
|
|
//! `if`, `while`, `for`, `foreach`, `switch`, `return`, `break`,
|
|
//! `continue`, `new`, and `class<...>`.
|
|
//!
|
|
//! By contrast, selectors, postfix operators, and infix operators are
|
|
//! not primaries. They cannot stand on their own here: they are parsed
|
|
//! only as continuations of an already parsed expression.
|
|
//!
|
|
//! So "primary" here does not mean "smallest atomic expression".
|
|
//! It means "an expression form that does not need a left-hand side
|
|
//! in order to be parsed".
|
|
//!
|
|
//! ## Keyword-led primaries and identifier fallback
|
|
//!
|
|
//! Some lexer keywords are always parsed as keyword-led primary expressions
|
|
//! in expression position: `if`, `while`, `do`, `foreach`, `return`, `break`,
|
|
//! `continue`, `new`, `true`, `false`, and `none`.
|
|
//!
|
|
//! Other keywords are accepted as keyword-led forms only when the following
|
|
//! tokens commit to that syntax. Otherwise they remain available as
|
|
//! identifier-like primaries.
|
|
//!
|
|
//! - `for` is parsed as a loop only when followed by a parenthesized header
|
|
//! containing a top-level `;`, matching `for (init; condition; step)`.
|
|
//! - `switch` is parsed as a switch expression only when followed by `(`.
|
|
//! - `goto` is parsed as a label jump only when it is not followed by `(`.
|
|
//! - `class` is parsed as a class type expression only when followed by `<`.
|
|
//!
|
|
//! These rules are local and syntactic. They avoid name resolution while still
|
|
//! supporting existing legacy code that uses some keywords as ordinary names.
|
|
//!
|
|
//! ### Why is `switch` handled differently?
|
|
//!
|
|
//! `switch` is handled differently because, in existing `UnrealScript` code,
|
|
//! it may appear either as a keyword-led construct or as an identifier.
|
|
//!
|
|
//! Its disambiguation rule is simpler than for `for`: if the next token is
|
|
//! `(`, `switch` is parsed as a `switch` expression; otherwise it remains
|
|
//! available as an identifier.
|
|
|
|
use crate::ast::{Expression, ExpressionRef, OptionalExpression};
|
|
use crate::lexer::{Keyword, Token, TokenPosition, TokenSpan};
|
|
use crate::parser::{ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, SyncLevel};
|
|
|
|
mod new;
|
|
|
|
impl<'src, 'arena> Parser<'src, 'arena> {
|
|
/// Parses a primary expression starting from the provided token.
|
|
///
|
|
/// The provided token is assumed to be the already consumed first token of
|
|
/// the primary expression.
|
|
///
|
|
/// This includes literals, identifiers, grouped expressions, block
|
|
/// expressions, and certain keyword-led forms.
|
|
///
|
|
/// It does not parse selectors, postfix operators, or infix operators;
|
|
/// those are handled afterwards as continuations of the parsed primary.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`ParseErrorKind::ExpressionExpected`] if the provided
|
|
/// token cannot begin any valid primary expression in this position.
|
|
pub(super) fn parse_primary_from_current_token(
|
|
&mut self,
|
|
token: Token,
|
|
token_lexeme: &'src str,
|
|
token_position: TokenPosition,
|
|
) -> ParseExpressionResult<'src, 'arena> {
|
|
Ok(match token {
|
|
Token::IntegerLiteral => {
|
|
let value = self.decode_integer_literal(token_lexeme, token_position)?;
|
|
self.arena
|
|
.alloc_node_at(Expression::Integer(value), token_position)
|
|
}
|
|
Token::FloatLiteral => {
|
|
let value = self.decode_float_literal(token_lexeme, token_position)?;
|
|
self.arena
|
|
.alloc_node_at(Expression::Float(value), token_position)
|
|
}
|
|
Token::StringLiteral => {
|
|
let value = self.unescape_string_literal(token_lexeme);
|
|
self.arena
|
|
.alloc_node_at(Expression::String(value), token_position)
|
|
}
|
|
Token::NameLiteral => self.arena.alloc_node_at(
|
|
Expression::NameLiteral {
|
|
tag: None,
|
|
name: token_lexeme,
|
|
},
|
|
token_position,
|
|
),
|
|
Token::LeftParenthesis => self.parse_parenthesized_expression_tail(token_position),
|
|
Token::LeftBrace => self.parse_block_body_tail(token_position),
|
|
Token::Keyword(keyword) => {
|
|
match self.try_parse_keyword_primary(keyword, token_position) {
|
|
Some(keyword_expression) => keyword_expression,
|
|
None => return self.parse_identifier_like_primary(token, token_position),
|
|
}
|
|
}
|
|
_ => return self.parse_identifier_like_primary(token, token_position),
|
|
})
|
|
}
|
|
|
|
/// Parses a keyword-led primary expression.
|
|
///
|
|
/// Returns `None` if the keyword should instead be interpreted as an
|
|
/// identifier in this position.
|
|
fn try_parse_keyword_primary(
|
|
&mut self,
|
|
keyword: Keyword,
|
|
token_position: TokenPosition,
|
|
) -> OptionalExpression<'src, 'arena> {
|
|
Some(match keyword {
|
|
Keyword::True => self
|
|
.arena
|
|
.alloc_node_at(Expression::Bool(true), token_position),
|
|
Keyword::False => self
|
|
.arena
|
|
.alloc_node_at(Expression::Bool(false), token_position),
|
|
Keyword::None => self.arena.alloc_node_at(Expression::None, token_position),
|
|
Keyword::If => self.parse_if_tail(token_position),
|
|
Keyword::While => self.parse_while_tail(token_position),
|
|
Keyword::Do => self.parse_do_until_tail(token_position),
|
|
Keyword::ForEach => self.parse_foreach_tail(token_position),
|
|
Keyword::Return => self.parse_return_tail(token_position),
|
|
Keyword::Break => self.parse_break_tail(token_position),
|
|
Keyword::Continue => self
|
|
.arena
|
|
.alloc_node_at(Expression::Continue, token_position),
|
|
Keyword::New => self.parse_new_expression_tail(token_position),
|
|
// These keywords remain valid identifiers unless the following
|
|
// tokens commit to the keyword-led form.
|
|
Keyword::For
|
|
if let Some(left_parenthesis_position) = self.peek_for_loop_header_left_parenthesis_position() =>
|
|
{
|
|
self.advance(); // `(`
|
|
self.parse_for_tail(token_position, left_parenthesis_position)
|
|
}
|
|
Keyword::Goto if !matches!(self.peek_token(), Some(Token::LeftParenthesis)) => {
|
|
self.parse_goto_tail(token_position)
|
|
}
|
|
// `switch` is only treated as keyword-led when followed by `(`
|
|
// to match the syntax accepted by the existing codebase.
|
|
Keyword::Switch if matches!(self.peek_token(), Some(Token::LeftParenthesis)) => {
|
|
self.parse_switch_tail(token_position)
|
|
}
|
|
Keyword::Class => {
|
|
if let Some(left_angle_bracket_position) = self.eat_with_position(Token::Less) {
|
|
self.parse_class_type_tail(token_position, left_angle_bracket_position)
|
|
} else {
|
|
return None;
|
|
}
|
|
}
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// Attempts to parse the already-consumed token as an identifier or tagged
|
|
/// name literal.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`ParseErrorKind::ExpressionExpected`] if the token
|
|
/// cannot be used as an identifier in this position.
|
|
fn parse_identifier_like_primary(
|
|
&mut self,
|
|
primary_token: Token,
|
|
primary_token_position: TokenPosition,
|
|
) -> ParseExpressionResult<'src, 'arena> {
|
|
let identifier_token =
|
|
Self::identifier_token_from_token(primary_token, primary_token_position).ok_or_else(
|
|
|| self.make_error_at(ParseErrorKind::ExpressionExpected, primary_token_position),
|
|
)?;
|
|
// A token that is valid as an identifier may still start a tagged-name
|
|
// literal such as `Texture'Foo.Bar'`.
|
|
let expression = if let Some((Token::NameLiteral, lexeme, name_position)) =
|
|
self.peek_token_lexeme_and_position()
|
|
{
|
|
self.advance();
|
|
self.arena.alloc_node_between(
|
|
Expression::NameLiteral {
|
|
tag: Some(identifier_token),
|
|
name: lexeme,
|
|
},
|
|
primary_token_position,
|
|
name_position,
|
|
)
|
|
} else {
|
|
self.arena.alloc_node_at(
|
|
Expression::Identifier(identifier_token),
|
|
primary_token_position,
|
|
)
|
|
};
|
|
Ok(expression)
|
|
}
|
|
|
|
/// Parses a parenthesized expression.
|
|
///
|
|
/// Assumes the opening `(` has already been consumed.
|
|
/// Reports and recovers from a missing closing `)`.
|
|
pub(super) fn parse_parenthesized_expression_tail(
|
|
&mut self,
|
|
left_parenthesis_position: TokenPosition,
|
|
) -> ExpressionRef<'src, 'arena> {
|
|
if self.next_token_definitely_cannot_start_expression() {
|
|
return self
|
|
.make_error_at_last_consumed(ParseErrorKind::ParenthesizedExpressionInvalidStart)
|
|
.widen_error_span_from(left_parenthesis_position)
|
|
.extend_blame_to_next_token(self)
|
|
.related_token("left_parenthesis", left_parenthesis_position)
|
|
.sync_error_at_matching_delimiter(self, left_parenthesis_position)
|
|
.fallback(self);
|
|
};
|
|
let inner_expression = self.parse_expression();
|
|
let right_parenthesis_position = self
|
|
.expect(
|
|
Token::RightParenthesis,
|
|
ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis,
|
|
)
|
|
.widen_error_span_from(left_parenthesis_position)
|
|
.sync_error_at_matching_delimiter(self, left_parenthesis_position)
|
|
.extend_blame_start_to_covered_start()
|
|
.related_token("left_parenthesis", left_parenthesis_position)
|
|
.unwrap_or_fallback(self);
|
|
self.arena.alloc_node_between(
|
|
Expression::Parentheses(inner_expression),
|
|
left_parenthesis_position,
|
|
right_parenthesis_position,
|
|
)
|
|
}
|
|
|
|
/// Parses a class type expression of the form `class<...>`.
|
|
///
|
|
/// Assumes the `class` keyword and following '<' token have already been
|
|
/// consumed. Reports and recovers from malformed type syntax locally.
|
|
fn parse_class_type_tail(
|
|
&mut self,
|
|
class_keyword_position: TokenPosition,
|
|
left_angle_bracket_position: TokenPosition,
|
|
) -> ExpressionRef<'src, 'arena> {
|
|
match self.peek_token_and_position() {
|
|
Some((Token::Greater, right_angle_bracket_position)) => self
|
|
.report_missing_class_type_argument(
|
|
class_keyword_position,
|
|
left_angle_bracket_position,
|
|
right_angle_bracket_position,
|
|
),
|
|
Some((first_token, _)) if first_token.is_valid_identifier_name() => self
|
|
.parse_nonempty_class_type_tail(
|
|
class_keyword_position,
|
|
left_angle_bracket_position,
|
|
),
|
|
Some((_, bad_position)) => self.report_invalid_class_type_start(
|
|
class_keyword_position,
|
|
left_angle_bracket_position,
|
|
bad_position,
|
|
),
|
|
None => self.report_invalid_class_type_start(
|
|
class_keyword_position,
|
|
left_angle_bracket_position,
|
|
self.file.eof(),
|
|
),
|
|
}
|
|
}
|
|
|
|
fn parse_nonempty_class_type_tail(
|
|
&mut self,
|
|
class_keyword_position: TokenPosition,
|
|
left_angle_bracket_position: TokenPosition,
|
|
) -> ExpressionRef<'src, 'arena> {
|
|
let class_type = match self
|
|
.parse_qualified_identifier(ParseErrorKind::ClassTypeExpectedQualifiedTypeName)
|
|
.widen_error_span_from(class_keyword_position)
|
|
.extend_blame_to_next_token(self)
|
|
.sync_error_at(self, SyncLevel::CloseAngleBracket)
|
|
.related_token("class_keyword", class_keyword_position)
|
|
{
|
|
Ok(class_type) => class_type,
|
|
Err(error) => return self.report_error_with_fallback(error),
|
|
};
|
|
let right_angle_bracket_position = self
|
|
.expect(
|
|
Token::Greater,
|
|
ParseErrorKind::ClassTypeMissingClosingAngleBracket,
|
|
)
|
|
.widen_error_span_from(class_keyword_position)
|
|
.sync_error_at(self, SyncLevel::CloseAngleBracket)
|
|
.related_token("left_angle_bracket", left_angle_bracket_position)
|
|
.related_token("class_keyword", class_keyword_position)
|
|
.unwrap_or_fallback(self);
|
|
self.arena.alloc_node_between(
|
|
Expression::ClassType(class_type),
|
|
class_keyword_position,
|
|
right_angle_bracket_position,
|
|
)
|
|
}
|
|
|
|
fn report_missing_class_type_argument(
|
|
&mut self,
|
|
class_keyword_position: TokenPosition,
|
|
left_angle_bracket_position: TokenPosition,
|
|
right_angle_bracket_position: TokenPosition,
|
|
) -> ExpressionRef<'src, 'arena> {
|
|
self.advance();
|
|
self.make_error_at_last_consumed(ParseErrorKind::ClassTypeMissingTypeArgument)
|
|
.widen_error_span_from(class_keyword_position)
|
|
.blame(TokenSpan::range(
|
|
left_angle_bracket_position,
|
|
right_angle_bracket_position,
|
|
))
|
|
.related_token("left_angle_bracket", left_angle_bracket_position)
|
|
.related_token("class_keyword", class_keyword_position)
|
|
.fallback(self)
|
|
}
|
|
|
|
fn report_invalid_class_type_start(
|
|
&mut self,
|
|
class_keyword_position: TokenPosition,
|
|
left_angle_bracket_position: TokenPosition,
|
|
bad_position: TokenPosition,
|
|
) -> ExpressionRef<'src, 'arena> {
|
|
self.make_error_at_last_consumed(ParseErrorKind::ClassTypeInvalidStart)
|
|
.widen_error_span_from(class_keyword_position)
|
|
.sync_error_at(self, SyncLevel::CloseAngleBracket)
|
|
.blame_token(bad_position)
|
|
.related_token("left_angle_bracket", left_angle_bracket_position)
|
|
.related_token("class_keyword", class_keyword_position)
|
|
.fallback(self)
|
|
}
|
|
|
|
/// Returns `true` iff the next token is definitely not a valid start of an
|
|
/// expression.
|
|
///
|
|
/// This is intentionally conservative:
|
|
/// - `true` means parsing an expression here is pointless;
|
|
/// - `false` means "might be valid", so the normal expression parser should
|
|
/// decide and potentially emit a more specific error.
|
|
#[must_use]
|
|
pub(super) fn next_token_definitely_cannot_start_expression(&mut self) -> bool {
|
|
self.peek_token()
|
|
.map_or(true, Token::is_definitely_not_expression_start)
|
|
}
|
|
}
|