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

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)
}
}