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