//! Parsing of struct definitions for Fermented `UnrealScript`. //! //! ## C++ block handling //! //! The Fermented `UnrealScript` parser must support parsing several legacy //! source files that contain `cpptext` or `cppstruct`. Our compiler does not //! compile with C++ code and therefore does not need these blocks in //! the resulting AST. We treat them the same as trivia and skip them. //! //! However, some related tokens are context-sensitive, so handling these //! blocks in the general trivia-skipping path would complicate the separation //! between the lexer and the parser. //! //! The resulting files will not be compiled, but they can still be used to //! extract type information. use crate::arena::ArenaVec; use crate::ast::{ AstSpan, IdentifierToken, QualifiedIdentifierRef, StructDefRef, StructDefinition, StructField, StructFieldRef, StructModifier, StructModifierKind, TypeSpecifierRef, VarEditorSpecifierRef, VarModifier, }; use crate::lexer::{Keyword, Token, TokenPosition}; use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel}; #[derive(Debug)] struct ParsedStructFieldPrefix<'src, 'arena> { editor_specifiers: Option>>, declaration_modifiers: ArenaVec<'arena, VarModifier>, type_specifier: TypeSpecifierRef<'src, 'arena>, } #[derive(Debug)] enum StructBodyItemParseOutcome<'src, 'arena> { Field(StructFieldRef<'src, 'arena>), Skip, Stop, } impl<'src, 'arena> Parser<'src, 'arena> { /// Parses a `struct` definition after the `struct` keyword has been /// consumed. pub(crate) fn parse_struct_definition_tail( &mut self, struct_keyword_position: TokenPosition, ) -> StructDefRef<'src, 'arena> { let modifiers = self.parse_struct_declaration_modifiers(); let (name, base_type_name) = self.parse_struct_name_base_and_open_brace(); let mut fields = self.arena.vec(); while let Some((next_token, next_position)) = self.peek_token_and_position() && next_token != Token::RightBrace { match self.parse_or_skip_struct_body_item() { StructBodyItemParseOutcome::Field(new_field) => fields.push(new_field), StructBodyItemParseOutcome::Skip => (), StructBodyItemParseOutcome::Stop => break, } self.ensure_forward_progress(next_position); } self.expect(Token::RightBrace, ParseErrorKind::StructMissingRightBrace) .widen_error_span_from(struct_keyword_position) .report_error(self); let span = AstSpan::range( struct_keyword_position, self.last_consumed_position_or_start(), ); self.arena.alloc_node( StructDefinition { name, base_type_name, modifiers, fields, }, span, ) } /// Parses one item in a struct body or skips an unsupported one. /// /// Returns [`StructBodyItemParseOutcome::Field`] for a successfully parsed /// field, [`StructBodyItemParseOutcome::Skip`] when recovery allows parsing /// to continue, and [`StructBodyItemParseOutcome::Stop`] when parsing /// should stop at this level. fn parse_or_skip_struct_body_item(&mut self) -> StructBodyItemParseOutcome<'src, 'arena> { let Some((token, token_position)) = self.peek_token_and_position() else { // This is the end of the file; // it will be handled by a higher-level parser. return StructBodyItemParseOutcome::Stop; }; match token { Token::Keyword(Keyword::CppText | Keyword::CppStruct) => { self.advance(); if !self.eat(Token::CppBlock) { self.report_error_here(ParseErrorKind::CppDirectiveMissingCppBlock); self.recover_until(SyncLevel::Statement); } StructBodyItemParseOutcome::Skip } Token::Keyword(Keyword::Var) => { self.advance(); self.parse_struct_field_tail(token_position) } _ => { self.report_error_here(ParseErrorKind::StructBodyUnexpectedItem); self.recover_until(SyncLevel::BlockBoundary); StructBodyItemParseOutcome::Skip } } } /// Parses a struct field after the `var` keyword has been consumed. /// /// Returns [`StructBodyItemParseOutcome::Skip`] if the field cannot be /// parsed far enough to produce a usable AST node after recovery. fn parse_struct_field_tail( &mut self, var_keyword_position: TokenPosition, ) -> StructBodyItemParseOutcome<'src, 'arena> { let Some(field_prefix) = self.parse_struct_field_prefix() else { return StructBodyItemParseOutcome::Skip; }; let declarators = self.parse_variable_declarators(); if !self.eat(Token::Semicolon) { self.report_error_here(ParseErrorKind::StructFieldMissingSemicolon); self.recover_until(SyncLevel::BlockBoundary); let _ = self.eat(Token::Semicolon); } if declarators.is_empty() { return StructBodyItemParseOutcome::Skip; } let span = AstSpan::range(var_keyword_position, self.last_consumed_position_or_start()); StructBodyItemParseOutcome::Field(self.arena.alloc_node( StructField { type_specifier: field_prefix.type_specifier, declaration_modifiers: field_prefix.declaration_modifiers, editor_specifiers: field_prefix.editor_specifiers, declarators, }, span, )) } fn parse_struct_field_prefix(&mut self) -> Option> { let editor_specifiers = self.parse_var_editor_specifier_list(); let declaration_modifiers = self.parse_var_declaration_modifiers(); let type_specification = self .parse_type_specifier() .sync_error_until(self, SyncLevel::BlockBoundary) .ok_or_report(self)?; Some(ParsedStructFieldPrefix { editor_specifiers, declaration_modifiers, type_specifier: type_specification, }) } /// Parses the struct name, optional base type, and opening brace. /// /// Accepts anonymous structs that begin immediately with `{`. fn parse_struct_name_base_and_open_brace( &mut self, ) -> ( Option, Option>, ) { if self.eat(Token::LeftBrace) { return (None, None); } let name = self .parse_identifier(ParseErrorKind::StructExpectedNameOrBrace) .ok_or_report(self); let base_type_name = if let Some((Token::Keyword(Keyword::Extends), extends_keyword_position)) = self.peek_token_and_position() { self.advance(); self.parse_qualified_identifier(ParseErrorKind::StructExpectedBaseName) .widen_error_span_from(extends_keyword_position) .ok_or_report(self) } else { None }; self.expect(Token::LeftBrace, ParseErrorKind::StructMissingLeftBrace) .report_error(self); (name, base_type_name) } fn parse_struct_declaration_modifiers(&mut self) -> ArenaVec<'arena, StructModifier> { let mut modifiers = self.arena.vec(); while let Some((next_keyword, next_keyword_position)) = self.peek_keyword_and_position() { let next_modifier_kind = match next_keyword { Keyword::Native => StructModifierKind::Native, Keyword::Init => StructModifierKind::Init, Keyword::Export => StructModifierKind::Export, Keyword::NoExport => StructModifierKind::NoExport, Keyword::Transient => StructModifierKind::Transient, Keyword::Deprecated => StructModifierKind::Deprecated, Keyword::Long => StructModifierKind::Long, _ => break, }; modifiers.push(StructModifier { kind: next_modifier_kind, position: next_keyword_position, }); self.advance(); } modifiers } }