Huge dump of refactored code. Still in the middle of the changes that are to be squashed later in a one huge monster commit, because there is no value in anything atomic here.
211 lines
8.4 KiB
Rust
211 lines
8.4 KiB
Rust
//! 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<ArenaVec<'arena, VarEditorSpecifierRef<'src, 'arena>>>,
|
|
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<ParsedStructFieldPrefix<'src, 'arena>> {
|
|
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<IdentifierToken>,
|
|
Option<QualifiedIdentifierRef<'arena>>,
|
|
) {
|
|
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
|
|
}
|
|
}
|